第2 章 SQL 問題

2.1 本章目的
在本章中就在 HSQLDB 主頁論壇或郵件列表中多次提出的問題進行解答,如果你打算在應
用程序中使用HSQLDB 的話,你應該閱讀一下本章。
2.2 對SQL 標準的支持
1.8.0 版本的HSQLDB 支持SQL92、99 和2003 標準規定的SQL 方言。這意味着HSQLDB
中支持的標準特性(例如左外連接)的語法是由標準文本規定的。許多SQL92、99 甚至更
高級的特徵在HSQLDB 中得到了支持,並且對SQL2003 標準的大多數以及一些可選的特性
進行支持。然而,對於某些標準的特性沒有支持,所以HSQLDB 就沒有做出支持各個級別
所有的標準特性的聲明。
“SQL 語法”一章列出了HSQLDB 所支持的所有的關鍵字和語法。當書寫有關HSQLDB 或者
轉換現有的有關HSQLDB 的SQL DDL(數據定義語言)和DML(數據操作語言)語句的
時候,你應該查閱一下HSQLDB 所支持的語法,並對SQL 語句作出相應的修改。
SQL 標準中保留的關鍵字是不能作爲表明或字段名使用的。例如,“POSITION”被作爲與Java
中的String.indexOf()作用類似的函數加以保留。HSQLDB 目前並不限制使用它不支持其用
法的關鍵字或用戶能夠區分清楚的關鍵字。例如,“BEGIN”是HSQLDB 目前沒有支持的關
鍵字,所以你也可使用它作爲表或者列的名稱。不過你應該避免使用這些保留字,因爲在
HSQLDB 以後的版本中有可能支持這些保留字,否則將拒絕含有這些保留字表定義或查詢
語句。全部SQL 保留字列表請參看org.hsqldb.Token 類。
HSQLDB 也支持一些SQL 標準之外的關鍵字和表達式作爲性能的增強。像SELECT TOP 5
FROM .., SELECT LIMIT 0 10 FROM ... 或者 DROP TABLE mytable IF EXISTS 這樣的表達
式都是HSQLDB 增強性能所支持。
所有被雙引號標註的關鍵字可以被用做數據庫對象。
2.3 約束和索引
2.3.1 主鍵約束
在 1.7.0 版本之前,一個CONSTRAINT <name> PRIMARY KEY(名爲name 的主鍵約束)
被在內部翻譯成一個唯一的索引,另外,一個隱藏列被添加到具有額外唯一索引的表上。從
1.7.0 開始,單一列主鍵和多列主鍵(single-column and multi-column PRIMARY KEY)約束都得

到支持。它們由主鍵列指定的唯一索引支持,而沒有額外的隱藏列來維護它們的索引。
2.3.2 唯一性約束
根據 SQL 標準,一個單一列上的唯一性約束表示不允許存在兩個相同的值(空值出外),也
就是說這樣的列中可以一個或更多爲空值(NULL)的行而不違反唯一性約束。
多個列(c1, c2, c3, ..)的唯一性約束表示這些列中的任何兩個值的集合都不相等(除非至少其
中有一個爲空)。每一個單一列內部可以有重複的值。下面這裏滿足兩列上的唯一性約束。
例 2.1. 滿足2 列唯一性約束的列值
1, 2
2, 1
2, 2
NULL, 1
NULL, 1
1, NULL
NULL, NULL
NULL, NULL
自從 1.7.2 版本以來,對於空值的唯一性約束和索引的處理已經向遵循SQL 標準改變。任何
唯一性約束列的值全爲空的行,通常都可以被添加到表中,所以對於唯一性約束的列來說,
如果其中的一個行的值爲空,那麼多個行就可以具有相同的值。
2.3.3 唯一性索引
在 1.8.0 中,用戶定義的唯一性索引仍然可以進行聲明,但是它已經不被贊成使用了,你應
該使用一個唯一性約束來替代唯一性索引。
像在早期版本的HSQLDB中一樣,名爲<name>的唯一性約束通常在內部創建列的唯一索引,
所以它實際上和已經廢除的唯一性索引聲明具有相同的效果。
2.3.4 外鍵
HSQLDB 從1.7.0 版開始具有單一列外鍵和多列外鍵的特性。一個外鍵也可以被指定引用一
個沒有命名的目標列的目標表。在這種情況下,目標表的主鍵列被用作引用列。任何外鍵中
的一對引用和被引用的列應該具有相同的數據庫類型。當聲明瞭一個外鍵時,主鍵表被引用
的列必須具有一個唯一性約束(或主鍵),而在引用列裏會自動創建一個非唯一性索引。例
如:

