閱讀986 返回首頁    go 微軟 go windows


PostgreSQL SQL語法(二):值表達式

本文檔為PostgreSQL 9.6.0文檔,本轉載已得到原譯者彭煜瑋授權。

值表達式被用於各種各樣的環境中,例如在SELECT命令的目標列表中、作為INSERT或UPDATE中的新列值或者若幹命令中的搜索條件。為了區別於一個表表達式(是一個表)的結果,一個值表達式的結果有時候被稱為一個標量。值表達式因此也被稱為標量表達式(或者甚至簡稱為表達式)。表達式語法允許使用算數、邏輯、集合和其他操作從原始部分計算值。

一個值表達式是下列之一:

  • 一個常量或文字值
  • 一個列引用
  • 在一個函數定義體或預備語句中的一個位置參數引用
  • 一個下標表達式
  • 一個域選擇表達式
  • 一個操作符調用
  • 一個函數調用
  • 一個聚集表達式
  • 一個窗口函數調用
  • 一個類型轉換
  • 一個排序規則表達式
  • 一個標量子查詢
  • 一個數組構造器
  • 一個行構造器
  • 另一個在圓括號(用來分組子表達式以及重載優先級)中的值表達式

在這個列表之外,還有一些結構可以被分類為一個表達式,但是它們不遵循任何一般語法規則。這些通常具有一個函數或操作符的語義並且在Chapter 9中的合適位置解釋。一個例子是IS NULL子句。

我們已經在Section 1.2中討論過常量。下麵的小節會討論剩下的選項。

一個列可以以下麵的形式被引用:

correlation.columnname

correlation是一個表(有可能以一個模式名限定)的名字,或者是在FROM子句中為一個表定義的別名。如果列名在當前索引所使用的表中都是唯一的,關聯名稱和分隔用的句點可以被忽略(另見Chapter 7)。

一個位置參數引用被用來指示一個由 SQL 語句外部提供的值。參數被用於 SQL 函數定義和預備查詢中。某些客戶端庫還支持獨立於 SQL 命令字符串來指定數據值,在這種情況中參數被用來引用那些線外數據值。一個參數引用的形式是:

$number

例如,考慮一個函數dept的定義:

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

這裏$1引用函數被調用時第一個函數參數的值。

如果一個表達式得到了一個數組類型的值,那麼可以抽取出該數組值的一個特定元素:

expression[subscript]

或者抽取出多個相鄰元素(一個"數組切片"):

expression[lower_subscript:upper_subscript]

(這裏,方括號[ ]表示其字麵意思)。每一個下標自身是一個表達式,它必須得到一個整數值。

通常,數組表達式必須被加上括號,但是當要被加下標的表達式隻是一個列引用或位置參數時,括號可以被忽略。還有,當原始數組是多維時,多個下標可以被連接起來。例如:

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

最後一個例子中的圓括號是必需的。詳見Section 8.15。

如果一個表達式得到一個組合類型(行類型)的值,那麼可以抽取該行的指定域

expression.fieldname

通常行表達式必須被加上括號,但是當該表達式是僅從一個表引用或位置參數選擇時,圓括號可以被忽略。例如:

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

(因此,一個被限定的列引用實際上隻是域選擇語法的一種特例)。一種重要的特例是從一個組合類型的表列中抽取一個域:

(compositecol).somefield
(mytable.compositecol).somefield

這裏需要圓括號來顯示compositecol是一個列名而不是一個表名,在第二種情況中則是顯示mytable是一個表名而不是一個模式名。

在一個選擇列表(見Section 7.3)中,你可以通過書寫.*來請求一個組合值的所有域:

(compositecol).*

對於一次操作符調用,有三種可能的語法:

expression operator expression(二元中綴操作符)
operator expression(一元前綴操作符)
expression operator(一元後綴操作符)

其中operator記號遵循Section 1.3的語法規則,或者是關鍵詞AND、OR和NOT之一,或者是一個如下形式的受限定操作符名:

OPERATOR(schema.operatorname)

哪個特定操作符存在以及它們是一元的還是二元的取決於由係統或用戶定義的那些操作符。

一個函數調用的語法是一個函數的名稱(可能受限於一個模式名)後麵跟上封閉於圓括號中的參數列表:

function_name ([expression [, expression ... ]] )

例如,下麵會計算 2 的平方根:

sqrt(2)

內建函數的列表在Chapter 9中。其他函數可以由用戶增加。

