Может ли MySQL убедиться в функциональной зависимости, когда HAVING COUNT (*) = 1? - PullRequest
0 голосов
/ 16 октября 2018

Я пытаюсь найти заказы только с одним элементом в базе данных, работающей на MySQL 5.7.23 на Ubuntu 18.04 LTS.Но почему-то MySQL не может сделать вывод, что COUNT(*) = 1 подразумевает функциональную зависимость.

Следующая база данных из 2 таблиц заказов с элементами заказов иллюстрирует ошибку:

DROP TABLE IF EXISTS t_o, t_oi;
CREATE TABLE t_o (
  order_id INTEGER UNSIGNED PRIMARY KEY,
  placed_on DATE NOT NULL,
  INDEX (placed_on)
);
INSERT INTO t_o (order_id, placed_on) VALUES
(1, '2018-10-01'),
(2, '2018-10-02');
CREATE TABLE t_oi (
  item_id INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  order_id INTEGER UNSIGNED NOT NULL,
  sku VARCHAR(31) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
  qty INTEGER UNSIGNED NOT NULL,
  unit_price INTEGER UNSIGNED NOT NULL,
  INDEX (sku),
  FOREIGN KEY (order_id) REFERENCES t_o (order_id)
    ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO t_oi (order_id, sku, qty, unit_price) VALUES
(1, 'SO', 1, 599),
(1, 'SF', 2, 399),
(2, 'SU', 1, 399);

SELECT t_oi.order_id, t_o.placed_on, t_oi.sku, t_oi.qty, t_oi.unit_price
FROM t_o
INNER JOIN t_oi ON t_o.order_id = t_oi.order_id
GROUP BY t_oi.order_id
HAVING COUNT(*) = 1

Я ожидаю, что этовернуть (2, '2018-10-02', 'SU', 1, 399), потому что это единственный заказ только с одним товаром.Я не хочу строк, где order_id = 1, потому что в этом заказе более одного элемента.Но вместо этого MySQL выдает следующую ошибку:

# 1055 - Выражение № 3 списка SELECT отсутствует в предложении GROUP BY и содержит неагрегированный столбец phs_apps.t_oi.sku, который функционально не зависит отстолбцы в предложении GROUP BY;это несовместимо с sql_mode = only_full_group_by

В руководстве поясняется «функционально зависимый».Но есть ли способ выразить эту функциональную зависимость для MySQL, которая чище, чем наматывать MIN() вокруг каждого выходного столбца, на который жалуется MySQL?Если это вообще возможно, я бы предпочел решение, которое не включает в себя объединение t_oi дважды, один раз, чтобы найти соответствующие значения t_o.order_id, и один раз, чтобы добавить детали каждого отдельного элемента каждого такого заказа, как включение таблицы водин запрос несовместим с использованием TEMPORARY TABLE из-за 13-летней ошибки "Can't reopen table" .

Ответы [ 4 ]

0 голосов
/ 29 октября 2018

Я полагаю, что ваше предположение о функциональной зависимости неверно.

Если R является отношением с атрибутами X и Y, функциональная зависимость между атрибутами представлена ​​в виде X-> Y, которая указывает, что Y функционально зависит от X. Здесь X - это определитель иY является зависимым атрибутом. Каждое значение X связано только с одним Y значением . techopedia

Эти 2 столбца являются функционально зависимыми (и работает запрос).nb: Каждое значение t_o.placed_on связано только с одним t_oi.order_id значением

SELECT t_oi.order_id, t_o.placed_on
FROM t_o
INNER JOIN t_oi ON t_o.order_id = t_oi.order_id
GROUP BY t_oi.order_id
HAVING COUNT(*) = 1

Они НЕ являются функционально зависимыми (и запрос не будет работать, пока вы не удалите ONLY_FULL_GROUP_BY)

SELECT t_oi.order_id, t_o.placed_on, t_oi.sku, t_oi.qty, t_oi.unit_price
FROM t_o
INNER JOIN t_oi ON t_o.order_id = t_oi.order_id
GROUP BY t_oi.order_id
HAVING COUNT(*) = 

Любой из этих столбцов t_oi.sku, t_oi.qty, t_oi.unit_price может содержать любое допустимое значение для своих типов данных.Так что они НЕ предопределены отношениями, включенными в запрос .

select @@sql_mode;
| @@sql_mode                                                                                                            |
| :-------------------------------------------------------------------------------------------------------------------- |
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
/* functionally dependent columns only */
SELECT t_oi.order_id, t_o.placed_on
FROM t_o
INNER JOIN t_oi ON t_o.order_id = t_oi.order_id
GROUP BY t_oi.order_id
HAVING COUNT(*) = 1
order_id | placed_on 
-------: | :---------
       2 | 2018-10-02
/* any columns some not functionally dependent */
SELECT t_oi.order_id, t_o.placed_on, t_oi.sku, t_oi.qty, t_oi.unit_price
FROM t_o
INNER JOIN t_oi ON t_o.order_id = t_oi.order_id
GROUP BY t_oi.order_id
HAVING COUNT(*) = 1
Expression #3 of SELECT list is not in GROUP BY clause and 
contains nonaggregated column 'fiddle_YRLHCAMPBMVSWYXFQGUD.t_oi.sku' 
which is not functionally dependent on columns in GROUP BY clause; 
this is incompatible with sql_mode=only_full_group_by
SET sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'
select @@sql_mode
| @@sql_mode                                                                                         |
| :------------------------------------------------------------------------------------------------- |
| STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
/* any columns some not functionally dependent */
SELECT t_oi.order_id, t_o.placed_on, t_oi.sku, t_oi.qty, t_oi.unit_price
FROM t_o
INNER JOIN t_oi ON t_o.order_id = t_oi.order_id
GROUP BY t_oi.order_id
HAVING COUNT(*) = 1
order_id | placed_on  | sku | qty | unit_price
-------: | :--------- | :-- | --: | ---------:
       2 | 2018-10-02 | SU  |   1 |        399

дБ <> скрипка здесь

0 голосов
/ 16 октября 2018

Нет, я не думаю, что можно убедить MySQL распознать функциональную зависимость с помощью специального условия в предложении HAVING.

Предложение HAVING оценивается намного позже при выполнении запросапосле доступа к строкам, после операции GROUP BY, после агрегатов и т. д.


Мы можем удалить ONLY_FULL_GROUP_BY из sql_mode.Это позволило бы MySQL обработать запрос, не выдавая ошибку.Но это просто старая школа с нестандартным расширением MySQL для поведения GROUP BY.Это не значит, что MySQL убежден в функциональной зависимости.

0 голосов
/ 17 октября 2018

Вы можете использовать функцию ANY_VALUE () :

MySQL 8.0 Справочное руководство / Функции и операторы / Разные функции
12.22 Разные функции

  • ANY_VALUE ( arg )

    Эта функция полезна для запросов GROUP BY, когда включен режим SQL ONLY_FULL_GROUP_BY, в случаях, когда MySQL отклоняет запрос, который, как вы знаете, действителен дляпричины, которые MySQL не может определить.Возвращаемое значение и тип функции совпадают с возвращаемым значением и типом ее аргумента, но результат функции не проверяется для режима SQL ONLY_FULL_GROUP_BY.

Или просто взятьMIN () каждого не сгруппированного столбца.Прокомментируйте это.Всегда будут случаи, когда СУБД не может или не может доказать статически для данных литералов и функций или во время выполнения.Таким образом, вам нужно решение, как MIN () в вашем наборе инструментов.У вас есть , чтобы иметь некоторую перестановку запросов / кодов, поскольку нет никакого способа предоставить DMBS подтверждение или переопределение.Хотя вы можете рассмотреть возможность очистки ONLY_FULL_GROUP_BY в качестве этого переопределения.Но разве вам не нужно комментировать очистку и восстановление этого тоже, потому что это не очевидно?

Вы можете назначить подзапрос к таблице с соответствующим ограничением PK (первичный ключ) или UNIQUE NOT NULL.Но вы все равно хотите прокомментировать почему.Поскольку СУБД не знает о FD (функциональная зависимость), мы можем ожидать, что назначение также не будет оптимизировано.Мы можем ожидать минимальные издержки от чего-то вроде MIN ().

Действительно, в этом разделе руководства говорится:

Существует несколько способов заставить MySQL принять запрос:

  • Измените таблицу, чтобы сделать [функционально зависимый столбец] первичным ключом или уникальным столбцом NOT NULL.[...]

  • Использовать ANY_VALUE () [...]

  • Отключить ONLY_FULL_GROUP_BY.[...]

0 голосов
/ 16 октября 2018

В этом запросе «SELECT t_oi.order_id, t_o.placed_on, t_oi.sku, t_oi.qty, t_oi.unit_price» вы группируете по первому столбцу.Вы должны сказать, что делать с другими колонками.Вы можете сделать group_concat для столбца sku или взять первые записи в таблице t_oi с помощью функции ранжирования, поэтому больше не потребуется группировать по.

Попробуйте это с ранжированием.Не уверен, не проверен.

SELECT t_o.order_id, t_o.placed_on, t_oi2.sku, t_oi2.qty, t_oi2.unit_price
FROM t_o
INNER JOIN (
    select t_oi.order_id, t_o.placed_on, t_oi.sku, t_oi.qty, t_oi.unit_price,
    @rank := case when @cur_order_id = t_oi.order_id then @rank + 1 else 1 end,
    @cur_order_id := t_oi.order_id
    from t_oi, (select @cur_order_id := 0, @rank := 0) tmp
    order by t_oi.order_id
    ) t_oi2 ON t_o.order_id = t_oi2.order_id and t_oi2.rnk = 1;
...