2017年2月25日土曜日

Codeigniterのupdate_batchで複数条件指定する時の罠。(whereメソッドに注意編)

オフィス狛 技術部です。

前回に引き続き、Codeigniterの「update_batch」について説明していきます。

前回は、下記の様な例で「update_batch」の説明をしました。
$data = array(
   array(
      'emp_id' => 'A001' ,
      'name' => 'koma taro' ,
      'tel' => '090-0000-1111'
   ),
   array(
      'emp_id' => 'A002' ,
      'name' => 'koma saya' ,
      'tel' => '090-0000-2222'
   )
);
$this->db->update_batch('emptable', $data, 'emp_id');

「emptable」(社員テーブル)が更新対象のテーブル名、
「data」が更新&条件用の配列、
「emp_id」(社員ID)が更新時に条件として使用するカラムの指定、となります。

さて、ここで例えば、社員テーブルには、年度ごとにデータが格納されているとします。
(「year」(年度)というカラムがある)
つまり、2016年度と2017年度だけを考えても社員ID「A001」は2件存在する事になります。
でも、更新対象としたいのは2017年度のデータだけだとしたら・・・???

「$this->db->update_batch('emptable', $data, 'emp_id');」の第三引数は、
1つしか指定できません。

さて、困った、という事で、調べてみると、下記の情報が見つかりました。
stackoverflow - Codeigniter update_batch() with included update of the where key
上記のベストアンサーは、「Codeigniterのupdate_batchを拡張しないと無理だよ」と言っています。
さすがにそれは影響範囲大きいなぁ、と思っていたところ、ベストアンサー以外の回答で、
$this->db->where('option1', $option1);
$this->db->update_batch('table_name', $data, 'option2');
とあり、「これだったらお手軽だ」と思って、試してみました。
【注意】結論から言うと、上記では正常に動作しないので、決して真似しないで下さい。

$data = array(
   array(
      'emp_id' => 'A001' ,
      'name' => 'koma taro' ,
      'tel' => '090-0000-1111'
   ),
   array(
      'emp_id' => 'A002' ,
      'name' => 'koma saya' ,
      'tel' => '090-0000-2222'
   )
);
$this->db->where('year', '2017');
$this->db->update_batch('emptable', $data, 'emp_id');
すると、生成されたSQLは、
UPDATE `emptable` SET `name` = CASE
WHEN `emp_id` = 'A001' THEN 'koma taro'
WHEN `emp_id` = 'A002' THEN 'koma saya'
ELSE `name` END,
`tel` = CASE
WHEN `emp_id` = 'A001' THEN '090-0000-1111'
WHEN `emp_id` = 'A002' THEN '090-0000-2222'
ELSE `tel` END
WHERE `year` = '2017'
AND `emp_id` IN ('A001','A002')
となり、年度の条件も付いているので、万々歳、と言う事で、
この方式を採用する事にしました。

やっぱりダメだった


ところが、しばらくして、この処理を実行すると、
2017年度のデータと一緒に、2016年度のデータも更新されている事が判明しました。
しかも、更新されない事もある、との事。

何度か試してみると、確かに生成SQLに「`year` = '2017'」が含まれていない事がありました。

ここで改めてリファレンスと読んでみると、
..note:: $batch_size より多くの行数が渡された場合、 複数のクエリが実行され、それぞれ $batch_size のフィールド/値ペア の分だけ操作を行う。

Codeigniter『データベースリファレンス - クエリビルダクラス - update_batch 』より。
とあります。
・・・・「複数のクエリが実行される」???

先の例だと、例えば1000人の社員を一気に更新する際は、
単純に1000 ÷ batch_size(デフォルトは100)の 10 回クエリが実行されるようです。

しかも、
「$this->db->whereで指定した条件は、最初の1回目のクエリにしか反映されない」
という事のようです。

つまり、最初の1回目のクエリのみ2017年度の社員が更新されて、
2回目以降は2016年度と、2017年度の社員が両方更新されてしまっていたようです。

・・・・いや、どうせだったら、最初の1回目も条件反映しないでよ。

という事で、今回は「update_batch」を使うのをやめて、
「update」複数回実行する、という実装に変更しました。

・・・まあ、テーブル設計が杜撰なのは認めます。
きっと「update_batch」は、そういうテーブル設計を想定していないのだと思います。

「update_batch」を使う際は、ご注意下さい。

【追記】

タイトル詐欺になってしまうので、補足しておくと、
基本は、「update_batch」で複数条件指定は出来ないと思います。
やるならば、先に記載した通り、
Codeigniterのupdate_batch(system/database/drivers/DB_query_builder.php)を
拡張する事になります。


