2023-04-14/2023-05-23

WordPressで小数点の比較がうまくいかない場合

WordPressで小数点の比較がうまくいかない場合

WordPressのMeta Queryで小数を比較するときの注意点について説明します。数字だからとりあえず「NUMERIC」を「type」に指定している場合は要注意です。

ほかの数字を扱う「SIGNED」「DECIMAL」などと比較しながらどのように取得すれば、小数でも意図したデータの取得ができるのか見ていきましょう。

Meta Queryによって生成されるSQL

Meta QueryでどのようにWHERE文が作成されるのかを確認しておきます。作成されたWHERE文を検証の際にも利用します。

下記はmeta_keyが「sample_meta_key」でmeta_valueが1.0以上のものを取得するものです。

$args = [
'meta_query' => [
[
'key' => 'sample_meta_key',
'value' => '1.0',
'type' => 'NUMERIC', // SIGNED DECIMAL
'compare' => '>=',
],
],
];
$query = new WP_Query( $args );

NUMERIC

( wp_postmeta.meta_key = ‘sample_meta_key’ AND CAST(wp_postmeta.meta_value AS SIGNED) >= ‘1.0’ )

SIGNED

( wp_postmeta.meta_key = ‘sample_meta_key’ AND CAST(wp_postmeta.meta_value AS SIGNED) >= ‘1.0’ )

UNSIGNED

( wp_postmeta.meta_key = ‘sample_meta_key’ AND CAST(wp_postmeta.meta_value AS UNSIGNED) >= ‘1.0’ )

DECIMAL

( wp_postmeta.meta_key = ‘sample_meta_key’ AND CAST(wp_postmeta.meta_value AS DECIMAL) >= ‘1.0’ )

検証用のデータ

それぞれの比較や正しく比較できているかを検証するようのデータを下記のようにします。point列はwp_postmetaテーブルのmeta_valueと同じくlongtext型を設定しています。

+-------+
| point |
+-------+
| 1.5 |
| 1.6 |
| -1.5 |
| -1.6 |
| 2 |
| 3 |
| 2.5 |
| -5.0 |
| 1.0 |
+-------+

うまくいくパターンとうまくいかないパターン

基本的に上で紹介したパターンの場合うまくいくかないケースが多いですが、場合によってはうまくいきます。検証用のデータをMeta Queryで生成されるSQLを使用して取得して確認してみます。

うまくいくパターン

比較対象の数値が「1.0」や「2.0」など小数でも整数でも変わらない場合、影響が少ないです。下記の場合、想定している通りの結果を得ることができます。

SELECT point FROM test WHERE CAST(point AS SIGNED) >= '1.0' ORDER BY point ASC
+-------+
| point |
+-------+
| 1.0 |
| 1.5 |
| 1.6 |
| 2 |
| 2.5 |
| 3 |
+-------+

うまくいかないパターン

比較対象の数値が「1.1」など小数の場合想定してない結果となってしまいます。

SIGNEDの場合「1.5」と「1.6」が取得できていません。DECIMALの場合「1.5」と「1.6」を取得してしまっています。

SELECT point FROM test WHERE CAST(point AS SIGNED) >= '1.1' ORDER BY point ASC
SELECT point from test WHERE CAST(point AS DECIMAL) >= '1.8' ORDER BY point ASC
+-------+
| point |
+-------+
| 2 |
| 2.5 |
| 3 |
+-------+
+-------+
| point |
+-------+
| 1.5 |
| 1.6 |
| 2 |
| 2.5 |
| 3 |
+-------+

うまくいかない原因

なぜ上記のやり方は失敗するのでしょうか。

それはCASTの型が間違っているからです。

SIGNEDでは符号付の整数にキャストされます。その結果「1.5」は「1」、「1.6」も「1」になります。「1.0以上」では取得でき、「1.1」以上で取得できないのはこういうことです。

-- result 1
SELECT CAST('1.1' AS SIGNED)

DECIMALの場合小数点第一位が四捨五入された整数にキャストされます。その結果「1.5」は2、「1.6」も2となります。よって「1.8以上」でも「1.5」「1.6」が取得されてしまいます。

-- result 2
SELECT CAST('1.5' AS DECIMAL)

どうすれば意図した結果となるか

意図した結果とするにはやはりキャストが必要です。上記のDECIMALの方法に少し手を加えると意図した結果となります。

DECIMALは引数をとることができます。DECIMAL(5,2)とした場合、最大桁数が5、小数点の桁数が「2」となります。(-999.99 ~ 999.99)

上記のパターンに当てはめてみます。

SELECT point from test WHERE CAST(point AS DECIMAL(3,2)) >= '1.8' ORDER BY point ASC;
+-------+
| point |
+-------+
| 2 |
| 2.5 |
| 3 |
+-------+

無事意図した形で取得できています。「1.8」以上で取得されてしまっていた「1.5」や「1.6」が排除されています。

これをMeta Queryで利用すると下記のようになります。利用する数値によって桁数や小数点桁数を変更してください。

$args = [
'meta_query' => [
[
'key' => 'sample_meta_key',
'value' => '1.0',
'type' => 'DECIMAL(3,2)',
'compare' => '>=',
],
],
];
$query = new WP_Query( $args );

まとめ

WordPressの公式サイトなどを見ると、使用できる値に「‘NUMERIC’, ‘BINARY’, ‘CHAR’, ‘DATE’, ‘DATETIME’, ‘DECIMAL’, ‘SIGNED’, ‘TIME’, ‘UNSIGNED’」となっています。

数値系全部試したけどうまくいかず、解決に結構時間がかかったのでまとめました。

今回はサンプルのため「>=」を条件として利用していますが、「BETWEEN」などを使用するとたまたまうまくいくことがなく、一目瞭然で結果がおかしいことに気づけます。

2020 KumaTechLab.