MySQL - одновременное приращение и присвоение поплавка в разрывах SELECT до 5.6
Почему запросы типа SELECT (@sum:=(@var:[email protected])+some_table.val)...
автоматически присваивают (@var:[email protected])
целому числу в MySQL ≤5.5 для DECIMAL
-type some_table.val
и заполняют его для DOUBLE/FLOAT
? Какая функция была изменена, чтобы обеспечить ожидаемое поведение в 5.6?
Контекст и разработка:
Рассмотрим следующую таблицу:
CREATE TABLE t (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
sum_component FLOAT
);
INSERT INTO t (sum_component) VALUES (0.5), (0.6), (0.4), (0.5);
Я создаю кумулятивный запрос суммы, который извлекает id
записи, где суммарная сумма больше некоторого значения. Обычно этот запрос будет соответствовать счету:
SELECT t.id,
@cumulative_sum
FROM t
CROSS JOIN (SELECT @cumulative_sum:=0) a
WHERE (@cumulative_sum:[email protected]_sum+t.sum_component) > 1.3
ORDER BY id ASC LIMIT 1;
... но мне также необходимо сохранить кумулятивную сумму до выбранной записи, а также для последующих вычислений, а cumulative_sum
из этого запроса не возвращает ожидаемый результат, дважды подсчитывая последнюю запись. В этом случае я хотел бы, чтобы этот запрос задал переменную, которая хранит значение 1.1
(0.5 + 0.6
), не требуя дополнительной математики.
Если я назначу старое значение @cumulative_sum
на @another_variable
на шаге инкремента, я должен был бы сделать именно это.
SELECT t.id,
@cumulative_sum
FROM t
CROSS JOIN (SELECT @cumulative_sum:=0) a
WHERE (@cumulative_sum:=(@another_variable:[email protected]_sum)+t.sum_component) > 1.3
ORDER BY id ASC LIMIT 1;
На двух моих машинах: один работает с MySQL 5.6, а другой работает с MariaDB 10.0.7 - указанный выше запрос выполняется так, как ожидалось:
MariaDB [a51]> SELECT t.id, @cumulative_sum
FROM t
CROSS JOIN
( SELECT @cumulative_sum:=0) a
WHERE (@cumulative_sum:=(@another_variable:[email protected]_sum)
+t.sum_component) > 1.3
ORDER BY id ASC
LIMIT 1;
+----+--------------------+
| id | @cumulative_sum |
+----+--------------------+
| 3 | 1.5000000298023224 |
+----+--------------------+
1 row in set (0.00 sec)
MariaDB [a51]> SELECT @another_variable;
+-------------------+
| @another_variable |
+-------------------+
| 1.100000023841858 |
+-------------------+
1 row in set (0.01 sec)
но в MySQL 5.5 это не так:
mysql> SELECT t.id, @cumulative_sum
FROM t
CROSS JOIN
( SELECT @cumulative_sum:=0) a
WHERE (@cumulative_sum:=(@another_variable:[email protected]_sum)+t.sum_component) > 1.3
ORDER BY id ASC
LIMIT 1;
Empty set (0.18 sec)
mysql> SELECT @another_variable;
+-------------------+
| @another_variable |
+-------------------+
| 0 |
+-------------------+
1 row in set (0.01 sec)
Наблюдая за тем, как увеличивается запрос, мы можем увидеть основную проблему. Ниже приведены результаты с помощью FLOAT
sum_component
:
mysql> SELECT t.id, (@cumulative_sum := (@another_variable:[email protected]_sum)
+ t.sum_component) AS cumulative_sum,
sum_component
FROM t
CROSS JOIN
( SELECT @cumulative_sum:=0) a
ORDER BY id ASC;
+----+----------------+---------------+
| id | cumulative_sum | sum_component |
+----+----------------+---------------+
| 1 | 0.5 | 0.5 |
| 2 | 0.6 | 0.6 |
| 3 | 0.4 | 0.4 |
| 4 | 0.5 | 0.5 |
+---+----------------+----------------+
4 rows in set (0.04 sec)
и вот что имеет DECIMAL
sum_component
выглядит следующим образом:
mysql> ALTER TABLE t MODIFY sum_component DECIMAL(4,2);
Query OK, 4 rows affected, 2 warnings (0.16 sec)
Records: 4 Duplicates: 0 Warnings: 2
mysql> SELECT t.id, (@cumulative_sum := (@another_variable:[email protected]_sum)
+ t.sum_component) AS cumulative_sum,
sum_component
FROM t
CROSS JOIN
( SELECT @cumulative_sum:=0) a
ORDER BY id ASC;
+----+----------------+---------------+
| id | cumulative_sum | sum_component |
+----+----------------+---------------+
| 1 | 0.50 | 0.50 |
| 2 | 1.60 | 0.60 |
| 3 | 2.40 | 0.40 |
| 4 | 2.50 | 0.50 |
+----+----------------+---------------+
4 rows in set (0.18 sec)
SQL Fiddle
Ответы
Ответ 1
Это связано с тем, что MySQL в старой версии обрабатывает 0
как целое число; ваше начальное назначение @cumulative_sum:=0
устанавливает переменную в целое. Изменение назначения на @cumulative_sum:=0.0
приводит к желаемому поведению на 5.5:
SELECT t.id,
@cumulative_sum
FROM t
CROSS JOIN (SELECT @cumulative_sum:=0.0) a
WHERE (@cumulative_sum:=(@another_variable:[email protected]_sum)+t.sum_component) > 1.3
ORDER BY id ASC LIMIT 1;
В руководстве рассматривается эта проблема, но мне не удалось найти упоминание об изменении поведения между 5.5 и 5.6:
Другая проблема с назначением значения переменной и считыванием значения в одном и том же не-SET-заявлении заключается в том, что тип результата по умолчанию для переменной основан на ее типе в начале инструкции. Следующий пример иллюстрирует это:
mysql> SET @a='test';
mysql> SELECT @a,(@a:=20) FROM tbl_name;
Для этого оператора SELECT MySQL сообщает клиенту, что столбец один является строкой и преобразует все обращения @a к строкам, хотя @a задается числом для второй строки. После выполнения инструкции SELECT @a рассматривается как число для следующего оператора.
Чтобы избежать проблем с этим поведением, либо не присваивайте значение и не читайте значение той же переменной в пределах одного оператора, либо установите для переменной значение 0, 0.0 или '', чтобы определить ее тип, прежде чем использовать он.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 5.5.44-log Source distribution
Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> create database test;
Query OK, 1 row affected (0.00 sec)
mysql> use test;
Database changed
mysql> CREATE TABLE t (
-> id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
-> sum_component FLOAT
-> );
Query OK, 0 rows affected (0.04 sec)
mysql> INSERT INTO t (sum_component) VALUES (0.5), (0.6), (0.4), (0.5);
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
mysql> SELECT t.id,
-> @cumulative_sum
-> FROM t
-> CROSS JOIN (SELECT @cumulative_sum:=0.0) a
-> WHERE (@cumulative_sum:=(@another_variable:[email protected]_sum)+t.sum_component) > 1.3
-> ORDER BY id ASC LIMIT 1;
+----+----------------------------------+
| id | @cumulative_sum |
+----+----------------------------------+
| 3 | 1.500000029802322400000000000000 |
+----+----------------------------------+
1 row in set (0.00 sec)
mysql> SELECT @another_variable;
+-------------------+
| @another_variable |
+-------------------+
| 1.100000023841858 |
+-------------------+
1 row in set (0.00 sec)