Подтвердить что ты не робот

Почему в SQL нет оператора "first more/less than [or equal to]"?

Я рассматриваю предложение 4 новых операторов сравнения в SQL. Они похожи на операторы >, <, >= и <=, но являются истинными, только если значение каждого операнда является среди всех значений, удовлетворяющих неравенству, одной ближайшей к значению другого операнда. Поскольку одно значение почти на другое значение, я пришел к выводу (после осознания отсутствия ключевого слова first и после отбрасывания ключевого слова unique), что хорошим выбором будет для определения этих 4 новых операторов:

  • a @> b: true, если a > b и no a' < a удовлетворяет a' > b, а no b' > b удовлетворяет a > b'
  • a @< b: true, если b @> a
  • a @>= b: true, если a ≥ b и no a' < a удовлетворяет a' ≥ b, а no b' > b удовлетворяет a ≥ b'
  • a @<= b: true, если b @>= a

Вопрос: есть ли веская причина, почему такие операторы уже не существуют?


(2014-03-20) Я переформулирую вопрос, потому что вышеуказанная формулировка, по-видимому, недостаточно ясна:

Есть ли причина, по которой такие операторы не должны существовать?


Следующие примеры предназначены в качестве отправной точки для выяснения, где могут быть проблемы с операторами @.... Я буду использовать 3 таблицы MySQL:

create table ta (id int auto_increment, ca char, primary key(id), unique index(ca));
create table tb (id int auto_increment, cb char, primary key(id), index(cb));
create table tc (id int auto_increment, cc char, primary key(id));
insert into ta (ca) values ('A'),('E'),('I'),('O'),('U');
insert into tb (cb) values ('C'),('D'),('E'),('F'),('F'),('M'),('N'),('O'),('Z');
insert into tc (cc) values ('C'),('D'),('E'),('F'),('F'),('M'),('N'),('O'),('Z');

Пример # 1

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

?> select * from ta where ca @> 'B'; -- currently not valid, equivalent to:
!> select * from ta where ca > 'B' order by ca limit 1;
+----+------+
| id | ca   |
+----+------+
|  2 | E    |
+----+------+

(limit 1 специфичен для MySQL, MariaDB, PostgreSQL и т.д., другие РСУБД имеют select top 1, where rownum = 1 и т.д.)

В таблице ta мы имеем уникальный индекс в столбце ca. Этот индекс можно использовать для перехода к выбранному значению с той же скоростью, что и для ca = 'E'. Оптимизатор может это реализовать, но если это не так, структуры данных могут быть настроены для ненужного сканирования, начиная с выбранного значения (MySQL explain говорит, что это запрос типа range).

Пример # 2

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

?> select * from tb where cb @> 'E'; -- currently not valid, equivalent to:
!> select * from tb where cb = (select min(cb) from tb where cb > 'E');
+----+------+
| id | cb   |
+----+------+
|  4 | F    |
|  5 | F    |
+----+------+

К счастью, если я правильно прочитал вывод explain, MySQL достаточно умен, чтобы оптимизировать подзапрос, но если бы он не был, индекс будет использоваться дважды, а не один раз.

В случае таблицы tc, которая не имеет индекса в столбце cc, MySQL выполняет два сканирования таблицы. Это понятно, поскольку односкатное сканирование будет означать использование неизвестного объема памяти для временного результата.

Пример # 3

Предположим, что вам нужны все пары, состоящие из значения и его преемника:

?> select t1.ca as c1, t2.ca as c2
   from ta t1
   join ta t2 on t1.ca @< t2.ca; -- currently not valid, equivalent to:
!> select t1.ca as c1, t2.ca as c2
   from ta t1
   join ta t2 on t2.ca = (select min(ca) from ta where ca > t1.ca);
+------+------+
| c1   | c2   |
+------+------+
| A    | E    |
| E    | I    |
| I    | O    |
| O    | U    |
+------+------+

Если я правильно прочитал вывод explain, оптимизатор MySQL не сможет обойтись без коррелированного подзапроса, тогда как мы, люди, узнаем лучше. Возможно, с помощью специальной обработки операторов @..., подключенных к сети, оптимизатор выполнит одно сканирование?

Пример # 4

Это похоже, но в двух таблицах, один из которых имеет уникальный код:

?> select * from ta join tb on ca @< cb; -- currently not valid, equivalent to:
!> select * from ta join tb on cb = (select min(cb) from tb where cb > ca);
+----+------+----+------+
| id | ca   | id | cb   |
+----+------+----+------+
|  1 | A    |  1 | C    |
|  2 | E    |  4 | F    |
|  2 | E    |  5 | F    |
|  3 | I    |  6 | M    |
|  4 | O    |  9 | Z    |
|  5 | U    |  9 | Z    |
+----+------+----+------+