CREATE TABLE child(c1 INTEGER, c2 VARCHAR, FOREIGN KEY (c1, c2) REFERENCES
parent(p1, p2));
parent 表中的 (p1,p2) 列一定存在一個唯一性約束。child 表中的(c1,c2)列會自動生成一個非
唯一性索引。p1 和c1 列一定具有相同的類型(INTEGER),p2 和c2 列一定具有相同的類
型(VARCHAR)。
2.3.5 索引和查詢速度
HSQLDB 沒有使用索引來改善查詢結果的分類,但是索引在提高查詢速度上起着至關重要
的作用。如果一個表沒有使用索引,進行類似於DELETE 這樣的查詢操作的時候,表中所
有的行記錄都要被檢查一遍。WHERE 從句中的列中,如果有一個列建立索引的話,那麼查
詢操作就可能直接從第一個候選行記錄開始,從而減少了要檢查的行記錄的數量。
索引在多個表之間進行連接操作的時候顯得更重要。在 SELECT ... FROM t1 JOIN t2 ON
t1.c1 = t2.c2 執行的時候,對t1 中的行一個接一個的進行操作,來查找t2 中沒有與之匹配的
行。如果沒有t2.c2 沒有任何索引的話,那麼,對於t1 的每一行來說,要必須檢查t2 的所有
行。然而有一個索引的話,在很短的時間內就能找到匹配的行。如果,查詢(query)在t1 上
還有一個條件(例如,SELECT ... FROM t1 JOIN t2 ON t1.c1 = t2.c2 WHERE t1.c3 = 4),那
麼在t1.c3 上創建一個索引的話,將不需要一個接一個的檢查所有的行,並且將每返回一行
的查詢時間降低到1 個毫秒以下。所以,如果t1 和t2 各有10000 行記錄,那麼沒有索引的
查詢將會進行100,000,000 次行檢查。如果在t2.c2 上創建一個索引的話,查詢次數將會降低
到10000 次行檢查和索引的查找。在t2.c2 上有另外一個索引的話,只需要檢查4 行就可以
得到第一個符合條件的結果行。
對於主鍵和唯一列,將對自動生成索引,否則,你要使用CREATE INDEX 命令來定義索引。
注意:在 HSQLDB 中,多列(multiple columns)上的一個唯一性索引可以在內部被用作列
表第一列上的非唯一性縮影。例如,CONSTRAINT name1 UNIQUE (c1, c2, c3);表示這是
CREATE INDEX name2 ON atable(c1)等價的表示方式,所以你只要列表第一列中一個的話,
就不需要指定一個額外的索引。
在 1.8.0 版本中,一個多列的索引將會加速包含所有列的連接和值的查詢。你不需要在這些
列上聲明任何附加的單獨的索引,除非你僅對這些列的子集進行查詢。例如,在三個列上擁
有主鍵或唯一性約束或只是一個普通的索引的表的行記錄,在三個列的值都在WHERE 從
句中指定了時候,查詢就會比較高效。例如,SELECT ... FROM t1 WHERE t1.c1 = 4 AND t1.c2
= 6 AND t1.c3 = 8 將會使用t1(c1,c2,c3)上的索引(如果存在的話)。
作爲多鍵索引改進的結果,和以前相比,聲明索引或約束的列的順序帶給查詢速度的影響大
大減少。如果包含多較多不同值的列首先出現的話,查詢速度將會有一點提高。
一個多列索引不會加快僅對第二列或第三列查詢的速度。第一列必須在JOIN .. ON 或者
WHERE 條件中指出。

