コードロード

エラー討伐

【Windows-WSL2】composer install と composer updateでエラー

普段使っているPCとは別のPCで git clone したあとに、PHPUnitcomposer install しようとしたときのエラー

環境

  • Windows11
  • WSL2
  • php 7.4.3

エラー文

  • it is missing from your system. Install or enable PHP's dom extension.
  • it is missing from your system. Install or enable PHP's mbstring extension.

解決手順

composer install しようとしたら下記のエラー

$ composer install
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Your lock file does not contain a compatible set of packages. Please run composer update.

  Problem 1
    - phar-io/manifest is locked to version 2.0.3 and an update of this package was not requested.
    - phar-io/manifest 2.0.3 requires ext-dom * -> it is missing from your system. Install or enable PHP's dom extension.
  Problem 2
    - phpunit/php-code-coverage is locked to version 9.2.15 and an update of this package was not requested.
    - phpunit/php-code-coverage 9.2.15 requires ext-dom * -> it is missing from your system. Install or enable PHP's dom extension.
  Problem 3
    - phpunit/phpunit is locked to version 9.5.19 and an update of this package was not requested.
    - phpunit/phpunit 9.5.19 requires ext-dom * -> it is missing from your system. Install or enable PHP's dom extension.
  Problem 4
    - theseer/tokenizer is locked to version 1.2.1 and an update of this package was not requested.
    - theseer/tokenizer 1.2.1 requires ext-dom * -> it is missing from your system. Install or enable PHP's dom extension.

To enable extensions, verify that they are enabled in your .ini files:       
    - /etc/php/7.4/cli/php.ini
    - /etc/php/7.4/cli/conf.d/10-opcache.ini
    - /etc/php/7.4/cli/conf.d/10-pdo.ini
    - /etc/php/7.4/cli/conf.d/20-calendar.ini
    - /etc/php/7.4/cli/conf.d/20-ctype.ini
    - /etc/php/7.4/cli/conf.d/20-exif.ini
    - /etc/php/7.4/cli/conf.d/20-ffi.ini
    - /etc/php/7.4/cli/conf.d/20-fileinfo.ini
    - /etc/php/7.4/cli/conf.d/20-ftp.ini
    - /etc/php/7.4/cli/conf.d/20-gettext.ini
    - /etc/php/7.4/cli/conf.d/20-iconv.ini
    - /etc/php/7.4/cli/conf.d/20-json.ini
    - /etc/php/7.4/cli/conf.d/20-phar.ini
    - /etc/php/7.4/cli/conf.d/20-posix.ini
    - /etc/php/7.4/cli/conf.d/20-readline.ini
    - /etc/php/7.4/cli/conf.d/20-shmop.ini
    - /etc/php/7.4/cli/conf.d/20-sockets.ini
    - /etc/php/7.4/cli/conf.d/20-sysvmsg.ini
    - /etc/php/7.4/cli/conf.d/20-sysvsem.ini
    - /etc/php/7.4/cli/conf.d/20-sysvshm.ini
    - /etc/php/7.4/cli/conf.d/20-tokenizer.ini
You can also run `php --ini` in a terminal to see which files are used by PHP in CLI mode.Alternatively, you can run Composer with `--ignore-platform-req=ext-dom --ignore-platform-req=ext-dom --ignore-platform-req=ext-dom --ignore-platform-req=ext-dom` to temporarily ignore these required extensions.

Please run composer update とのことなので composer update してみたら下記のエラー

$ composer update
Loading composer repositories with package information
Info from https://repo.packagist.org: #StandWithUkraine
Updating dependencies
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - phpunit/phpunit[9.5.0, ..., 9.5.19] require ext-dom * -> it is missing from your system. Install or enable PHP's dom extension.
    - Root composer.json requires phpunit/phpunit ^9.5 -> satisfiable by phpunit/phpunit[9.5.0, ..., 9.5.19].