參數可以有選擇地被附加名稱。

Note:
一個采用單一組合類型參數的函數可以被有選擇地稱為域選擇語法,並且反過來域選擇可以被寫成函數的風格。也就是說,記號col(table)和table.col是可以互換的。這種行為是非 SQL 標準的但是在PostgreSQL中被提供,因為它允許函數的使用來模擬"計算域"。

一個聚集表達式表示在由一個查詢選擇的行上應用一個聚集函數。一個聚集函數將多個輸入減少到一個單一輸出值,例如對輸入的求和或平均。一個聚集表達式的語法是下列之一:

aggregate_name (expression [ , ... ] [ order_by_clause ] ) [ FILTER ( WHERE filter_clause ) ]
aggregate_name (ALL expression [ , ... ] [ order_by_clause ] ) [ FILTER ( WHERE filter_clause ) ]
aggregate_name (DISTINCT expression [ , ... ] [ order_by_clause ] ) [ FILTER ( WHERE filter_clause ) ]
aggregate_name ( * ) [ FILTER ( WHERE filter_clause ) ]
aggregate_name ( [ expression [ , ... ] ] ) WITHIN GROUP ( order_by_clause ) [ FILTER ( WHERE filter_clause ) ]

這裏aggregate_name是一個之前定義的聚集(可能帶有一個模式名限定),並且expression是任意自身不包含聚集表達式的值表達式或一個窗口函數調用。可選的order_by_clause和filter_clause描述如下。

第一種形式的聚集表達式為每一個輸入行調用一次聚集。第二種形式和第一種相同,因為ALL是默認選項。第三種形式為輸入行中表達式的每一個可區分值(或者對於多個表達式是值的可區分集合)調用一次聚集。第四種形式為每一個輸入行調用一次聚集,因為沒有特定的輸入值被指定,它通常隻對於count(*)聚集函數有用。最後一種形式被用於有序集聚集函數,其描述如下。

大部分聚集函數忽略空輸入,這樣其中一個或多個表達式得到空值的行將被丟棄。除非另有說明,對於所有內建聚集都是這樣。

例如,count(*)得到輸入行的總數。count(f1)得到輸入行中f1為非空的數量,因為count忽略空值。而count(distinct f1)得到f1的非空可區分值的數量。

一般地,交給聚集函數的輸入行是未排序的。在很多情況中這沒有關係,例如不管接收到什麼樣的輸入,min總是產生相同的結果。但是,某些聚集函數(例如array_agg 和string_agg)依據輸入行的排序產生結果。當使用這類聚集時,可選的order_by_clause可以被用來指定想要的順序。order_by_clause與查詢級別的ORDER BY子句(如Section 7.5所述)具有相同的語法,除非它的表達式總是僅有表達式並且不能是輸出列名稱或編號。例如:

SELECT array_agg(a ORDER BY b DESC) FROM table;

在處理多參數聚集函數時,注意ORDER BY出現在所有聚集參數之後。例如,要這樣寫:

SELECT string_agg(a, ',' ORDER BY a) FROM table;

而不能這樣寫:

SELECT string_agg(a ORDER BY a, ',') FROM table;  -- 不正確

後者在語法上是合法的,但是它表示用兩個ORDER BY鍵來調用一個單一參數聚集函數(第二個是無用的,因為它是一個常量)。

如果在order_by_clause之外指定了DISTINCT,那麼所有的ORDER BY表達式必須匹配聚集的常規參數。也就是說,你不能在DISTINCT列表沒有包括的表達式上排序。

Note:
在一個聚集函數中指定DISTINCT以及ORDER BY的能力是一種PostgreSQL擴展。

按照到目前為止的描述,如果一個"normal"聚集中 排序是可選的,在要為它排序輸入行時可以在該聚集的常規參數 列表中放置ORDER BY。有一個聚集函數的子集叫 做有序集聚集,它要求一個 order_by_clause,通常是因為 該聚集的計算隻對其輸入行的特定順序有意義。有序集聚集的典 型例子包括排名和百分位計算。按照上文的最後一種語法,對於 一個有序集聚集, order_by_clause被寫在 WITHIN GROUP (...)中。 order_by_clause中的表達式 會像普通聚集參數一樣對每一個輸入行計算一次,按照每個 order_by_clause的要求排序並 且交給該聚集函數作為輸入參數(這和非 WITHIN GROUP order_by_clause的情況不同,在其中表達 式的結果不會被作為聚集函數的參數)。如果有在 WITHIN GROUP之前的參數表達式,會把它們稱 為直接參數以便與列在 order_by_clause中的 聚集參數相區分。與普通聚集參數不同,針對 每次聚集調用隻會計算一次直接參數,而不是為每一個輸入行 計算一次。這意味著隻有那些變量被GROUP BY 分組時,它們才能包含這些變量。這個限製同樣適用於根本不在 一個聚集表達式內部的直接參數。直接參數通常被用於百分數 之類的東西,它們隻有作為每次聚集計算用一次的單一值才有意 義。直接參數列表可以為空,在這種情況下,寫成() 而不是(*)(實際上 PostgreSQL接受兩種拚寫,但是隻有第一 種符合 SQL 標準)。有序集聚集的調用例子:

SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY income) FROM households;
 percentile_disc
-----------------
           50489

這會從表households的 income列得到第 50 個百分位或者中位的值。 這裏0.5是一個直接參數,對於百分位部分是一個 在不同行之間變化的值的情況它沒有意義。

如果指定了FILTER,那麼隻有對filter_clause計算為真的輸入行會被交給該聚集函數,其他行會被丟棄。例如:

SELECT
    count(*) AS unfiltered,
    count(*) FILTER (WHERE i < 5) AS filtered
FROM generate_series(1,10) AS s(i);
 unfiltered | filtered
------------+----------
         10 |        4
(1 row)

預定義的聚集函數在Section 9.20中描述。其他聚集函數可以由用戶增加。

一個聚集表達式隻能出現在SELECT命令的結果列表或是HAVING子句中。在其他子句(如WHERE)中禁止使用它,因為那些子句的計算在邏輯上是在聚集的結果被形成之前。

當一個聚集表達式出現在一個子查詢中,聚集通常在該子查詢的行上被計算。但是如果該聚集的參數(以及filter_clause,如果有)隻包含外層變量則會產生一個異常:該聚集則屬於最近的那個外層,並且會在那個查詢的行上被計算。該聚集表達式從整體上則是對其所出現於的子查詢的一種外層引用,並且在那個子查詢的任意一次計算中都作為一個常量。隻出現在結果列表或HAVING子句的限製適用於該聚集所屬的查詢層次。

一個窗口函數調用表示在一個查詢選擇的行的某個部分上應用一個聚集類的函數。和常規聚集函數調用不同,這不會被約束為將被選擇的行分組為一個單一的輸出行 — 在查詢輸出中每一個行仍保持獨立。不過,窗口函數能夠根據窗口函數調用的分組聲明(PARTITION BY列表)掃描屬於當前行所在分組中的所有行。一個窗口函數調用的語法是下列之一:

function_name ([expression [, expression ... ]]) [ FILTER ( WHERE filter_clause ) ] OVER window_name
function_name ([expression [, expression ... ]]) [ FILTER ( WHERE filter_clause ) ] OVER ( window_definition )
function_name ( * ) [ FILTER ( WHERE filter_clause ) ] OVER window_name
function_name ( * ) [ FILTER ( WHERE filter_clause ) ] OVER ( window_definition )

其中window_definition的語法是

[ existing_window_name ]
[ PARTITION BY expression [, ...] ]
[ ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS { FIRST | LAST } ] [, ...] ]
[ frame_clause ]

而可選的frame_clause是下列之一

{ RANGE | ROWS } frame_start
{ RANGE | ROWS } BETWEEN frame_start AND frame_end

其中frame_start和frame_end可以是下麵形式中的一種

UNBOUNDED PRECEDING
value PRECEDING
CURRENT ROW
value FOLLOWING
UNBOUNDED FOLLOWING

這裏,expression表示任何自身不含有窗口函數調用的值表達式。

window_name是對定義在查詢的WINDOW子句中的一個命名窗口聲明的引用。還可以使用在WINDOW子句中定義命名窗口的相同語法在圓括號內給定一個完整的window_definition,詳見SELECT參考頁。值得指出的是,OVER wname並不嚴格地等價於OVER (wname),後者表示複製並修改窗口定義,並且在被引用窗口聲明包括一個幀子句時會被拒絕。

PARTITION BY選項將查詢的行分組成為分區,窗口函數會獨立地處理它們。PARTITION BY工作起來類似於一個查詢級別的GROUP BY子句,不過它的表達式總是隻是表達式並且不能是輸出列的名稱或編號。如果沒有PARTITION BY,該查詢產生的所有行被當作一個單一分區來處理。ORDER BY選項決定被窗口函數處理的一個分區中的行的順序。它工作起來類似於一個查詢級別的ORDER BY子句,但是同樣不能使用輸出列的名稱或編號。如果沒有ORDER BY,行將被以未指定的順序被處理。