查詢速度很大程序上取決於 JOIN .. ON 或者 FROM 從句中表的順序。例如,下面的第二
個查詢在大的表中會更快一些(假如TB.COL3 上有索引的話)。因爲,如果下面的查詢被用
到第一表中(以及TB.COL3 上有索引)的話,TB.COL3 可以被很快的查出來:
(TB 是一個很大的表,但符合TB.COL3=4 條件的僅僅只有幾行記錄)
SELECT * FROM TA JOIN TB ON TA.COL1 = TB.COL2 AND TB.COL3 = 4;
SELECT * FROM TB JOIN TA ON TA.COL1 = TB.COL2 AND TB.COL3 = 4;
基本規則是將這樣表放在首位:其中的一個列上具有一個縮小的條件(narrowing condition)。
1.7.3 版本的HSQLDB 具有爲視圖或用在查詢中的子選擇(subselect)創建自動的,快速的
(on-the-fly)索引的特徵。當一個索引被連接到一個表或一個視圖的時候,會被添加到一個不
同於所連接的視圖裏。
2.3.6 Where 條件或者連接
使用 WHERE 條件連接表可能會降低執行速度。例如下面的查詢通常會比較慢,甚至有索
引的時候:
SELECT ... FROM TA, TB, TC WHERE TC.COL3 = TA.COL1 AND TC.COL3=TB.COL2 AND
TC.COL4
上面的查詢表示 TA.COL1 = TB.COL2 但是卻沒有顯式地設置條件。如果TA 和TB 每個都
含有100 行的記錄,那麼即使在被連接的列上建有索引時,也有10000 個組合combinations)
被連接到TC 來滿足這些列的條件。使用JOIN 關鍵詞,條件TA.COL1 = TB.COL2 將必須被
顯示聲明,它將會使TA 和TB 行在被TC 連接前降低它們的結合(combinations)次數,這
樣將會使對大表查詢的執行大大加快:
SELECT ... FROM TA JOIN TB ON TA.COL1 = TB.COL2 JOIN TC ON TB.COL2 = TC.COL3
WHERE TC.COL4 = 1
如果連接查詢中表的順序改變了的話,那麼下面查詢的執行速度會被大大加快,所以使用了
TC.COL1 = 1 以及將列的行的較小集合連接在一起:
SELECT ... FROM TC JOIN TB ON TC.COL3 = TB.COL2 JOIN TA ON TC.COL3 =
TA.COL1 WHERE TC.COL4 = 1
在上述的例子中,數據庫引擎將TC.COL4 = 1 自動應用到TC,僅僅連接了行的集合來使這
個條件也滿足其他的表。TC.COL4, TB.COL2 和TA.COL1 上的索引如果存在的話將會被使
用,進而加速查詢。


