許多SQL數據庫引擎(除SQLite之外的各種SQL數據庫引擎)使用靜態、嚴格的數據類型。對於靜態類型,一個值的數據類型由它的容器,即存儲這個值的列來決定。SQLite則使用更加通用的動態類型系統。在SQLite中,一個值的數據類型被關聯到這個值本身,而不是它的容器。SQLite的動態類型系統向後兼容一般靜態類型系統的數據庫引擎。在某種意義上,工作在靜態類型數據庫上的SQL聲明也同樣能工作在SQLite上。但是SQLite動態類型還允許做一些在傳統嚴格類型的數據庫中不能做的事情。
一、存儲類別及數據類型
在SQLite數據庫中存儲(或被數據庫引擎操作)的每個值,都屬於下面存儲類別之一:
* NULL: 值爲一個NULL空值。
* INTEGER: 值被標識爲整數,依據值的大小可以依次被存儲爲1,2,3,4,6或8個字節。
* REAL: 所有值都是浮點數值,被存儲爲8字節的IEEE浮點數。
* TEXT: 值爲文本字符串,使用數據庫編碼存儲,如UTF-8、UTF-16BE或UTF-16-LE。
* BLOB: 值是數據的二進制對象,如何輸入就如何存儲,不改變格式。
注意一個存儲類別比一個數據類型更通用。例如INTEGER存儲類別就包括6個不同長度的整型數據類型,這在磁盤上是不同的。不過只要INTEGER值從磁盤上讀到內存中進行處理,它們會轉換成最通用的數據類型(8字節的整型),因此在大多數情況下,對“存儲類別”和“數據類型”並不做嚴格區分,這兩個術語可交換使用。
在SQLite3數據庫中,除了INTEGER PRIMARY KEY這一列,任何列都可以存儲任何類型的數據。SQL語句中的所有值,不管是嵌入到SQL語句文本中的字面值還是綁定到預先編譯好的SQL語句中的參數值,都有一個隱式存儲類別。在下述情況中,數據庫引擎將在執行查詢時,可以讓存儲的值在數值類型(INTEGER和REAL)和文本類型之間轉換。
(1)Boolean數據類型
SQLite沒有單獨的布爾數據類型。相應的,布爾值被存儲爲整型0(false)和1(true)。
(2)日期和時間數據類型
SQLite沒有單獨的日期/時間數據類型。相應的,內建的日期和時間函數能夠把日期和時間存儲爲文本、實數或整數值,以TEXT、REAL和INTEGER類型分別不同的格式表示該類型,如:
* 文本值爲ISO8601字符串("YYYY-MM-DD HH:MM:SS.SSS")。
TEXT: "YYYY-MM-DD HH:MM:SS.SSS"。
* 實數值爲儒略日數,即從公元前4714年11月24日格林威治正午時刻開始的天數,按公曆來算。
REAL: 以Julian日期格式存儲。
* 整數值爲Unix時間,即從1970-01-01 00:00:00 UTC開始的秒數。
INTEGER: 以Unix時間形式保存數據值,即從1970-01-01 00:00:00到當前時間所流經的秒數。
應用程序可以選擇其中的一種格式來存儲日期和時間,也可以通過內建的日期和時間函數在這些格式之間轉換。
二、列的親和類型(類型親緣性)
爲了最大化SQLite和其它數據庫引擎之間的數據類型兼容性,SQLite提出了"類型親緣性(Type Affinity)"的概念。我們可以這樣理解"類型親緣性",在表字段被聲明之後,SQLite都會根據該字段聲明時的類型爲其選擇一種親緣類型,當數據插入時,該字段的數據將會優先採用親緣類型作爲該值的存儲方式,除非親緣類型不匹配或無法轉換當前數據到該親緣類型,這樣SQLite纔會考慮其它更適合該值的類型存儲該值。SQLite目前的版本支持以下五種親緣類型:
親緣類型 | 描述 |
TEXT | 數值型數據在被插入之前,需要先被轉換爲文本格式,之後再插入到目標字段中。 |
NUMERIC | 當文本數據被插入到親緣性爲NUMERIC的字段中時,如果轉換操作不會導致數據信息丟失以及完全可逆,那麼SQLite就會將該文本數據轉換爲INTEGER或REAL類型的數據,如果轉換失敗,SQLite仍會以TEXT方式存儲該數據。對於NULL或BLOB類型的新數據,SQLite將不做任何轉換,直接以NULL或BLOB的方式存儲該數據。需要額外說明的是,對於浮點格式的常量文本,如"30000.0",如果該值可以轉換爲INTEGER同時又不會丟失數值信息,那麼SQLite就會將其轉換爲INTEGER的存儲方式。 |
INTEGER | 對於親緣類型爲INTEGER的字段,其規則等同於NUMERIC,唯一差別是在執行CAST表達式時。 |
REAL | 其規則基本等同於NUMERIC,唯一的差別是不會將"30000.0"這樣的文本數據轉換爲INTEGER存儲方式。 |
NONE | 不做任何的轉換,直接以該數據所屬的數據類型進行存儲。 |
在SQLite 3中,值被定義爲何種類型只和值自身有關,與列無關,和變量也沒有關係(被稱作弱類型)。而其它的數據庫引擎都受靜態類型系統的限制,其值的類型是由其所屬列的屬性決定的,而與值本身無關。爲了最大限度的增加SQLite數據庫和其他數據庫的兼容性,SQLite支持列的“親和類型”概念。列的親和類型是指爲該列所存儲的數據建議一個類型,要注意這個類型是建議而不是強迫。任何列依然是可以存儲任何類型的數據的。只是針對某些列,如果有建議類型的話,數據庫將優先按所建議的類型存儲。這個被優先使用的數據類型稱爲“親和類型”。
SQLite 3的每個列均可以使用以下親和類型中的一種:TEXT、NUMERIC、INTEGER、REAL、NONE。
帶有文本親和類型的列可以使用NULL、TEXT或BLOB類型來存儲所有數據。如果數值數據被插入到這樣的列中,會在存儲之前轉換成文本類型。
帶有數值親和類型的列可以使用所有五種類型來存儲值。當文本數據被插入到數據值型的列中時,如果轉換是無損的且可逆的,則文本會被轉換成INTEGER或REAL(按優先順序)。爲了在TEXT和REAL之間轉換,SQLite嘗試無損且可逆地轉換文本的開頭15個有效十進制數字。如果不能成功轉換的話,值則只能按文本類型存儲了,而不會被轉換成NULL類型或BLOB類型來存儲。帶有小數點或指數記法的字符串可能看起來像一個浮點字面值,但只要值能表示爲一個整數,數值親和類型將把它轉換成一個整數。因此,字符串'3.0e+5'會被轉換成整數300000,而不是浮點數300000.0。
使用整數親和類型的列,其行爲與數值親和類型的列一樣。但也有些區別,比如沒有小數部分的實數字面值被插入整數親和類型的列時,它將被轉換成整數並按整數類型存儲。
使用實數親和類型的列,其行爲與數值親和類型的列一樣。但有一個區別,就是整數會強制用浮點數來表示。(作爲一個內部優化,無小數部分的小浮點數會當作整數寫入磁盤,以佔用更少的空間。當讀出這個值時會自動轉換回浮點數。這個優化在SQL級別完全不可見,並且只有通過檢測數據庫文件的原始比特位才能發現)。
使用NONE親和類型的列不會優先選擇使用哪個類型,在數據被存儲前也不會強迫轉換它的類型,而是直接按它聲明時的原始類型來存儲。
(1)列親和類型的確定(決定字段親緣性的規則)
列的親和類型由列的聲明類型(在寫SQL語句時指定)來確定,即字段的親緣性是根據該字段在聲明時被定義的類型來決定,根據以下規則順序來判斷:
1)如果列的聲明類型包含字符串"INT",則被定義爲整數親和類型。
2)如果列的聲明類型包含字符串"CHAR"、"CLOB"或"TEXT"中的某一個,則列具有文本親和類型。注意VARCHAR類型包含字符串"CHAR",因此被定義爲文本親和類型。
3)如果列的聲明類型包含字符串"BLOB",或者沒有爲列聲明數據類型,則列具有NONE親和類型。
4)如果列的聲明類型包含字符串"REAL"、"FLOA"或"DOUB"中的某一個,則列具有REAL親和類型。
5)否則,列的親和類型爲NUMERIC。
需要注意的是以下情況的列表順序,即如果某一字段類型同時符合兩種親緣性,那麼排在前面的規則將先產生作用。確定列親和類型的規則順序非常重要,聲明類型爲"CHARINT"的列匹配規則1和2,但按順序優先使用規則1,因此列被定義爲整數親和類型。
(2)親和類型名稱實例
按照上面5條規則,下面例子顯示傳統SQL實現中的各種通用數據類型(CREATE TABLE語句或CAST表達式中的數據類型)怎樣被轉化成SQLite中的親和類型。這裏只是所有傳統數據類型中的一部分,它們能夠被SQLite接受。注意跟在類型名後面的括號中的數字參數在SQLite中被忽略。SQLite不在字符串、BLOB對象或數值上強加任何長度限制(除了大的全局SQLITE_MAX_LENGTH限制)。
* INT, INTEGER, TINYINT, SMALLINT, MEDIUMINT, BIGINT, UNSIGNED BIG INT, INT2, INT8: 定義爲INTEGER親和類型(按規則1)。
* CHARACTER(20), VARCHAR(255), VARYING CHARACTER(255), NCHAR(255), NATIVE CHARACTER(70), NVARCHAR(100), TEXT, CLOB: 定義爲TEXT親和類型(按規則2)。
* BLOB, 不聲明類型: 定義爲NONE親和類型(按規則3)。
* REAL, DOUBLE, DOUBLE PRECISION, FLOAT: 定義爲REAL親和類型(按規則4)。
* NUMERIC, DECIMAL(10,5), BOOLEAN, DATE, DATETIME: 定義爲NUMERIC親和類型(按規則5)。
注意聲明類型"FLOATING POINT"將得到INTEGER親和類型,而不是REAL親和類型,因爲"POINT"中有子串"INT"。聲明類型"STRING"爲NUMERIC親和類型,而不是TEXT。
(3)列親和類型轉化實例
SQL示範:當值被插入到表中時,SQLite是怎樣使用親和類型來做類型轉換。
CREATE TABLE t1( t TEXT, -- text affinity by rule 2 nu NUMERIC, -- numeric affinity by rule 5 i INTEGER, -- integer affinity by rule 1 r REAL, -- real affinity by rule 4 no BLOB -- no affinity by rule 3 ); -- Values stored as TEXT, INTEGER, INTEGER, REAL, TEXT. INSERT INTO t1 VALUES('500.0', '500.0', '500.0', '500.0', '500.0'); SELECT typeof(t), typeof(nu), typeof(i), typeof(r), typeof(no) FROM t1; text|integer|integer|real|text -- Values stored as TEXT, INTEGER, INTEGER, REAL, REAL. DELETE FROM t1; INSERT INTO t1 VALUES(500.0, 500.0, 500.0, 500.0, 500.0); SELECT typeof(t), typeof(nu), typeof(i), typeof(r), typeof(no) FROM t1; text|integer|integer|real|real -- Values stored as TEXT, INTEGER, INTEGER, REAL, INTEGER. DELETE FROM t1; INSERT INTO t1 VALUES(500, 500, 500, 500, 500); SELECT typeof(t), typeof(nu), typeof(i), typeof(r), typeof(no) FROM t1; text|integer|integer|real|integer -- BLOBs are always stored as BLOBs regardless of column affinity. DELETE FROM t1; INSERT INTO t1 VALUES(x'0500', x'0500', x'0500', x'0500', x'0500'); SELECT typeof(t), typeof(nu), typeof(i), typeof(r), typeof(no) FROM t1; blob|blob|blob|blob|blob -- NULLs are also unaffected by affinity DELETE FROM t1; INSERT INTO t1 VALUES(NULL,NULL,NULL,NULL,NULL); SELECT typeof(t), typeof(nu), typeof(i), typeof(r), typeof(no) FROM t1; null|null|null|null|null
具體示例:
聲明類型 | 親緣類型 | 應用規則 |
INT INTEGER TINYINT SMALLINT MEDIUMINT BIGINT UNSIGNED BIG INT INT2 INT8 |
INTEGER | 1 |
CHARACTER(20) VARCHAR(255) VARYING CHARACTER(255) NCHAR(55) NATIVE CHARACTER(70) NVARCHAR(100) TEXT CLOB |
TEXT | 2 |
BLOB | NONE | 3 |
REAL DOUBLE DOUBLE PRECISION FLOAT |
REAL | 4 |
NUMERIC DECIMAL(10,5) BOOLEAN DATE DATETIME |
NUMERIC | 5 |
注意:在SQLite中,類型VARCHAR(255)的長度信息255沒有任何實際意義,僅僅是爲了保證與其它數據庫的聲明一致性。
三、比較表達式
SQLite 3有常用的SQL比較運算符集,包括"=", "==", "<", "<=", ">", ">=", "!=", "<>", "IN", "NOT IN", "BETWEEN", "IS"和"IS NOT"。
比較的結果取決於操作數的存儲類型,即數據的比較結果主要依賴於操作數的存儲方式,根據以下規則來判斷:
* NULL類型的值被認爲小於任何(存儲類型的)值(包括另外一個值也是NULL類型時)。
* INTEGER或REAL類型的值小於任何TEXT或BLOB類型的值。當一個INTEGER或REAL與另一個INTEGER或REAL比較時,則按照實際數值來比較。
* TEXT值小於BLOB值。當兩個同爲TEXT類型的文本值進行比較時,指定的比較序列(默認規則爲使用memcmp())會被用來確定比較結果,基於文本規則(ASCII值)進行比較。
* 當兩個BLOB值比較時,由memcmp()確定比較結果,其結果爲C運行時函數memcmp()的結果。
在開始比較前,SQLite嘗試着把值在數值類型(整數和實數)和文本之間相互轉換。是否進行轉換取決於操作數的親和類型。操作數親和類型根據以下規則確定:
* 若表達式只是列值的簡單引用,則親和類型與列的親和類型一樣。注意如果X和Y.Z是列名,則+X和+Y.Z是用來確定親和類型的表達式。
* "CAST(expr AS type)"形式的表達式,其親和類型與聲明類型爲"type"的列相同。
* 否則,表達式爲NONE親和類型。
“應用親和類型”表示轉換一個操作數爲特定的存儲類型,當且僅當轉換是無損且可逆的。應用於比較運算符的操作數的親和類型根據下面規則來確定:
* 如果一個操作數是INTEGER, REAL或NUMERIC親和類型,另一個操作數是TEXT或NONE親和類型,則NUMERIC親和類型被應用於另外一個操作數。
* 如果一個操作數是TEXT親和類型,另一個操作數是NONE親和類型,則TEXT親和類型被應用於另一個操作數。
* 否則,沒有親和類型需要轉換,兩個操作數按照原有親和類型進行比較。
表達式"a BETWEEN b AND c"被當作兩個分開的二進制表達式"a >= b AND a <= c",即使兩次比較中'a'應用了不同的親和類型也是如此。表達式"x IN (SELECT y ...)"實際執行的是"x=y"。例如如果'b'是一個列值,'a'是一個表達式,那麼在開始比較前,'b'的親和性就被轉換爲'a'的親和性了。"a IN(x,y,z,...)"形式的表達式等價於"a = +x OR a = +y OR a = +z OR ..."。也就是說,IN運算符右邊的值(這裏的"x"、"y"和"z")的類型被認爲沒有親和性,即使它們是列值或CAST表達式。
SQL示範:比較運算。
CREATE TABLE t1( a TEXT, -- text affinity b NUMERIC, -- numeric affinity c BLOB, -- no affinity d -- no affinity ); -- Values will be stored as TEXT, INTEGER, TEXT, and INTEGER respectively INSERT INTO t1 VALUES('500', '500', '500', 500); SELECT typeof(a), typeof(b), typeof(c), typeof(d) FROM t1; text|integer|text|integer -- Because column "a" has text affinity, numeric values on the -- right-hand side of the comparisons are converted to text before -- the comparison occurs. SELECT a < 40, a < 60, a < 600 FROM t1; 0|1|1 -- Text affinity is applied to the right-hand operands but since -- they are already TEXT this is a no-op; no conversions occur. SELECT a < '40', a < '60', a < '600' FROM t1; 0|1|1 -- Column "b" has numeric affinity and so numeric affinity is applied -- to the operands on the right. Since the operands are already numeric, -- the application of affinity is a no-op; no conversions occur. All -- values are compared numerically. SELECT b < 40, b < 60, b < 600 FROM t1; 0|0|1 -- Numeric affinity is applied to operands on the right, converting them -- from text to integers. Then a numeric comparison occurs. SELECT b < '40', b < '60', b < '600' FROM t1; 0|0|1 -- No affinity conversions occur. Right-hand side values all have -- storage class INTEGER which are always less than the TEXT values -- on the left. SELECT c < 40, c < 60, c < 600 FROM t1; 0|0|0 -- No affinity conversions occur. Values are compared as TEXT. SELECT c < '40', c < '60', c < '600' FROM t1; 0|1|1 -- No affinity conversions occur. Right-hand side values all have -- storage class INTEGER which compare numerically with the INTEGER -- values on the left. SELECT d < 40, d < 60, d < 600 FROM t1; 0|0|1 -- No affinity conversions occur. INTEGER values on the left are -- always less than TEXT values on the right. SELECT d < '40', d < '60', d < '600' FROM t1; 1|1|1
如果交換比較方向,例子中的所有結果還是一樣的。例如表達式"a<40"交換成"40>a"。
四、運算符
所有數學運算符(+, -, *, /, %, <<, >>, &和|)在執行計算前會把兩邊的操作數轉換成NUMERIC存儲類型,即使轉換有損或不可逆也會進行。若運算符上有NULL操作數,則運算產生NULL結果。若操作數不是NULL,也不是任何數值,則轉換成0或0.0。
五、排序、分組和複合式SELECT
當查詢結果被一個ORDER BY子句排序時,NULL類型的值排在最前面,然後是INTERGER和REAL值,按數值大小排序。接着是文本值,按指定的比較序列排序。最後是BLOB值,按memcmp()比較來排序。在排序時沒有存儲類型的轉換。
當用GROUP BY子句對值進行分組時,不同存儲類型的值是分開來的,但對INTERGER和REAL值,如果它們數值上相等,則被認爲是相等的。對GROUP BY子句的結果,不會應用任何親和類型。
複合式SELECT操作符,包括UNION、INTERSECT和EXCEPT在值之間執行隱式的比較,在比較時不使用親和類型轉換,只是按照它們原有類型進行比較。
六、比較序列
當SQLite比較兩個字符串時,它使用一個比較序列(即比較函數)來確定哪個字符串更大或者相等。SQLite有3個內建的比較函數:BINARY、NOCASE和RTRIM。
* BINARY: 使用memcmp()來比較字符串,即進行二進制比較,不管什麼文本編碼。
* NOCASE: 與BINARY相同,但在比較前26個大寫的ASCII字母會被摺疊成對應小寫字母。注意只有ASCII字母纔會做大小寫摺疊。由於UTF字符表比較龐大,因此SQLite並不嘗試做全部UTF字符的大小寫摺疊。
* RTRIM: 與BINARY相同,但是忽略掉尾部的空格。
每個表的每列都有一個關聯的比較函數。如果沒有顯式地自定義比較函數,則默認使用BINARY。列定義的COLLATE子句用來爲列定義可選的比較函數。
要確定二進制比較運算符(=, <, >, <=, >=, !=, IS和IS NOT)使用哪個比較函數,可根據以下規則來判斷:
1)如果有一個操作數通過尾部的COLLATE聲明符分配了顯式的比較函數,則用這個顯式的比較函數來做比較,並且在左邊操作數上優先使用這個比較函數。
2)如果有一個操作數是列,則在左邊操作數上優先使用這個列的比較函數。注意這時帶有"+"的列名仍然被認爲是一個列名。
3)否則,使用默認的BINARY比較函數來進行比較。
如果不是操作數,而是操作數的某個子表達式通過尾部的COLLATE聲明符分配了顯式的比較函數,則這個操作數也被認爲有一個顯式的比較函數。因此,單個COLLATE聲明符可用在一個比較表達式的任何地方,由它定義的比較函數用於字符串比較,無論這個比較表達式使用了表的哪個列。如果一個表達式中有多個帶COLLATE聲明的子表達式,則使用最左邊的顯式比較函數,無論COLLATE聲明符嵌套得有多深,也無論表達式有多少嵌套的括號。
表達式"x BETWEEN y and z"在邏輯上與"x >= y AND x <= z"等價,因此使用兩個獨立比較的比較函數。表達式"x IN (SELECT y ...)"在確定比較函數時與表達式"x = y"的方法相同。 "x IN (y, z, ...)"形式的表達式,其比較函數是x的比較函數。
SELECT語句中的ORDER BY子句也可以使用COLLATE聲明符來分配一個比較函數,這個比較函數將被用來進行排序。否則,如果ORDER BY子句排序的表達式是一個列,則使用列的比較函數來確定排序順序。如果表達式不是一個列且沒有COLLATE子句,則使用BINARY比較函數。
SQL示範:用於確定文本比較結果的比較函數,這可用在各種各樣的SQL語句中(注意文本比較並不是必需的,如果是數值、BLOB或NULL值,則無需比較函數)。
CREATE TABLE t1( x INTEGER PRIMARY KEY, a, /* collating sequence BINARY */ b COLLATE BINARY, /* collating sequence BINARY */ c COLLATE RTRIM, /* collating sequence RTRIM */ d COLLATE NOCASE /* collating sequence NOCASE */ ); /* x a b c d */ INSERT INTO t1 VALUES(1,'abc','abc', 'abc ','abc'); INSERT INTO t1 VALUES(2,'abc','abc', 'abc', 'ABC'); INSERT INTO t1 VALUES(3,'abc','abc', 'abc ', 'Abc'); INSERT INTO t1 VALUES(4,'abc','abc ','ABC', 'abc'); /* Text comparison a=b is performed using the BINARY collating sequence. */ SELECT x FROM t1 WHERE a = b ORDER BY x; --result 1 2 3 /* Text comparison a=b is performed using the RTRIM collating sequence. */ SELECT x FROM t1 WHERE a = b COLLATE RTRIM ORDER BY x; --result 1 2 3 4 /* Text comparison d=a is performed using the NOCASE collating sequence. */ SELECT x FROM t1 WHERE d = a ORDER BY x; --result 1 2 3 4 /* Text comparison a=d is performed using the BINARY collating sequence. */ SELECT x FROM t1 WHERE a = d ORDER BY x; --result 1 4 /* Text comparison 'abc'=c is performed using the RTRIM collating sequence. */ SELECT x FROM t1 WHERE 'abc' = c ORDER BY x; --result 1 2 3 /* Text comparison c='abc' is performed using the RTRIM collating sequence. */ SELECT x FROM t1 WHERE c = 'abc' ORDER BY x; --result 1 2 3 /* Grouping is performed using the NOCASE collating sequence (Values ** 'abc', 'ABC', and 'Abc' are placed in the same group). */ SELECT count(*) FROM t1 GROUP BY d ORDER BY 1; --result 4 /* Grouping is performed using the BINARY collating sequence. 'abc' and ** 'ABC' and 'Abc' form different groups */ SELECT count(*) FROM t1 GROUP BY (d || '') ORDER BY 1; --result 1 1 2 /* Sorting or column c is performed using the RTRIM collating sequence. */ SELECT x FROM t1 ORDER BY c, x; --result 4 1 2 3 /* Sorting of (c||'') is performed using the BINARY collating sequence. */ SELECT x FROM t1 ORDER BY (c||''), x; --result 4 2 3 1 /* Sorting of column c is performed using the NOCASE collating sequence. */ SELECT x FROM t1 ORDER BY c COLLATE NOCASE, x; --result 2 4 3 1