frame_clause指定構成窗口幀的行集合,它是當前分區的一個子集,窗口函數將作用在該幀而不是整個分區。 幀可以被指定為RANGE或ROWS模式,在兩種情況中它都從frame_start運行到frame_end。如果frame_end被忽略,它默認運行到CURRENT ROW。

UNBOUNDED PRECEDING的一個frame_start表示該幀開始於分區的第一行,類似地UNBOUNDED FOLLOWING的一個frame_end表示該幀結束於分區的最後一行。

在RANGE模式下, CURRENT ROW的一個frame_start表示該幀開始於當前行的第一個平級行(一個被ORDER BY認為與當前行等效的行),而CURRENT ROW的一個frame_end表示該幀結束於最後一個等效的ORDER BY平級行。在ROWS模式下,CURRENT ROW僅表示當前行。

value PRECEDING和value FOLLOWING情況當前隻在ROWS模式中被允許。它們指示幀開始或結束於當前行之前或之後的指定數量的行。value必須是一個不包含任何變量、聚集函數或窗口函數的整數表達式。該值不能為空或負,但是可以為零,零表示隻選擇當前行。

默認的幀選項是RANGE UNBOUNDED PRECEDING,它和RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW相同。如果使用ORDER BY,這會把該幀設置為從分區開始一直到當前行的最後一個ORDER BY平級行的所有行。如果不使用ORDER BY,分區中所有的行都被包括在窗口幀中,因為所有行都成為了當前行的平級行。

限製是frame_start不能為UNBOUNDED FOLLOWING、frame_end不能為UNBOUNDED PRECEDING並且在上述列表中frame_end的選擇不能早於frame_start的選擇出現 — 例如RANGE BETWEEN CURRENT ROW AND value PRECEDING是不被允許的。

如果指定了FILTER,那麼隻有對filter_clause計算為真的輸入行會被交給該窗口函數,其他行會被丟棄。隻有是聚集的窗口函數才接受FILTER 。

內建的窗口函數在Table 9-56中描述。 用戶可以增加其他的窗口函數。還有,任何內建或用戶定義的普通聚集 函數可以被用作窗口函數。不過,有序集聚集當前不能被用作窗口函數。

使用*的語法被用來把參數較少的聚集函數當作窗口函數調用,例如count(*) OVER (PARTITION BY x ORDER BY y)。星號(*)通常不被用於非聚集窗口函數。與通常的聚集函數不同,聚集窗口函數不允許在函數參數列表中使用DISTINCT或ORDER BY。

隻有在SELECT列表和查詢的ORDER BY子句中才允許窗口函數調用。

一個類型造型指定從一種數據類型到另一種數據類型的轉換。PostgreSQL接受兩種等價的類型造型語法:

CAST ( expression AS type )
expression::type

CAST語法遵從 SQL,而用::的語法是PostgreSQL的曆史用法。

當一個造型被應用到一種未知類型的值表達式上時,它表示一種運行時類型轉換。隻有已經定義了一種合適的類型轉換操作時,該造型才會成功。注意這和常量的造型使用不同。應用於一個未修飾串文字的造型表示一種類型到一個文字常量值的初始賦值,並且因此它將對任意類型都成功(如果該串文字的內容對於該數據類型的輸入語法是可接受的)。

如果一個值表達式必須產生的類型沒有歧義(例如當它被指派給一個表列),通常可以省略顯式類型造型,在這種情況下係統會自動應用一個類型造型。但是,隻有對在係統目錄中被標記為"OK to apply implicitly"的造型才會執行自動造型。其他造型必須使用顯式造型語法調用。這種限製是為了防止出人意料的轉換被無聲無息地應用。

還可以用像函數的語法來指定一次類型造型:

typename ( expression )

不過,這隻對那些名字也作為函數名可用的類型有效。例如,double precision不能以這種方式使用,但是等效的float8可以。還有,如果名稱interval、time和timestamp被用雙引號引用,那麼由於語法衝突的原因,它們隻能以這種風格使用。因此,函數風格的造型語法的使用會導致不一致性並且應該盡可能被避免。