To enable extensions, verify that they are enabled in your .ini files:
    - /etc/php/7.4/cli/php.ini
    - /etc/php/7.4/cli/conf.d/10-opcache.ini
    - /etc/php/7.4/cli/conf.d/10-pdo.ini
    - /etc/php/7.4/cli/conf.d/20-calendar.ini
    - /etc/php/7.4/cli/conf.d/20-ctype.ini
    - /etc/php/7.4/cli/conf.d/20-curl.ini
    - /etc/php/7.4/cli/conf.d/20-exif.ini
    - /etc/php/7.4/cli/conf.d/20-ffi.ini
    - /etc/php/7.4/cli/conf.d/20-fileinfo.ini
    - /etc/php/7.4/cli/conf.d/20-ftp.ini
    - /etc/php/7.4/cli/conf.d/20-gettext.ini
    - /etc/php/7.4/cli/conf.d/20-iconv.ini
    - /etc/php/7.4/cli/conf.d/20-json.ini
    - /etc/php/7.4/cli/conf.d/20-phar.ini
    - /etc/php/7.4/cli/conf.d/20-posix.ini
    - /etc/php/7.4/cli/conf.d/20-readline.ini
    - /etc/php/7.4/cli/conf.d/20-shmop.ini
    - /etc/php/7.4/cli/conf.d/20-sockets.ini
    - /etc/php/7.4/cli/conf.d/20-sysvmsg.ini
    - /etc/php/7.4/cli/conf.d/20-sysvsem.ini
    - /etc/php/7.4/cli/conf.d/20-sysvshm.ini
    - /etc/php/7.4/cli/conf.d/20-tokenizer.ini
You can also run `php --ini` in a terminal to see which files are used by PHP in CLI mode.
Alternatively, you can run Composer with `--ignore-platform-req=ext-dom` to temporarily ignore these required extensions.

it is missing from your system. Install or enable PHP's dom extension. とのことなので、DOM Extensionをインストールする。

i$ sudo apt-get install php7.4-dom
[sudo] password for nakamura: 
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Note, selecting 'php7.4-xml' instead of 'php7.4-dom'
The following package was automatically installed and is no longer required:
  libfwupdplugin1
Use 'sudo apt autoremove' to remove it.
The following NEW packages will be installed:
  php7.4-xml
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 97.6 kB of archives.
After this operation, 447 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu focal-updates/main amd64 php7.4-xml amd64 7.4.3-4ubuntu2.10 [97.6 kB]
Fetched 97.6 kB in 1s (68.4 kB/s)     
Selecting previously unselected package php7.4-xml.
(Reading database ... 33161 files and directories currently installed.)
Preparing to unpack .../php7.4-xml_7.4.3-4ubuntu2.10_amd64.deb ...
Unpacking php7.4-xml (7.4.3-4ubuntu2.10) ...
Setting up php7.4-xml (7.4.3-4ubuntu2.10) ...

Creating config file /etc/php/7.4/mods-available/dom.ini with new version

Creating config file /etc/php/7.4/mods-available/simplexml.ini with new version

Creating config file /etc/php/7.4/mods-available/xml.ini with new version

Creating config file /etc/php/7.4/mods-available/xmlreader.ini with new version

Creating config file /etc/php/7.4/mods-available/xmlwriter.ini with new version

Creating config file /etc/php/7.4/mods-available/xsl.ini with new version
Processing triggers for php7.4-cli (7.4.3-4ubuntu2.10) ...

からの、 composer update を再度実行。

$ composer update
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - phpunit/phpunit[9.5.0, ..., 9.5.19] require ext-mbstring * -> it is missing from your system. Install or enable PHP's mbstring extension.
    - Root composer.json requires phpunit/phpunit ^9.5 -> satisfiable by phpunit/phpunit[9.5.0, ..., 9.5.19].