SELECT ... FROM TA WHERE TA.COL1 = (SELECT MAX(TB.COL2) FROM TB
WHERE TB.COL3 = 4)
SELECT ... FROM (SELECT MAX(TB.COL2) C1 FROM TB WHERE TB.COL3 = 4) T2
JOIN TA ON TA.COL1 = T2.C1
第二個查詢將 MAX(TB.COL2)轉變成一個單行的表,但後將它連接到TA。因爲TA.COL1
上存在索引,所以這個查詢的速度會非常快。第一個查詢將會測試TA 中的每一行記錄,重
復的對MAX(TB.COL2)進行估算。
2.4 類型和算術操作
HSQLDB 支持的所有類型的數據庫表都可以被索引,也可以進行比較。所有的類型都可以
用CONVERT()庫函數進行顯式轉換,但是在大多數情況下它們可以被自動的轉換。不推薦
在LONGVARBINARY, LONGVARCHAR 和 OTHER 列使用索引,因爲這些索引可能在將
來的版本中是不允許的。
早期版本的 HSQLDB 在算術操作的處理上有些不足。例如,不可能把10/2.5 插入到任何
DOUBLE 或者DECIMAL 列。在1.7.0 版本中,遵循下列規則的全操作(full operations)是可
能的:
TINYINT, SMALLINT, INTEGER, BIGINT, NUMBER 和DECIMAL(沒有小數點)是
HSQLDB 支持的整值類型,它們在Java 中被映射成byte, short, int, long 和 BigDecimal。SQL
類型規定了每個類型的最大值和最小值。例如,TINYINT 的值範圍是從-128 到 +127,雖
然實際用於處理TINYINT 類型的Java 類型是java.lang.Integer。
REAL, FLOAT, DOUBLE 在Java 中都被映射成double。
DECIMAL 和NUMERIC 被映射成java.math.BigDecimal,它們可以有很多的位數。
2.4.1 整型類型
TINYINT, SMALLINT, INTEGER, BIGINT, NUMBER 和 DECIMAL(不帶小數點)在內部都
是完全可以互換的,不存在數據窄化(narrowing)的情況。返回的操作結果依賴於不同的操作
數類型,它可以是存在於JDBC 結果集裏面的任何相關的Java 類型(Integer, Long or

BigDecimal)。只要返回值可以被結果類型所表示,ResultSet.getXXXX()方法就能夠用來獲取
數據。這個類型是基於查詢的,而不是實際返回的行。當向數據庫的表中添加了更多數據之
後返回多行記錄的時候,返回一條行記錄的相同查詢的類型不發生變化。
如果 SELECT 語句涉及到一個簡單的列或函數的話,那麼返回類型就是所對應的列的類型
或者函數的返回值類型。例如:
CREATE TABLE t(a INTEGER, b BIGINT); SELECT MAX(a), MAX(b) FROM t;
上面的依據將會返回一個結果集,其第一列的類型是java.lang.Integer,第二列的類型是
java.lang.Long。然而,
SELECT MAX(a) + 0, MAX(b) + 0 FROM t;
將返回 java.lang.Long 和 BigDecimal 的值, 它是作爲所有返回值的統一類型的提升生成
的。
在表達式中的中間整形數值的大小上,沒有內建的限制。因此,你應該檢查結果集列的類型,
選擇一個合適的getXXXX()方法來獲取該值。另外你也可以使用getObject()方法,然後,將
結果轉換成java.lang.Number,在結果上使用intValue() 或 longValue()方法。
當表達式的結果存在數據庫表的一個列中,它必須適合目標列,否則,會返回一個錯誤。例
如,當計算1234567890123456789012 / 12345687901234567890 時,結果可以被存儲在任何
類型的列中,甚至是個TINY 列,因爲它是一個很小的值。
2.4.2 其他數字類型
在 SQL 語句中,如果不是指數形式,帶有小數點的數字都被當作DECIMAL 處理。因此0.2
被認爲是DECIMAL 類型的值,而0.2E0 則被看作DOUBLE 類型。
當對某個值使用PreparedStatement.setDouble()或setFloat()方法時,該值就被自動默認爲
DOUBLE 類型處理。
當表達式的一部分是 REAL、FLOAT 或者DOUBLE(所有同義的類型)類型時,那麼結果
類型則是DOUBLE。
此外,當DOUBLE 類型不存在時,如果表達式的一部分是DECIMAL 或NUMERIC 類型時,
結果類型就是DECIMAL,只要結果可以用這種數字類型表示,那麼它可以從ResultSet 中
按照任何所需的數字類型來獲取。這就意味着如果一個DECIMAL 值的範圍在
Double.MIN_VALUE- Double_MAX_VALUE 之間,那麼它就可以被轉換成DOUBLE 類型。
和整形數值相似,當表達式結果存入表的列中時,它的類型必須符合該列的類型,否則將有
錯誤出現。

