Как стать автором
Обновить

Комментарии 21

В голове любого топа ошибок всегда стоИт одна и та же ошибка - ОПЕЧАТКА. Любой ляп, который делает запрос неверным. В лучшем случае синтаксически - это хотя бы сразу ловится. А вот если ляп приводит к логической ошибке, то иной раз проще всё стереть и начать заново, чем искать, где промарьяжился.

Я бы сказал, что это не топ ошибок. Это что-то вроде небольшого списка факапов. Причём в основном - по невнимательности. А то и вообще по причине недостаточного знания предмета.

Я дальше ошибки номер 2 не читал. Это топ для кого? Ценность нулевая.

SELECT * 
 FROM Employees 
 WHERE LOWER(Name) = 'john';

Теперь в результате есть 'john', 'John' или 'JOHN'.

Зато перестал работать индекс по Name, если он был.
Если вам постоянно нужно регистронезависимое сравнение в колонке, то следует объявить для неё соответствующее сопоставление (collation). По умолчанию в MySQL как раз такое и используется, utf8mb4_0900_ai_ci (accent insensitive, case insensitive).

Пример
CREATE TABLE `test` (
  `id` INT PRIMARY KEY AUTO_INCREMENT,
  `name_ci` VARCHAR(32),
  `name_cs` VARCHAR(32) COLLATE utf8mb4_0900_as_cs
);

INSERT INTO `test` (`name_ci`, `name_cs`)
  VALUES ('John', 'John'), ('john', 'john'), ('JOHN', 'JOHN');

SELECT `id`, `name_ci`, `name_cs`
  FROM `test`
  WHERE `name_ci` = 'john';

| id  | name_ci | name_cs |
| --- | ------- | ------- |
| 1   |   John  |   John  |
| 2   |   john  |   john  |
| 3   |   JOHN  |   JOHN  |

SELECT `id`, `name_ci`, `name_cs`
  FROM `test`
  WHERE `name_cs` = 'john';

| id  | name_ci | name_cs |
| --- | ------- | ------- |
| 2   |   john  |   john  |

Для случаев, когда сравнение не использует индекс, можно указать требуемый COLLATION непосредственно в выражении сравнения.

WHERE `name_ci` = 'john' COLLATE utf8mb4_0900_as_cs;

https://dbfiddle.uk/SOPobhrl

Отсутствие group by это все же compile-time ошибка, она выявляется легко, если запрос хотя бы запускали. Сюда можно и пропущенные запятые и несоответствие скобок/кавычек включать тогда. А вот отсутствие указания алиасов таблиц перед столбцами, не являющееся ошибкой в общем случае, может давать очень интересные результаты, особенно с подзапросом:

select *
from t1
where id in (
  select id
  from t2
)

Дропаем колонку id в таблице t2 и получаем where t1.id is not null. Ну а спутать count и sum - без комментариев. Там можно и с sttdev спутать, чего бы и нет.

А вот не всегда. В SQLite и MariaDB, например, ошибки не будет: https://dbfiddle.uk/AlprB1Xb

Зачем так сделано - я не знаю, но пару раз стоило мне потраченного времени на поиск ошибки.

Зачем так сделано - я не знаю

В случае MySQL/MariaDB это следствие неправильной настройки сервера, lamer-friendly отсутствие флага ONLY_FULL_GROUP_BY в установках Server SQL Mode (см. SELECT @@sql_mode;). Если добавить этот флаг - будет выдано сообщение типа "In aggregated query without GROUP BY, expression #1 of SELECT list contains nonaggregated column 'database.table.column'; this is incompatible with sql_mode=only_full_group_by". Так что это не проблемы сервера, это ляп от программиста.

В случае SQLite это документированное и неизменяемое поведение. Программист просто должен лучше следить за руками.

В любом случае значение такого неагрегированного поля - случайно выбранное из всех возможных. А потому его получение просто не имеет смысла, и такое поле можно совершенно без вреда для результата удалить из выходного набора. За исключением случая, когда оно коррелировано с агрегированным уникальным выражением. Большинство СУБД уже научились распознавать такую корреляцию для агрегированного поля/выражения первичного индекса и не генерировать ошибку.

Про настройку в MySQL/MariaDB не знал, спасибо. Предполагал, что это стандартное поведение, как и в SQLite.

Всё равно не отвечает на мой (риторический, уточню) вопрос - зачем. Не могу представить ситуацию, когда такой вот вывод случайного поля может быть полезен.

Ну а про внимательность - все мы иногда пишем чушь, и хочется ожидать, что написанная чушь приводит к сообщению об ошибке вместо непредсказуемых результатов :)

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