To enable extensions, verify that they are enabled in your .ini files:
    - /etc/php/7.4/cli/php.ini
    - /etc/php/7.4/cli/conf.d/10-opcache.ini
    - /etc/php/7.4/cli/conf.d/10-pdo.ini
    - /etc/php/7.4/cli/conf.d/15-xml.ini
    - /etc/php/7.4/cli/conf.d/20-calendar.ini
    - /etc/php/7.4/cli/conf.d/20-ctype.ini
    - /etc/php/7.4/cli/conf.d/20-curl.ini
    - /etc/php/7.4/cli/conf.d/20-dom.ini
    - /etc/php/7.4/cli/conf.d/20-exif.ini
    - /etc/php/7.4/cli/conf.d/20-ffi.ini
    - /etc/php/7.4/cli/conf.d/20-fileinfo.ini
    - /etc/php/7.4/cli/conf.d/20-ftp.ini
    - /etc/php/7.4/cli/conf.d/20-gettext.ini
    - /etc/php/7.4/cli/conf.d/20-iconv.ini
    - /etc/php/7.4/cli/conf.d/20-json.ini
    - /etc/php/7.4/cli/conf.d/20-phar.ini
    - /etc/php/7.4/cli/conf.d/20-posix.ini
    - /etc/php/7.4/cli/conf.d/20-readline.ini
    - /etc/php/7.4/cli/conf.d/20-shmop.ini
    - /etc/php/7.4/cli/conf.d/20-simplexml.ini
    - /etc/php/7.4/cli/conf.d/20-sockets.ini
    - /etc/php/7.4/cli/conf.d/20-sysvmsg.ini
    - /etc/php/7.4/cli/conf.d/20-sysvsem.ini
    - /etc/php/7.4/cli/conf.d/20-sysvshm.ini
    - /etc/php/7.4/cli/conf.d/20-tokenizer.ini
    - /etc/php/7.4/cli/conf.d/20-xmlreader.ini
    - /etc/php/7.4/cli/conf.d/20-xmlwriter.ini
    - /etc/php/7.4/cli/conf.d/20-xsl.ini
You can also run `php --ini` in a terminal to see which files are used by PHP in CLI mode.
Alternatively, you can run Composer with `--ignore-platform-req=ext-mbstring` to temporarily ignore these required extensions.

次は it is missing from your system. Install or enable PHP's mbstring extension. とのことなのでインストール。

$ sudo apt-get install php7.4-mbstring
[sudo] password for nakamura: 
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following package was automatically installed and is no longer required:
  libfwupdplugin1
Use 'sudo apt autoremove' to remove it.
The following additional packages will be installed:
  libonig5
The following NEW packages will be installed:
  libonig5 php7.4-mbstring
0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded.
Need to get 539 kB of archives.
After this operation, 1696 kB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://archive.ubuntu.com/ubuntu focal/universe amd64 libonig5 amd64 6.9.4-1 [142 kB]
Get:2 http://archive.ubuntu.com/ubuntu focal-updates/universe amd64 php7.4-mbstring amd64 7.4.3-4ubuntu2.10 [397 kB]
Fetched 539 kB in 2s (251 kB/s)
Selecting previously unselected package libonig5:amd64.
(Reading database ... 33180 files and directories currently installed.)
Preparing to unpack .../libonig5_6.9.4-1_amd64.deb ...
Unpacking libonig5:amd64 (6.9.4-1) ...
Selecting previously unselected package php7.4-mbstring.
Preparing to unpack .../php7.4-mbstring_7.4.3-4ubuntu2.10_amd64.deb ...
Unpacking php7.4-mbstring (7.4.3-4ubuntu2.10) ...
Setting up libonig5:amd64 (6.9.4-1) ...
Setting up php7.4-mbstring (7.4.3-4ubuntu2.10) ...

Creating config file /etc/php/7.4/mods-available/mbstring.ini with new version
Processing triggers for libc-bin (2.31-0ubuntu9.7) ...
Processing triggers for php7.4-cli (7.4.3-4ubuntu2.10) ..

これで無事 composer update ができた

$ composer update
Loading composer repositories with package information
Updating dependencies
Nothing to modify in lock file
Writing lock file
Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
Generating autoload files
26 packages you are using are looking for funding.
Use the `composer fund` command to find out more

参考

composerでPHPUnitがインストールできない - 終電23時15分って早くね?

WSL2にcomposerをインストールするのは下記を参考にした

WSL2にComposerをインストール - G STYLE

【PHP】SERVER_NAMEじゃなくてphp_unameで開発環境と本番環境を振り分ける

困ったこと

filter_input(INPUT_SERVER, 'SERVER_NAME')

これでサーバーの名前をとって、 localhost かどうかで振り分けていたが、PHPUnitを使った時に、 null で返ってきたので代替法。

解決策

php_uname('n')

これでPC名がでる。

PHP が稼動しているオペレーティングシステムに関する情報を返す https://www.php.net/manual/ja/function.php-uname