Здесь тоже оптимизатор MySQL не оптимизирует подзапрос, хотя (возможно, с подсказкой @<) он мог бы.

Пример # 5

(Добавлено в 2014-03-20 гг.) Операторы @..., по-видимому, имеют смысл везде, где их @ не имеют аналогов. Вот надуманный пример выражения в условии where:

?> select * from ta join tb
   where round((ascii(ca)+ascii(cb))/2) @> ascii('E');
   -- currently not valid, equivalent to:
!> select * from ta join tb
   where round((ascii(ca)+ascii(cb))/2) = (
      select min(round((ascii(ca)+ascii(cb))/2)) from ta, tb
      where      round((ascii(ca)+ascii(cb))/2) > ascii('E')
   );
+----+------+----+------+
| id | ca   | id | cb   |
+----+------+----+------+
|  3 | I    |  1 | C    |
|  2 | E    |  4 | F    |
|  2 | E    |  5 | F    |
+----+------+----+------+

Пример # 6

... и это еще один пример, на этот раз выражения select:

?> select *, cb @< ca
   from tb, ta; -- currently not valid, equivalent to:
!> select *, ifnull(cb = (select max(cb) from tb where cb < ca), 0) as 'cb @< ca'
   from tb, ta;
+----+------+----+------+----------+
| id | cb   | id | ca   | cb @< ca |
+----+------+----+------+----------+
|  1 | C    |  1 | A    |        0 |
|  1 | C    |  2 | E    |        0 |
|  1 | C    |  3 | I    |        0 |
|  1 | C    |  4 | O    |        0 |
|  1 | C    |  5 | U    |        0 |
|  2 | D    |  1 | A    |        0 |
|  2 | D    |  2 | E    |        1 |
| -- (omitting rows with cb @< ca equal to 0 from here on)
|  4 | F    |  3 | I    |        1 |
|  5 | F    |  3 | I    |        1 |
|  7 | N    |  4 | O    |        1 |
|  8 | O    |  5 | U    |        1 |

Мне известны следующие оговорки:

Предостережение № 1

Операторы @... являются "нелокальными", потому что им нужно знать все возможные значения их операндов. Это, кажется, не проблема во всех условиях видов, показанных в приведенных выше примерах, но может быть проблемой в других местах (хотя я еще не нашел пример, который не может быть разрешен дополнительным подзапросом).

Предостережение # 2

Операторы @..., в отличие от своих @ -свободных аналогов, не являются транзитивными. Они делят это свойство с оператором <>.

Предостережение № 3

Полностью использование операторов @... может означать введение новых типов доступа к индексам и таблицам (как описано в примерах).


Обратите внимание, что этот вопрос не означает отправную точку для обсуждения. Я ищу причины, по которым что-то вроде операторов @... не находится в стандарте или в любом диалоговом диалекте SQL, который мне известен. Я ожидаю, что эти причины будут связаны с некоторой проблемой с определением и/или внедрением этих операторов, что я упустили.

Я знаю, что одной из причин является "бритва Оккама" (pluralitas non est ponenda sine необходимо), но, как я попытался показать выше, плюралиты здесь также приносят некоторые преимущества (краткость и легкость оптимизации). Я ищу более веские причины.


(2014-03-31) @>, @<, @>= и @<= могут стать |>, |<, |>= и |<= или аналогичными (читайте: first more/менее [равно]), чтобы не сталкиваться с установленными обычаями префикса идентификатора @.

4b9b3361

Ответ 1

Я смущен в отношении того, является ли это подходящим форумом для этого вопроса. Но причина, по которой эти операторы не существуют, заключается в том, что они не особенно полезны, и другие функции ANSI SQL занимают свое место.

Во-первых, операторы сравнения в предложении on в каждой базе данных, я знаю, доступны в предложениях where и case. Неясно, как эти операторы будут использоваться в этих контекстах.

Во-вторых, операторы не указывают, что делать в случае связей. Вернуть все строки? Но это приведет к возврату нескольких строк, когда пользователь такого оператора ожидает только одну строку.

В-третьих, стандартная функциональность ANSI, такая как row_number(), может генерировать эквивалентные результаты. Хотя эта проблема может быть не такой оптимальной, она более общая. И стандартный.

Кстати, Postgres имеет приятную возможность с distinct on(), которая часто более эффективна, чем эквивалент аналитической функции.

Я тайно хотел новый тип join, lookup join, который потерпит неудачу, если будет сопоставлено более одной записи. Однако я не уверен, что весь язык должен быть изменен для этой цели.