進行除法運算時,DOUBLE 和DECIMAL 類型間的區別就顯得很重要的。當操作數類型是
DECIMAL,結果的取值範圍(小數點右邊的位數)取的是兩個操作數中較大的範圍。如果
是DOUBLE 類型,那麼範圍則是運算的精確結果。例如,10.0/8.0(DECIMAL)等於1.2,
但是10.0E0/8.0E0(DOUBLE)等於1.25。如果不是除法運算,DECIMAL 類型能精確的描
述算法;如果兩個相乘,那麼範圍就是兩個數範圍之和。
REAL、FLOAT 和DOUBLE 類型數值都被當作java.long.Double 對象存入數據庫中。同時也
可以存儲和支持一些特殊值如NaN 和正負無窮大。這些值可以通過JDBC PreparedStatement
方法提交給數據庫,並且可以返回結果集對象。
2.4.3 二進制和Boolean 類型
從 1.7.2 開始,BIT 通常也可以看作BOOLEAN 的別名。BOOLEAN 列的主要描述是’true’
或’false’,通過JDBC 使用時,可以被看作boolean 或者String 類型。BOOLEAN 列的類型
還可以使用任何數字類型的值進行初始化。這樣的情況下,0 將被轉化成false,其他非0 值
將被轉換成true。
從 1.7.3 開始,BOOLEAN 類型遵循SQL 標準,除了支持TRUE 和FALSE 類型以外,也提
供了對UNDEFINED 狀態的支持。NULL 值被當作未定義類型處理。這一改進影響了那些
包含NOT IN 的查詢。這樣的查詢語句,請參看測試文本文件TestSelfNot.txt。
2.4.4 Java 對象的存儲和操作
從 1.7.2 開始,對Java 對象的存儲和操作的支持有了很大的提高,通過使用各種變量的
PreparedStatement.setObject()方法,任一個序列化的JAVA 對象都可以被直接的插入到
OTHER 類型的列中。
爲了比較,在索引裏邊,任何兩個Java 對象都被看作是相等,除非他們中的一個是NULL。
在OTHER 類型的列中,你不能搜索一個指定的對象或者進行連接操作。
請注意,HSQLDB 不是一個對象-關係數據庫。Java 對象只是簡單地存儲在內部,除了將
OTHER 類型的列值賦爲NULL 或測試該列值是否爲NULL 之外,不應該對它們進行其他的
操作。測試例如WHERE object1=object2,或者WHERE ojbect1=?並不能得到你預期的結果。
任何非空的對象都能滿足這樣的測試。但是WHERE object1 IS NOT NULL 就能很好的執行。
普通列的數據被分配給 Java 對象列(例如,在類似UPDATE mytable SET objectcol = intcol
WHERE…的SQL 語句中,向列中填充一個整型或字符串)時,數據庫並不會返回錯誤,但
在將來,這樣的做法時很不恰當的。因此請使用OTHER 類型列來存貯對象而不是其他類型
的數據。

2.4.5 類型的大小、精度和範圍
在 1.7.2 版本以前,所有帶有大小、精度或範圍的限制的數據庫表的列類型定義可有可無。
在 1.8.0 版本中,這樣的限定必須和SQL 標準一致。例如INTEGER(8)這樣的形式將不能
被接受。除非設置了數據庫的屬性, 要你這個限定仍然可以被忽略。SET
PROPERTY ”sql.enforce_strict_size” TRUE語句將爲CHARACTER或VARCHAR列強制設置
大小,並且當插入或更新一個CHARACTER 列時填充字符串。精度和範圍限制也被轉化爲
DECIMAL 和NUMERIC 類型。TIMESTAMP 只能使用0 或6 這樣的精度。
如你所料,將一個值轉換成一個CHARACTER 的限定類型將導致切割或填充。因此象
CAST(mycol AS VARCHAR(2))=’xy’這樣的測試語句結果得到以xy 開頭的值。這和
SUBSTRING(mycol FROM 1 FOR 2)=’xy’是相同的。
2.5 序列和標識
SEQUENCE關鍵字作爲SQL200n 標準語法的子集被引入到1.7.2 版本中。相應的,IDENTITY
列的SQL200n 語法也被引進。
2.5.1 標識自動增長列
每個表都可以有一個自動增長列,衆所周知的就是 IDENTITY 列。一個IDENTITY 列總是
被當作表的主鍵處理(因此,多列主鍵不可能有一個IDENTITY 列)。作爲一個捷徑,已經
爲CREATE TABLE <tablename>(<colname>IDENTITY,…)提供了支持。
從1.7.2版本開始,SQL標準語法默認的支持指定初始值。支持的形式是: (<colname>
INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH n, [INCREMENT BY
m])PRIMARY KEY, ...).。同時也對BIGINT特性列提供了支持。因此,一個IDENTITY是一
個由Sequence生成器生成默認值的INTEGER或者BIGINT列。
當使用INSERT INTO <tablename>…語句向表中添加一個新列時;你可以在IDENTITY列中
使用NULL值,這樣該列的值將會自動生成。IDENTITY()函數返回通過連接插入到
IDENTITY列中的最後一個值。使用CALL IDENTITY()這個SQL語句取得這個值。如果
想在子表中使用這個值,你可以使用語句INSERT INTO <childtable> VALUES(...,
IDENTITY(),...);。在任何附加的更新或插入語句在數據庫中執行前,兩種類型的IDENTITY
()函數必須被調用。
接下來要使用的的IDENTITY值可以通過語句ALTER TABLE ALTER COLUMN
<columnname> RESTART WITH <new value>;來設置。

