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.5 所示。 一個應用於某個未修飾的字串文本的轉換資料表示給一個字串文本數值賦予一個初始化類型, 因此它對於任何類型都會成功(如果字串文本的內容符合該資料類型的輸入語法接受。)

如果對於一個值資料表達式生成的數值對某類型而言不存在混淆的情況, 那麼我們可以省略明確的類型轉換(比如,在給一個資料表字串賦值的時候); 在這樣的情況下,系統將自動附加一個類型轉換。 不過,自動轉換只適用於那些系統資料表中標記著 "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. 行構造

一個行構造器是一個從提供給它的成員字串數值中製作行數值(也叫復合類型值)的資料表達式。 一個行構造器由關鍵字 ROW,一個左圓括弧, 零個或者多個用做行字串值的資料表達式(用逗號分隔),以及最後一個右圓括弧。比如,

SELECT ROW(1,2.5,'this is a test');

如果在列資料表裡有多個資料表達式,那麼關鍵字 ROW 是可選的。

預設時,ROW 資料表達式建立的值是一個匿名的記錄類型。如果必要,您可以把它轉換成一個命名的復合類型 — 既可以是一個資料表的行類型,也可以是一個用 CREATE TYPE AS 建立的復合類型。 可能會需要一個明確的轉換以避免歧義。比如:

CREATE TABLE mytable(f1 int, f2 float, f3 text);

CREATE FUNCTION getf1(mytable) RETURNS int AS 'SELECT $1.f1' LANGUAGE SQL;

-- 因為只有一個 getf1() 存在,所以不需要類型轉換
SELECT getf1(ROW(1,2.5,'this is a test'));
 getf1
-------
     1
(1 row)

CREATE TYPE myrowtype AS (f1 int, f2 text, f3 numeric);

CREATE FUNCTION getf1(myrowtype) RETURNS int AS 'SELECT $1.f1' LANGUAGE SQL;

-- 現在我們需要類型轉換以資料表明調用哪個函數:
SELECT getf1(ROW(1,2.5,'this is a test'));
ERROR:  function getf1(record) is not unique

SELECT getf1(ROW(1,2.5,'this is a test')::mytable);
 getf1
-------
     1
(1 row)

SELECT getf1(CAST(ROW(11,'this is a test',2.5) AS myrowtype));
 getf1
-------
    11
(1 row)

行構造器可以用於製作儲存在復合類型資料表字串裡面的復合類型值, 或者是傳遞給一個接受復合類型參數的函數。還有,我們也可以比較兩個行數值或者用 IS NULLIS NOT NULL 測試一個行數值,比如

SELECT ROW(1,2.5,'this is a test') = ROW(1, 3, 'not the same');

SELECT ROW(a, b, c) IS NOT NULL FROM table;

更多的細節,請參閱 Section 9.17。 行構造還可以用於連接子查詢,這些在 Section 9.16 裡面有詳細討論。

4.2.12. 資料表達式計算規則

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

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

SELECT true OR somefunc();

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

SELECT somefunc() OR true;

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

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

如果強制計算順序非常重要,那麼可以使用 CASE 構造(參閱 Section 9.13)。 比如,下面是一種視圖避免在 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 更好。)