Note:
函數風格的語法事實上隻是一次函數調用。當兩種標準造型語法之一被用來做一次運行時轉換時,它將在內部調用一個已注冊的函數來執行該轉換。簡而言之,這些轉換函數具有和它們的輸出類型相同的名字,並且因此"函數風格的語法"無非是對底層轉換函數的一次直接調用。顯然,一個可移植的應用不應當依賴於它。

COLLATE子句會重載一個表達式的排序規則。它被追加到它適用的表達式:

expr COLLATE collation

這裏collation可能是一個受模式限定的標識符。COLLATE子句比操作符綁得更緊,需要時可以使用圓括號。

如果沒有顯式指定排序規則,數據庫係統會從表達式所涉及的列中得到一個排序規則,如果該表達式沒有涉及列,則會默認采用數據庫的默認排序規則。

COLLATE子句的兩種常見使用是重載ORDER BY子句中的排序順序,例如:

SELECT a, b, c FROM tbl WHERE ... ORDER BY a COLLATE "C";

以及重載具有區域敏感結果的函數或操作符調用的排序規則,例如:

SELECT * FROM tbl WHERE a > 'foo' COLLATE "C";

注意在後一種情況中,COLLATE子句被附加到我們希望影響的操作符的一個輸入參數上。COLLATE子句被附加到該操作符或函數調用的哪個參數上無關緊要,因為被操作符或函數應用的排序規則是考慮所有參數得來的,並且一個顯式的COLLATE子句將重載所有其他參數的排序規則(不過,附加非匹配COLLATE子句到多於一個參數是一種錯誤。詳見Section 23.2)。因此,這會給出和前一個例子相同的結果:

SELECT * FROM tbl WHERE a COLLATE "C" > 'foo';

但是這是一個錯誤:

SELECT * FROM tbl WHERE (a > 'foo') COLLATE "C";

因為它嚐試把一個排序規則應用到>操作符的結果,而它的數據類型是非可排序數據類型boolean。

一個標量子查詢是一種圓括號內的普通SELECT查詢,它剛好返回一行一列(關於書寫查詢可見Chapter 7)。SELECT查詢被執行並且該單一返回值被使用在周圍的值表達式中。將一個返回超過一行或一列的查詢作為一個標量子查詢使用是一種錯誤(但是如果在一次特定執行期間該子查詢沒有返回行則不是錯誤,該標量結果被當做為空)。該子查詢可以從周圍的查詢中引用變量,這些變量在該子查詢的任何一次計算中都將作為常量。對於其他涉及子查詢的表達式還可見Section 9.22。

例如,下列語句會尋找每個州中最大的城市人口:

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

一個數組構造器是一個能構建一個數組值並且將值用於它的成員元素的表達式。一個簡單的數組構造器由關鍵詞ARRAY、一個左方括號[、一個用於數組元素值的表達式列表(用逗號分隔)以及最後的一個右方括號]組成。例如:


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

默認情況下,數組元素類型是成員表達式的公共類型,使用和UNION或CASE結構(見Section 10.5)相同的規則決定。你可以通過顯式將數組構造器造型為想要的類型來重載,例如:

SELECT ARRAY[1,2,22.7]::integer[];
  array
----------
 {1,2,23}
(1 row)

這和把每一個表達式單獨地造型為數組元素類型的效果相同。

多維數組值可以通過嵌套數組構造器來構建。在內層的構造器中,關鍵詞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構造器的造型會自動傳播到所有的內層構造器。

多維數組構造器元素可以是任何得到一個正確種類數組的任何東西,而不僅僅是一個子-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)

你可以構造一個空數組,但是因為無法得到一個無類型的數組,你必須顯式地把你的空數組造型成想要的類型。例如:


SELECT ARRAY[]::integer[];
 array
-------
 {}
(1 row)

也可以從一個子查詢的結果構建一個數組。在這種形式中,數組構造器被寫為關鍵詞ARRAY後跟著一個加了圓括號(不是方括號)的子查詢。例如:

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

SELECT ARRAY(SELECT ARRAY[i, i*2] FROM generate_series(1,5) AS a(i));
              array
----------------------------------
 {{1,2},{2,4},{3,6},{4,8},{5,10}}
(1 row)

子查詢必須返回一個單一列。如果子查詢的輸出列是非數組類型, 結果的一維數組將為該子查詢結果中的每一行有一個元素, 並且有一個與子查詢的輸出列匹配的元素類型。如果子查詢的輸出列 是一種數組類型,結果將是同類型的一個數組,但是要高一個維度。 在這種情況下,該子查詢的所有行必須產生同樣維度的數組,否則結果 就不會是矩形形式。

