MySQL + UPDATE + PDOStatement::rowCount の罠

MySQLのセットアップはテーブル作成とかも含めて、非常に面倒なので、作成途中(開発中)はSQLiteで作って動作確認して、作成大詰めの段階でMySQLに切り替え完成・・・という工程はわりかし一般的?だと思う。ファイル一個でバックアップ・リストアも簡単で開発効率も上がります、僕は。

で、SQLite + PDO で何の問題もなくある程度コーディングが終わり、MySQLに移行して検証していると、おかしな挙動の解決に半日かかってしまった備忘録のエントリです。

データを空更新、特定の行を取得して、編集画面表示、その後、内容を変えずに同じデータで更新すると、update文は成功するのに、作用した行数が0を返す・・・。だいたい下のような感じ。

<?php
/*************************************************************

  あらかじめ下記mysqlクライアントで実行

  >> CREATE TABLE test_table(id INTEGER,data CHAR(255));
  >> INSERT INTO test_table values(1,'Kenji Nakagawa');

**************************************************************/
$pdo = new PDO('sqlite:log.sqlite');

if(modify_name(1,'Kenji Nakagawa'))
{
  header('location: list.php');
}

function modify_name($id,$name)
{
  global $pdo;

  $rv = false;
  $sql = 'update test_table set name = ? where id = ?';

  if(false !== ($stmt = $pdo->prepare($sql)))
    {
      if(false !== ($result = $stmt->execute(array($name,$id))))
        {
          $rv = $stmt->rowCount();
        }
    }

  return $rv;
}

この一連のコーディングでの最大の失敗は、rowCount()メソッドが返す行数で、update文の成功・失敗を判断したところ。分かってしまえば、何でもないことだけど、はまってしまった。

分かったことは、

MySQL + UPDATE文の実行では、PDOStatement::rowCount() は「実際に変更した(あった?)行数」(←ここ重要)を返す

・・・・ということ。

元々のレコードと同じデータをupdateすると、update文の実行はfalseは返さない(成功する)が、rowCount()は1ではなく、0を返す・・・。PHPサイトのドキュメントを検索したらちゃんと書いてありました・・・・とほほ(T-T)

UPDATE を使用する場合、MySQL では新旧の値が同じときには更新処理を行いません。 このことから、必ずしも mysql_affected_rows() の返す値が マッチする行の数と一致するとは限りません。返す値は実際に更新処理が行われた 行の数です。

そんな・・・・僕が勉強したときにちょろっと読んだMySQL入門書には書いてない!(笑)

ってわけで、解説書なんかで勉強するときは、いかに良い本に巡り会うことの重要性を改めて痛感しました。。。

MySQL Workbenchのメニューを日本語に。

※2015年5月12日 追記
グーグル検索などでこの記事に辿りつくアクセスが多いのですが、この記事はもう最新のバージョンにはあてはまりません。ご注意を。OneDriveにおいてあるファイルを最新のバージョンには絶対適用してはいけません。おそらくWorkbench自体がエラーで立ち上がらなくなるでしょう。記録のためだけに置いてあります。あしからず、ご了承くださいm(__)m

追記ここまで。 続きを読む

MySQL for Excel

知らない間に、MySQL for Excel という素敵なツールがあるのに気づき(遅すぎ)、会社のPCにダウンロードしてインストールして、テストサーバー(CentOS6 / MySQL 5.1)に接続してみました。。。

家のパソコンにはエクセルなんて無用の長物なのでインストールしていないし、持ってもいない。

結果、撃沈。

Authentication with old password no longer suppored. Use 4.1+ style….

とかいう、エラーダイアログが出て止まる。んー、ユーザーの設定は全部phpMyAdmin経由で使用しているから、古いパスワードが埋め込まれてんのかな?と思い、sshで接続してmysqlコマンドで、下記確認。

select user,host,password from mysql.user;

新しいパスワードは、アスタリスク(*)から始まるけど、全部16文字の古い形式やった。。。どおりで通らんはず。

全部パスワードを設定しなおしたった。

