読者です 読者をやめる 読者になる 読者になる

奇特なブログ

「殊勝に値する行いや心掛け」を意味する、奇特な人になる為のブログです

型なし(PHP、MySQL)と型あり(Java、Oracle)の違い

まず、結論を。

「型なしでも、型を強く意識して開発する事」です。

次に、型なしと型ありの定義ですが、

ここでは、数値(int型等)と文字列(String型等)の比較が「出来るかどうか」とします。

型なしは「出来て」、型ありは「出来ない」です。

また、結構恐い内容も出てきますが、

別に脅すつもりは更々ありません。

単に事実を知って頂きたいと思っているだけです。

1.条件分岐において



以下は、各種バージョン情報です。

OS



Fedora12

PHP



5.3.0

Java



1.6.0.10

・まず、PHPで書いた場合。

-------------------------------------------------------


<?php

$b = 0;
$c = 'a1';

if($b == $c) {
echo "bとcは同じです\n";
} else {
echo "bとcは違います\n";
}

?>

結果:

[root@www ~]# php a.php

bとcは同じです

[root@www ~]#


-------------------------------------------------------

なぜ、こうなるのかは以下辺りのマニュアルをご参照下さい。

PHP: 比較演算子 - Manual

PHP: 文字列 - Manual

・一方、Javaで書いた場合。

-------------------------------------------------------


class a {

public static void main(String args[]) {

int b = 0;
String c = "a1";

if(b == c) {
System.out.println("bとcは同じです");
} else {
System.out.println("bとcは違います");
}
}
}

結果:


[root@www ~]# javac a.java
a.java:8: 型 int と java.lang.String は比較できません。
if(b == c) {
^
エラー 1 個
[root@www ~]#

-------------------------------------------------------

「javac」というのは、あくまでコンパイルするだけであって、

プログラムを実行するわけではありません。

実行するには、「java」と入力しますが、

javaを実行するには、javacでエラーが1件も出ない事が条件になります。

つまり、上記の「やっている事は同じコード」でも、

PHP→実行可能

Java→実行不可能

となるわけです。

ちなみに、ちょっと恐い話ですが、PHPにおいて以下の様なケースも。

-------------------------------------------------------



<?php

// WEB画面上で入力したパスワード
$input_password = '0010000';
// DBに保存されているパスワード
$db_password = '10e3';

if($input_password == $db_password) {
echo "パスワードが同じです\n";
} else {
echo "パスワードが違います\n";
}

?>


結果:

[root@www ~]# php b.php

パスワードが同じです

[root@www ~]#

-------------------------------------------------------

上記は、個人的に浮動小数点数が苦手なので、はっきり分かりませんが、

$input_password = (float) '0010000';

$db_password = (float) '10e3';

var_dump($input_password);

var_dump($db_password);

上記の結果が、

float(10000)

float(10000)

となるので、おそらくfloatに変換してから比較していると思われます。

更にちなみに、文字列型以外でも安心は出来なくて、

if(0 == false)

上記も「true」となります。

・解決方法

マニュアルにもヒントが書いてありますが、以外と簡単で、

「==(イコール2つ)」を、「===(イコール3つ)」にする

です。

個人的には、パッと見で型を判断出来るメリットがあると思い、

文字列型だけはstrcmp関数を使って比較しています。

2.WHERE句の比較において



以下は、各種バージョン情報です。

OS



Fedora12

MySQL



5.1.41

Oracle



11.1.0.6.0

・まず、MySQLの場合

-------------------------------------------------------

テーブル作成〜データ確認


mysql> CREATE TABLE a (
-> user_id VARCHAR(100) NOT NULL primary key,
-> password VARCHAR(100) NOT NULL
-> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.03 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into a values('a', 'b');
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from a;

user_id password
a b

1 row in set (0.00 sec)

mysql>

ここから、以下のSQL文を実行します。


mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> delete from a where user_id = 0;
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from a;
Empty set (0.00 sec)

mysql>

-------------------------------------------------------

上記の通り、「0」と「'a'」が同じと見なされて、

データが削除されています。

なぜ、こうなるのかは現時点では判明していませんが、

PHPと同じく、0と'a'の比較時に、

'a'が0に変換されてから比較されていると予想します。

実際、DELETE文のWHERE句を0→1に変更したら、

データは削除されなかったので。

・一方、Oracleの場合

-------------------------------------------------------

テーブル作成〜データ確認


SQL> CREATE TABLE a (
user_id VARCHAR2(100) NOT NULL primary key,
password VARCHAR2(100) NOT NULL
);

Table created.

SQL> insert into a values('a', 'b');

1 row created.

SQL> commit;

Commit complete.

SQL> select * from a;

USER_ID PASSWORD
a b

SQL>

ここから、以下のSQL文を実行します。


SQL> delete from a where user_id = 0;

delete from a where user_id = 0
*
ERROR at line 1:
ORA-01722: invalid number

SQL>

-------------------------------------------------------

上記の様に、「0」と「'a'」が別と見なされてエラーが発生しました。

ちなみに、DELETE文のWHERE句を0→'0'に変更した場合。

エラーは発生しなくなりますが、やはり'a'とは一致せずに正常に終了します。

そして、上記のMySQLの挙動が、以下の処理の様な時に問題となります。

-------------------------------------------------------


<?php

$user_id = 'test';
$result = null;

$conn = new mysqli('ホスト名', 'ユーザー名', 'パスワード', 'データベース名', ポート番号);

$conn->set_charset('utf8');

$conn->autocommit(false);

$stmt = $conn->prepare('DELETE FROM a WHERE user_id = ?');

$stmt->bind_param('i', $user_id);

$stmt->execute();

if(0 < $stmt->affected_rows) {
$result = "削除成功\n";
$conn->commit();
} else {
$result = "削除失敗\n";
$conn->rollback();
}

$stmt->close();

$conn->close();

echo $result;

?>

結果:

[root@www ~]# php bind_param_test.php

削除成功

[root@www ~]#

上記の実行時に発行されるSQLは以下の通りです。

MySQLのクエリログから抜粋しています。

Prepare DELETE FROM a WHERE user_id = ?

Execute DELETE FROM a WHERE user_id = 0

-------------------------------------------------------

やはり、データが削除されます。

ポイントは、mysqli_stmt::bind_param(マニュアル)の第1引数です。

'i'は数値型を期待しているのに、文字列型を渡している為、

文字列→数値への変換により予期せぬ値になってしまっています。

・解決方法

変数の型(上記では$user_id)が文字列型で、

DB内のカラムの型(上記ではuser_id)がVARCHAR(文字列型)なのですから、

mysqli_stmt::bind_paramの第1引数も's'とすればよいだけです。

第2引数以降を文字列型と判断し、シングルクォーテーションで囲んでくれます。

PostgreSQLは?

PostgreSQL8.4.4において。

VARCHARで定義されているカラムの値(例:'a'など)と、

数値型の値(例:0など)を比較しようとすると、「エラーになります」。

なので、この部分についてはOracleと同じの様です。

3.まとめ



PHP試験が秋に開始、オライリー本が教科書 − @ITや、

LAMP経験者のニーズ高し。ポテンシャル採用も − @IT自分戦略研究所を見ての通り、

PHPMySQLに対するニーズが非常に高まってきています。

よって、これからプログラミングを始める人達も、

PHPMySQLからというケースも多いと思います。

とりあえず、そういった方を見かけたら、

可能な限り、「型の存在と重要性」を伝えてあげれば良いのではないでしょうか。