, ,

2017年2月18日土曜日

Codeigniterのupdate_batchで複数条件指定する時の罠。(update_batchの仕組み編)

オフィス狛 技術部です。

Codeigniterのクエリビルダクラスには便利なメソッドが多くありますが、
その中でも特殊なのが「update_batch」です。

複数レコードへの更新が発生する際、
ループで回し、update文を複数回実行する事も可能ですが、
この「update_batch」を使えば、一回の処理で済ませる事が出来ます。

例えば、更新用に以下の様な配列を作成したとします。
$data = array(
   array(
      'emp_id' => 'A001' ,
      'name' => 'koma taro' ,
      'tel' => '090-0000-1111'
   ),
   array(
      'emp_id' => 'A002' ,
      'name' => 'koma saya' ,
      'tel' => '090-0000-2222'
   )
);

そして、update文を実行する際は、
$this->db->update_batch('emptable', $data, 'emp_id');

となります。

「emptable」(社員テーブル)が更新対象のテーブル名、
「data」が先程作成した配列、
「emp_id」(社員ID)が更新時に条件として使用するカラムの指定、となります。

つまり、
  • 社員テーブルの社員IDが「A001」の名前を「koma taro」に、電話番号を「090-0000-1111」に変更する。
  • 社員テーブルの社員IDが「A002」の名前を「koma saya」に、電話番号を「090-0000-2222」に変更する。
という処理を行う、という事です。

普通に考えると、update文を2回実行しないといけない様に思えます。
しかし、「update_batch」の呼び出しは1回です。
一体内部ではどんなSQLに展開しているのでしょうか?
・・・という事で、実際に実行されるSQLを調べてみると、
UPDATE `emptable` SET `name` = CASE
WHEN `emp_id` = 'A001' THEN 'koma taro'
WHEN `emp_id` = 'A002' THEN 'koma saya'
ELSE `name` END,
`tel` = CASE
WHEN `emp_id` = 'A001' THEN '090-0000-1111'
WHEN `emp_id` = 'A002' THEN '090-0000-2222'
ELSE `tel` END
WHERE `emp_id` IN ('A001','A002')

なんともトリッキーですが、確かにこれだとSQL文は1つで済みます。
update文を何度も実行するよりは、処理的にも早いと思います。

でも、ここでふと疑問が。
複数条件指定する場合はどうするのだろう?

ここに罠が潜んでいるのですが、ちょっと長くなってしまったので、
次回に持ち越します。

次回:Codeigniterのupdate_batchで複数条件指定する時の罠。(whereメソッドに注意編)


, ,

2017年2月6日月曜日

CocoaPodsでAbort trap: 6が発生した場合の対処方法。

オフィス狛 技術部です。

iOSアプリのアーカイブ(リリース準備)作業をしている時に、
ある開発端末で「pod update」を行なったら、「Abort trap: 6」が発生し、
Rubyが強制終了しました。
$ pod update
Analyzing dependencies
Downloading dependencies
Installing xxxxxx (1.1.11)
Installing yyyyyy (2.2.22)
Generating Pods project
Abort trap: 6

エラーの詳細(レポート)を見ると、
Application Specific Information:
abort() called
terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Error getting value for key 'initializationClass' of extension 'Xcode.DVTKitDFRSupport.Initializer' in plug-in 'com.apple.dt.IDE.DVTKitDFRSupport''
ProductBuildVersion: 8B62
と、色々出ていますが、
こういう時は、大抵「CocoaPods」のバージョンが古い事が原因だったりします。
(実際、エラーが発生した端末では、しばらく開発作業していませんでした。)

何はともあれ、CocoaPodsのバージョンアップをしてみます。
$ sudo gem update cocoapods
Updating installed gems
Updating cocoapods
Fetching: ruby-macho-0.2.6.gem (100%)
Successfully installed ruby-macho-0.2.6
Fetching: fourflusher-2.0.1.gem (100%)
Successfully installed fourflusher-2.0.1
Fetching: nanaimo-0.2.3.gem (100%)
Successfully installed nanaimo-0.2.3
Fetching: claide-1.0.1.gem (100%)
Successfully installed claide-1.0.1
Fetching: CFPropertyList-2.3.5.gem (100%)
Successfully installed CFPropertyList-2.3.5
Fetching: xcodeproj-1.4.2.gem (100%)
ERROR:  While executing gem ... (Errno::EPERM)
    Operation not permitted - /usr/bin/xcodeproj
