4.2. 值表達式

值表達式用在各種語法環境中,比如在 SELECT 命令的目標列表中,在 INSERTUPDATE 中用做新的列值,或者在許多命令中的搜索條件中使用。 我們有時候把值表達式的結果叫做標量, 以便與一個表表達式的結果相區別(是一個表)。因此值表達式也叫做標量表達式 (或者更簡單的表達式)。表達式語法允許對來自基本部分的數值進行算術,邏輯,集合,和其它操作的運算。

值表達式是下列內容之一:

除了這個列表以外,還有許多構造可以歸類為表達式,但是不遵循任何通用的語法規則。 它們通常有函數或操作符的語義,並且在 Chapter 9 裡合適的位置描述。 一個例子是 IS NULL 子句。

我們已經在 Section 4.1.2 裡有討論過的內容了。下面的節討論剩下的選項。

4.2.1. 字段引用

一個字段可以用下面形式的引用:

correlation.columnname

correlation 是一個表的名字(可能有模式修飾), 或者是用FROM子句這樣的方法定義的表的別名,或者是關鍵字 NEWOLD。 (NEWOLD只能出現在一條改寫規則中, 而其他相關的名字可以用于任意 SQL 語句中。) 如果在當前查詢中所使用的所有表中,該字段名字是唯一的, 那麼這個相關名字和分隔用的點就可以省略。 (又見 Chapter 7。)

4.2.2. 位置參數

位置參數引用用于標識從外部給一個 SQL 語句的一個參數。 參數用于 SQL 函數定義語句和準備好的查詢。 有些客戶端庫還支持在 SQL 命令字串外邊聲明數據值,這種情況下參數用于引用 SQL 字串行外的數據。 一個參數的形式如下:

$number

比如,看看一個函數 dept 的定義, 如下

CREATE FUNCTION dept(text) RETURNS dept
  AS 'SELECT * FROM dept WHERE name = $1'
  LANGUAGE SQL;

在函數被調用的時候這裡的 $1 將被第一個函數的參數代替。

4.2.3. 下標

如果一個表達式生成一個數組類型的數值,那麼我們可以通過寫下面這樣的表達式來聲明數組值的元素

expression[subscript]

如果是多個相鄰的元素(一個"數組片斷")可以用下面的方法抽取

expression[lower_subscript:upper_subscript]

(在這裡,方括弧 [ ] 的意思是按照字面文本的方式出現。) 每個subscript自己都是一個表達式,它必須生成一個整數值。

通常,數組 expression 必須用圓括弧包圍, 但如果要進行腳標計算的表達式只是一個字段引用或者一個位置參數,那麼圓括弧可以省略。 同樣,如果源數組是多維的,那麼多個腳標可以連接在一起。比如,

mytable.arraycolumn[4]
mytable.two_d_column[17][34]
$1[10:42]
(arrayfunction(a,b))[42]

最後一個例子裡的圓括弧是必須的。參閱 Section 8.10 獲取有關數組的更多信息。

4.2.4. 字段選擇

如果一個表達式生成一個復合類型(行類型),那麼用下面的方法可以抽取一個指定的字段

expression.fieldname

通常,行 expression 必須用圓括弧包圍, 但是如果要選取的表達式只是一個表引用或者位置參數,可以省略圓括弧。 比如

mytable.mycolumn
$1.somecolumn
(rowfunction(a,b)).col3

(因此,一個全稱的字段引用實際上只是一個字段選擇語法的特例。)

4.2.5. 操作符調用

操作符調用有三種語法︰

expression operator expression (雙目中綴操作符)
operator expression (單目前綴操作符)
expression operator (單目後綴操作符)

這裡的 operator 記號遵循語法規則: Section 4.1.3, 或者是記號:ANDOR,和 NOT 之一。 或者是一個被修飾的操作符名

OPERATOR(schema.operatorname)

具體存在哪個操作符以及它們是單目還是雙目取決于系統或用戶定義了什麼操作符。Chapter 9 描述了內置的操作符。

4.2.6. 函數調用

函數調用的語法是合法函數名字(可能有模式名修飾), 後面跟著在圓括弧裡的它的參數列表:

function ([expression [, expression ... ]] )

比如,下面的代碼計算 2 的平方根:

sqrt(2)

內置函數的列表在 Chapter 9 裡。 其它函數可以由用戶添加。

4.2.7. 聚集表達式

