2011-03-31 181 views
13

在我目前的应用程序中,我需要能够做这种类型的查询: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 < y相同的值。

值得一提的是,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); 

添加该指数似乎导致表按字典顺序排序,这很有趣。我们的生产系统并不是这样。

+1

你是如何定义的元组排序? – 2011-03-31 21:52:43

+0

对于记录来说,'('B','K','K')'不会使'BETWEEN'条件成为行,对吧? – 2011-03-31 22:09:11

+1

@ypercube:是的。 (B,K,K)在(A,B,C)和(D,E,F)之间排序 – bukzor 2011-03-31 22:16:47

回答

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

完美!你应该删除你的其他答案。 – bukzor 2011-04-01 00:55:25

+0

如果<=对元组有效,那么BETWEEN似乎应该起作用,因为它应该简单地等同于该操作。 – 2011-04-01 17:21:58

+0

@Cade:我试过了。它不起作用。 – bukzor 2011-04-02 00:29:03

3

---编辑---:(上一页wrong测试删除)

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

这工作,但仅当字段是字符(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)

不准为好。但是我错了。

+0

统计满足相同条件的所有行吗? – 2011-03-31 22:06:34

+0

这个答案有效,但以我的经验来看,OR是性能灾难。你认为还有更好的方法吗? – bukzor 2011-03-31 23:38:24

+0

我想你应该测试一下,看看它是不是灾难。我现在无法想到其他任何东西。 – 2011-04-01 00:14:47