2011-03-31 75 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