579
技術社區[雲棲]
PostgreSQL SQL語法(一):詞法結構
本文檔為PostgreSQL 9.6.0文檔,本轉載已得到原譯者彭煜瑋授權。
SQL輸入由一個命令序列組成。一個命令由一個記號的序列構成,並由一個分號(";")終結。輸入流的末端也會標誌一個命令的結束。具體哪些記號是合法的與具體命令的語法有關。
一個記號可以是一個關鍵詞、一個標識符、一個帶引號的標識符、一個literal(或常量)或者一個特殊字符符號。記號通常以空白(空格、製表符、新行)來分隔,但在無歧義時並不強製要求如此(唯一的例子是一個特殊字符緊挨著其他記號)。
例如,下麵是一個(語法上)合法的SQL輸入:
SELECT * FROM MY_TABLE;
UPDATE MY_TABLE SET A = 5;
INSERT INTO MY_TABLE VALUES (3, 'hi there');
這是一個由三個命令組成的序列,每一行一個命令(盡管這不是必須地,在同一行中可以有超過一個命令,而且命令還可以被跨行分割)。
另外,注釋也可以出現在SQL輸入中。它們不是記號,它們和空白完全一樣。
根據標識命令、操作符、參數的記號不同,SQL的語法不很一致。最前麵的一些記號通常是命令名,因此在上麵的例子中我們通常會說一個"SELECT"、一個"UPDATE"和一個"INSERT"命令。但是例如UPDATE命令總是要求一個SET記號出現在一個特定位置,而INSERT則要求一個VALUES來完成命令。
上例中的SELECT、UPDATE或VALUES記號是關鍵詞的例子,即SQL語言中具有特定意義的詞。記號MY_TABLE和A則是標識符的例子。它們標識表、列或者其他數據庫對象的名字,取決於使用它們的命令。因此它們有時也被簡稱為"名字"。關鍵詞和標識符具有相同的詞法結構,這意味著我們無法在沒有語言知識的前提下區分一個標識符和關鍵詞。一個關鍵詞的完整列表可以在Appendix C中找到。
SQL標識符和關鍵詞必須以一個字母(a-z,也可以是帶變音符的字母和非拉丁字母)或一個下劃線(_)開始。後續字符可以是字母、下劃線(_)、數字(0-9)或美元符號($)。注意根據SQL標準的字母規定,美元符號是不允許出現在標識符中的,因此它們的使用可能會降低應用的可移植性。SQL標準不會定義包含數字或者以下劃線開頭或結尾的關鍵詞,因此這種形式的標識符不會與未來可能的標準擴展衝突 。
係統中一個標識符的長度不能超過 NAMEDATALEN-1 字節,在命令中可以寫超過此長度的標識符,但是它們會被截斷。默認情況下,NAMEDATALEN 的值為64,因此標識符的長度上限為63字節。如果這個限製有問題,可以在src/include/pg_config_manual.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;
受限標識符可以包含任何字符,除了代碼為0的字符(如果要包含一個雙引號,則寫兩個雙引號)。這使得可以構建原本不被允許的表或列的名稱,例如包含空格或花號的名字。但是長度限製依然有效。
一種受限標識符的變體允許包括轉義的用代碼點標識的Unicode字符。這種變體以U&(大寫或小寫U跟上一個花號)開始,後麵緊跟雙引號修飾的名稱,兩者之間沒有任何空白,如U&"foo"(注意這裏與操作符&似乎有一些混淆,但是在&操作符周圍使用空白避免了這個問題) 。在引號內,Unicode字符可以以轉義的形式指定:反斜線接上4位16進製代碼點號碼或者反斜線和加號接上6位16進製代碼點號碼。例如,標識符"data"可以寫成:
U&"d\0061t\+000061"
下麵的例子用斯拉夫語字母寫出了俄語單詞 "slon"(大象):
U&"\0441\043B\043E\043D"
如果希望使用其他轉義字符來代替反斜線,可以在字符串後使用UESCAPE子句,例如:
U&"d!0061t!+000061" UESCAPE '!'
轉義字符可以是除了16進製位、加號、單引號、雙引號、空白字符之外的任意單個字符。注意轉義字符是被寫在單引號而不是雙引號內。
為了在標識符中包括轉義字符本身,將其寫兩次即可。
Unicode轉義語法隻有在服務器編碼為UTF8時才起效。當使用其他服務器編碼時,隻有在ASCII範圍內(最高到\007F)的編碼點才能被使用。4位和6位形式都可以被用來定義UTF-16代理對來組成代碼點大於U+FFFF的字符,盡管6位形式的存在使得這種做法變得不必要(代理對並不被直接存儲,而是被被綁定到一個單獨的代碼點然後被編碼到UTF-8)。
將一個標識符變得受限同時也使它變成大小寫敏感的,反之非受限名稱總是被轉換成小寫形 式。例如,標識符FOO、foo和"foo"在PostgreSQL中被認為是相同的,而"Foo"和"FOO"則互 不相同且也不同於前麵三個標識符(PostgreSQL將非受限名字轉換為小寫形式與SQL標準是不兼容 的,SQL標準中要求將非受限名稱轉換為大寫形式。這樣根據標準, foo應該和 "FOO"而不是"foo"相同。如果希望寫一個可移植的應用,我們應該總是用引號修飾一個特定名字或者 從不使用 引號修飾)。
在PostgreSQL中有三種隱式類型常量:字符串、位串和數字。常量也可以被指定顯示類型,這可以使得它被更精確地展示以及更有效地處理。這些選擇將會在後續小節中討論。
1.2.1. 字符串常量
在SQL中,一個字符串常量是一個由單引號(')包圍的任意字符序列,例如'This is a string'。為了在一個字符串中包括一個單引號,可以寫兩個相連的單引號,例如'Dianne''s horse'。注意這和一個雙引號(")不同。
兩個隻由空白及至少一個新行分隔的字符串常量會被連接在一起,並且將作為一個寫在一起的字符串常量來對待。例如:
SELECT 'foo'
'bar';
等同於:
SELECT 'foobar';
但是:
SELECT 'foo' 'bar';
則不是合法的語法(這種有些奇怪的行為是SQL指定的,PostgreSQL遵循了該標準)。
1.2.2. C風格轉義的字符串常量
PostgreSQL也接受"轉義"字符串常量,這也是SQL標準的一個擴展。一個轉義字符串常量可以通過在開單引號前麵寫一個字母E(大寫或小寫形式)來指定,例如E'foo'(當一個轉義字符串常量跨行時,隻在第一個開引號之前寫E)。在一個轉義字符串內部,一個反斜線字符(\)會開始一個 C 風格的反斜線轉義序列,在其中反斜線和後續字符的組合表示一個特殊的字節值(如Table 4-1中所示)。
Table 4-1. 反斜線轉義序列
跟隨在一個反斜線後麵的任何其他字符被當做其字麵意思。因此,要包括一個反斜線字符,請寫兩個反斜線(\)。在一個轉義字符串中包括一個單引號除了普通方法''之外,還可以寫成\'。
你要負責保證你創建的字節序列由服務器字符集編碼中合法的字符組成,特別是在使用八進製或十六進製轉義時。如果服務器編碼為 UTF-8,那麼應該使用 Unicode 轉義或替代的 Unicode 轉義語法(在Section 4.1.2.3中解釋)。替代方案可能是手工寫出 UTF-8 編碼字節,這可能會非常麻煩。
隻有當服務器編碼是UTF8時,Unicode 轉義語法才能完全工作。當使用其他服務器編碼時,隻有在 ASCII 範圍(低於\u007F)內的代碼點能夠被指定。4 位和 8 位形式都能被用來指定 UTF-16 代理對,用來組成代碼點超過 U+FFFF 的字符,不過 8 位形式的可用從技術上使得這種做法不再是必須的(當服務器編碼為UTF8並使用代理對時,它們首先被結合到一個單一代碼點,然後會被用 UTF-8 編碼)。
Caution
如果配置參數standard_conforming_strings為off,那麼PostgreSQL對常規字符串常量和轉義字符串常量中的反斜線轉義都識別。不過,從PostgreSQL 9.1 開始,該參數的默認值為on,意味著隻在轉義字符串常量中識別反斜線轉義。這種行為更兼容標準,但是可能打斷依賴於曆史行為(反斜線轉義總是會被識別)的應用。作為一種變通,你可以設置該參數為off,但是最好遷移到符合新的行為。如果你需要使用一個反斜線轉義來表示一個特殊字符,為該字符串常量寫上一個E。
在standard_conforming_strings之外,配置參數escape_string_warning和backslash_quote也決定了如何對待字符串常量中的反斜線。
代碼零的字符不能出現在一個字符串常量中。
1.2.3. 帶有 Unicode 轉義的字符串常量
PostgreSQL也支持另一種類型的字符串轉義語法,它允許用代碼點指定任意 Unicode 字符。一個 Unicode 轉義字符串常量開始於U&(大寫或小寫形式的字母 U,後跟花號),後麵緊跟著開引號,之間沒有任何空白,例如U&'foo'(注意這產生了與操作符&的混淆。在操作符周圍使用空白來避免這個問題)。在引號內,Unicode 字符可以通過寫一個後跟 4 位十六進製代碼點編號或者一個前麵有加號的 6 位十六進製代碼點編號的反斜線來指定。例如,字符串'data'可以被寫為
U&'d\0061t\+000061'
下麵的例子用斯拉夫字母寫出了俄語的單詞"slon"(大象):
U&'\0441\043B\043E\043D'
如果想要一個不是反斜線的轉義字符,可以在字符串之後使用UESCAPE子句來指定,例如:
U&'d!0061t!+000061' UESCAPE '!'
轉義字符可以是出一個十六進製位、加號、單引號、雙引號或空白字符之外的任何單一字符。
隻有當服務器編碼是UTF8時,Unicode 轉義語法才能完全工作。當使用其他服務器編碼時,隻有在 ASCII 範圍(低於\u007F)內的代碼點能夠被指定。4 位和 8 位形式都能被用來指定 UTF-16 代理對,用來組成代碼點超過 U+FFFF 的字符,不過 8 位形式的可用從技術上使得這種做法不再是必須的(當服務器編碼為UTF8並使用代理對時,它們首先被結合到一個單一代碼點,然後會被用 UTF-8 編碼)。
還有,隻有當配置參數standard_conforming_strings被打開時,用於字符串常量的 Unicode 轉義語法才能工作。這是因為否則這種語法將迷惑客戶端中肯地解析 SQL 語句,進而會導致 SQL 注入以及類似的安全性問題。如果這個參數被設置為關閉,這種語法將被拒絕並且報告一個錯誤消息。
要在一個字符串中包括一個表示其字麵意思的轉義字符,把它寫兩次。
1.2.4. 美元引用的字符串常量
雖然用於指定字符串常量的標準語法通常都很方便,但是當字符串中包含了很多單引號或反斜線時很難理解它,因為每一個都需要被雙寫。要在這種情形下允許可讀性更好的查詢,PostgreSQL提供了另一種被稱為"美元引用"的方式來書寫字符串常量。一個美元引用的字符串常量由一個美元符號($)、一個可選的另個或更多字符的"標簽"、另一個美元符號、一個構成字符串內容的任意字符序列、一個美元符號、開始這個美元引用的相同標簽和一個美元符號組成。例如,這裏有兩種不同的方法使用美元引用指定字符串"Dianne's horse":
$$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 標準的一部分,但是在書寫複雜字符串文字方麵,它常常是一種比兼容標準的單引號語法更方便的方法。當要表示的字符串常量位於其他常量中時它特別有用,這種情況常常在過程函數定義中出現。如果用單引號語法,上一個例子中的每個反斜線將必須被寫成四個反斜線,這在解析原始字符串常量時會被縮減到兩個反斜線,並且接著在函數執行期間重新解析內層字符串常量時變成一個。
1.2.5. 位串常量
位串常量看起來像常規字符串常量在開引號之前(中間無空白)加了一個B(大寫或小寫形式),例如B'1001'。位串常量中允許的字符隻有0和1。
作為一種選擇,位串常量可以用十六進製記號法指定,使用一個前導X(大寫或小寫形式),例如X'1FF'。這種記號法等價於一個用四個二進製位取代每個十六進製位的位串常量。
兩種形式的位串常量可以以常規字符串常量相同的方式跨行繼續。美元引用不能被用在位串常量中。
1.2.6. 數字常量
在這些一般形式中可以接受數字常量:
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。
一個數字常量初始指派的數據類型隻是類型轉換算法的一個開始點。在大部分情況中,常量將被根據上下文自動被強製到最合適的類型。必要時,你可以通過造型它來強製一個數字值被解釋為一種指定數據類型。例如,你可以這樣強製一個數字值被當做類型real(float4):
REAL '1.23' -- string style
1.23::REAL -- PostgreSQL (historical) style
這些實際上隻是接下來要討論的一般造型記號的特例。
1.2.7. 其他類型的常量
一種任意類型的一個常量可以使用下列記號中的任意一種輸入:
type 'string'
'string'::type
CAST ( 'string' AS type )
字符串常量的文本被傳遞到名為type的類型的輸入轉換例程中。其結果是指定類型的一個常量。如果對該常量的類型沒有歧義(例如,當它被直接指派給一個表列時),顯式類型造型可以被忽略,在那種情況下它會被自動強製。
字符串常量可以使用常規 SQL 記號或美元引用書寫。
也可以使用一個類似函數的語法來指定一個類型強製:
typename ( 'string' )
但是並非所有類型名都可以用在這種方法中,詳見Section 2.9。
如Section 2.9中討論的,::、CAST()以及函數調用語法也可以被用來指定任意表達式的運行時類型轉換。要避免語法歧義,type 'string'語法隻能被用來指定簡單文字常量的類型。type 'string'語法上的另一個限製是它無法對數組類型工作,指定一個數組常量的類型可使用::或CAST()。
CAST()語法符合 SQL。type 'string'語法是該標準的一般化:SQL 指定這種語法隻用於一些數據類型,但是PostgreSQL允許它用於所有類型。帶有::的語法是PostgreSQL的曆史用法,就像函數調用語法一樣。
一個操作符名是最多NAMEDATALEN-1(默認為 63)的一個字符序列,其中的字符來自下麵的列表:
+ - * / < > = ~ ! @ # % ^ & | ` ?
不過,在操作符名上有一些限製:
-- and /*不能在一個操作符名的任何地方出現,因為它們將被作為一段注釋的開始。
一個多字符操作符名不能以+或-結尾,除非該名稱也至少包含這些字符中的一個:
~ ! @ # % ^ & | ` ?
例如,@-是一個被允許的操作符名,但*-不是。這些限製允許PostgreSQL解析 SQL 兼容的查詢而不需要在記號之間有空格。
當使用非 SQL 標準的操作符名時,你通常需要用空格分隔相鄰的操作符來避免歧義。例如,如果你定義了一個名為@的左一元操作符,你不能寫X*@Y,你必須寫X* @Y來確保PostgreSQL把它讀作兩個操作符名而不是一個。
一些不是數字字母的字符有一種不同於作為操作符的特殊含義。這些字符的詳細用法可以在描述相應語法元素的地方找到。這一節隻是為了告知它們的存在以及總結這些字符的目的。
跟隨在一個美元符號($)後麵的數字被用來表示在一個函數定義或一個預備語句中的位置參數。在其他上下文中該美元符號可以作為一個標識符或者一個美元引用字符串常量的一部分。
圓括號(())具有它們通常的含義,用來分組表達式並且強製優先。在某些情況中,圓括號被要求作為一個特定 SQL 命令的固定語法的一部分。
方括號([])被用來選擇一個數組中的元素。更多關於數組的信息見Section 8.15。
逗號(,)被用在某些語法結構中來分割一個列表的元素。
分號(;)結束一個 SQL 命令。它不能出現在一個命令中間的任何位置,除了在一個字符串常量中或者一個被引用的標識符中。
冒號(:)被用來從數組中選擇"切片"(見Section 8.15)。在某些 SQL 的“方言”(例如嵌入式 SQL)中,冒號被用來作為變量名的前綴。
星號(*)被用在某些上下文中標記一個表的所有域或者組合值。當它被用作一個聚集函數的參數時,它還有一種特殊的含義,即該聚集不要求任何顯式參數。
句點(.)被用在數字常量中,並且被用來分割模式、表和列名。
一段注釋是以雙斜線開始並且延伸到行結尾的一個字符序列,例如:
-- This is a standard SQL comment
另外,也可以使用 C 風格注釋塊:
/* multiline comment
* with nesting: /* nested block comment */
*/
這裏該注釋開始於/*並且延伸到匹配出現的*/。這些注釋塊可按照 SQL 標準中指定的方式嵌套,但和 C 中不同。這樣我們可以注釋掉一大段可能包含注釋塊的代碼。
在進一步的語法分析前,注釋會被從輸入流中被移除並且實際被替換為空白。
Table 4-2顯示了PostgreSQL中操作符的優先級和結合性。大部分操作符具有相同的優先並且是左結合的。操作符的優先級和結合性被硬寫在解析器中。
此外,當使用二元和一元操作符的組合時,有時你將需要增加圓括號。例如:
SELECT 5 ! - 6;
將被解析為:
SELECT 5 ! (- 6);
因為解析器不知道 — 知道時就為時已晚 — !被定義為一個後綴操作符而不是一個中綴操作符。在這種情況下要得到想要的行為,你必須寫成:
SELECT (5 !) - 6;
隻是為了擴展性必須付出的代價。
Table 4-2. 操作符優先級(從高到低)
注意該操作符有限規則也適用於與上述內建操作符具有相同名稱的用戶定義的操作符。例如,如果你為某種自定義數據類型定義了一個"+"操作符,它將具有和內建的"+"操作符相同的優先級,不管你的操作符要做什麼。
當一個模式限定的操作符名被用在OPERATOR語法中時,如下麵的例子:
SELECT 3 OPERATOR(pg_catalog.+) 4;
OPERATOR結構被用來為"任意其他操作符"獲得Table 4-2中默認的優先級。不管出現在OPERATOR()中的是哪個指定操作符,這都是真的。
Note:
版本 9.5 之前的PostgreSQL使用的操作符優先級 規則略有不同。特別是,<=、>= 和<>習慣於被當作普通操作符,IS 測試習慣於具有較高的優先級。並且在一些認為NOT比 BETWEEN優先級高的情況下,NOT BETWEEN 和相關的結構的行為不一致。為了更好地兼容 SQL 標準並且減少對 邏輯上等價的結構不一致的處理,這些規則也得到了修改。在大部分情況下, 這些變化不會導致行為上的變化,或者可能會產生"no such operator" 錯誤,但可以通過增加圓括號解決。不過在一些極端情況中,查詢可能在 沒有被報告解析錯誤的情況下發生行為的改變。如果你發覺這些改變悄悄地 破壞了一些事情,可以打開operator_precedence_warning 配置參數,然後測試你的應用看看有沒有一些警告被記錄。
最後更新:2017-08-16 15:02:16