SQL: сравнение кортежей - PullRequest
       23

SQL: сравнение кортежей

14 голосов
/ 01 апреля 2011

В моем текущем приложении мне нужно иметь возможность выполнять этот тип запроса:

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 <y. </p>

Стоит отметить, что SQL (или, по крайней мере, mysql) реализует это правильно:

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)

Вот необходимый SQL для создания примера:

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 ]

7 голосов
/ 01 апреля 2011

Просто сделай:

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) )
  );
3 голосов
/ 01 апреля 2011

--- РЕДАКТИРОВАТЬ ---: (Предыдущие неправильные испытания удалены)

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)

также не разрешено. Но я был не прав.

...