ラベル mysql の投稿を表示しています。 すべての投稿を表示
ラベル mysql の投稿を表示しています。 すべての投稿を表示

2012年9月4日火曜日

MySQLでvarcharカラムをintと比較するとおかしな結果になる

9月7日に真相(?)について追記しました。

自明のことだがSQLではWHERE句で指定した条件にマッチしたレコードのみを取得することが出来る。だが、MySQLでは文字列型のカラムに対して = 数値 という条件にすると、おもしろい(おかしな)結果になる。

対象テーブルの定義とデータ


以下のようにとてもシンプルなテーブルがある。

CREATE TABLE `string_table` (
`id` int(11) NOT NULL,
`value_string` varchar(1024) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

データはそれなりにたくさん入っている。

select count(*) from string_table;
→ 296,458行

クエリとその結果


まずは普通に文字列と完全一致で検索。

select * from string_table where value_string = 'aaa';


これは自明。
では次に0と完全一致で検索。予想では型が異なるのでSQLエラーになると思いきや、

select * from string_table where value_string = 0;

と、結果が取得出来る。それも件数を確認すると296,442行となっており、ほぼ全てのレコードが検索結果として出ていることが分かる。???なんだこれは。

ということで似たような条件で検索してみると…

select * from string_table where value_string = 1;


SQLはエラーにならず、結果は0件。

select * from string_table where value_string = 5;


SQLは当然エラーにならず、結果は3件。

select * from string_table where value_string like '5%';

で検索してみた結果と同一だったので、先頭が5の文字列のレコードがヒットしている。

結論


MySQLでは文字列型のカラムに対して条件を integer で指定すると、思わぬレコードが取得できてしまう。

なんでこうなるのかが気になるので色々と調べてみたが理由が分かっていない…。


真相


ブログを見てくれた方から情報をいただいた。


http://blog.usoinfo.info/article/180598548.html
解説はほぼ上記のブログで完結しているが、少しだけ補足する。

MySQLでは、左辺と右辺の型が異なる場合、どちらかに合わせて(変換して)比較が行われる。

文字列と文字列であれば変換は発生しないので意図した通りの結果になるが、今回は左辺が文字列で右辺が数値になっている。この場合、MySQLでは左辺と右辺の両方を数値に変換してから比較を行う。今回のケースでは左辺には既存のレコードの値が入ってくるが、数字になり得ないもの( aaaaa, bbbbb, aa033e など)が来た場合は全て0に変換されてしまう。

今回の検索結果の1行目を例にすると、

select * from string_table where value_string = 0;

↓ (1行目のデータが入ったとすると)

select * from string_table where 'sc167' = 0;

↓ (sc167は数字になり得ないので0に変換される)

select * from string_table where 0 = 0;

この時、 where句は true となるためこのレコードが抽出結果として出てくる。

なお、=5 の時は先頭が5となっている文字列が全て抽出されている。
どうやらMySQLは先頭が数字で始まっている文字列は数字が終わるまでの部分で数値に変換してくれるようだ。

とてもすっきりした。

情報をくださった方、ありがとうございました。

補足


そもそも、こんなことが起きたのは Prepared Statement を使用して php から MySQL のデータを取得していたのだが、埋め込むパラメータを変数で渡していたのだが、明示的に文字列にキャスト等をしておらず、文字列が全て数字で構成されていた場合に文字列ではなく数値と扱われてしまい、最終的に生成されたSQLが = 0 というようになってしまって今回の現象が起きた。

ちなみに Oracle で = 0 を試したところ結果は0件だった。SQLの仕様ではなさそうだが真相はよく分かっていない。
ソースを読めば分かるけどね…。

2012年1月4日水曜日

JBoss4.2にMySQLに接続するためのDatasourceを作ってS2JDBCで接続する

JBoss の Datasource を経由して MySQL に接続してみた。

1.準備

JBoss は 4.2.3GA を準備。
MySQL は 5.1.58 community を準備。

インストール後に以下の手順が必要になります。

1.1. データベーススキーマの作成

MySQLに任意の名前でスキーマを作成します。既に存在していればそれを使用しても構いません。
詳細は省略します。

1.2. MySQL用のJDBCドライバーを配置

ここ( MySQL :: Download Connector/J )から対応するJDBCドライバーをダウンロードします。
その後、 mysql-connector-java-5.1.6.jar を以下のディレクトリに配置します。

{JBossのインストールディレクトリ}/server/default/lib/

2.Datasource の定義ファイルを作成

2.1. ファイルの作成

以下のようにファイルを作成する(中身は空で良い)

ディレクトリ:{JBossのインストールディレクトリ}/server/default/deploy
ファイル名:{任意の名前}-ds.xml

ちなみに自分はここでファイル名を ds-hogehoge.xml としてしまったために、
JBossが以下のように表示するだけで全く読み取ってくれなくて悩んでたのは秘密。
いやー、ホント悩んだ。

--- Incompletely deployed packages ---
org.jboss.deployment.DeploymentInfo@13376b0a { url=file:/C:/Servers/jbos-4.2.3.GA/server/default/deploy/ds-hogehoge.xml }
deployer: null
status: null
state: INIT_WAITING_DEPLOYER
watch: file:/C:/Servers/jboss-4.2.3.GA/server/default/deploy/ds-hogehoge.xml
altDD: null
lastDeployed: 1325039984621
lastModified: 1325039984621
mbeans:

ちなみにこちらのサイトの情報でファイル名の問題に気づきました。

2.2. Datasource の定義を記述

2.1.で作成したファイルに以下のように記述する。


  
    JNDI名
    jdbc:mysql://ホスト名:ポート番号/スキーマ名?useUnicode=true&
    characterEncoding=UTF-8
    
    com.mysql.jdbc.Driver
    ユーザー名
    パスワード
    org.jboss.resource.adapter.jdbc.vendor.
     MySQLExceptionSorter
    8
    8
    0

    
    
       JNDI名
    
  


3.JBoss に Datasource が登録されているか確認

この状態で JBoss を起動。以下のように表示されJNDIに登録されればとりあえず成功です。

15:21:23,170 INFO  [ConnectionFactoryBindingService] Bound ConnectionManager 'jboss.jca:service=DataSourceBinding,name=JNDI名' to JNDI name 'java:JNDI名'
15:21:23,189 INFO  [TomcatDeployer] deploy, ctxPath=/jmx-console, warUrl=.../deploy/jmx-console.war/
15:21:23,316 INFO  [Http11Protocol] Coyote HTTP/1.1を http-127.0.0.1-8080 で起動します
15:21:23,330 INFO  [AjpProtocol] Starting Coyote AJP/1.3 on ajp-127.0.0.1-8009
15:21:23,340 INFO  [Server] JBoss (MX MicroKernel) [4.2.3.GA (build: SVNTag=JBoss_4_2_3_GA date=200807181417)] Started in 7s:379ms

4.S2JDBC経由でデータベースにアクセス

登録したJNDIを使ってデータベースに接続するにはアプリケーションの jdbc.dicon を以下のように記述。


  

  
  
    
      
    
    100
  

  
        @org.seasar.extension.j2ee.JndiResourceLocator@lookup("java:データソース名")
  



自分はこれで出来たが、諸事情により実行結果の画面やログは省略させてもらいます。

今回参考にさせていただいたサイト

JBossにMySQL用のデータソースを定義する方法について参考にさせていただいた
JBoss/MySQLと接続する / http://www.masatom.in/pukiwiki/JBoss/MySQL%A4%C8%C0%DC%C2%B3%A4%B9%A4%EB/

S2JDBCでJBossのデータソースを利用する方法について参考にさせていただいた
APサーバのコネクションプールを使う : JDBCの設定 / http://s2container.seasar.org/2.4/ja/jdbc.html#GenericApplicationServer

途中でそもそもJBossって何だ?ってなった時に参考にさせていただいた(JBossってTomcatを含んでるものなのね。そりゃtomcatより起動とか遅いわけだ…
TomcatとJBossの違い - OpenGroove : http://open-groove.net/tomcat/difference-tomcat-jboss/