Chapter 4. SQL 語法

Table of Contents
4.1. 詞法結構
4.1.1. 標識符和關鍵字
4.1.2. 常量
4.1.3. 操作符
4.1.4. 特殊字元
4.1.5. 註釋
4.1.6. 詞法優先級
4.2. 值資料表達式
4.2.1. 字串引用
4.2.2. 位置參數
4.2.3. 下標
4.2.4. 字串選擇
4.2.5. 操作符調用
4.2.6. 函數調用
4.2.7. 聚集資料表達式
4.2.8. 類型轉換
4.2.9. 標量子查詢
4.2.10. 數組構造器
4.2.11. 行構造
4.2.12. 資料表達式計算規則

本章描述 SQL 的語法。 這些內容是理解隨後各章的基礎,那些章裡面將詳細介紹 SQL 命令如何用於定義和修改資料。

我們也建議那些已經很熟悉 SQL 的用戶仔細閱讀本章,因為有一些規則和概念在 SQL 資料庫之間實現得並不一致,或者是有些東西是 PostgreSQL 特有的。

4.1. 詞法結構

SQL 輸入由一系列命令組成。 一條命令是由一系列記號構成, 用一個分號(";")結尾。 輸入流的終止也結束一條命令。那些記號是合法的取決於特定命令的語法。

記號可以是一個關鍵字, 一個標識符,一個 引號包圍的標識符, 一個文本(或常量),或者是特殊的字元符號。 記號通常由空白分隔(空格,tab,換行符),但如果不存在混淆的時候也可以不用 (通常只是一個特殊字元與一些其它記號類型相聯的時候)。

另外,在 SQL 輸入裡可以有註釋。 它們不是記號,它們實際上等效於空白。

比如,下列命令是(語法上)合法的 SQL 輸入:

SELECT * FROM MY_TABLE;
UPDATE MY_TABLE SET A = 5;
INSERT INTO MY_TABLE VALUES (3, 'hi there');

這裡是三條命令的序列,每條一行(儘管並不要求這麼做; 多條命令可以在一行裡,並且命令可以合理地分裂成多個行)。

如果從哪些記號標識命令,哪些是操作數或參數的角度考慮, SQL 語法並不是非常一致。通常頭幾個記號是命令名字, 因此上面的例子我們通常可以說是一個"SELECT", 一個"UPDATE",和一個"INSERT"命令。 不過, UPDATE 命令總是要求一個 SET 在某個位置出現,並且這個變體的 INSERT 還要求有一個 VALUES 才完整。每條命令的準確語法規則都在 Part VI 裡描寫。

4.1.1. 標識符和關鍵字

象上面的例子裡的 SELECTUPDATE, 或 VALUES 這樣的記號都是關鍵字的例子, 也就是那些在 SQL 語言裡有固定含義的單詞。 記號 MY_TABLEA標識符的例子。 根據使用它們的命令的不同,它們標識資料表,字串,或者其它資料庫對象的名字。 因此,有時候只是簡單地叫它們"名字"。 關鍵字和標識符有著同樣的詞法結構,意思是我們在沒有認識這種語言之前是無法區分一個記號是標識符還是名字。 您可以在 Appendix C 裡找到一個關鍵字的完整列資料表。

SQL 標識符和關鍵字必須以一個字母開頭 (a-z 以及帶可區別標記的字母以及非拉丁字母 )或下劃線開頭 (_)開頭。標識符和關鍵字裡隨後的字元可以是字母,數字(0-9), 或者下劃線,但 SQL 標準不會定義包含數字或者以下劃線開頭或結尾的關鍵字。

系統使用不超過 NAMEDATALEN-1 個字元作為標識符; 您可以在命令中寫更長的名字,但它們會被截斷。預設時, NAMEDATALEN 是 64,因此標識符最大長度是 63 如果覺得這個限制有問題,那麼您可以在 src/include/postgres_ext.h 裡修改 NAMEDATALEN 來改變它。

標識符和關鍵字名字都是大小寫無關的。因此

UPDATE MY_TABLE SET A = 5;

也可以等效地寫成

uPDaTE my_TabLE SeT a = 5;

一種好習慣是把關鍵字寫成大寫,而名字等用小寫。

UPDATE my_table SET a = 5;