一個聚集表達式代表一個聚集函數對一個查詢選出的行的處理。 一個聚集函數把多個輸入縮減為一個輸出值, 比如給輸入求和或平均。一個聚集表達式的語法是下列之一:

aggregate_name (expression)
aggregate_name (ALL expression)
aggregate_name (DISTINCT expression)
aggregate_name ( * )

這裡 aggregate_name 是前面定義的聚集,(可能是全稱), 而 expression 是一個本身不包含聚集表達式的任意值表達式。

第一種形式的聚集表達式為所有表達式生成非空值的輸入行調用聚集。 (實際上,是否忽略空值由聚集函數決定 --- 但是所有標準的聚集函數都忽略它們。) 第二種形式和第一種一樣,因為 ALL 是缺省值。 第三種形式為所有輸入行裡找到表達式的所有唯一的非空值調用聚集。 最後一種形式為每個輸入行(不管是空還是非空)調用一次聚集; 因為沒有聲明特定的輸入值。通常它只是對 count() 聚集函數有用。

比如,count(*) 生成輸入行的總數; count(f1) 生成 f1 為非空的輸入行數; count(distinct f1) 生成 f1 唯一非空的行數。

預定義的聚集函數在 Section 9.15 裡描述。 其它聚集函數可以由用戶增加。

一個聚集表達式只能在 SELECT 命令的結果列表或者 HAVING 子句裡出現。 禁止在其它子句裡出現,比如 WHERE 裡面,因為這些子句邏輯上在生成聚集結果之前計算。

如果一個聚集表達式出現在一個子查詢裡(參閱 Section 4.2.9Section 9.16), 聚集通常是在子查詢的行上進行計算。但是如果聚集的參數只包含外層查詢的變量則有一個例外: 這個聚集會屬于離他最近的外層查詢,並且在該查詢上進行計算。 該聚集表達式整體上屬于它出現的子查詢對外層查詢的引用,其作用相當于子查詢任何一次計算中的一個常量。 這個聚集表達式的有關只能出現在結果列或者 HAVING 子句的限制適用于聚集所屬的查詢層。

4.2.8. 類型轉換

一個類型轉換聲明一個從一種數據類型到另外一種數據類型的轉換。 PostgreSQL 接受兩種等效的類型轉換語法:

CAST ( expression AS type )
expression::type

CAST 語法遵循 SQL;:: 的語法是 PostgreSQL 傳統用法。

如果對一個已知類型的值表達式應用轉換,它代表一個運行時類型轉換。 只有在存在合適的類型轉換函數的情況下,該轉換才能成功。 請注意這一點和用于常量的轉換略有區別,如 Section 4.1.2.4 所示。 一個應用于某個未修飾的字串文本的轉換表示給一個字串文本數值賦予一個初始化類型, 因此它對于任何類型都會成功(如果字串文本的內容符合該數據類型的輸入語法接受。)

如果對于一個值表達式生成的數值對某類型而言不存在混淆的情況, 那麼我們可以省略明確的類型轉換(比如,在給一個表字段賦值的時候); 在這樣的情況下,系統將自動附加一個類型轉換。 不過,自動轉換只適用于那些系統表中標記著 "OK to apply implicitly" 的轉換函數。 其它轉換函數必須用明確的轉換語法調用。 這些限制是為了避免一些怪異的轉換被應用。

我們也可以用函數樣的語法聲明一個類型轉換:

typename ( expression )

不過,這個方法只能用于那些名字同時也是有效函數名字的類型。 比如,double precision 就不能這麼用, 但是等效的 float8 可以。同樣,intervaltime,和 timestamp 如果加了雙引號也只能這麼用, 因為存在語法衝突。因此,函數樣的類型轉換會導致不一致, 所以可能應該避免在新應用中這麼用。 (函數樣語法實際上就似乎一個函數調用。如果使用兩種標準轉換語法做運行時轉換, 那麼它將在內部調用一個已注冊得函數執行轉換。通常, 這種轉換函數和它們得輸出類型同名,但是這個要點可不是那些可以移植的程序可以依賴的東西。)

4.2.9. 標量子查詢

一個標量子查詢是一個放在圓括弧裡的普通 SELECT查詢, 它只返回只有一個字段的一行。(參閱 Chapter 7 獲取有關寫查詢的信息。) 該 SELECT 將被執行, 而其單個返回值將在週圍的值表達式中使用。 把一個返回超過一行或者超過一列的查詢用做標量查詢是錯誤的。 (不過,在特定的執行中,子查詢不返回行則不算錯誤;標量結果認為是NULL。) 該子查詢可以引用週圍查詢的變量,那些變量也是在計算任意子查詢的時候當做常量使用的。 又見 Section 9.16