こんな感じになった。

if (mb_strpos(php_uname('n'), '***.localhost') !== false) {
// 開発環境

} else {
// 本番環境
}

参考

PHPの$_SERVER['SERVER_NAME']が取れなかったので調べた - Qiita

【PHP】MAMPでcURLを使えるようにするために

結論

  • cacert.pem(CA証明書をダウンロードする)
  • php.iniを書き換える

そもそもcURLが使える状況かどうか

  • php -m で確認
  • phpinfo() で確認

PHPでcurlを使えない場合の対処法 - Qiita

% php -i | grep cURL
cURL support => enabled
cURL Information => 7.64.1

cURLのインストール状況の確認

  • curl --version で確認
% curl --version
curl 7.78.0 (x86_64-apple-darwin20.4.0) libcurl/7.78.0 (SecureTransport) OpenSSL/1.1.1k zlib/1.2.11 brotli/1.0.9 zstd/1.5.0 libidn2/2.3.2 libssh2/1.9.0 nghttp2/1.44.0 librtmp/2.3 OpenLDAP/2.5.5
Release-Date: 2021-07-21
Protocols: dict file ftp ftps gopher gophers http https imap imaps ldap ldaps mqtt pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp 
Features: alt-svc AsynchDNS brotli GSS-API HSTS HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz MultiSSL NTLM NTLM_WB SPNEGO SSL TLS-SRP UnixSockets zstd

→使える環境は整っていそう??

だが、cacert.pemファイルが指定された場所になかったのが原因だった

php - Windows MAMP-PHP CURLの問題

エラー

$curl_error = curl_error($ch) これでエラー内容をキャッチして表示する

“error setting certificate verify locations”

対応策

macのCAファイルの証明書が原因だった。

PHPのcurlでSSL certificate problemが出る場合 | tm23forest.com

  • cacert.pem(CA証明書をダウンロードする)
  • php.iniを書き換える

MacOS - How to fix curl throwing "error setting certificate verify locations"

証明書が本来あるべき場所にない(または潜在的に読み取れない)と不平を言っている

php.iniの場所を確認

phpinfo() で確認

もういい加減覚えよう。php.iniはどこにあるのか - Qiita

php.iniに下記を追記してMAMPを再起動。

curl.cainfo =" " には、 curl_error($ch) で表示したエラーに「どこにファイルがない!」と教えてくれているので、そのパスをコピペする。

[curl]
; A default value for the CURLOPT_CAINFO option. This is required to be an
; absolute path.
curl.cainfo = "/Applications/MAMP/Library/OpenSSL/cacert.pem"

最後に

自分の場合、MAMPをworkspaceにコピーして使用していたため(Railsとかをworkspaceで管理していたためそちらに合わせた)、User/名前/workspace/MAMP/〜の中のphp.iniを変更していたため、変更が反映されなかった。

エラーが教えてくれている通り、Applicationsフォルダの中のphp.iniに追記白しろということだった。

【IoT】IoTシステムのバックエンド(クラウド設計・運用のポイント)

IoTエンジニア養成教本の学習記録です。

機能とサービスの選択

センサネットワークのバックエンドでデータを扱うには下記が必要。

  • ディスパッチ(データの受け取り)
  • プロセッシング(データ加工などの処理)
  • ストアリング(データの保存)

ディスパッチ

バイスから送られてくるデータを受け取り、適切なプロセッシングに転送する。

  • 同時接続数
    • 事前の設計時に正確な値を求めるのは難しいため、負荷試験など検証を行って算出するか、大まかな想定接続数を定める。
  • 持続的接続
    • 接続を自足させる場合と、都度接続する場合で同時接続数は大きく異なる。
    • 持続させるなら、デバイス数=同時接続数になる
    • 都度接続なら、デバイスごとに接続するタイミングをランダムにしたりすることで、同時接続数を削減させたりする。

