Quantcast
Channel: vinxru
Viewing all articles
Browse latest Browse all 319

Мой AUTOJOIN и противоиндусская технология в MySQL

$
0
0
Мне очень нравится SQL, но есть в нём одна очень неудобная вещь, которая так и просит, что бы её исправили. И я говорю о JOIN-ах.

Сейчас объясню. Допустим, вы храните какой то документ в 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` t1
  Left 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`


Вот такой патчик я сегодня добавил с утра.

Viewing all articles
Browse latest Browse all 319

Latest Images

Trending Articles