比如,下面的查詢找出每個州中的最大人口數量的城市:

SELECT name, (SELECT max(pop) FROM cities WHERE cities.state = states.name)
FROM states;

4.2.10. 數組構造器

一個數組構造器是一個表達式,它從它的成員元素上構造一個數組值。 一個簡單的數組構造器由關鍵字 ARRAY,一個左方括弧 [, 一個或多個表達式(用逗號分隔)表示數組圓熟值,以及最後一個右方括弧 ]。 比如

SELECT ARRAY[1,2,3+4];
  array
---------
 {1,2,7}
(1 row)

數組元素類型是成員表達式的公共類型,使用和 UNIONCASE 構造一樣的規則決定。 (參閱 Section 10.5)。

多維數組值可以通過嵌套數組構造器的方法來制作。 在內層構造器裡,關鍵字 ARRAY 可以省略。比如,下面的兩句生成同樣的結果:

SELECT ARRAY[ARRAY[1,2], ARRAY[3,4]];
     array
---------------
 {{1,2},{3,4}}
(1 row)

SELECT ARRAY[[1,2],[3,4]];
     array
---------------
 {{1,2},{3,4}}
(1 row)

因為多維數組必須式方形,同層的內層構造器必須生成同維的子數組。

多維數組構造器元素可以是任何生成合適數組的東西,而不僅僅是一個子 ARRAY 構造。 比如:

CREATE TABLE arr(f1 int[], f2 int[]);

INSERT INTO arr VALUES (ARRAY[[1,2],[3,4]], ARRAY[[5,6],[7,8]]);

SELECT ARRAY[f1, f2, '{{9,10},{11,12}}'::int[]] FROM arr;
                     array
------------------------------------------------
 {{{1,2},{3,4}},{{5,6},{7,8}},{{9,10},{11,12}}}
(1 row)

我們也可以從一個子查詢的結果中構造一個數組。在這種形式下, 數組構造器是用關鍵字 ARRAY 後面跟著一個用圓括弧(不是方括弧)包圍的子查詢。 比如:

SELECT ARRAY(SELECT oid FROM pg_proc WHERE proname LIKE 'bytea%');
                          ?column?
-------------------------------------------------------------
 {2011,1954,1948,1952,1951,1244,1950,2005,1949,1953,2006,31}
(1 row)

子查詢必須返回一個字段。生成的一維數組將為子查詢裡每行結果生成一個元素, 元素類型匹配子查詢的輸出字段。

ARRAY 建立的數組值的腳標總是從一開始。 有關數組的更多信息,參閱 Section 8.10

4.2.11. 表達式計算規則

子表達式的計算順序是沒有定義的。特別要指出的是, 一個操作符或者函數的輸入並不一定是按照從左向右的順序或者以某種特定的順序進行計算的。

另外,如果一個表達式的結果可以通過只判斷它的一部分就可以得到, 那麼其它子表達式就可以完全不計算了。比如,如果我們這麼寫

SELECT true OR somefunc();

那麼 somefunc() 就(可能)根本不會被調用。 如果我們寫下面的,也可能會是這樣

SELECT somefunc() OR true;

請注意這裡和某些編程語言裡的從左向右"短路"是不一樣的。

因此,拿那些有副作用的函數作為復雜表達式的一部分是不明智的選擇。 在 WHEREHAVING 子句裡面依賴副作用或者是計算順序是特別危險的, 因為這些子句都是作為生成一個執行規劃的一部分進行了大量的再處理。 在這些子句裡的布爾表達式(AND/OR/NOT 的組合)可以以布爾代數運算律允許的任意方式進行識別。

如果強制計算順序非常重要,那麼可以使用 CASE 構造(參閱 Section 9.12)。 比如,下面是一種視圖避免在 WHERE 子句裡被零除的不可信的方法:

SELECT ... WHERE x <> 0 AND y/x > 1.5;

但是下面這樣的是安全的:

SELECT ... WHERE CASE WHEN x <> 0 THEN y/x > 1.5 ELSE false END;

用這種風格的 CASE 構造會阻止優化,因此應該只在必要的時候使用。 (在這個特殊的例子裡,毫無疑問寫成 y > 1.5*x 更好。)