2.5.2 序列
SQL200n語法和用法和現有的大多數據庫引擎所支持的是有區別的。可以通過命令CREATE
SEQUENCE創建Sequences,並且可以通過命令ALTER SEQUENCE在任何時候進行修改它
們的當前值。一個sequence的下一個值是通過NEXT VALUE FOR <name>表達式取得。這個
表達式可以用來插入或者更新表的行,你也可以在select語句中使用它。例如,如果想計算
在一個Sequence序列中SELECT返回的行,可以使用:
例2.3 計算選擇操作以後返回的行
SELECT NEXT VALUE FOR mysequence, col1, col2 FROM mytable WHERE ...
請注意,順序的語義並不完全和SQL200n定義的相同。例如,如果你在相同的列插入操作中
兩次使用相同的序列,你將會得到兩個不同的值,而不是標準所說的同一個值。
你可以通過查詢SYSTEM SEQUENCES表得到下一個值,這個值將從任何一個已定義的序列
中返回。SEQUENCE_NAME列保存的是名稱,NEXT_VALUE列保存的是下一個被返回的
值。
2.5.3 事務的問題
HSQLDB在READ_UNCOMMITTED級別上支持事務,就象0級別上的事務隔離。這就意味
着在一個事務的生命週期內,其他數據庫的連接可以看到這個事務對數據的改變。總的來說,
對事務的支持還是不錯的。象數據庫突然關閉而事務被提交這樣的BUG已經被修復了。然
而,下面的問題將會在多個數據庫連接使用事務時出現問題:
如果兩個事務修改同一行,而兩個事務提交時沒有異常出現。這樣的情況可以通過這種方式
的設計來避免:應用程序數據的一致性不依賴一個事務對數據的獨佔性修改。你可以通過設
置一個數據庫屬性,在這樣的情況發生時會拋出一個異常:SET PROPERTY
"sql.tx_no_multi_rewrite" TRUE
當一個ALTER TABLE…INSERT COLUMN或者DROP COLUMN命令導致數表的結構變化
時,當前的會話被提交。如果由另一個連接啓動的未提交的事務此時應在受影響的表中修改
了數據,那麼這個事務在ALTER TABLE命令後不可能被回滾。這一點同樣也適用於ADD
INDEX 或ADD CONSTRAINT命令。只有在其他連接沒有使用事務時,才推薦使用ALTER
等這樣的命令。
在CHECKPOINT命令執行以後,未授權事務才能繼續運行、提交或回滾。然而,如果數據
庫隨後並沒有使用SHUTDOWN命令正常的關閉的話,在數據庫關閉時仍然是未提交狀態的
任何這樣事務,將被在下次啓動數據庫時被部分提交(CHECKPOINT的狀態)。不管程序
中沒有未授權的事務的情況,還是因爲想這樣的事務不可能持續過長時間,以至於非正常關
閉可能影響數據的情況,都推薦使用CHECKPOINT。

2.5.4 新特性和變化
在最新的1.8.0版本中增加了許多更好的對SQL的支持。這些都在SQL語法這一章和文件
在../changelog_1_8_0.txt,../changelog_1_7_2.txt中列出了。象POSITION(),SUBSTRING(),
NULLIF(), COALESCE(), CASE ... WHEN .. ELSE, ANY, ALL這樣的表達式和函數也在其
中。另外一些改進雖然在文檔中不是很明顯,但能使數據庫性能比以前版本有很大的改進。
這其中最重要的就是能在連接(非空列將不再被連接)和外連接(現在的結果是正確的)中
處理NULL值。你應該對使用新版本數據庫引擎的應用程序進行測試,確定這些程序不再使
用舊版本的那些錯誤的特性。在將來的版本中,數據庫引擎仍將朝着完全支持SQL標準的方
向改進,因此最好不要依賴當前版本中任何的非標準特性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章