・・・エラーになってしまいました。
これは、macのOSがEl Capitan以降の場合、rootlessというセキュリティ機能があり、
一部のディレクトリ(/usr、/sbin、/System)に対しては、
root権限でも編集出来なくなっているからです。

仕方無いので、CocoaPodsを再インストールしたいと思います。
まずはアンイストールから。
$ sudo gem uninstall cocoapods
Remove executables:
 pod, sandbox-pod

in addition to the gem? [Yn]  Y
Removing pod
Removing sandbox-pod
Successfully uninstalled cocoapods-1.1.0.beta.1

続いて再インストールします。
インストールするディレクトリを指定しているところがポイントです。
$ sudo gem install -n /usr/local/bin cocoapods
Successfully installed xcodeproj-1.4.2
Fetching: molinillo-0.5.5.gem (100%)
Successfully installed molinillo-0.5.5
Fetching: cocoapods-trunk-1.1.2.gem (100%)
Successfully installed cocoapods-trunk-1.1.2
Fetching: cocoapods-downloader-1.1.3.gem (100%)
Successfully installed cocoapods-downloader-1.1.3
Fetching: cocoapods-deintegrate-1.0.1.gem (100%)
Successfully installed cocoapods-deintegrate-1.0.1
Fetching: cocoapods-core-1.2.0.gem (100%)
Successfully installed cocoapods-core-1.2.0
Fetching: cocoapods-1.2.0.gem (100%)
Successfully installed cocoapods-1.2.0
Parsing documentation for xcodeproj-1.4.2
Installing ri documentation for xcodeproj-1.4.2
Parsing documentation for molinillo-0.5.5
Installing ri documentation for molinillo-0.5.5
Parsing documentation for cocoapods-trunk-1.1.2
Installing ri documentation for cocoapods-trunk-1.1.2
Parsing documentation for cocoapods-downloader-1.1.3
Installing ri documentation for cocoapods-downloader-1.1.3
Parsing documentation for cocoapods-deintegrate-1.0.1
Installing ri documentation for cocoapods-deintegrate-1.0.1
Parsing documentation for cocoapods-core-1.2.0
Installing ri documentation for cocoapods-core-1.2.0
Parsing documentation for cocoapods-1.2.0
Installing ri documentation for cocoapods-1.2.0
7 gems installed
無事に再インストール完了しました。
では、改めて「pod update」します。
$ pod update
Update all pods
Re-creating CocoaPods due to major version update.
Updating local specs repositories
Analyzing dependencies
Downloading dependencies
Installing xxxxxx (1.1.11)
Installing yyyyyy (2.2.22)
Generating Pods project
Integrating client project
Sending stats
Pod installation complete! There are 9 dependencies from the Podfile and 11 total pods installed.
無事に成功しました。

急いでいる時程、こういう類のエラーが出ると焦ってしまいますが、
急がば回れで対応すると、案外あっさり解決するものです。


, ,

2017年2月4日土曜日

PHPで Fatal error: Uncaught exception 'ErrorException' with message date() が発生した場合の対処方法

オフィス狛 技術部です。

以前、別の記事で、PHPのインストール(バージョンアップ)を説明しましたが、
その流れで発生するエラーについて、対象方法を記載しておきます。

エラーは
Fatal error: Uncaught exception 'ErrorException' with message date()
です。

さらに詳細は、
Fatal error: Uncaught exception 'ErrorException' with message 'date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone.
となります。要は、
「タイムゾーンが指定されていないよ。指定されていないと、システムのタイムゾーン使うけど、それは安全じゃないよ。一応今はUTCが選択されているけど、ちゃんと自分で指定してね」
という事です。
※PHPのインストール直後は、php.ini がデフォルトの状態になっているので、タイムゾーンは未設定となっています。

PHPのタイムゾーンを設定します

いざ、設定しようとする時、いつも思うのが、
「あれ?php.ini ってどこにあったかな?」
という事です。

まずは、php.ini の場所を探し出します。
$ php -i | grep php.ini
Configuration File (php.ini) Path => /opt/remi/php56/root/etc
Loaded Configuration File => /opt/remi/php56/root/etc/php.ini
Loaded Configuration File にあるのが、パスになります。

あとは、php.ini 内の「date.timezone」を変更するだけです。
例えば、日本だったら、「date.timezone = 'Asia/Tokyo'」という感じです。

PHPのインストールをする場合は、ここまでワンセットで行なっておくのが確実です。
(私はいつも忘れてしまいますが。。。)


,