mysql

これで、MySQL for Excel で無事に接続でけた。

テーブルのインポートから・・・mysqlexcelでもデータのインポートだけなら、Connector/ODBCをインストールしてODBC 経由で普通にできるやん!という無粋なことは言っちゃいけない。

でもって、テーブルのレコード追加・編集まで・・・使い慣れた表計算グリッドでデータをぶち込めたり、上書きできたり、という。素敵!

mysqlexcel2

しかし・・・結局のところ、エクセル使いの方は、アクセスを使うと思うし・・・Windowsで飯を食ってる人はそもそもSQLServer使うし・・・、MySQL for Excelは誰が何のために使うのだろうか・・・? そもそもMySQLを日常的に使用する開発者がわざわざExcelなんて使うだろうか? ふつうはOpen Officeでそ?僕なんて、いまだにIBMのLotus Symphony 3だし。

ってわけで、HDDの肥やしになる。

PHPで形態素解析とMySQLで全文検索

備忘録メモです。長ったらしいタイトルっす。

ブログの簡易版みたいなスクリプト(管理者だけが書き込める掲示板みたいなやつ)の改造をちょっと前に依頼されたんですが、その中で検索機能(全文検索)を付けるというのがありました。全文検索っていっても、入力された単語にマッチしたレコードを全部表示する、要はSQLクエリーのselect文でlike演算子でマッチさせるだけでいい、ということだったんですが、ただでさえ、面白くないPHPの仕事だし(^^;;;、それだけでは僕にとっても得るものが少ないので(^^;、もうちょっと勉強になるものを作ってみよう、ということで調べました。

仕事しながら勉強って・・・ま、いいか。

日本語の文章をMySQLで全文検索させるには(FULLTEXTインデックスってことね。)、まず日本語の文章を形態素解析にかけて、名詞・動詞・助詞・・・といった風に分解することから始めなければいけません。英文などでは単語間は必ずスペースもしくはカンマで区切られますから、特に意識しなくても済むのですが、日本語の文章や主にアジア圏の言語では、そう簡単にはいきません。

幸いにもフリーで使用できる形態素解析エンジンは結構豊富にあります。有名なものとして、KAKASIやMeCab、Igoなどがあります。が、一般人が実際に使用するには、結構ハードルが高いものです。

まず、何よりレンタルサーバーなどの一般的なサーバーではほぼこのようなライブラリはインストールされていませんし、新たに追加できることは不可能でしょう。でも、最近では非常に低コストのVPSサーバーがあるので、それなりに知識がある人は導入できるでしょうけど、サーバー管理の知識がない方にとっては難しいでしょう。

ただ、PHP、しかも、レンタルサーバーでも利用できる・・・という条件だと、選択肢は非常に限られると思います。その中でも小規模なサイトに必要十分なものとして、Igo-PHPが手軽に利用できて、サイトへの組み込みも少ない工数で行えると思います。

Igo-PHPは、Javaの形態素解析エンジンIgoのPHP移植版で、Igo同様、MITライセンスで自由に利用できるというありがたいライブラリです。作者に感謝です。

流れとしては・・・

  1. Igo-PHPのダウンロード
  2. Igo本体のダウンロード(辞書生成に必要。別途Java実行環境が必要)
  3. 辞書の元となるファイルをダウンロード(MeCabサイト→ダウンロード→Mecab用の辞書(IPA辞書)
    (2017-10-22 リンク先修正)
  4. 2)でダウンロードしたIgo(Javaプログラム)を使用して辞書を生成。
    3)でダウンロードしたファイルを展開し、以下のコマンドをうつ。Windowsだと java.exeがあるディレクトリが%WINDIR%や%PROGRAMFILES%にあったりと環境によって違うと思います。

    >> java -cp igo-0.4.3.jar net.reduls.igo.bin.BuildDic ipadic mecab-ipadic-2.7.0-20070801 EUC-JP

といった感じになります。これらは全部Windows上で行えます。あとは・・・PHPスクリプトからIgo-PHP、生成した辞書を使って形態素解析ができます。

生成されたipadicディレクトリに辞書がビルドされていますので、以後、このipadicディレクトリとIgo-PHPだけを使用します。

WindowsにPHPをインストールされている方は、下記のようなスクリプトを作成して実行してみてください。

<?php
// test.php

// Igo-PHPとipadicディレクトリを 'lib'というディレクトリにまとめて置いとく。
require_once 'lib/Igo.php';
 
$igo = new Igo("./lib/ipadic");
$text = "私には夢がある。";

// 詳細な結果が欲しい場合は、parseメソッド
//$result = $igo->parse($text);

//単に区切ればいいだけなら、wakatiメソッド
$result = $igo->wakati($text);

//それぞれ、単語の配列が返ります。

echo mb_convert_encoding(implode('/',$result),'SJIS');

実行すると、こんな結果がでました。ちゃんと分解されてますね。

>>php test.php
私/に/は/夢/が/ある/。

さて、検索される文章・記事は、MySQLのデータベースに入れることが多いのでMySQL + PHPでの組み込みを中心に。

MySQLでテーブルを作成する際に、記事などを格納させるカラムとは別に、検索用のカラムを一個追加して、そのカラムにFULLTEXTインデックスを張ります。以下のような感じですかね?

CREATE TABLE  posts (id INT,title TEXT,content TEXT,content_s TEXT,FULLTEXT(content_s));

というようなテーブルを用意しておいて、INSERT,UPDATEする際に、contentの内容を形態素解析にかけ、名詞のみ抜き出し、content_sに抜き出した単語を半角スペース区切りでつなげた文字列を格納しておく。要は検索インデックスをcontent_sに貯めておくという方法です。

英語ならば、語と語は必ずスペースで区切られるから、こんなめんどっちーことをしなくてもいいんですが・・・。
データをINSERTするときは、こんな感じでしょうか。

<?php
$igo = new Igo("./lib/ipadic");
$pdo = new PDO('mysql:dbname=testdb;host=localhost','dbuser','password');

//テーブル作成
$pdo->exec('CREATE TABLE posts (id INT,title TEXT,content TEXT,content_s TEXT,FULLTEXT(content_s))');

//テストデータを用意
$id = 1;
$title = '私には夢がある。';
$content = '私には夢がある。私の四人の幼い子ども達が、いつの日か肌の色ではなく人格そのものによって評価される国に住めるようになるという夢です。';

//形態素解析
$result = $igo->wakati($content);
$content_s = implode(' ',$result);

//INSERT文組み立て
$sql = sprintf("INSERT INTO posts(id,title,content,content_s) VALUES(%d,'%s','%s','%s')",
               $id,
               $pdo->quote($title),
               $pdo->quote($content),
               $pdo->quote($content_s));

//クエリー実行
$pdo->exec($sql);

$pdo = null;
$igo = null;

こんな感じでデータをどんどん追加して、

で、全文検索させたいときは、contentカラムを検索するのではなく、content_sカラムを全文検索させるように、

SELECT * FROM posts WHERE MATCH(content_s) AGAINST('検索単語',IN BOOLEAN MODE);

と、するだけ。

ただ、これだけだと、ちょっと不便なことがあります。形態素解析にかけると、辞書にある単語を元に解析するので、二つ以上の名詞がくっついて一つの名詞になるような語がバラバラになってしまいます。

たとえば、「神戸市」の結果は、「神戸」と「市」に分かれてしまいます。「神戸市」と一つの単語で登録したい場合などは、ひと手間かける必要があります。

全文検索以外の他の用途でも使えそう。漢字混じりの文章をすべて平仮名や片仮名に変換したりも可能なので、使い道は結構あると思います。

またデスクトップアプリの機能として形態素解析エンジンを使いたい場合は、.NETアプリで使用できるNMeCabというMeCabの.NET移植版がありますし、形態素解析というと、ものすごく難しい、というイメージですが結構簡単に自分のアプリにも組み込めたりできますので、もっと活用の場があってもいいと思います。