SQLite剖析之數據類型

許多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 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章