Ответ 2

Отложив на минутку достоинства вашего предлагаемого добавления, у вас действительно был только один вопрос:

Вопрос: есть ли веская причина, почему такие операторы уже не существуют?

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

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

  • Случаи, где это полезно, являются необычными, и загрязнение языка новым оператором для каждой мыслимой задачи не является хорошей идеей.
  • Обходные пути для достижения той же задачи с существующим синтаксисом просты и адекватны.
  • Если люди действительно чувствовали, что функция стоит реализовать, они могут использовать другой синтаксис, чем один, используя @ как часть операторов.

Быть кратким, конечно, замечательно, но это, похоже, не является целью SQL вообще (иначе SQL был бы написан в реляционной алгебре обозначение).

Для большинства пользователей достаточно нестандартных статей типа LIMIT (MySQL, PostgreSQL, SQLite) или TOP (Microsoft, Sybase) или ROWNUM (Oracle), или FIRST (Informix, InterBase).

Стандартный синтаксис с использованием упорядоченных оконных функций на самом деле является решением, определяемым спецификацией SQL: 2003. Я понимаю, что функции окна не поддерживаются MySQL еще, но они поддерживаются практически во всех других версиях РСУБД.

Если вы предлагаете добавить эту функциональность специально для MySQL, было бы лучше сделать ее более совместимой с другими брендами с использованием стандартного синтаксиса, а не вводить новый нестандартный синтаксис.


Теперь к вашему другому вопросу:

Я ищу РЕАЛЬНЫЕ проблемы, которые могут иметь эти операторы @....

На ум приходит сразу: это не общее назначение.

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

  • Верните следующие две строки в окне.
  • Возвращает последнюю строку в окне.
  • Верните три верхние строки по рангу, а не по номеру строки.
  • и др.

Он также вводит новую часть пунктуации, которая не имеет прецедента в стандартном SQL, и которая может конфликтовать с некоторыми дополнениями поставщиков, например, префиксом пользовательских переменных в MySQL или параметрами префикса в Microsoft SQL Server.


Я тестировал ваши примеры в PostgreSQL 8.4, который поддерживает функции окна. Я знаю, что не текущая версия PostgreSQL, но это версия по умолчанию в репозитории CentOS, и она достаточна для демонстрации функций окна.

Пример # 1

select * 
from (select *, row_number() over (order by ca) as rn from ta where ca > 'B') as t
where rn = 1;

 id | ca | rn 
----+----+----
  2 | E  |  1

Пример # 2

select * 
from (select *, rank() over (order by cb) as rk from tb where cb > 'E') as t
where rk = 1;

 id | cb | rk 
----+----+----
  5 | F  |  1
  4 | F  |  1

Пример # 3

select t1.ca as c1, t2.ca as c2
from (select ca, row_number() over (order by ca) AS rn from ta) as t1
join (select ca, row_number() over (order by ca) AS rn from ta) as t2
  on t1.rn+1 = t2.rn;

 c1 | c2 
----+----
 A  | E
 E  | I
 I  | O
 O  | U

Пример # 4

select *
from (select ta.id, ta.ca, tb.id, tb.cb, 
    rank() over (partition by ca order by cb) AS rk 
    from ta join tb on ca < cb) as t
where rk = 1;

 id | ca | id | cb | rn 
----+----+----+----+----
  1 | A  |  1 | C  |  1
  2 | E  |  5 | F  |  1
  2 | E  |  4 | F  |  1
  3 | I  |  6 | M  |  1
  4 | O  |  9 | Z  |  1
  5 | U  |  9 | Z  |  1

Ответ 3

Возможно, это не вопрос для чего-то подобного. Должен признаться, это звучит как хорошая функция, но есть и другие способы сделать это. (Хотя, я думаю, было добавлено много других функций, которые имели другие способы сделать это тоже!)

Я полагаю, что истинные ответы на что-то вроде этого могут быть примерно такими:

а. Спрос - Стоит ли разработчикам добавлять такие функции на SQL, если они чувствуют, что мало кто их будет использовать?

В. Функциональность - Действительно ли это позволяет разработчику делать БОЛЬШЕ, или это просто другой способ достичь той же цели?

С. Лень - Могут ли они на самом деле беспокоиться о том, чтобы развить что-то вроде этого?

Лучший способ получить ответ на этот вопрос - отправить его в свою команду разработчиков и посмотреть, каков их ответ, если бы достаточно людей могли принять участие в просьбе о том, чтобы что-то подобное было реализовано, тогда оно могло бы... власть большинства!