用ARRAY構建的一個數組值的下標總是從一開始。更多關於數組的信息,請見Section 8.15。

2.13. 行構造器

一個行構造器是能夠構建一個行值(也稱作一個組合類型)並用值作為其成員域的表達式。一個行構造器由關鍵詞ROW、一個左圓括號、用於行的域值的零個或多個表達式(用逗號分隔)以及最後的一個右圓括號組成。例如:

SELECT ROW(1,2.5,'this is a test');
當在列表中有超過一個表達式時,關鍵詞ROW是可選的。

一個行構造器可以包括語法rowvalue.*,它將被擴展為該行值的元素的一個列表,就像在一個頂層SELECT列表中使用.*時發生的事情一樣。例如,如果表t有列f1和f2,那麼這些是相同的:

SELECT ROW(t.*, 42) FROM t;
SELECT ROW(t.f1, t.f2, 42) FROM t;
Note: 在PostgreSQL 8.2 以前,.*語法不會被擴展,這樣寫ROW(t.*, 42)會創建一個有兩個域的行,其第一個域是另一個行值。新的行為通常更有用。如果你需要嵌套行值的舊行為,寫內層行值時不要用.*,例如ROW(t, 42)。

默認情況下,由一個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 NULL或IS NOT NULL測試一個行,例如:

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

SELECT ROW(table.*) IS NULL FROM table;  -- detect all-null rows

詳見Section 9.23。如Section 9.22中所討論的,行構造器也可以被用來與子查詢相連接。

子表達式的計算順序沒有被定義。特別地,一個操作符或函數的輸入不必按照從左至右或其他任何固定順序進行計算。

此外,如果一個表達式的結果可以通過隻計算其一部分來決定,那麼其他子表達式可能完全不需要被計算。例如,如果我們寫:

SELECT true OR somefunc();

那麼somefunc()將(可能)完全不被調用。如果我們寫成下麵這樣也是一樣:

SELECT somefunc() OR true;

注意這和一些編程語言中布爾操作符從左至右的"短路"不同。

因此,在複雜表達式中使用帶有副作用的函數是不明智的。在WHERE和HAVING子句中依賴副作用或計算順序尤其危險,因為在建立一個執行計劃時這些子句會被廣泛地重新處理。這些子句中布爾表達式(AND/OR/NOT的組合)可能會以布爾代數定律所允許的任何方式被重組。

當有必要強製計算順序時,可以使用一個CASE結構(見Section 9.17)。例如,在一個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來回避這個問題)。

不過,CASE不是這類問題的萬靈藥。上述技術的一個限製是, 它無法阻止常量子表達式的提早計算。如Section 36.6 中所述,當查詢被規劃而不是被執行時,被標記成 IMMUTABLE的函數和操作符可以被計算。因此

SELECT CASE WHEN x > 0 THEN x ELSE 1/0 END FROM tab;

很可能會導致一次除零失敗,因為規劃器嚐試簡化常量子表達式。即便是 表中的每一行都有x > 0(這樣運行時永遠不會進入到 ELSE分支)也是這樣。

雖然這個特別的例子可能看起來愚蠢,沒有明顯涉及常量的情況可能會發生 在函數內執行的查詢中,因為因為函數參數的值和本地變量可以作為常量 被插入到查詢中用於規劃目的。例如,在PL/pgSQL函數 中,使用一個IF-THEN-ELSE語句來 保護一種有風險的計算比把它嵌在一個CASE表達式中要安全得多。

另一個同類型的限製是,一個CASE無法阻止其所包含的聚集表達式 的計算,因為在考慮SELECT列表或HAVING子句中的 其他表達式之前,會先計算聚集表達式。例如,下麵的查詢會導致一個除零錯誤, 雖然看起來好像已經這種情況加以了保護:


SELECT CASE WHEN min(employees) > 0
            THEN avg(expenses / employees)
       END
    FROM departments;

min()和avg()聚集會在所有輸入行上並行地計算, 因此如果任何行有employees等於零,在有機會測試 min()的結果之前,就會發生除零錯誤。取而代之的是,可以使用 一個WHERE或FILTER子句來首先阻止有問題的輸入行到達 一個聚集函數。

最後更新:2017-08-16 16:02:16

  上一篇:go  RDC實驗室文檔
  下一篇:go  MySQL分布式集群搭建