Вариант 1. Значение данного поля для всех записей группы одинаково (денормализованные данные, или просто особенность данных).

Вариант 2. Требуемая структура выходного набора определена неким внешним фактором, поле обязано присутствовать.

Вариант 3. Реализуемая логика требует наличия хотя бы одного соответствующего выражению группировки значения, причём любого из имеющихся.

В любом случае - для того, чтобы в таких случаях получить синтаксически и логически корректный запрос, существует агрегатная функция ANY_VALUE(), назначение которой именно в том, чтобы вернуть любое значение в данном поле из всех записей группы. Только о ней мало кто помнит... Впрочем, когда важно наличие и частное соответствие, но не детерминированный агрегированием выбор, ничто не мешает использовать MIN() или MAX().

Дропаем колонку id в таблице t2 и получаем where t1.id is not null.

Да Syntax Error мы получаем, а не призрачное IS NOT NULL.

В том то и дело, что не получаем.

create table t1 (id int, name varchar(16));
insert into t1 values (1, 'John'), (2, 'Mary'), (null, 'Anonymous');
create table t2 (id int, xxx int);
insert into t2 values (1, 1), (2, 2), (4, 4);
select * from t1 where id in (select id from t2);
| id | name |
|----|------|
|  1 | John |
|  2 | Mary |

Пока всё как ожидалось. Удаляем колонку id.

alter table t2 drop column id;
select id from t2;

Закономерно получаем ошибку.

Unknown column 'id' in 'field list'

Повторяем запрос.

select * from t1 where id in (select id from t2);
| id | name |
|----|------|
|  1 | John |
|  2 | Mary |

И вуаля, никакой ошибки нет. А результат есть.

fiddle

В данном случае срабатывает эффект одноимённости полей, и запрос получается коррелированным. То есть при отсутствии поля id в таблице t2 запрос фактически выглядит так:

select t1.* from t1 where t1.id in (select t1.id from t2);

Это, к слову, одна из причин, почему я всегда требую, чтобы в любом запросе, имеющем в источнике данных более одной таблицы либо входящем в код хранимого объекта, абсолютно каждое поле имело спецификатор алиаса таблицы.

PS. На самом деле фраза должна быть такая: Дропаем колонку id в таблице t2 и получаем WHERE EXISTS (SELECT 1 FROM t2) AND t1.id IS NOT NULL

На основании чего мы получим Syntax Error? Где-то запрещены корелляции в списке выражений select/where в подзапросах, если это не join? Ниже есть работающий пример, и с такими ситуациями сталкивался много раз: начиная с того, что кто-то сгенерировал имя поля с кириллицей (и вместо выборки из таблицы получаем кореллированный подзапрос, который возвращает "что-то не то"), заканчивая тем, что оптимизатор напутал в портянке вложенных подзапросов.

Я ж вроде объяснил. В общем случае запрос к несуществующему полю приведёт к ошибке синтаксиса. И только в данном весьма частном случае ошибки не будет - но зато будет полностью изменена логика получения результата.

Именно об этом и было сказано:

отсутствие указания алиасов таблиц перед столбцами, не являющееся ошибкой в общем случае, может давать очень интересные результаты

Что это не ошибка, но крайне полезное и важное дополнение запроса для последующего сопровождения и отладки, которым новички обычно пренебрегают. Запрос несуществующего столбца нигде не рассматривался. И комментарий был о том, что ошибки, которые выявляются на этапе парсинга запроса, упоминать в качестве типовых очень странно: их легко исправить (если это не Oracle с его крайне общими текстами ошибок, хотя сейчас LLM должны легко и с ним помочь).

Надо же, аж перевод…

Это ошибки тех, кто первые 2 недели SQL занимается?

"delete from" без "where"? Вы смеётесь? У меня рука не поднимается писать delete без полностью сформированного и оттестированного условия. Сначала select from ...., отлаживаю условия, убеждаюсь, что в select попало только подлежащее удалению и ни строчки больше и только после этого "select " заменяю на , delete.

Ну да. Опять же, если надо зачистить таблицу, есть куда как более подходящее для этого TRUNCATE.

А мне сразу вспомнилась вот эта статья: https://zaidesanton.substack.com/p/how-i-destroyed-the-companys-db

TL;DR: герой статьи написал запрос вида

UPDATE orders
SET is_deleted is true

WHERE id in (1, 2, 3)

Но написал он это в dbeaver, который считает пустую строку концом запроса.

"Ма-ма мы-ла ра-му".

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации