SQL: сравнение кортежей
В моем текущем приложении мне нужно выполнить этот тип запроса:
SELECT MIN((colA, colB, colC))
FROM mytable
WHERE (colA, colB, colC) BETWEEN (200, 'B', 'C') AND (1000, 'E', 'F')
и получить ответ (333, 'B', 'B')
, учитывая эти данные:
+------+------+------+
| colA | colB | colC |
+------+------+------+
| 99 | A | A |
| 200 | A | Z |
| 200 | B | B |
| 333 | B | B |
| 333 | C | D |
| 333 | C | E |
| 333 | D | C |
| 1000 | E | G |
| 1000 | F | A |
+------+------+------+
Каков наиболее эффективный способ сделать это в реальном SQL? Пожалуйста, имейте в виду, что это игрушечный пример, и что мое настоящее приложение имеет таблицы с различными столбцами и типами данных, а также сотни миллионов строк. Я использую MySQL, если это поможет. Вы также можете предположить, что эти столбцы имеют индекс PRIMARY или UNIQUE.
Если решение легко расширяется на большее / меньшее количество столбцов, это даже лучше.
Сравнение кортежей:
Некоторые спрашивали, поэтому я должен поставить это в вопросе. Кортежи упорядочены лексикографически, что означает, что последовательности упорядочены так же, как их первые отличающиеся элементы. Например, (1,2,x) <(1,2, y) возвращает то же самое, что и x Стоит отметить, что SQL (или, по крайней мере, mysql) реализует это правильно: Вот необходимый SQL для создания примера: Добавление этого индекса, похоже, приводит к лексикографической сортировке таблицы, что интересно. Это не так в нашей производственной системе.mysql> select (200, 'B', 'C') < (333, 'B', 'B') and (333, 'B', 'B') < (1000, 'E', 'F');
+--------------------------------------------------------------------------+
| (200, 'B', 'C') < (333, 'B', 'B') and (333, 'B', 'B') < (1000, 'E', 'F') |
+--------------------------------------------------------------------------+
| 1 |
+--------------------------------------------------------------------------+
1 row in set (0.00 sec)
create table mytable select 333 colA, 'B' colB, 'B' colC;
insert into mytable values (200, 'B', 'B'), (333, 'C', 'D'), (1000, 'E', 'G'),
(200, 'A', 'Z'), (1000, 'F', 'A'), (333, 'C', 'E'), (333, 'D', 'C'),
(99, 'A', 'A');
alter table mytable add unique index myindex (colA, colB, colC);
2 ответа
Просто делать:
SELECT colA
, colB
, colC
FROM mytable
WHERE ( ('A', 'B', 'C') <= (colA, colB, colC ) )
AND ( (colA, colB, colC) <= ('D', 'E', 'F' ) )
ORDER BY colA, colB, colC
LIMIT 1
;
Работает просто отлично. И я подозреваю, что это тоже должно быть довольно быстро.
Это эквивалентно, но может иметь лучшую производительность, в зависимости от ваших таблиц:
SELECT m.colA
, m.colB
, m.colC
FROM mytable m
WHERE ( ('A', 'B', 'C') <= (m.colA, m.colB, m.colC) )
AND ( (m.colA, m.colB, m.colC) <= ('D', 'E', 'F') )
AND NOT EXISTS
( SELECT 1
FROM mytable b
WHERE (b.colA, b.colB, b.colC) < (m. colA, m.colB, m.colC)
AND ( ('A', 'B', 'C') <= (b.colA, b.colB, b.colC) )
);
--- РЕДАКТИРОВАТЬ ---: (Предыдущие неправильные испытания удалены)
2-я попытка (не совсем реляционная алгебра).
Это работает, но только когда поля имеют тип char(1):
SELECT colA, colB, colC
FROM mytable
WHERE CONCAT(colA, colB, colC)
BETWEEN CONCAT('A', 'B', 'C')
AND CONCAT('D', 'E', 'F')
ORDER BY colA, colB, colC
LIMIT 1 ;
Я думал, что представление, которое показывает все комбинации кортежей из mytable
которые могут быть меньше или равны кортежам одной и той же таблицы, могут быть полезны, так как это может использоваться для других сравнений:
CREATE VIEW lessORequal AS
( SELECT a.colA AS smallA
, a.colB AS smallB
, a.colC AS smallC
, b.colA AS largeA
, b.colB AS largeB
, b.colC AS largeC
FROM mytable a
JOIN mytable b
ON (a.colA < b.colA)
OR ( (a.colA = b.colA)
AND ( (a.colB < b.colB)
OR (a.colB = b.colB
AND a.colC <= b.colC)
)
)
) ;
Используя подобную технику, это решает вопрос. Работает с любыми полями (int, float, char любой длины). Это будет немного странно и сложно, хотя, если попытаться добавить больше полей.
SELECT colA, colB, colC
FROM mytable m
WHERE ( ('A' < colA)
OR ( ('A' = colA)
AND ( ('B' < colB)
OR ('B' = colB
AND 'C' <= colC)
)
)
)
AND ( (colA < 'D')
OR ( (colA = 'D')
AND ( (colB < 'E')
OR (colB = 'E'
AND colC <= 'F')
)
)
)
ORDER BY colA, colB, colC
LIMIT 1 ;
Также определим функцию:
CREATE FUNCTION IslessORequalThan( lowA CHAR(1)
, lowB CHAR(1)
, lowC CHAR(1)
, highA CHAR(1)
, highB CHAR(1)
, highC CHAR(1)
)
RETURNS boolean
RETURN ( (lowA < highA)
OR ( (lowA = highA)
AND ( (lowB < highB)
OR ( (lowB = highB)
AND (lowC <= highC)
)
)
)
);
и использовать его для решения тех же или подобных проблем. Это решает вопрос снова. Запрос является элегантным, но новая функция должна быть создана, если тип или количество полей изменено.
SELECT colA
, colB
, colC
FROM mytable
WHERE IslessORequalThan( 'A', 'B', 'C', colA, colB, colC )
AND IslessORequalThan( colA, colB, colC, 'D', 'E', 'F' )
ORDER BY colA, colB, colC
LIMIT 1;
До тех пор и потому что условие
(colA, colB, colC) BETWEEN ('A', 'B', 'C') AND ('D', 'E', 'F')
не было разрешено в MySQL, я думал, что
('A', 'B', 'C') <= (colA, colB, colC)
не было позволено также. Но я был неправ.