f:id:naka_no_mura:20220227114447p:plain

  • バイスの接続エラー処理
    • バックエンドでエラーを返すと、デバイスからデータを再送する処理を実装することになるが、一定間隔で繰り返し再送すると、その都度同時接続数が超過してしまい、改善されない。
    • 再送処理には、エクスポネンシャルバックオフというアルゴリズムを取り入れると良い。

      f:id:naka_no_mura:20220227114501p:plain

    • ただし、複数のデバイスが同時刻に接続するようになっていると、間隔は指数的に空くが、やはり同じタイミングで再送を繰り返すことになるため、間隔にランダム生(ジッター)を持たせると良い。

      f:id:naka_no_mura:20220227114513p:plain

プロセッシング

受け取ったデータを保存、分析に適した形式に処理して、ストアリングサービスに渡す。

データを受け取ってすぐに処理するストリーミング処理と、一定の間隔を置いてまとめて処理するバッチ処理がある。

  • ストリーミング処理
    • ストアリングサービスに渡る際の遅延が小さく、拡張性を確保しやすい。
  • バッチ処理
    • 移動平均や、集計など、複数のデータを必要とする処理向き

    f:id:naka_no_mura:20220227114524p:plain

ストアリング

プロセッシングサービスから受け取ったデータを保存する。

【IoT】IoTに最適な通信規格とは(LPWA)

IoTエンジニア養成読本の学習記録です。

LPWAとは

  • Low Power Wide Area の略で、消費電力を抑えて遠距離通信が可能なためIoTに特化している仕様。
  • 「アンライセンス系(免許不要)」と「ライセンス系(免許必要)」がある。

LoRaWAN(アンライセンス系)

  • サブギガ帯と呼ばれる920MHz帯を使う
  • 広域通信(数km)
    • 規格上では10〜20kmで、最大無線出力(20mA)の実測値は約1.5〜6km
  • 低消費電力(20mA程度)
    • 送信時の電力が20mAで、待機中は1/100
  • 常にDevice主導の通信(Uplinkから通信開始)
  • 低データレート(1通信あたりのデータ量=11byte)
  • マルチポップ機能はなし
    • LoRaゲートウェイに対してLoRaモジュールが紐づくようになる(BLEと同じ)
  • IPではなくDev Addr(32bit)で管理

LoRaWANアーキテクチャ

f:id:naka_no_mura:20220220105310p:plain

セキュリティ

セルラーなどのように思い認証/暗号化処理はできないため、デバイス、Network Severであらかじめ、Nwskey、Apskeyという事前共有鍵(PSK)を双方で事前共有しておき、通信する際はこの鍵で暗号化の正当性を確認する。

ユースケース

一度に送信できるデータは最大11byteで小さいが、温度、湿度、加速度等のセンシングであれば数バイト程度で事足りるケースが多い。

  • インフラ(電気、水道、ガス)
  • 一次産業(農業、酪農、狩猟)
  • 産業機器(工場、重機)
  • 防災(水害、地滑り、落盤、橋梁監視)
  • GPSラッキング(子供、高齢者、ペット)

実装例

LoRaWANモジュール自体は通信モジュールなので、デバイスを制御するためにマイコンが必要。低スペックのマイコンで十分。

各システムの選定と実装のポイント

下記を考慮して総合的に判断する。

1つの方式にこだわるとだいたい無理なデバイス実装、システム構成になるため、複数の通信を組み合わせるのが良い。

  • 通信範囲
  • データ量
  • 通信頻度
  • 消費電流
  • モビリティの有無(定点か移動か)
  • IPの有無
  • 通信方向(双方向、片方向)
  • 通信量

f:id:naka_no_mura:20220220105330p:plain

引用元:https://www.gsma.com/iot/wp-content/uploads/2016/10/3GPP-Low-Power-Wide-Area-Technologies-GSMA-White-Paper.pdf

【IoT】IoTシステムの全体像

IoTエンジニア養成読本の学習記録です。

バイスゲートウェイ経由で接続する

f:id:naka_no_mura:20220220102022p:plain

データの流れは、「センサデバイス(計測)」→「ゲートウェイ(処理)」→クラウド

  • 物理線接続
    • 高速で安定だけど、物理ポートを設ける必要があるため小型デバイスや移動するデバイスには不向き
  • 無線接続
    • ゲートウェイから電波の届く範囲であればデバイスの設置場所を自由に動かせる。
    • 周波数やプロトコル規格がたくさんある
    • 規格ごとの特徴
      • 通信速度
      • 通信距離
      • 通信方向(双方向か一方向か)
      • 消費電力
    • ex)LoRaは、通信速度は数十bpsと非常に遅いが、通信距離は数キロ。消費電力も少なく、乾電池1本で数年間起動可能。

