SQL:元组比较

时间:2011-03-31 21:47:14

标签: mysql sql aggregate-functions row-value-expression

在我目前的应用程序中,我需要能够执行此类查询:

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)正确实现了这一点:

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 个答案:

答案 0 :(得分:7)

只是做:

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) )
  );

答案 1 :(得分:3)

---编辑--- :(以前错误的试验已删除)

第二次尝试(不是真正的关系代数)。

这只适用于字段为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)

也不被允许。但我错了。