10. 唯一NULL
當在列上宣告唯一約束後,SQL Server會把所有的NULL值當視為唯一的。這就意味著在一個唯一約束的列你不能有超過一個NULL.
這種性質違背了標準並且僅僅是SQL Server特有的。
為了解決這一問題,要通過宣告一個排除NULL值的唯一索引來執行該約束。
1 2 3 |
CREATE UNIQUE INDEX ux_mytable_mycolumn ON mytable (mycolumn) WHERE mycolumn IS NOT NULL |
請注意,這將阻止使用索引來 搜尋NULL值。
幸運的是,要搜尋一個NULL值(即使是在一個變數中)你也不得不使用一個謂詞,IS NULL,由於NULL=NULL不等於TRUE,所以不滿足WHERE條件。
因此,你可以建立一個額外的(非唯一的)索引:
1 2 3 |
CREATE INDEX ix_mytable_mycolumn_null ON mytable (mycolumn) WHERE mycolumn IS NULL |
這將用於IS NULL搜尋。
9. NULLS FIRST
當對包含NULL值的一列使用ORDER BY時,這些NULL值會最先出現。這種行為沒有被標準規定,在SQL Server和MySQL中可以看到。
Oracle和PostgreSQL預設把NULL值排在最後。同時,這兩個資料庫管理系統支援一個擴充套件來生成ORDER BY語句: 每個表示式接收可選修飾符:NULL FIRST和NULL LAST,指導引擎怎樣排序NULL值然後通過表示式返回。這些修飾符可以在索引定義時使用,因此結果集的次序不僅可以在查詢中定義,而且可以在索引中定義。
為了解決NULL值問題,可以在查詢時使用額外的排序表示式:
1 2 3 |
SELECT * FROM mytable ORDER BY CASE WHEN mycolumn IS NULL THEN 1 END, mycolumn |
不幸的是,SQL Server不像Oracle和PostgreSQL,不允許使用這些語句來建立索引。所以為了能夠在索引中使用它,你不得不增加一已計算好的列到表中:
1 2 3 4 5 6 7 |
ALTER TABLE mytable ADD mycolumn_nulls_last AS CASE WHEN mycolumn IS NULL THEN 1 END GO CREATE INDEX ix_mytable_mycolumn_nulls_last ON mytable (mycolumn_nulls_last, mycolumn) GO |
為了這個索引能夠在查詢中被使用,你應該總是在ORDER BY使用mycolumn_nulls_last,如:
1 2 3 |
SELECT * FROM mytable ORDER BY mycolumn_nulls_last, mycolumn |
8.謂詞 IN 中的多列
你無法在IN謂詞中使用多個列。下面這個查詢是無效的:
1 2 3 |
SELECT * FROM mytable WHERE (col1, col2) IN ( SELECT col1, col2 FROM othertable ) |
這種性質違反了標準並且是SQL Server特有的。
為了解決這個問題,用EXISTS來代替IN:
1 2 3 4 5 |
SELECT * FROM mytable m WHERE EXISTS ( SELECT NULL FROM othertable o WHERE o.col1 = m.col1 AND o.col2 = m.col2 ) |
請注意這僅僅適用於IN,而不適用於NOT IN.
在處理NULL值方面,NOT IN與NOT EXISTS有一點不同。
為了模仿下面這個NOT IN查詢:
1 2 3 |
SELECT * FROM mytable m WHERE (col1, col2) NOT IN ( SELECT col1, col2 FROM othertable ) |
我們會用下面這個語句:
1 2 3 4 5 6 7 8 9 |
SELECT * FROM mytable m WHERE NOT EXISTS ( SELECT NULL FROM othertable o WHERE o.col1 = m.col1 AND o.col2 = m.col2 ) AND NOT EXISTS ( SELECT NULL FROM othertable o WHERE o.col1 IS NULL OR o.col2 IS NULL ) |
第二個謂詞確保othertable在col1或者col2列都沒有NULL值。任何此類值都會使原始查詢不會返回任何記錄,儘管其他值會。
7. No USING clause(沒有USING語句)
SQL Server在連線中不支援USING語句。
這種性質有悖於標準,只針對於SQL Server·[@Lesus 注:像MySQL和PostgreSQL都支援USING語句].
USING語句允許你像這樣寫連線語句:
1 2 3 |
SELECT * FROM mytable JOIN othertable USING (id) |
代替
1 2 3 |
SELECT * FROM mytable m JOIN othertable o USING o.id = m.id |
注意這兩個查詢並不是完全相同的:第一個會一次性返回id。
這通常並不是個問題關鍵,然而有種情況就是FULL JOIN時就有用了。
想象一個像這樣的PostgreSQL查詢:
1 2 3 4 5 6 7 8 |
SELECT * FROM table1 FULL JOIN table2 USING (id) FULL JOIN table3 USING (id) ORDER BY id LIMIT 10 |
這個相當明瞭:它會選取3個表中全連線有序記錄的前10條,如果可能的話,帶有NULL值的其它兩個表會潛在出現丟失記錄。
讓我們在SQL Server中實現同樣的:
1 2 3 4 5 6 7 8 |
SELECT TOP 10 * FROM table1 t1 FULL JOIN table2 t2 ON t2.id = t1.id FULL JOIN table3 t3 ON t3.id = COALESCE(t1.id, t2.id) ORDER BY COALESCE(t1.id, t2.id, t3.id) |
如你所看到的那樣,我需要使用醜陋的COALESCE來連線和排序,因為使用全連線任何一個表中都會可能在id列上產生NULL值。這個會阻止優化器使用合併連線,它是用來對有索引的id列的最快的方法。
為了享受MERGE JOIN的好處,我需要像這樣重寫這個查詢:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
SELECT * FROM ( SELECT TOP 10 id FROM ( SELECT id FROM table1 UNION ALL SELECT id FROM table2 UNION SELECT id FROM table3 ) q ORDER BY id ) q LEFT JOIN table1 t1 ON t1.id = q.id LEFT JOIN table2 t2 ON t2.id = q.id LEFT JOIN table3 t3 ON t3.id = q.id |
SQL Server能夠使用MERGE UNION而不是MERGE JOIN,它使我們的目的十分有益。
6. String concatenation(字串連線)
連線字串使用的和數字的加法一樣的操作符+。這可能會在數字和字串混合時導致衝突。這種行為還沒在標準中定義,只是針對SQL Server。
當試圖連線數字和字串時,請記住是字串轉換為數字,然後在加,而不是數字轉換為字串,然後連線。
操作符從左到右執行。
這裡是一些查詢[示例]:
1 |
SELECT '1' + 1 -- 2 |
一個字串加上一個數字等價於一個數字和一個轉換為數字的字串。
1 |
SELECT 1 + '1' -- 2 |
一個數字加上一個字串等價於一個數字和一個轉換為數字的字串。
1 |
SELECT '3' + '2' -- '32' |
一個字串加上字串等價於一個字串和其它字串的連線。
1 |
SELECT '1' + '2' + 3 -- 15 |
一個字串加上字串再加上數字為一個數字。首先,兩個字串連線(產生為’12’),然後,這個結果字串轉換為數字再和3相加(產生15).
1 |
SELECT '1' + 'a' + 3 -- Conversion failed when converting the varchar value '1a' to data type int.: SELECT '1' + 'a' + 3 |
首先,這個兩個字串相連線,產生’1a’。然後,試圖將結果字串轉換為一個數字,這會失敗,就會產生如上的錯誤。
如果你想連線一個數字和一個字串,顯式轉換前者為後者:
1 |
SELECT 'Value' + CAST(123 AS CHAR) -- 'Value123' |
5. MAX(BIT)
SQL Server 支援一種特別的資料型別來儲存布林值(booleans),稱之為位(BIT)。然而,你不能在這些領域上聚合AND 或者OR 等(通常可以使用MAX和MIN完成)。
這種行為還沒有被標準定義,只針對SQL Server。
位型別是為儲存布林值而設計的,一條記錄中1位元組儲存8位,9到16位儲存到兩個位元組中,以此類推,是這種型別儲存十分高效。
然而如果我們想聚合這些值時,會遇到錯誤:
1 2 3 4 5 6 7 8 9 10 |
CREATE TABLE bitvalues ( id INT NOT NULL PRIMARY KEY, v1 BIT, v2 BIT, v3 BIT, ) CREATE INDEX ix_bitvalues_v1 ON bitvalues (v1) CREATE INDEX ix_bitvalues_v2 ON bitvalues (v2) CREATE INDEX ix_bitvalues_v3 ON bitvalues (v3) INSERT INTO bitvalues VALUES (1, 0, 0, 1), (2, 0, 1, 1) SELECT MAX(v1), MAX(v2), MAX(v3), MIN(v1), MIN(v2), MIN(v3) FROM bitvalues -- Operand data type bit is invalid for max operator.: SELECT MAX(v1), MAX(v2), MAX(v3), MIN(v1), MIN(v2), MIN(v3) FROM bitvalues |
一個簡單的方法就是把它們轉換為整數:
1 |
SELECT MAX(CAST(v1 AS INT)), MAX(CAST(v2 AS INT)), MAX(CAST(v3 AS INT)), MIN(CAST(v1 AS INT)), MIN(CAST(v2 AS INT)), MIN(CAST(v3 AS INT)) FROM bitvalues |
但是這會阻止使用索引。
更高效的方法就是使用TOP 1 / ORDER BY來代替MAX和MIN。與MAX和MIN不一樣排序,它可以在位上工作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
SELECT ( SELECT TOP 1 v1 FROM bitvalues ORDER BY v1 DESC ), ( SELECT TOP 1 v2 FROM bitvalues ORDER BY v2 DESC ), ( SELECT TOP 1 v3 FROM bitvalues ORDER BY v1 DESC ), ( SELECT TOP 1 v1 FROM bitvalues ORDER BY v1 ), ( SELECT TOP 1 v2 FROM bitvalues ORDER BY v2 ), ( SELECT TOP 1 v3 FROM bitvalues ORDER BY v3 ) |
4. TRIM
SQL Server 沒有實現可以去除字串兩端的空白字元的TRIM函式。
這種行為違反了標準,只針對SQL Server。
聯合使用LTRIM和RTRIM來實現:
1 |
SELECT LTRIM(RTRIM(' my string with spaces ')) -- 'my string with spaces' |
3. LEAST and GREATEST(最小值和最大值)
SQL Server不支援LEAST和GREATEST:這兩個函式對應於從引數列表中返回最小值或最大值。
這沒有在標準中規定,在SQL Server中很特殊。
你可以使用巢狀的CASE語句來模擬這些函式:
1 |
SELECT CASE WHEN x > y THEN x ELSE y END FROM mytable |
但是這個並不具備可讀性和即使只是3列就容易出錯(就不用說更多了(to say nothing of more))
更好的方式是可以在子查詢中使用MAX或者MIN,使用組合聯接(UNION)查詢來返回合適的欄位域。
1 2 3 4 5 6 7 |
SELECT ( SELECT MIN(v) FROM ( SELECT x UNION ALL SELECT y UNION ALL SELECT z UNION ALL SELECT t ) q(v) ) FROM mytable |
2. ORDER BY in nested queries(巢狀查詢中的ORDER BY)
SQL Server 不支援在巢狀查詢中使用ORDER BY。這個查詢不會工作:
1 2 3 |
SELECT * FROM ( SELECT * FROM mytable WHERE col1 = 42 ORDER BY id ) q |
這個性質在標準中沒有定義,對SQL Server也是很特別的。
在巢狀查詢和內聯(單句查詢)表很重視函式,ORDER BY是不支援,除非在SELECT中使用TOP。
SQL 不能保證結果皆的次序,除非你使用ORDER BY顯式定義它,這就是為什麼ORDER BY在巢狀查詢中很重要了。如果你像指定次序,你應該在最外層查詢中指定它。
然而,這個可能對於那些從其它引擎移植過來的查詢會失敗。一般地,ORDER BY允許在巢狀查詢中以及要麼傳遞到最外層查詢中要麼對排序沒有假設(如ORDER BY被隱式的忽略了)。
這裡沒有變通方案,因為你不得不修改巢狀查詢,如移除ORDER BY:
1 2 |
SELECT * FROM ( SELECT * FROM mytable WHERE col1 = 42 ) q |
或者是新增TOP 100部分:
1 2 3 |
SELECT * FROM ( SELECT TOP 100 PERCENT * FROM mytable WHERE col1 = 42 ORDER BY id ) q |
這兩個解決方案會讓優化引擎忽略ORDER BY。
1. Readers and writers block each other(讀寫相互阻塞)
SQL Server的鎖系統使讀阻塞寫,反之亦然,有時甚至它們不能進入相同記錄。
這種性質在標準中沒有定義,在SQL Server和MySQL中的MyISAM中遵守。
任何正派的資料庫系統都應遵守ACID性質,這個會確保事務具有原子性(Atomic),一致性(Consistent), 隔離性(Isolated)和 永續性(Durable).
除此之外,這就意味著沒有任何語句可以部分改變資料庫的狀態(如同外面所看到的那樣)。如果一個查詢更新了100行,一定沒有其它會話會發現30行已更新而70行沒有:它總是那麼做完,要麼什麼都不做。
不同的系統使用不同的方式達到這一目的,但是通常這個歸結於兩個方面中的一個:在更新時建立一份記錄集備份,或者鎖定記錄使只可以一個會話進入。
SQL Server實現的第二種方法。如果一個會話更新一條記錄,它會建立一個叫做鎖(lock)的特殊的物件,它會阻止併發進入這條記錄。即使一個會話讀取一條記錄,它會放置一個鎖在上面(儘管有種方式可以使併發讀成為可能)。
鎖存放在記憶體中,並且維護起來很昂貴。所以隨著語句的數目變得很大時,SQL Server可能決定增加鎖的數目:使鎖保護而不至於影響到記錄,但是這條記錄或者即使是整個表都存在一個資料庫頁中。這會減少記憶體,但是使鎖缺少了可選性,所以這些記錄當查詢時可能會鎖住,然後對於其它會話不可見。
為了解決這個問題,SQL Server提供了所謂的SNAPSHOT的特殊的事務隔離級別。它不是建立在實現一致性上上鎖,而是在一個tempdb上覆制一份受影響的記錄(臨時資料儲存在一個特別的儲存區域)。這允許寫不阻塞讀,反之亦然。但是會增加儲存需求和系統I/O壓力,所以可能會影響效能。
參與翻譯(2人):Lesus, 媛媛小譯