Мне очень нравится SQL, но есть в нём одна очень неудобная вещь, которая так и просит, что бы её исправили. И я говорю о JOIN-ах.
Сейчас объясню. Допустим, вы храните какой то документ в SQL. Пусть это будет таблица с колонками: дата, сумма, клиент. И в поле клиент вы размещаете не имя клиента, а его код. Потому что имя не уникально, по нему нельзя сказать, что это именно тот самый клиент. И есть еще таблица клиентов. В которой по минимуму будет код и имя. На С++ это бы выглядело так:
Если вы захотите потом получить список документов, вы напишите.
Просто и понятно. Но если вы заходите еще получить имя клиента, то вам придется дописывать JOIN:
Вы должны будете объяснить SQL-серверу, что поле клиенты, это не просто число, а ссылка на таблицу "клиенты". И искать надо по полю "код". Минусы такого синтаксиса следующие:
1) База данных и так знает, что поле клиент это ссылка на таблицу клиенты. В 99.9% случаев это лишняя информация.
2) При чтении запроса надо прыгать глазами (и курсором) вперед-назад. Это явный аналог нелюбимого во всем мире GOTO и спагетти-кода.
3) Нет изоляции частей запроса друг от друга. В идеале, всё что вы бы не написали в выражении, не должно выходить за рамки выражения: SELECT выражение1, выражение2, выражение3 FROM таблица WHERE выражение4.
Например, в вашей программе отображается список документов и есть настраиваемый пользователем фильтр этих документов. На основе него вы формируете выражение4. Но пользователь решает отфильтровать документы по имени клиента. И вам надо помимо выражения4 сформировать еще блок JOIN-ов. А для этого знать структуру базы данных. И при этом учитывать, какие уже JOIN-ы находятся в запросе, что бы их не дублировать.
В идеале запрос мог вы выглядеть просто и наглядно:
И аналогичная программа на Си:
Собственно вот и вся моя доработка. Если поле ссылается на запись другой таблицы (т.е. существует внешний ключ), то можно просто через точку получить доступ к полям той записи. При всей примитивности идеи, это сокращает длину запросов в разы. И упрощает разработку генераторов запросов.
Я написал этот преобразователь лет 7 назад. И думал что это маленькие ноухау. Ну и 7 лет после я не дорабатывал этот транслятор. К сожалению, написать что крутое можно лишь постоянно этим занимаясь, а я лишу лишь прототипы многие годы. Какой бы язык запросов получился бы :)
Я кстати посылал в 1С резюме со всеми моими идеями, в том числе этой :) Я 1С Платформу знаю плохо и относительно недавно узнал, что там есть подобная технология. И даже больше. 1С поддерживают авто-джоины сгруппированных значений:
У MySQL не существует прямого аналога этого запроса (или я что то не знал), поэтому я его и не сделал. У меня подобное то же можно сделать, но чуть глупее:
Написал я об этом, только потому что сегодня дорабатывал транслятор. Я теперь буду делать заметки о том, с чем сталкиваюсь. И столкнулся и интересной фишкой MYSQL. Наши программисты написали запрос, который без ошибок транслировался, но MySQL отказался его выполнять. Было:
(Точка перед именем поля значит, что это поле внешнего запроса. Т.е. таблицы Table1)
Стало:
Синтаксически запрос составлен правильно, но не запускается. Это специальная противоиндусская технология разработчиков MySQL! С точки зрения оптимизации Join не имеет смысла выполнять во внутреннем запросе, так как результат его выполнения зависит только от внешнего. Аналог на C++ выглядел бы так:
Вот эту строчку Table* t2 стоит вынести за цикл и об этом разработчики MySQL недвусмысленно намекают:
Вот такой патчик я сегодня добавил с утра.
Сейчас объясню. Допустим, вы храните какой то документ в SQL. Пусть это будет таблица с колонками: дата, сумма, клиент. И в поле клиент вы размещаете не имя клиента, а его код. Потому что имя не уникально, по нему нельзя сказать, что это именно тот самый клиент. И есть еще таблица клиентов. В которой по минимуму будет код и имя. На С++ это бы выглядело так:
struct Client { string name; }; struct Document { DateTime date; Currency summa; Client* client; };
Если вы захотите потом получить список документов, вы напишите.
SELECT дата, сумма, клиент FROM документы
Просто и понятно. Но если вы заходите еще получить имя клиента, то вам придется дописывать JOIN:
SELECT документ.дата, документ.сумма, документ.клиент, клиенты.имя FROM документы AS документ LEFT OUTER JOIN клиенты AS клиент ON клиент.код=документ.клиент
Вы должны будете объяснить SQL-серверу, что поле клиенты, это не просто число, а ссылка на таблицу "клиенты". И искать надо по полю "код". Минусы такого синтаксиса следующие:
1) База данных и так знает, что поле клиент это ссылка на таблицу клиенты. В 99.9% случаев это лишняя информация.
2) При чтении запроса надо прыгать глазами (и курсором) вперед-назад. Это явный аналог нелюбимого во всем мире GOTO и спагетти-кода.
3) Нет изоляции частей запроса друг от друга. В идеале, всё что вы бы не написали в выражении, не должно выходить за рамки выражения: SELECT выражение1, выражение2, выражение3 FROM таблица WHERE выражение4.
Например, в вашей программе отображается список документов и есть настраиваемый пользователем фильтр этих документов. На основе него вы формируете выражение4. Но пользователь решает отфильтровать документы по имени клиента. И вам надо помимо выражения4 сформировать еще блок JOIN-ов. А для этого знать структуру базы данных. И при этом учитывать, какие уже JOIN-ы находятся в запросе, что бы их не дублировать.
В идеале запрос мог вы выглядеть просто и наглядно:
SELECT дата, сумма, клиент.имя FROM документы
И аналогичная программа на Си:
foreach(d : documents) cout << d.date << d.summa << d.client->name;
Собственно вот и вся моя доработка. Если поле ссылается на запись другой таблицы (т.е. существует внешний ключ), то можно просто через точку получить доступ к полям той записи. При всей примитивности идеи, это сокращает длину запросов в разы. И упрощает разработку генераторов запросов.
Я написал этот преобразователь лет 7 назад. И думал что это маленькие ноухау. Ну и 7 лет после я не дорабатывал этот транслятор. К сожалению, написать что крутое можно лишь постоянно этим занимаясь, а я лишу лишь прототипы многие годы. Какой бы язык запросов получился бы :)
Я кстати посылал в 1С резюме со всеми моими идеями, в том числе этой :) Я 1С Платформу знаю плохо и относительно недавно узнал, что там есть подобная технология. И даже больше. 1С поддерживают авто-джоины сгруппированных значений:
SELECT клиент.имя, клиент.адрес FROM таблица GROUP BY таблица
У MySQL не существует прямого аналога этого запроса (или я что то не знал), поэтому я его и не сделал. У меня подобное то же можно сделать, но чуть глупее:
SELECT клиент_имя, клиент_адрес FROM таблица GROUP BY клиент.имя AS клиент_имя, клиент_адрес AS клиент_адрес
Противоиндусская технология
Написал я об этом, только потому что сегодня дорабатывал транслятор. Я теперь буду делать заметки о том, с чем сталкиваюсь. И столкнулся и интересной фишкой MYSQL. Наши программисты написали запрос, который без ошибок транслировался, но MySQL отказался его выполнять. Было:
Select ..., (Select Sum(Summa) From Table2 Where Contragent = .Header.Contragent), ... From Table1
(Точка перед именем поля значит, что это поле внешнего запроса. Т.е. таблицы Table1)
Стало:
Select ..., ( Select Sum(`t1`.`Summa`) From `Table2` t1 Left Join `Table3` t2 on `t2`.`Id`<=>`t0`.`Header` Where (`t1`.`Contragent`<=>`t2`.`Contragent`) ), ... From `Table1` t0
Синтаксически запрос составлен правильно, но не запускается. Это специальная противоиндусская технология разработчиков MySQL! С точки зрения оптимизации Join не имеет смысла выполнять во внутреннем запросе, так как результат его выполнения зависит только от внешнего. Аналог на C++ выглядел бы так:
foreach(t0 : Table0) { double summa = 0; foreach(t1 : Table2) { Table* t2 = t0->header; // Надо вынести во внешний цикл! if(t1.contragent == t2->contragent) summa += t1.summa; } cout << summa << endl; }
Вот эту строчку Table* t2 стоит вынести за цикл и об этом разработчики MySQL недвусмысленно намекают:
Select ..., ( Select Sum(`t1`.`Summa`) From `Table2` t1Left Join `Table3` t2 on `t2`.`Id`<=>`t0`.`Header`Where (`t1`.`Contragent`<=>`t2`.`Contragent`) ), ... From `Table1` t0 Left Join `Table3` t2 on `t2`.`Id`<=>`t0`.`Header`
Вот такой патчик я сегодня добавил с утра.