ゲートウェイからクラウドへの接続

  • ISP経由のインターネット接続
  • 専用線/WAN経由の閉域網接続
  • 通信キャリア経由の接続(無線で使えて、設置場所が自由)

センサの構成

f:id:naka_no_mura:20220220102034p:plain

センサからの信号をCPUで処理するために、OSやライブラリが搭載されている。

センサ素子とCPU(マイコン)が一体化している製品は少数で、センサ素子のみを指してセンサと称している。

どのタイミングで誰がどんなデータを付与するのか

バイスの能力や数を考慮して設計することが重要。

タイムスタンプを付与する場合、

f:id:naka_no_mura:20220220102047p:plain

f:id:naka_no_mura:20220220102054p:plain

データフォーマットの例

・固定朝の例
  31020170102102030
・カンマ区切り(CSV)の例
  31.0,20170102,102030
・JSONの場合
  {'temperature': 31.0, 'datetime': '2017-01-02T10:20:30z'}

バイス選定ガイド

  • 測定範囲(-40〜+125℃)、分解能(0.1℃、0.01℃)、精度(±0.2℃)
  • マイコンの有無(あればLinuxなどのOSからでも制御可能性あり)とデータ取得方法(I2C:Inter-Integrated Circuitであれば、C言語で開発可能)
  • データサイズと伝送速度(データサイズ=センサ1つあたりが出力するデータサイズ × データ取得頻度 × センサ数)
  • 電源と消費電力
  • 設置場所と伝送距離
  • 法規定(ex:電波法、電気通信事業法
  • 費用(「マイコン × 5 + 通信回線 × 5 」と「マイコン × 5 + ゲートウェイ × 1 + 通信回線 × 1 」どっちが安くなるか)

【SQL】ウィンドウ関数で行間比較を行う(相関サブクエリとはバイバイして可読性&パフォーマンスUP)

達人に学ぶSQL徹底指南所の学習記録です。

SQLで同一行内の列同士の値を比較するのはWHERE句で記述すればいいだけだが、異なる行を比較するのは工夫が必要。

SQLで行間比較を行いたいときは、相関サブクエリを使うのが常套手段だったが、相関サブクエリのコードはパフォーマンスと可読性が悪いという欠点がある。

そこで、「行の順列」に基づいて、手続き型言語のループの動作をSQLに持ち込んだのが、ウィンドウ関数。

概要

  • 昔は、SQLで行同士を比較するときは、比較対象のテーブルを追加して、相関サブクエリを行なっていた。
  • しかし相関サブクエリは、パフォーマンスと可読性が悪い。
  • ウィンドウ関数は可読性が高く、パフォーマンス改善も見込める。
  • 手続き型のループの動作をSQLに持ち込んだのがウィンドウ関数。

具体例

ある会社の年商を記録するテーブルを考えてみる。

前年と比較する

f:id:naka_no_mura:20220213092210p:plain

このテーブルから「前年と比べて年商が増えたのか、減ったのか、変化なしなのか」をSQLで出力してみる。

まずは、「変化なし」の年を取得してみる。

もし手続き型言語で計算するなら、

  1. 年度で昇順にソートする
  2. ループさせて1行ずつ直前の行のsale列と比較する

というやり方になるはず。

SQLだとこんな感じ。

// 相関サブクエリを利用
SELECT year, sale
  FROM sales S1
 WHERE sale = (SELECT sale FROM sales S2 where S2.yaer = S1.year - 1)
 ORDER BY year;

year  sale
----  ----
1993  52
1995  50

f:id:naka_no_mura:20220213092222p:plain

相関サブクエリで2つのテーブルにおける比較対象の行をズラすことで手続き型のループの代役をしている。

ウィンドウ関数ならこうなる。

// ウィンドウ関数を利用
SELECT year, current_sale
  FROM (SELECT year,
               sale AS current_sale,
               SUM(sale) OVER (ORDER BY year
                               RANGE BETWEEN 1 PRECEDING
                                         AND 1 PRECEDING) AS pre_sale
          FROM sales) AS TMP
 WHERE current_sale = pre_sale
 ORDER BY year;

year  sale
----  ----
1993  52
1995  50

ポイントは、サブクエリ内部のウィンドウ関数単体で実行するとわかりやすい。

// ウィンドウ関数のみで実行
SELECT year,
       sale AS current_sale,
       SUM(sale) OVER (ORDER BY year
                       RANGE BETWEEN 1 PRECEDING
                                 AND 1 PRECEDING) AS pre_sale
  FROM sales;

year  current_sale  pre_sale
----  ------------  --------
1990            50
1991            51        50
1992            52        51
1993            52        52
1994            50        52
1995            50        50
1996            49        50
1997            55        49

RANGE BETWEEN 1 PRECEDING AND 1 PRECEDING で、「カレント行の年よりも1年前に限定する」という条件で、列を一時的に追加している(フレーム句)。

時系列に歯抜けがない場合

次に、各年について、前年に比べて成長したのか、後退したのか、現状維持なのかを一度に求めてみる(音楽や映画の週間ランキングみたいな)。

// 相関サブクエリを利用
SELECT year, current_sale AS sale,
       CASE WHEN current_sale = pre_sale
            THEN ''
            WHEN current_sale > pre_sale
            THEN ''
            WHEN current_sale < pre_sale
            THEN ''
       ELSE '' END AS var
  FROM (SELECT year,
               sale AS current_sale,
               (SELECT sal
                  FROM sales S2
                 WHERE S2.year = S1.year - 1) AS pre_sale
          FROM sales S1) AS TMP
 ORDER BY year;

year  sale  var
----  ----  ---
1990     501991     511992     521993     521994     501995     501996     491997     55
// ウィンドウ関数を利用
SELECT year, current_sale AS sale,
       CASE WHEN current_sale = pre_sale
            THEN ''
            WHEN current_sale > pre_sale
            THEN ''
            WHEN current_sale < pre_sale
            THEN ''
       ELSE '' END AS var
  FROM (SELECT year,
               sale AS current_sale,
               SUM(sale) OVER (ORDER BY year
                               RANGE BETWEEN 1 PRECEDING
                                         AND 1 PRECEDING) AS pre_sale
          FROM sales S1) AS TMP
 ORDER BY year;

year  sale  var
----  ----  ---
1990     501991     511992     521993     521994     501995     501996     491997     55

時系列に歯抜けがある場合

f:id:naka_no_mura:20220213092240p:plain

年に歯抜けがなければこれでいいが、歯抜けがあると、「今年 - 1」の条件設定ではうまくいかなくなる。より一般化して、「直近」の行を比較対象にする必要がある。

まずは相関サブクエリから。

ある年から見て、「過去の直近の年」ということは、「条件1:自分より前のとしてあること」「条件2:条件1を満たす年の中で最大であること」。

// 相関サブクエリ
SELECT year, sale
  FROM sales2 S1
 WHERE sale = (SELECT sale
                 FROM sales2 S2
                WHERE S2.year = (SELECT MAX(year)  // 条件2:条件1を満たす年の中で最大
                                   FROM sales2 S3
                                  WHERE S1.year > S3.year)) // 条件1:自分より過去
 ORDER BY year;

year  sale
----  ----
1992   50
1997   55

相関サブクエリだとネストが深くなり、パフォーマンスは劣化する。

ウィンドウ関数ならこうなる。

// ウィンドウ関数を利用
SELECT year, sale
  FROM (SELECT year,
               sale AS current_sale
               SUM(sale) OVER (ORDER BY year
                                ROWS BETWEEN 1 PRECEDING
                                         AND 1 PRECEDING) AS pre_sale
          FROM sales2) AS TMP
 WHERE current_sale = pre_sale
 ORDER BY year;

year  sale
----  ----
1992   50
1997   55

ウィンドウ関数 VS 相関サブクエリ

  • ウィンドウ関数は、サブクエリを使っているが「相関」サブクエリではないので、サブクエリ単体で実行できる。結果、可読性が高く、動作も理解しやすいため、デバッグが簡単。
  • テーブルに対するスキャンも一度だけで済むので、パフォーマンスがいい。