還有第二種標識符:分隔標識符引號包圍的標識符。 它是透過在雙引號(" ) 裡包圍任意字元序列形成的。 分隔標識符總是一個標識符,而不是關鍵字。因此,您可以用 "SELECT" 資料表示一個字串名字或者名字叫 "SELECT" 的資料表,而一個沒有引號的 SELECT 將被當做一條命令的一部分,因此如果把它當做一個資料表的名字或者字串名字用的話就會產生一個分析錯誤。 上面的例子可以用引起的標識符這麼寫:

UPDATE "my_table" SET "a" = 5;

引號包圍的標識符可以包含除引號本身以外的任何其它字元。 要包含一個雙引號,我們可以寫兩個雙引號。 這樣我們就可以構造那些原本是不允許的資料表或者字串名字, 比如那些包含空白或與號的名字。但長度限制依舊。

把一個標識符用引號包圍的起來同時也令它大小寫相關,而沒有引號包圍起來的名字總是轉成小寫。 比如,我們認為標識符 FOOfoo"foo" 是一樣的 PostgreSQL名字, 但 "Foo""FOO" 與上面三個以及它們之間都是不同的。 (PostgreSQL 裡對未加引號的名子總是轉換成小寫, 這和 SQL 是不兼容的,SQL 裡要求未用引號包圍起來的名字總是轉成大寫。 因此 foo 等於 "FOO"。 如果您想寫可移植的程序,那麼我們建議您要麼就總是引號包圍的某個名字,要麼就堅決不引。)

4.1.2. 常量

PostgreSQL 裡有三種隱含類型的常量: 字元串,位串,和數值。 常量也可以聲明為明確的類型,這樣就可以使用更準確的資料表現形式以及可以透過系統更有效地處理。 這些候選的在後面的小節描述。

4.1.2.1. 字元串常量

SQL 裡的一個字串文本是用單引號(')包圍的任意字元序列, 比如,'This is a string'。 這種聲明字串常量的方法是 SQL 標準定義的。 在這種類型的字串常量裡嵌入單引號的標準兼容的做法是敲入兩個連續的單引號比如,'Dianne''s horse'。 另外,PostgreSQL 允許用用一個反斜槓("\")來逃逸單引號, 因此同一個字串可以寫成'Dianne\'s horse'

另外一個 PostgreSQL 擴展是還可以使用 C-風格的反斜槓逃逸: \b 是一個退格,\f 是一個進紙,\n 是一個換行符, \r 是一個回車,\t 是一個水平製資料表符,而\xxx, 這裡 xxx 是一個八進制數,是對應 ASCII 碼的字元。任何其它跟在反斜槓後面的字元都當做文本看待。 因此,要在字元串常量裡包含反斜槓,您可以寫兩個反斜槓。

編碼為零的字元不能出現在字元串常量中。

兩個只是透過至少有一個換行符的空白分隔的字元串常量會被連接在一起,並當做它們是寫成一個常量處理。 比如:

SELECT 'foo'
'bar';

等效於

SELECT 'foobar';

SELECT 'foo'      'bar';

是非法的語法,(這個略微有些怪異的行為是 SQL 聲明的; PostgreSQL 遵循標準。)

4.1.2.2. 美元符包圍字串常量

儘管聲明字串常量的標準方法通常都很方便,但是如果字串包含很多單引號或者反斜槓, 那麼理解字串的內容可能就會變得很苦澀,因為每個單引號都要加倍。 為了讓這種場合下的查詢更具可讀性,PostgreSQL 允許另外一種稱作"美元符包圍"的字串常量聲明辦法。 一個透過美元符包圍聲明的字串常量由一個美元符號($),一個可選的零個或多個字元"記號", 另外一個美元符號,一個組成字串常量的任意字元的序列,一個美元符號,以及一個和開始這個美元符包圍的記號相同的記號,和一個美元符號組成。 比如,下面是兩個不同的方法,用美元符包圍聲明了前面的例子:

$$Dianne's horse$$
$SomeTag$Dianne's horse$SomeTag$

請注意,在美元符包圍的字串裡,單引號可以不用逃逸使用。 實際上,在一個美元符包圍的字串裡,沒有什麼字元需要逃逸: 字串內容總是按照字面內容寫。反斜槓不是特殊的, 美元符自己也不是特殊的,除非它們和開標籤的一部分匹配。

我們可以透過在不同嵌套級別使用不同的美元符引號字串常量來實現嵌套。 最常見的是寫函數定義的時候。比如:

$function$
BEGIN
    RETURN ($1 ~ $q$[\t\r\n\v\\]$q$);
END;
$function$

這裡,序列 $q$[\t\r\n\v\\]$q$ 資料表示一個美元符包圍的字串文本 [\t\r\n\v\\], 在函數體被 PostgreSQL 執行的時候,它將被識別出來。 但是因為這個序列不匹配外層的美元符分隔符$function$, 所以只要考慮了外層字串,它就只是常量裡面的一些額外的字元而已。

如果有標籤的話,一個美元符包圍的字串遵循和無引號包圍的標識符相同的規則, 只是它不能包含美元符。標籤是大小寫相關的,因此 $tag$String content$tag$ 是正確的,而 $TAG$String content$tag$ 不對。

一個後面跟著關鍵字或者標識符的美元包圍的字串必須用空白隔開; 否則美元符包圍分隔符將會被認為前面標識符的一部分。

美元符包圍不是 SQL 標準,但是在寫複雜的字串文本的時候,它通常比標準的單引號語法更方便。 尤其是在其它常量裡資料表現字串常量的時候更有用,比如經常在過程函數定義裡面的。 如果用單引號語法,每個上面例子裡的反斜槓都必須寫四個,它們在作為字串文本分析的時候會減少為兩個, 然後在函數執行的時候在內層字串常量裡會再次被解析為一個。

4.1.2.3. 位串常量

位串常量看起來很像在開引號前面有一個 B (大寫或小寫)的普通字元串(它們之間沒有空白), 比如 B'1001'。位串常量裡可以用的字元只有 01

另外,位串常量可以用十六進製資料表示法聲明,方法是使用前綴的 X (大寫或者小寫),比如,X'1FF'。 這種資料表示法等效於一個每個十六進制位四個二進制位的位串常量。

兩種形式的位串常量都可以像普通字串常量那樣跨行連續。 美元符包圍不能用於位串常量。

4.1.2.4. 數值常量

數值常量接受下列通用的形式:

digits
digits.[digits][e[+-]digits]
[digits].digits[e[+-]digits]
digitse[+-]digits

這裡的 digits 是一個或多個十進制位(0 到 9)。 如果有小數點,那麼至少有一位在小數點前面或後面。如果出現了指數分隔符(e),那麼至少有一個位跟在它後面。 在常量裡不能有空格或者其他字元嵌入在內。 請注意任何前導地正號或者負號實際上都不認為是常量的一部分; 它是施加於常量的一個操作符。

這裡是一些合法的數值常量的例子:

42
3.5
4.
.001
5e2
1.925e-3

如果一個數值常量既不包含小數點,也不包含指數操作符, 那麼如果它的數值可以放在integer類型中(32位), 則認為它是integer類型;如果它的數值可以放在 bigint中(64位),則認為它是 bigint; 否則認為它是 numeric類型。包含小數點和/或指數操作符的常量總是被認為是numeric類型。

給一個數值常量賦予初始資料類型只是類型解析算法的開端。 在大多數情況下該常量會根據環境被自動強制轉換成最合適的類型。 必要時,您可以透過強制類型轉換把一個數值解析成特定的資料類型。 比如,您可以強制要求把一個數值當作類型realfloat4)來看,方法時這麼寫:

REAL '1.23'  -- 字串風格
'1.23'::REAL -- PostgreSQL (歷史原因)風格
     

這些實際上只是下面討論的通用轉換的特例。

4.1.2.5. 其它類型的常量

任意類似的常量可以用下列資料表示法中的任何一種來輸入:

type 'string'
'string'::type
CAST ( 'string' AS type )

在字串常量的文本將傳遞給那種叫 type 的類型的輸入轉換過程。 結果是這種類型的一個常量。如果不存在該常量所屬類型的歧義, 那麼明確的類型映射可以省略(比如,當您把它直接賦予一個資料表字串的時候), 這種情況下它會自動轉換。

字串常量可以用普通 SQL 資料表示法或者美元符包圍來書寫。

我們還可以用函數樣的語法來聲明類型轉換:

typename ( 'string' )

不過並非所有類型名可以這樣使用;參閱 Section 4.2.8 獲取細節。

::CAST(),和函數調用語法也可以用於聲明任意資料表達式的執行時類型轉換, 如 Section 4.2.8 中討論的那樣。 但是 type 'string' 的形式只能用於聲明一個文本常量的類型。 type 'string' 的另外一個限制是它不能用於數組類型;要用 :: 或者 CAST() 聲明一個數組常量的類型。

4.1.3. 操作符

一個操作符是最多 NAMEDATALEN-1 (預設 63 個字元)個下列字元的序列:

+ - * / < > = ~ ! @ # % ^ & | ` ?

不過,對操作符名字有幾個限制:

  • --/* 不能出現在操作符名字中的任何地方,因為它們會被當做註釋開始對待。

  • 多字元操作符不能以 +- 結束, 除非其名字至少還包含下列操作符之一:

    ~ ! @ # % ^ & | ` ?

    比如,@- 是允許的操作符名字, 但 *- 不是。這個限制允許 PostgreSQL 在不要求記號之間有空白的情況下分析 SQL 兼容的查詢。

當您使用非 SQL 標準的操作符名字的時候,您通常需要用空白分隔相鄰的操作符以避免歧義。 比如,如果您定義了一個叫 "@" 的左單目操作符,那麼您就不能寫 X*@Y;而是要寫成 X* @Y 以確保 PostgreSQL 把它讀成兩個操作符,而不是一個。

4.1.4. 特殊字元

有些非字母數字字元有一些特殊含義,因此不能用做操作符。 它們的用法的細節可以在相應的描述語法元素的地方找到。 本節只是描述它們的存在和概括一下這些字元的目的。

  • 美元符號($)後面跟著數字用於在一個函數體定義或者準備好的語句中 資料表示參數的位置。在其他環境裡美元符號可能是一個標識符名字或者是一個美元符包圍的字串常量的一部分。

  • 圓括弧(())用於分組和強制優先級的時候含義與平常一樣。 有些場合裡圓括弧是作為一個特定 SQL 命令的固定語法的一部分要求的。

  • 方括弧([])用於選取數組元素。 參閱 Section 8.10 獲取更多訊息。

  • 逗號(,在一些語法構造裡用於分隔一個列資料表的元素。

  • 分號(;)結束一條 SQL 命令。 它不能出現在一條命令裡的任何地方,除非引號包圍的來當做字元串常量或者標識符用。

  • 冒號 (:)用於從數組中選取"片段"。(參閱 Section 8.10。)在一些 SQL 方言裡(比如嵌入 SQL ), 冒號用於前綴變量名。

  • 星號 (* 在某些環境裡資料表示一個資料表行或者一個符合類型值的全部字串。 在用作聚集函數 COUNT 的參數時還有特殊含義。

  • 句點 (.用在數字常量裡,並用於分隔模式,資料表和字串名字。

4.1.5. 註釋

註釋是任意以雙劃線開頭並延伸到行尾的任意字元序列,比如:

-- 這是標準的 SQL92 註釋

另外,還可以使用 C-風格的塊註釋:

/* 多行註釋
 * 可以嵌套︰/* 嵌套的塊註釋 */
 */

這裡註釋以 /* 開頭並擴展到對應的 */。這些塊註釋可以嵌套,就像 SQL99 裡說的那樣, 但和 C 不一樣,因此我們可以註釋掉一大塊已經包含塊註釋的代碼。

註釋在進一步的語法分析之前被從輸入流刪除並用空白代替。

4.1.6. 詞法優先級

Table 4-1 顯示了 PostgreSQL 裡面的操作符的優先級和關聯性。 大多數操作符都有相同的優先級並且都是左關聯的。 這種情況可能會有不那麼直觀的行為;比如,布爾操作符 <> 和布爾操作符 <=>= 之間有著不同的優先級。同樣,當您把雙目和單目操作符組合使用的時候, 有時候也需要加圓括弧。比如

SELECT 5 ! - 6;

會被分析成

SELECT 5 ! (- 6);

因為分析器不知道 ! 定義成了後綴操作符, 而不是中綴操作符。— 知道的時候只能是太晚了 — 要在本例中獲得您需要的特性,您要寫成

SELECT (5 !) - 6;

這是我們為擴展性付出的代價。

Table 4-1. 操作符優先級(遞減)

操作符/元素關聯性描述
.資料表/字串名分隔符
::PostgreSQL-特有的類型轉換操作符
[ ]數組元素選則
-單目負號
^冪操作
* / %乘,除,模
+ -加,減
IS IS TRUE, IS FALSE, IS UNKNOWN, IS NULL
ISNULL 測試是否為空值
NOTNULL 測試是否為非空值
(任何其它的)所有其它的本地和用戶定義操作符
IN 集合成員
BETWEEN 範圍包含
OVERLAPS 時間間隔重疊
LIKE ILIKE SIMILAR 字元串模式匹配
< > 小於,大於
=等於,賦值
NOT邏輯反
AND邏輯與
OR邏輯或

請注意操作符優先級也適用於和上面提到的同名的內置操作符用戶定義操作符。 比如,如果您為一些客戶資料類型定義一個 "+" 操作符, 那麼它和內置的 "+" 操作符有同樣的優先級,不管您幹了什麼。

如果在 OPERATOR 語法裡使用了模式修飾的操作符名, 比如

SELECT 3 OPERATOR(pg_catalog.+) 4;

那麼 OPERATOR 構造就會有 Table 4-1 資料表裡面為"任何其它"操作符顯示的預設優先級。 不管什麼特定的操作符出現在 OPERATOR()裡,都是這樣。