SELECT
SELECT (SELECT)
테이블이나 뷰에서 행을 가져오는 명령이에요. SELECT는 데이터베이스에서 데이터를 읽는 가장 기본이 되는 명령으로, TABLE과 WITH도 함께 다룹니다. 단순히 행을 조회하는 것부터 집계, 조인, 서브쿼리, 창 함수까지 PostgreSQL이 제공하는 거의 모든 질의 기능이 여기에서 시작돼요.
출처: PostgreSQL 문서
본문
개요 (Synopsis)
[ WITH [ RECURSIVE ] with_query [, ...] ]
SELECT [ ALL | DISTINCT [ ON ( expression [, ...] ) ] ]
[ { * | expression [ [ AS ] output_name ] } [, ...] ]
[ FROM from_item [, ...] ]
[ WHERE condition ]
[ GROUP BY [ ALL | DISTINCT ] grouping_element [, ...] ]
[ HAVING condition ]
[ WINDOW window_name AS ( window_definition ) [, ...] ]
[ { UNION | INTERSECT | EXCEPT } [ ALL | DISTINCT ] select ]
[ ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS { FIRST | LAST } ] [, ...] ]
[ LIMIT { count | ALL } ]
[ OFFSET start [ ROW | ROWS ] ]
[ FETCH { FIRST | NEXT } [ count ] { ROW | ROWS } { ONLY | WITH TIES } ]
[ FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE } [ OF from_reference [, ...] ] [ NOWAIT | SKIP LOCKED ] [...] ]
여기서 from_item은 다음 중 하나예요:
[ ONLY ] table_name [ * ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
[ TABLESAMPLE sampling_method ( argument [, ...] ) [ REPEATABLE ( seed ) ] ]
[ LATERAL ] ( select ) [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
with_query_name [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
[ LATERAL ] function_name ( [ argument [, ...] ] )
[ WITH ORDINALITY ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
[ LATERAL ] function_name ( [ argument [, ...] ] ) [ AS ] alias ( column_definition [, ...] )
[ LATERAL ] function_name ( [ argument [, ...] ] ) AS ( column_definition [, ...] )
[ LATERAL ] ROWS FROM( function_name ( [ argument [, ...] ] ) [ AS ( column_definition [, ...] ) ] [, ...] )
[ WITH ORDINALITY ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
from_item join_type from_item { ON join_condition | USING ( join_column [, ...] ) [ AS join_using_alias ] }
from_item NATURAL join_type from_item
from_item CROSS JOIN from_item
그리고 grouping_element은 다음 중 하나예요:
( )
expression
( expression [, ...] )
ROLLUP ( { expression | ( expression [, ...] ) } [, ...] )
CUBE ( { expression | ( expression [, ...] ) } [, ...] )
GROUPING SETS ( grouping_element [, ...] )
그리고 with_query는:
with_query_name [ ( column_name [, ...] ) ] AS [ [ NOT ] MATERIALIZED ] ( select | values | insert | update | delete | merge )
[ SEARCH { BREADTH | DEPTH } FIRST BY column_name [, ...] SET search_seq_col_name ]
[ CYCLE column_name [, ...] SET cycle_mark_col_name [ TO cycle_mark_value DEFAULT cycle_mark_default ] USING cycle_path_col_name ]
또한 TABLE 명령 형태:
TABLE [ ONLY ] table_name [ * ]
설명 (Description)
SELECT는 0개 이상의 테이블에서 행을 가져와요. SELECT의 일반적인 처리 과정은 다음과 같아요:
WITH목록의 모든 쿼리가 계산돼요. 이들은 사실상FROM목록에서 참조할 수 있는 임시 테이블 역할을 해요.FROM에서 두 번 이상 참조되는WITH쿼리는NOT MATERIALIZED로 달리 지정되지 않는 한 한 번만 계산돼요. (WITH 절 참고)FROM목록의 모든 요소가 계산돼요. (FROM목록의 각 요소는 실제 또는 가상 테이블이에요.)FROM목록에 둘 이상의 요소가 지정되면 이들은 서로 크로스 조인(cross join)돼요. (FROM 절 참고)WHERE절이 지정되면 조건을 만족하지 않는 모든 행이 출력에서 제거돼요. (WHERE 절 참고)GROUP BY절이 지정되거나 집계 함수 호출이 있으면 출력이 하나 이상의 값이 일치하는 행 그룹으로 결합되고 집계 함수의 결과가 계산돼요.HAVING절이 있으면 주어진 조건을 만족하지 않는 그룹을 제거해요. (GROUP BY 절과 HAVING 절 참고) 질의 출력 컬럼은 명목상 다음 단계에서 계산되지만,GROUP BY절에서 (이름이나 서수로) 참조될 수도 있어요.- 실제 출력 행은 각 선택된 행이나 행 그룹에 대해
SELECT출력 표현식을 사용해 계산돼요. (SELECT 리스트 참고) SELECT DISTINCT는 결과에서 중복 행을 제거해요.SELECT DISTINCT ON은 지정된 모든 표현식이 일치하는 행을 제거해요.SELECT ALL(기본값)은 중복을 포함해 모든 후보 행을 반환해요. (DISTINCT 절 참고)UNION,INTERSECT,EXCEPT연산자를 사용해 둘 이상의SELECT문의 출력을 결합해 단일 결과 집합을 만들 수 있어요.UNION연산자는 두 결과 집합 중 하나 또는 둘 다에 있는 모든 행을 반환해요.INTERSECT연산자는 두 결과 집합에 모두 엄격히 있는 모든 행을 반환해요.EXCEPT연산자는 첫 번째 결과 집합에는 있지만 두 번째에는 없는 행을 반환해요. 세 경우 모두ALL이 지정되지 않으면 중복 행이 제거돼요. 잡음 단어DISTINCT를 추가해 중복 행 제거를 명시적으로 지정할 수 있어요. 여기서SELECT자체의 기본값이ALL인데도 중복 제거가 기본 동작이라는 점에 유의하세요. (UNION 절, INTERSECT 절, EXCEPT 절 참고)ORDER BY절이 지정되면 반환된 행이 지정된 순서로 정렬돼요.ORDER BY가 없으면 행은 시스템이 가장 빠르게 만들 수 있는 순서로 반환돼요. (ORDER BY 절 참고)LIMIT(또는FETCH FIRST)나OFFSET절이 지정되면SELECT문은 결과 행의 부분 집합만 반환해요. (LIMIT 절 참고)FOR UPDATE,FOR NO KEY UPDATE,FOR SHARE,FOR KEY SHARE가 지정되면SELECT문은 선택된 행을 동시 업데이트에 대해 잠가요. (잠금 절 참고)
SELECT 명령에서 사용되는 각 컬럼에 대한 SELECT 권한이 있어야 해요. FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE, FOR KEY SHARE를 사용하려면 (선택된 각 테이블의 컬럼 하나 이상에 대해) UPDATE 권한도 필요해요.
파라미터 (Parameters)
WITH 절
WITH 절을 사용하면 기본 쿼리에서 이름으로 참조할 수 있는 하나 이상의 서브쿼리를 지정할 수 있어요. 서브쿼리는 기본 쿼리가 실행되는 동안 사실상 임시 테이블이나 뷰 역할을 해요. 각 서브쿼리는 SELECT, TABLE, VALUES, INSERT, UPDATE, DELETE, MERGE 문이 될 수 있어요. WITH에서 데이터 수정 문(INSERT, UPDATE, DELETE, MERGE)을 작성할 때는 보통 RETURNING 절을 포함하는 게 일반적이에요. 기본 쿼리가 읽는 임시 테이블을 형성하는 것은 문이 수정하는 기본 테이블이 아니라 RETURNING의 출력이에요. RETURNING이 생략되면 문은 여전히 실행되지만 출력을 만들지 않으므로 기본 쿼리가 테이블로 참조할 수 없어요.
각 WITH 쿼리에는 (스키마 한정이 아닌) 이름을 지정해야 해요. 선택적으로 컬럼 이름 목록을 지정할 수 있는데, 생략하면 컬럼 이름이 서브쿼리에서 추론돼요.
RECURSIVE가 지정되면 SELECT 서브쿼리가 이름으로 자신을 참조할 수 있게 돼요. 그런 서브쿼리는 다음과 같은 형태여야 해요:
non_recursive_term UNION [ ALL | DISTINCT ] recursive_term
이때 재귀적 자기 참조는 UNION의 오른쪽에 나타나야 해요. 쿼리당 재귀적 자기 참조는 하나만 허용돼요. 재귀적 데이터 수정 문은 지원되지 않지만, 재귀적 SELECT 쿼리의 결과를 데이터 수정 문에서 사용할 수는 있어요. 예시는 Section 7.8을 참고하세요.
RECURSIVE의 또 다른 효과는 WITH 쿼리가 순서대로 정렬될 필요가 없다는 거예요. 쿼리가 목록에서 더 뒤에 있는 다른 쿼리를 참조할 수 있거든요. (다만 순환 참조나 상호 재귀는 구현되지 않아요.) RECURSIVE가 없으면 WITH 쿼리는 WITH 목록에서 더 앞에 있는 형제 WITH 쿼리만 참조할 수 있어요.
WITH 절에 여러 쿼리가 있을 때 RECURSIVE는 WITH 바로 뒤에 한 번만 써야 해요. 그것은 WITH 절의 모든 쿼리에 적용되지만, 재귀나 전방 참조를 사용하지 않는 쿼리에는 효과가 없어요.
선택적인 SEARCH 절은 재귀 쿼리의 결과를 너비 우선(breadth-first) 또는 깊이 우선(depth-first) 순서로 정렬하는 데 사용할 수 있는 *검색 순서 컬럼(search sequence column)*을 계산해요. 제공된 컬럼 이름 목록은 방문한 행을 추적하는 데 사용할 행 키를 지정해요. *search_seq_col_name*이라는 이름의 컬럼이 WITH 쿼리의 결과 컬럼 목록에 추가될 거예요. 이 컬럼은 외부 쿼리에서 정렬해 해당 순서를 얻을 수 있어요. 예시는 Section 7.8.2.1을 참고하세요.
선택적인 CYCLE 절은 재귀 쿼리에서 순환을 감지하는 데 사용돼요. 제공된 컬럼 이름 목록은 방문한 행을 추적하는 데 사용할 행 키를 지정해요. *cycle_mark_col_name*이라는 이름의 컬럼이 WITH 쿼리의 결과 컬럼 목록에 추가될 거예요. 순환이 감지되면 이 컬럼은 *cycle_mark_value*로, 아니면 *cycle_mark_default*로 설정될 거예요. 또한 순환이 감지되면 재귀적 union의 처리가 중지돼요. *cycle_mark_value*와 *cycle_mark_default*는 상수여야 하고 공통 데이터 타입으로 강제 변환(coercible) 가능해야 하며, 그 데이터 타입에는 부등 연산자가 있어야 해요. (SQL 표준은 이들이 Boolean 상수나 문자 문자열일 것을 요구하지만, PostgreSQL은 그렇게 요구하지 않아요.) 기본적으로 TRUE와 FALSE(boolean 타입)가 사용돼요. 또한 *cycle_path_col_name*이라는 이름의 컬럼이 WITH 쿼리의 결과 컬럼 목록에 추가될 거예요. 이 컬럼은 방문한 행을 추적하기 위해 내부적으로 사용돼요. 예시는 Section 7.8.2.2를 참고하세요.
SEARCH와 CYCLE 절은 모두 재귀 WITH 쿼리에서만 유효해요. *with_query*는 두 SELECT(또는 동등한) 명령의 UNION(또는 UNION ALL)이어야 해요 (중첩 UNION 없이). 두 절을 모두 사용하면 SEARCH 절이 추가한 컬럼이 CYCLE 절이 추가한 컬럼보다 먼저 나타나요.
기본 쿼리와 WITH 쿼리는 모두 (개념적으로) 동시에 실행돼요. 이는 WITH의 데이터 수정 문의 효과가 RETURNING 출력을 읽는 것 외에는 쿼리의 다른 부분에서 보이지 않음을 뜻해요. 두 개의 그런 데이터 수정 문이 같은 행을 수정하려 하면 결과는 지정되지 않아요.
WITH 쿼리의 핵심 속성은 기본 쿼리가 그들을 두 번 이상 참조해도 보통 기본 쿼리 실행당 한 번만 평가된다는 거예요. 특히 데이터 수정 문은 기본 쿼리가 그 출력의 전부 또는 일부를 읽는지와 무관하게 정확히 한 번만 실행되도록 보장돼요.
다만 WITH 쿼리를 NOT MATERIALIZED로 표시하면 이 보장이 제거될 수 있어요. 그 경우 WITH 쿼리는 기본 쿼리의 FROM 절에 있는 단순한 sub-SELECT인 것처럼 기본 쿼리로 접힐 수 있어요. 이는 기본 쿼리가 그 WITH 쿼리를 두 번 이상 참조하면 중복 계산을 초래해요. 하지만 그러한 각 사용이 WITH 쿼리의 전체 출력에서 몇 행만 요구한다면, NOT MATERIALIZED는 쿼리들을 공동으로 최적화할 수 있게 함으로써 순 이득을 줄 수 있어요. NOT MATERIALIZED가 재귀적이거나 부수 효과가 없는(즉 volatile 함수를 포함하지 않는 순수 SELECT가 아닌) WITH 쿼리에 붙으면 무시돼요.
기본적으로 부수 효과가 없는 WITH 쿼리가 기본 쿼리의 FROM 절에서 정확히 한 번 사용되면 기본 쿼리로 접힙니다. 이는 의미론적으로 보이지 않는 상황에서 두 쿼리 레벨의 공동 최적화를 허용해요. 다만 WITH 쿼리를 MATERIALIZED로 표시하면 그러한 접기를 방지할 수 있어요. 예를 들어 플래너가 나쁜 계획을 선택하지 못하게 하는 최적화 펜스(optimization fence)로 WITH 쿼리를 사용하는 경우에 유용할 수 있어요. v12 이전 PostgreSQL 버전은 그런 접기를 절대 하지 않았으므로, 이전 버전을 위해 작성된 쿼리는 WITH가 최적화 펜스 역할을 하는 데 의존할 수 있어요.
추가 정보는 Section 7.8을 참고하세요.
FROM 절
FROM 절은 SELECT의 하나 이상의 소스 테이블을 지정해요. 여러 소스가 지정되면 결과는 모든 소스의 데카르트 곱(크로스 조인)이에요. 하지만 보통 데카르트 곱의 작은 부분 집합으로 반환 행을 제한하기 위해 (WHERE로) 조건이 추가돼요.
FROM 절은 다음 요소를 포함할 수 있어요:
-
table_name— 기존 테이블이나 뷰의 이름으로, (스키마 한정으로 쓸 수도 있어요.) 테이블 이름 앞에ONLY가 지정되면 그 테이블만 스캔돼요.ONLY가 지정되지 않으면 테이블과 모든 하위 테이블(있으면)이 스캔돼요. 선택적으로 테이블 이름 뒤에*를 지정해 하위 테이블이 포함됨을 명시적으로 나타낼 수 있어요. -
alias— alias를 포함하는FROM항목의 대체 이름이에요. alias는 간결함을 위해 또는 자기 조인(self-join, 같은 테이블이 여러 번 스캔되는 경우)에서 모호성을 없애기 위해 사용돼요. alias가 제공되면 테이블이나 함수의 실제 이름을 완전히 숨겨요. 예를 들어FROM foo AS f가 주어지면SELECT의 나머지 부분은 이FROM항목을foo가 아니라f로 참조해야 해요. alias가 쓰이면 테이블의 하나 이상의 컬럼에 대한 대체 이름을 제공하기 위해 컬럼 alias 목록도 쓸 수 있어요. -
TABLESAMPLE sampling_method(argument[, ...] ) [ REPEATABLE (seed) ] —table_name뒤의TABLESAMPLE절은 지정된 *sampling_method*를 사용해 그 테이블의 행 부분 집합을 검색하라는 뜻이에요. 이 샘플링은WHERE절 같은 다른 필터의 적용보다 먼저 일어나요. 표준 PostgreSQL 배포판에는BERNOULLI와SYSTEM두 개의 샘플링 방법이 포함되며, 다른 샘플링 방법은 확장을 통해 데이터베이스에 설치할 수 있어요.BERNOULLI와SYSTEM샘플링 방법은 각각 단일 *argument*를 받는데, 그것은 0과 100 사이의 백분율로 표현된 표본화할 테이블의 비율이에요. 이 인자는real값 표현식이 될 수 있어요. (다른 샘플링 방법은 더 많거나 다른 인자를 받을 수 있어요.) 이 두 방법은 각각 테이블의 행 중 대략 지정된 백분율을 포함할 무작위로 선택된 표본을 반환해요.BERNOULLI방법은 전체 테이블을 스캔하고 지정된 확률로 개별 행을 독립적으로 선택하거나 무시해요.SYSTEM방법은 블록 레벨 샘플링을 수행하며 각 블록이 선택될 지정된 확률을 가져요. 선택된 각 블록의 모든 행이 반환돼요.SYSTEM방법은 작은 샘플링 백분율이 지정되면BERNOULLI보다 훨씬 빠르지만, 클러스터링 효과 때문에 덜 무작위적인 표본을 반환할 수 있어요.- 선택적인
REPEATABLE절은 샘플링 방법 내에서 난수를 생성하는 데 사용할seed숫자나 표현식을 지정해요. seed 값은 null이 아닌 어떤 부동소수점 값도 될 수 있어요. 같은 seed와argument값을 지정하는 두 쿼리는, 그동안 테이블이 바뀌지 않았다면 같은 표본을 선택할 거예요. 하지만 다른 seed 값은 보통 다른 표본을 만들어요.REPEATABLE이 주어지지 않으면 시스템 생성 seed를 기반으로 각 쿼리에 대해 새 무작위 표본이 선택돼요. 일부 부가 샘플링 방법은REPEATABLE을 받아들이지 않고, 사용할 때마다 항상 새 표본을 만들 거란 점에 유의하세요.
-
select— sub-SELECT가FROM절에 나타날 수 있어요. 이는 그 출력이 이 단일SELECT명령이 실행되는 동안 임시 테이블로 생성된 것처럼 동작해요. sub-SELECT는 괄호로 둘러싸여야 하고, 테이블과 같은 방식으로 alias를 제공할 수 있음을 유의하세요. 여기서VALUES명령도 사용할 수 있어요. -
with_query_name—WITH쿼리는 그 이름을 마치 테이블 이름인 것처럼 써서 참조돼요. (사실WITH쿼리는 기본 쿼리 목적상 같은 이름의 실제 테이블을 숨겨요. 필요하다면 테이블 이름을 스키마 한정해 같은 이름의 실제 테이블을 참조할 수 있어요.) 테이블과 같은 방식으로 alias를 제공할 수 있어요. -
function_name— 함수 호출이FROM절에 나타날 수 있어요. (결과 집합을 반환하는 함수에 특히 유용하지만 어떤 함수든 사용할 수 있어요.) 이는 함수의 출력이 이 단일SELECT명령이 실행되는 동안 임시 테이블로 생성된 것처럼 동작해요. 함수의 결과 타입이 복합(composite, 여러OUT파라미터를 가진 함수의 경우 포함)이면 각 속성이 암시적 테이블의 별도 컬럼이 돼요. -
WITH ORDINALITY— 함수 호출에 선택적WITH ORDINALITY절이 추가되면bigint유형의 추가 컬럼이 함수의 결과 컬럼에 덧붙여져요. 이 컬럼은 함수의 결과 집합 행에 1부터 번호를 매겨요. 기본적으로 이 컬럼 이름은ordinality예요.- 테이블과 같은 방식으로 alias를 제공할 수 있어요. alias를 쓰면 함수의 복합 반환 타입의 하나 이상의 속성(있으면 ordinality 컬럼 포함)에 대한 대체 이름을 제공하기 위해 컬럼 alias 목록도 쓸 수 있어요.
-
ROWS FROM( ... )— 여러 함수 호출을ROWS FROM( ... )로 둘러싸 단일FROM-절 항목으로 결합할 수 있어요. 그러한 항목의 출력은 각 함수의 첫 번째 행, 그다음 각 함수의 두 번째 행 등의 연쇄예요. 일부 함수가 다른 것보다 적은 행을 만들면 누락된 데이터에 null 값이 대체되어, 반환되는 총 행 수는 항상 가장 많은 행을 만든 함수의 행 수와 같아져요. -
함수가
record데이터 타입을 반환하도록 정의됐다면, alias 또는 키워드AS가 있어야 하고 그 뒤에( column_namedata_type[, ... ]) 형태의 컬럼 정의 목록이 와야 해요. 컬럼 정의 목록은 함수가 반환하는 실제 컬럼 수와 타입과 일치해야 해요. -
ROWS FROM( ... )구문을 사용할 때 함수 중 하나가 컬럼 정의 목록을 요구하면, 컬럼 정의 목록을ROWS FROM( ... )안의 함수 호출 뒤에 두는 것이 선호돼요.ROWS FROM( ... )구조 뒤에는 함수가 단 하나이고WITH ORDINALITY절이 없을 때만 컬럼 정의 목록을 둘 수 있어요. -
ORDINALITY를 컬럼 정의 목록과 함께 사용하려면ROWS FROM( ... )구문을 써서 컬럼 정의 목록을ROWS FROM( ... )안에 두어야 해요. -
join_type— 다음 중 하나예요:[ INNER ] JOINLEFT [ OUTER ] JOINRIGHT [ OUTER ] JOINFULL [ OUTER ] JOIN
INNER와OUTER조인 유형에는 조인 조건이 지정돼야 해요. 즉ON join_condition,USING (join_column [, ...]),NATURAL중 정확히 하나예요. 의미는 아래를 참고하세요.JOIN절은 두FROM항목을 결합하는데, 편의상 "테이블"이라고 부르지만 실제로는 어떤 유형의FROM항목이든 될 수 있어요. 필요하면 괄호를 사용해 중첩 순서를 결정해요. 괄호가 없으면JOIN은 왼쪽에서 오른쪽으로 중첩돼요. 어쨌든JOIN은FROM-목록 항목을 구분하는 쉼표보다 더 강하게 결합해요. 모든JOIN옵션은 표기상의 편의일 뿐이에요. 평범한FROM과WHERE로 할 수 있는 것 이상을 하는 게 아니기 때문이에요.LEFT OUTER JOIN은 조건이 적용된 데카르트 곱의 모든 행(즉 조인 조건을 통과하는 모든 결합 행)과, 오른쪽에 조인 조건을 통과한 행이 없었던 왼쪽 테이블의 각 행의 사본 하나를 반환해요. 이 왼쪽 행은 오른쪽 컬럼에 null 값을 삽입해 조인된 테이블의 전체 너비로 확장돼요. 어떤 행이 일치하는지 결정할 때JOIN절 자신의 조건만 고려된다는 점에 유의하세요. 외부 조건은 그 후에 적용돼요.반대로
RIGHT OUTER JOIN은 모든 조인된 행과, 일치하지 않는 각 오른쪽 행에 대한 행 하나(왼쪽에 null로 확장)를 반환해요. 왼쪽과 오른쪽 테이블을 바꾸면LEFT OUTER JOIN으로 바꿀 수 있으므로 이는 표기상의 편의일 뿐이에요.FULL OUTER JOIN은 모든 조인된 행과, 일치하지 않는 각 왼쪽 행에 대한 행 하나(오른쪽에 null로 확장), 일치하지 않는 각 오른쪽 행에 대한 행 하나(왼쪽에 null로 확장)를 반환해요. -
ON join_condition— *join_condition*은 (WHERE절과 비슷하게)boolean타입의 값을 결과로 하는 표현식으로, 조인에서 어떤 행이 일치하는 것으로 간주되는지 지정해요. -
USING ( join_column [, ...] ) [ AS *join_using_alias* ]—USING ( a, b, ... )형태의 절은ON left_table.a = right_table.a AND left_table.b = right_table.b ...의 줄임 표현이에요. 또한USING은 동등한 컬럼 쌍 각각의 하나만 조인 출력에 포함된다는 뜻이에요. 둘 다가 아니라요.join_using_alias이름이 지정되면 조인 컬럼에 대한 테이블 alias를 제공해요.USING절에 나열된 조인 컬럼만 이 이름으로 주소 지정할 수 있어요. 일반 *alias*와 달리 이는 조인된 테이블의 이름을 쿼리의 나머지 부분에서 숨기지 않아요. 또한 일반 *alias*와 달리 컬럼 alias 목록을 쓸 수 없어요 — 조인 컬럼의 출력 이름은USING목록에 나타나는 것과 같아요.
-
NATURAL—NATURAL은 일치하는 이름을 가진 두 테이블의 모든 컬럼을 언급하는USING목록의 줄임 표현이에요. 공통 컬럼 이름이 없으면NATURAL은ON TRUE와 동등해요. -
CROSS JOIN—CROSS JOIN은INNER JOIN ON (TRUE)와 동등해요. 즉 조건에 의해 제거되는 행이 없어요. 두 테이블을FROM의 최상위에 나열해서 얻는 결과와 같은 단순 데카르트 곱을 만들지만, (있으면) 조인 조건에 의해 제한돼요. -
LATERAL—LATERAL키워드는 sub-SELECTFROM항목 앞에 올 수 있어요. 이는FROM목록에서 그 앞에 나타나는FROM항목의 컬럼을 sub-SELECT가 참조할 수 있게 해 줍니다. (LATERAL이 없으면 각 sub-SELECT는 독립적으로 평가되므로 다른FROM항목을 교차 참조할 수 없어요.)LATERAL은 함수 호출FROM항목 앞에도 올 수 있지만, 이 경우에는 잡음 단어예요. 함수 표현식은 어차피 앞선FROM항목을 참조할 수 있기 때문이에요.LATERAL항목은FROM목록의 최상위 또는JOIN트리 안에 나타날 수 있어요. 후자의 경우 자신이 오른쪽에 있는JOIN의 왼쪽에 있는 항목들도 참조할 수 있어요.FROM항목이LATERAL교차 참조를 포함하면 평가는 다음과 같이 진행돼요: 교차 참조된 컬럼을 제공하는FROM항목의 각 행(또는 컬럼을 제공하는 여러FROM항목의 행 집합)에 대해,LATERAL항목이 그 행 또는 행 집합의 컬럼 값을 사용해 평가돼요. 결과 행은 평소처럼 계산된 원래 행과 조인돼요. 이는 컬럼 소스 테이블의 각 행 또는 행 집합에 대해 반복돼요.- 컬럼 소스 테이블은
LATERAL항목에INNER또는LEFT조인되어야 해요. 그렇지 않으면LATERAL항목의 각 행 집합을 계산할 잘 정의된 행 집합이 없을 거예요. 따라서XRIGHT JOIN LATERALY같은 구조는 구문상 유효하지만, *Y*가 *X*를 참조하는 것은 실제로 허용되지 않아요.
WHERE 절
선택적 WHERE 절은 일반적으로 다음과 같은 형태예요:
WHERE condition
여기서 *condition*은 boolean 타입의 결과로 평가되는 어떤 표현식이든 될 수 있어요. 이 조건을 만족하지 않는 행은 출력에서 제거돼요. 행은 실제 행 값이 어떤 변수 참조에 대입될 때 true를 반환하면 조건을 만족해요.
GROUP BY 절
선택적 GROUP BY 절은 일반적으로 다음과 같은 형태예요:
GROUP BY [ ALL | DISTINCT ] grouping_element [, ...]
GROUP BY는 그룹화된 표현식에 대해 같은 값을 공유하는 모든 선택된 행을 단일 행으로 응축해요. grouping_element 안에서 사용되는 *expression*은 입력 컬럼 이름, 출력 컬럼(SELECT 목록 항목)의 이름이나 서수, 또는 입력 컬럼 값으로 형성된 임의의 표현식일 수 있어요. 모호한 경우 GROUP BY 이름은 출력 컬럼 이름이 아니라 입력 컬럼 이름으로 해석돼요.
GROUPING SETS, ROLLUP, CUBE 중 하나라도 그룹화 요소로 있으면 GROUP BY 절 전체가 어떤 수의 독립 *grouping sets*를 정의해요. 이 효과는 개별 그룹화 집합을 GROUP BY 절로 하는 서브쿼리들 사이에 UNION ALL을 구성하는 것과 동등해요. 선택적 DISTINCT 절은 처리 전에 중복 집합을 제거해요. UNION ALL을 UNION DISTINCT로 변환하지는 않아요. 그룹화 집합 처리에 대한 자세한 내용은 Section 7.2.4를 참고하세요.
집계 함수가 (있다면) 각 그룹을 구성하는 모든 행에 걸쳐 계산되어, 각 그룹에 대해 별도의 값을 만들어요. (GROUP BY 절은 없지만 집계 함수가 있으면 쿼리는 선택된 모든 행으로 구성된 단일 그룹을 가진 것으로 취급돼요.) 각 집계 함수에 공급되는 행 집합은 집계 함수 호출에 FILTER 절을 붙여 추가로 필터링할 수 있어요. 자세한 내용은 Section 4.2.7을 참고하세요. FILTER 절이 있으면 그에 일치하는 행만 그 집계 함수의 입력에 포함돼요.
GROUP BY가 있거나 어떤 집계 함수가 있으면, SELECT 목록 표현식이 그룹화되지 않은 컬럼을 참조하는 것은 — 집계 함수 안에서이거나, 그룹화되지 않은 컬럼이 그룹화된 컬럼에 기능적으로 종속될 때를 제외하면 — 유효하지 않아요. 그룹화되지 않은 컬럼에 대해 반환할 값이 하나 이상 있을 수 있기 때문이에요. 기능적 종속은 그룹화된 컬럼(또는 그 부분 집합)이 그룹화되지 않은 컬럼을 포함하는 테이블의 기본 키일 때 존재해요.
모든 집계 함수는 HAVING 절이나 SELECT 목록의 "스칼라" 표현식보다 먼저 평가된다는 점을 명심하세요. 이는 예를 들어 CASE 표현식으로 집계 함수의 평가를 건너뛸 수 없음을 뜻해요. Section 4.2.14을 참고하세요.
현재 FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE, FOR KEY SHARE는 GROUP BY와 함께 지정할 수 없어요.
HAVING 절
선택적 HAVING 절은 일반적으로 다음과 같은 형태예요:
HAVING condition
여기서 *condition*은 WHERE 절에 지정된 것과 같아요.
HAVING은 조건을 만족하지 않는 그룹 행을 제거해요. HAVING은 WHERE와 다릅니다: WHERE는 GROUP BY 적용 전에 개별 행을 필터링하는 반면, HAVING은 GROUP BY가 만든 그룹 행을 필터링해요. *condition*에서 참조되는 각 컬럼은 — 집계 함수 안의 참조이거나 그룹화되지 않은 컬럼이 그룹화 컬럼에 기능적으로 종속되는 경우를 제외하면 — 그룹화 컬럼을 명확하게 참조해야 해요.
HAVING의 존재는 GROUP BY 절이 없어도 쿼리를 그룹화된 쿼리로 바꿔요. 이는 쿼리에 GROUP BY 절은 없지만 집계 함수가 있을 때 일어나는 것과 같아요. 모든 선택된 행이 단일 그룹을 형성한다고 간주되고, SELECT 목록과 HAVING 절은 집계 함수 안에서만 테이블 컬럼을 참조할 수 있어요. 그런 쿼리는 HAVING 조건이 true면 단일 행을, true가 아니면 0개의 행을 출력해요.
현재 FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE, FOR KEY SHARE는 HAVING과 함께 지정할 수 없어요.
WINDOW 절
선택적 WINDOW 절은 일반적으로 다음과 같은 형태예요:
WINDOW window_name AS ( window_definition ) [, ...]
여기서 *window_name*은 OVER 절이나 이후의 윈도우 정의에서 참조할 수 있는 이름이고, *window_definition*은:
[ existing_window_name ]
[ PARTITION BY expression [, ...] ]
[ ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS { FIRST | LAST } ] [, ...] ]
[ frame_clause ]
*existing_window_name*이 지정되면 WINDOW 목록의 앞선 항목을 참조해야 해요. 새 윈도우는 그 항목에서 분할 절(partitioning clause)과 (있으면) 정렬 절을 복사해요. 이 경우 새 윈도우는 자신의 PARTITION BY 절을 지정할 수 없고, 복사된 윈도우에 ORDER BY가 없을 때만 ORDER BY를 지정할 수 있어요. 새 윈도우는 항상 자신의 프레임 절을 사용해요. 복사된 윈도우는 프레임 절을 지정해서는 안 돼요.
PARTITION BY 목록의 요소는 GROUP BY 절의 요소와 상당히 같은 방식으로 해석되지만, 항상 단순 표현식이고 출력 컬럼의 이름이나 번호는 절대 아니라는 점이 달라요. 또 다른 차이는 이러한 표현식이 집계 함수 호출을 포함할 수 있다는 것인데, 이는 일반 GROUP BY 절에서는 허용되지 않아요. 윈도우화가 그룹화와 집계 이후에 발생하므로 여기서는 허용돼요.
마찬가지로 ORDER BY 목록의 요소는 명령문 레벨 ORDER BY 절의 요소와 상당히 같은 방식으로 해석되지만, 표현식이 항상 단순 표현식으로 취급되고 출력 컬럼의 이름이나 번호는 절대 아니라는 점이 달라요.
선택적 *frame_clause*는 프레임에 의존하는 윈도우 함수(전부는 아님)에 대한 윈도우 프레임을 정의해요. 윈도우 프레임은 쿼리의 각 행(이를 현재 행이라 함)에 대한 관련 행 집합이에요. *frame_clause*는 다음 중 하나일 수 있어요:
{ RANGE | ROWS | GROUPS } frame_start [ frame_exclusion ]
{ RANGE | ROWS | GROUPS } BETWEEN frame_start AND frame_end [ frame_exclusion ]
여기서 *frame_start*와 *frame_end*는 다음 중 하나이고:
UNBOUNDED PRECEDING
offset PRECEDING
CURRENT ROW
offset FOLLOWING
UNBOUNDED FOLLOWING
*frame_exclusion*은 다음 중 하나예요:
EXCLUDE CURRENT ROW
EXCLUDE GROUP
EXCLUDE TIES
EXCLUDE NO OTHERS
*frame_end*가 생략되면 기본은 CURRENT ROW예요. 제한 사항은 *frame_start*가 UNBOUNDED FOLLOWING일 수 없고, frame_end가 UNBOUNDED PRECEDING일 수 없으며, frame_end 선택이 위의 frame_start·frame_end 옵션 목록에서 frame_start 선택보다 앞에 나타날 수 없다는 거예요 — 예를 들어 RANGE BETWEEN CURRENT ROW AND offset PRECEDING은 허용되지 않아요.
기본 프레임 옵션은 RANGE UNBOUNDED PRECEDING으로, 이는 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW와 같아요. 이는 프레임을 파티션 시작부터 현재 행의 마지막 peer(윈도우의 ORDER BY 절이 현재 행과 동등하다고 보는 행; ORDER BY가 없으면 모든 행이 peer)까지의 모든 행으로 설정해요. 일반적으로 UNBOUNDED PRECEDING은 프레임이 파티션의 첫 행에서 시작함을 뜻하고, 마찬가지로 UNBOUNDED FOLLOWING은 RANGE, ROWS, GROUPS 모드와 무관하게 프레임이 파티션의 마지막 행에서 끝남을 뜻해요. ROWS 모드에서 CURRENT ROW는 프레임이 현재 행에서 시작하거나 끝남을 뜻해요. 하지만 RANGE나 GROUPS 모드에서는 프레임이 ORDER BY 순서에서 현재 행의 첫 번째 또는 마지막 peer에서 시작하거나 끝남을 뜻해요. offset PRECEDING과 offset FOLLOWING 옵션은 프레임 모드에 따라 의미가 달라져요. ROWS 모드에서 *offset*은 프레임이 현재 행보다 그만큼 많은 행 앞이나 뒤에서 시작하거나 끝남을 나타내는 정수예요. GROUPS 모드에서 *offset*은 프레임이 현재 행의 peer 그룹보다 그만큼 많은 peer 그룹 앞이나 뒤에서 시작하거나 끝남을 나타내는 정수예요. 여기서 peer group은 윈도우의 ORDER BY 절에 따라 동등한 행들의 그룹이에요. RANGE 모드에서 offset 옵션을 사용하려면 윈도우 정의에 ORDER BY 컬럼이 정확히 하나 있어야 해요. 그러면 프레임은 정렬 컬럼 값이 현재 행의 정렬 컬럼 값보다 (PRECEDING의 경우) offset 이하로 작거나, (FOLLOWING의 경우) offset 이하로 큰 행들을 포함해요. 이 경우 offset 표현식의 데이터 타입은 정렬 컬럼의 데이터 타입에 따라 달라져요. 숫자 정렬 컬럼의 경우 보통 정렬 컬럼과 같은 타입이지만, 날짜·시간 정렬 컬럼의 경우 interval이에요. 이 모든 경우 offset 값은 null이 아니고 음수가 아니어야 해요.
frame_exclusion 옵션은 현재 행 주변의 행이 프레임 시작·끝 옵션에 따라 포함될 수 있더라도 프레임에서 제외되게 할 수 있어요. EXCLUDE CURRENT ROW는 현재 행을 프레임에서 제외해요. EXCLUDE GROUP은 현재 행과 그 정렬 peers를 프레임에서 제외해요. EXCLUDE TIES는 현재 행의 peers를 프레임에서 제외하지만 현재 행 자체는 제외하지 않아요. EXCLUDE NO OTHERS는 현재 행이나 그 peers를 제외하지 않는 기본 동작을 명시적으로 지정할 뿐이에요.
ROWS 모드는 ORDER BY 정렬이 행을 고유하게 정렬하지 않으면 예측할 수 없는 결과를 만들 수 있다는 점에 주의하세요. RANGE와 GROUPS 모드는 ORDER BY 정렬에서 peers인 행들이 동일하게 취급되도록 설계됐어요: 주어진 peer 그룹의 모든 행이 프레임에 있거나 제외되거나 합니다.
WINDOW 절의 목적은 쿼리의 SELECT 목록이나 ORDER BY 절에 나타나는 윈도우 함수의 동작을 지정하는 거예요. 이 함수들은 OVER 절에서 이름으로 WINDOW 절 항목을 참조할 수 있어요. WINDOW 절 항목이 어디에도 참조될 필요는 없어요. 쿼리에서 사용되지 않으면 그냥 무시되요. 윈도우 함수 호출이 자신의 OVER 절에서 윈도우 정의를 직접 지정할 수 있으므로 WINDOW 절 없이도 윈도우 함수를 사용할 수 있어요. 다만 같은 윈도우 정의가 둘 이상의 윈도우 함수에 필요할 때 WINDOW 절은 타이핑을 줄여 줍니다.
현재 FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE, FOR KEY SHARE는 WINDOW와 함께 지정할 수 없어요.
윈도우 함수는 Section 3.5, Section 4.2.8, Section 7.2.5에서 자세히 설명돼요.
SELECT 리스트
SELECT 목록(키워드 SELECT와 FROM 사이)은 SELECT 문의 출력 행을 형성하는 표현식을 지정해요. 표현식은 (보통) FROM 절에서 계산된 컬럼을 참조해요.
테이블에서처럼 SELECT의 모든 출력 컬럼에는 이름이 있어요. 단순 SELECT에서 이 이름은 표시용으로 컬럼에 라벨을 붙이는 데만 쓰이지만, SELECT가 더 큰 쿼리의 서브쿼리일 때는 더 큰 쿼리가 그 이름을 서브쿼리가 만든 가상 테이블의 컬럼 이름으로 봐요. 출력 컬럼에 사용할 이름을 지정하려면 컬럼 표현식 뒤에 AS *output_name*을 써요. (AS를 생략할 수 있지만, 원하는 출력 이름이 어떤 PostgreSQL 키워드와도 일치하지 않을 때만 그래요. Appendix C 참고.) 미래 키워드 추가에 대비해 항상 AS를 쓰거나 출력 이름을 큰따옴표로 묶는 것이 권장돼요. 컬럼 이름을 지정하지 않으면 PostgreSQL이 자동으로 이름을 선택해요. 컬럼 표현식이 단순 컬럼 참조면 선택된 이름은 그 컬럼의 이름과 같아요. 더 복잡한 경우 함수나 타입 이름이 사용되거나, 시스템이 ?column? 같은 생성된 이름으로 폴백할 수 있어요.
출력 컬럼의 이름은 ORDER BY와 GROUP BY 절에서 컬럼의 값을 참조하는 데 사용할 수 있지만, WHERE나 HAVING 절에서는 사용할 수 없어요. 거기서는 표현식을 그대로 써야 해요.
표현식 대신 *를 출력 목록에 써서 선택된 행의 모든 컬럼의 줄임 표현으로 쓸 수 있어요. 또한 table_name.*를 써서 해당 테이블에서 오는 컬럼만의 줄임 표현으로 쓸 수 있어요. 이 경우 AS로 새 이름을 지정할 수 없어요. 출력 컬럼 이름은 테이블 컬럼의 이름과 같아질 거예요.
SQL 표준에 따르면 출력 목록의 표현식은 DISTINCT, ORDER BY, LIMIT를 적용하기 전에 계산되어야 해요. 이는 DISTINCT를 사용할 때 분명히 필요해요. 그렇지 않으면 어떤 값들이 distinct 대상인지 분명하지 않기 때문이에요. 다만 많은 경우 출력 표현식이 ORDER BY와 LIMIT 후에 계산되면 편리해요. 특히 출력 목록에 volatile이나 값비싼 함수가 있으면 더 그래요. 그런 동작에서는 함수 평가 순서가 더 직관적이고, 출력에 결코 나타나지 않는 행에 대응하는 평가가 없을 거예요. PostgreSQL은 출력 표현식이 DISTINCT, ORDER BY, GROUP BY에서 참조되지 않는 한, 정렬과 제한 후에 효과적으로 평가해요. (반례: SELECT f(x) FROM tab ORDER BY 1은 분명히 정렬 전에 f(x)를 평가해야 해요.) 집합 반환 함수를 포함하는 출력 표현식은 정렬 후, 제한 전에 효과적으로 평가되어, LIMIT가 집합 반환 함수의 출력을 잘라 내도록 동작해요.
Note: 9.6 이전 PostgreSQL 버전은 출력 표현식의 평가 시기를 정렬·제한과 관련해 어떤 보장도 제공하지 않았어요. 선택된 쿼리 계획의 형태에 달려 있었죠.
DISTINCT 절
SELECT DISTINCT가 지정되면 결과 집합에서 모든 중복 행이 제거돼요 (각 중복 그룹에서 한 행이 유지돼요). SELECT ALL은 반대를 지정해요: 모든 행이 유지되고, 이는 기본값이에요.
SELECT DISTINCT ON ( expression [, ...] )은 주어진 표현식들이 동등하게 평가되는 각 행 집합의 첫 행만 유지해요. DISTINCT ON 표현식은 ORDER BY와 같은 규칙을 사용해 해석돼요 (위 참고). 각 집합의 "첫 행"은 ORDER BY로 원하는 행이 먼저 나오도록 강제하지 않으면 예측할 수 없다는 점에 유의하세요. 예:
SELECT DISTINCT ON (location) location, time, report
FROM weather_reports
ORDER BY location, time DESC;
이것은 각 위치에 대한 가장 최근의 기상 보고를 가져와요. 하지만 ORDER BY로 각 위치에 대해 시간 값의 내림차순을 강제하지 않았다면, 각 위치에 대해 예측할 수 없는 시간의 보고를 얻었을 거예요.
DISTINCT ON 표현식은 가장 왼쪽 ORDER BY 표현식과 일치해야 해요. ORDER BY 절은 보통 각 DISTINCT ON 그룹 내에서 행의 원하는 우선 순위를 결정하는 추가 표현식을 포함해요.
현재 FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE, FOR KEY SHARE는 DISTINCT와 함께 지정할 수 없어요.
UNION 절
UNION 절은 일반적으로 다음과 같은 형태예요:
select_statement UNION [ ALL | DISTINCT ] select_statement
*select_statement*는 ORDER BY, LIMIT, FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE, FOR KEY SHARE 절이 없는 어떤 SELECT 문이든 돼요. (ORDER BY와 LIMIT는 괄호로 둘러싸인 하위 표현식에는 붙일 수 있어요. 괄호가 없으면 이 절들은 UNION의 오른쪽 입력 표현식이 아니라 UNION의 결과에 적용되는 것으로 간주돼요.)
UNION 연산자는 관련된 SELECT 문이 반환한 행의 집합 합집합을 계산해요. 행은 두 결과 집합 중 적어도 하나에 나타나면 두 결과 집합의 집합 합집합에 있어요. UNION의 직접 피연산자를 나타내는 두 SELECT 문은 같은 수의 컬럼을 만들어야 하고, 대응하는 컬럼은 호환 가능한 데이터 타입이어야 해요.
UNION의 결과는 ALL 옵션이 지정되지 않으면 중복 행을 포함하지 않아요. ALL은 중복 제거를 방지해요. (따라서 UNION ALL이 보통 UNION보다 크게 빠릅니다. 가능하면 ALL을 사용하세요.) DISTINCT를 써서 중복 행 제거라는 기본 동작을 명시적으로 지정할 수 있어요.
같은 SELECT 문의 여러 UNION 연산자는 괄호로 달리 표시되지 않는 한 왼쪽에서 오른쪽으로 평가돼요.
현재 FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE, FOR KEY SHARE는 UNION 결과나 UNION의 어떤 입력에도 지정할 수 없어요.
INTERSECT 절
INTERSECT 절은 일반적으로 다음과 같은 형태예요:
select_statement INTERSECT [ ALL | DISTINCT ] select_statement
*select_statement*는 ORDER BY, LIMIT, FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE, FOR KEY SHARE 절이 없는 어떤 SELECT 문이든 돼요.
INTERSECT 연산자는 관련된 SELECT 문이 반환한 행의 집합 교집합을 계산해요. 행은 두 결과 집합 모두에 나타나면 교집합에 있어요.
INTERSECT의 결과는 ALL 옵션이 지정되지 않으면 중복 행을 포함하지 않아요. ALL을 쓰면 왼쪽 테이블에 m개, 오른쪽 테이블에 n개의 중복이 있는 행이 결과 집합에 min(m,n)번 나타나요. DISTINCT를 써서 중복 행 제거라는 기본 동작을 명시적으로 지정할 수 있어요.
같은 SELECT 문의 여러 INTERSECT 연산자는 괄호가 달리 지시하지 않는 한 왼쪽에서 오른쪽으로 평가돼요. INTERSECT는 UNION보다 더 강하게 결합해요. 즉 A UNION B INTERSECT C는 A UNION (B INTERSECT C)로 읽힐 거예요.
현재 FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE, FOR KEY SHARE는 INTERSECT 결과나 INTERSECT의 어떤 입력에도 지정할 수 없어요.
EXCEPT 절
EXCEPT 절은 일반적으로 다음과 같은 형태예요:
select_statement EXCEPT [ ALL | DISTINCT ] select_statement
*select_statement*는 ORDER BY, LIMIT, FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE, FOR KEY SHARE 절이 없는 어떤 SELECT 문이든 돼요.
EXCEPT 연산자는 왼쪽 SELECT 문의 결과에는 있지만 오른쪽 결과에는 없는 행 집합을 계산해요.
EXCEPT의 결과는 ALL 옵션이 지정되지 않으면 중복 행을 포함하지 않아요. ALL을 쓰면 왼쪽 테이블에 m개, 오른쪽 테이블에 n개의 중복이 있는 행이 결과 집합에 max(m-n,0)번 나타나요. DISTINCT를 써서 중복 행 제거라는 기본 동작을 명시적으로 지정할 수 있어요.
같은 SELECT 문의 여러 EXCEPT 연산자는 괄호가 달리 지시하지 않는 한 왼쪽에서 오른쪽으로 평가돼요. EXCEPT는 UNION과 같은 레벨로 결합해요.
현재 FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE, FOR KEY SHARE는 EXCEPT 결과나 EXCEPT의 어떤 입력에도 지정할 수 없어요.
ORDER BY 절
선택적 ORDER BY 절은 일반적으로 다음과 같은 형태예요:
ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS { FIRST | LAST } ] [, ...]
ORDER BY 절은 지정된 표현식에 따라 결과 행을 정렬해요. 두 행이 가장 왼쪽 표현식에 따라 같으면 다음 표현식에 따라 비교되고, 이런 식으로 계속돼요. 지정된 모든 표현식에 따라 같으면 구현 의존적인 순서로 반환돼요.
각 *expression*은 출력 컬럼(SELECT 목록 항목)의 이름이나 서수, 또는 입력 컬럼 값으로 형성된 임의의 표현식일 수 있어요.
서수는 출력 컬럼의 서수(왼쪽에서 오른쪽) 위치를 가리켜요. 이 기능을 사용하면 고유한 이름이 없는 컬럼에 기반해 정렬을 정의할 수 있어요. AS 절로 출력 컬럼에 이름을 할당하는 것이 항상 가능하므로 이것이 절대적으로 필요하지는 않아요.
ORDER BY 절에서 임의의 표현식을 사용할 수도 있으며, SELECT 출력 목록에 나타나지 않는 컬럼도 포함해요. 따라서 다음 문장은 유효해요:
SELECT name FROM distributors ORDER BY code;
이 기능의 제한은 UNION, INTERSECT, EXCEPT 절의 결과에 적용되는 ORDER BY 절은 출력 컬럼 이름이나 번호만 지정할 수 있고 표현식은 지정할 수 없다는 거예요.
ORDER BY 표현식이 출력 컬럼 이름과 입력 컬럼 이름 둘 다 일치하는 단순 이름이면, ORDER BY는 그것을 출력 컬럼 이름으로 해석해요. 이는 GROUP BY가 같은 상황에서 내리는 선택의 반대예요. 이 불일치는 SQL 표준과 호환되도록 만들어진 거예요.
선택적으로 ORDER BY 절의 어떤 표현식 뒤에라도 키워드 ASC(오름차순)나 DESC(내림차순)를 추가할 수 있어요. 지정하지 않으면 기본적으로 ASC가 가정돼요. 대안으로 USING 절에 특정 정렬 연산자 이름을 지정할 수 있어요. 정렬 연산자는 어떤 B-tree 연산자 패밀리의 less-than 또는 greater-than 멤버여야 해요. ASC는 보통 USING <과, DESC는 보통 USING >과 동등해요. (다만 사용자 정의 데이터 타입의 생성자는 기본 정렬 순서가 정확히 무엇인지 정의할 수 있고, 그것이 다른 이름의 연산자에 대응할 수 있어요.)
NULLS LAST가 지정되면 null 값이 모든 non-null 값 뒤에 정렬되고, NULLS FIRST가 지정되면 null 값이 모든 non-null 값 앞에 정렬돼요. 둘 다 지정되지 않으면 ASC가 지정되거나 암시될 때 기본 동작은 NULLS LAST이고, DESC가 지정되면 NULLS FIRST예요 (따라서 기본은 null이 non-null보다 크다고 행동하는 거예요). USING이 지정되면 기본 null 정렬은 연산자가 less-than인지 greater-than인지에 달려 있어요.
정렬 옵션은 그 뒤에 오는 표현식에만 적용된다는 점에 유의하세요. 예를 들어 ORDER BY x, y DESC는 ORDER BY x DESC, y DESC와 같은 뜻이 아니에요.
문자열 데이터는 정렬되는 컬럼에 적용되는 콜레이션(collation)에 따라 정렬돼요. 필요하면 *expression*에 COLLATE 절을 포함해 덮어쓸 수 있어요, 예: ORDER BY mycolumn COLLATE "en_US". 자세한 내용은 Section 4.2.10과 Section 23.2를 참고하세요.
LIMIT 절
LIMIT 절은 두 개의 독립적인 하위 절로 구성돼요:
LIMIT { count | ALL }
OFFSET start
count 파라미터는 반환할 최대 행 수를 지정하고, *start*는 행 반환을 시작하기 전에 건너뛸 행 수를 지정해요. 둘 다 지정되면 start 행이 건너뛰어진 후에 반환할 count 행을 세기 시작해요.
count 표현식이 NULL로 평가되면 LIMIT ALL, 즉 제한 없음으로 취급돼요. *start*가 NULL로 평가되면 OFFSET 0과 같게 취급돼요.
SQL:2008은 같은 결과를 얻는 다른 구문을 도입했는데, PostgreSQL도 지원해요. 그것은:
OFFSET start { ROW | ROWS }
FETCH { FIRST | NEXT } [ count ] { ROW | ROWS } { ONLY | WITH TIES }
이 구문에서 *start*나 count 값은 표준에 따라 리터럴 상수, 파라미터, 또는 변수 이름이어야 해요. PostgreSQL 확장으로 다른 표현식도 허용되지만, 보통 모호성을 피하려면 괄호로 묶어야 해요. FETCH 절에서 *count*가 생략되면 기본은 1이에요. WITH TIES 옵션은 ORDER BY 절에 따라 결과 집합의 마지막 자리와 동점인 추가 행을 반환하는 데 사용돼요. 이 경우 ORDER BY는 필수이고 SKIP LOCKED는 허용되지 않아요. ROW와 ROWS, FIRST와 NEXT는 이 절들의 효과에 영향을 주지 않는 잡음 단어예요. 표준에 따르면 OFFSET 절은 둘 다 있을 때 FETCH 절보다 먼저 와야 해요. 하지만 PostgreSQL은 더 관대해서 어느 순서든 허용해요.
LIMIT를 사용할 때는 결과 행을 고유한 순서로 제한하는 ORDER BY 절을 사용하는 것이 좋아요. 그렇지 않으면 쿼리 행의 예측할 수 없는 부분 집합을 얻게 될 거예요 — 10번째부터 20번째 행을 요구할 수 있지만, 10번째부터 20번째가 어느 정렬 기준이죠? ORDER BY를 지정하지 않으면 어느 정렬 기준인지 모릅니다.
쿼리 플래너는 쿼리 계획을 생성할 때 LIMIT를 고려하므로, LIMIT와 OFFSET에 무엇을 쓰느냐에 따라 (다른 행 순서를 주는) 다른 계획을 얻을 가능성이 매우 높아요. 따라서 다른 LIMIT/OFFSET 값을 사용해 쿼리 결과의 다른 부분 집합을 선택하면 ORDER BY로 예측 가능한 결과 순서를 강제하지 않는 한 일관되지 않은 결과를 줄 것입니다. 이것은 버그가 아니에요. SQL이 ORDER BY로 순서를 제한하지 않으면 쿼리 결과를 어떤 특정 순서로도 제공하겠다고 약속하지 않는다는 사실의 내재적 결과예요.
ORDER BY가 결정적 부분 집합의 선택을 강제하지 않으면, 같은 LIMIT 쿼리를 반복 실행해도 테이블 행의 다른 부분 집합이 반환될 수 있어요. 이것도 버그가 아니에요. 그런 경우 결과의 결정성이 그냥 보장되지 않는 것뿐이에요.
잠금 절 (The Locking Clause)
FOR UPDATE, FOR NO KEY UPDATE, FOR SHARE, FOR KEY SHARE는 잠금 절이에요. 이들은 SELECT가 테이블에서 행을 얻을 때 어떻게 행을 잠그는지에 영향을 줘요.
잠금 절은 일반적으로 다음과 같은 형태예요:
FOR lock_strength [ OF from_reference [, ...] ] [ NOWAIT | SKIP LOCKED ]
여기서 *lock_strength*는 다음 중 하나예요:
UPDATE
NO KEY UPDATE
SHARE
KEY SHARE
*from_reference*는 FROM 절에서 참조된 테이블 alias 또는 hidden이 아닌 *table_name*이어야 해요. 각 행 레벨 잠금 모드에 대한 자세한 내용은 Section 13.3.2를 참고하세요.
작업이 다른 트랜잭션이 커밋하기를 기다리지 않게 하려면 NOWAIT 또는 SKIP LOCKED 옵션을 사용해요. NOWAIT를 쓰면 선택된 행을 즉시 잠글 수 없을 때 대기하지 않고 문이 오류를 보고해요. SKIP LOCKED를 쓰면 즉시 잠글 수 없는 선택된 행은 건너뛰어져요. 잠긴 행 건너뛰기는 데이터의 일관되지 않은 뷰를 제공하므로 일반적인 목적의 작업에는 적합하지 않지만, 큐 같은 테이블에 접근하는 여러 소비자 사이의 잠금 경합을 피하는 데는 사용할 수 있어요. NOWAIT와 SKIP LOCKED는 행 레벨 잠금에만 적용된다는 점에 유의하세요 — 필요한 ROW SHARE 테이블 레벨 잠금은 여전히 일반적인 방식으로 획득돼요 (see Chapter 13). 테이블 레벨 잠금을 기다리지 않고 획득해야 한다면 먼저 NOWAIT 옵션으로 LOCK을 사용할 수 있어요.
잠금 절에 특정 테이블이 이름으로 지정되면 해당 테이블에서 오는 행만 잠겨요. SELECT에서 사용된 다른 테이블은 평소처럼 그냥 읽힙니다. 테이블 목록이 없는 잠금 절은 문에서 사용된 모든 테이블에 영향을 줘요. 잠금 절이 뷰나 서브쿼리에 적용되면 뷰나 서브쿼리에서 사용된 모든 테이블에 영향을 줘요. 다만 이 절들은 기본 쿼리가 참조하는 WITH 쿼리에는 적용되지 않아요. WITH 쿼리 내에서 행 잠금이 발생하길 원하면 WITH 쿼리 안에 잠금 절을 지정해요.
서로 다른 테이블에 대해 서로 다른 잠금 동작을 지정해야 한다면 여러 잠금 절을 쓸 수 있어요. 같은 테이블이 둘 이상의 잠금 절에 언급(또는 암묵적으로 영향)되면 가장 강한 것만 지정된 것처럼 처리돼요. 마찬가지로 테이블은 그것에 영향을 주는 어떤 절에서 지정됐다면 NOWAIT로 처리돼요. 그렇지 않으면 그것에 영향을 주는 어떤 절에서 지정됐다면 SKIP LOCKED로 처리돼요.
잠금 절은 반환된 행을 개별 테이블 행과 명확히 식별할 수 없는 컨텍스트에서는 사용할 수 없어요. 예를 들어 집계와 함께 사용할 수 없어요.
잠금 절이 SELECT 쿼리의 최상위에 나타나면, 잠기는 행은 정확히 쿼리가 반환하는 행이에요. 조인 쿼리의 경우 잠기는 행은 반환된 조인 행에 기여하는 행입니다. 또한 쿼리 스냅샷 시점에 쿼리 조건을 만족한 행도 잠기지만, 스냅샷 후에 갱신되어 더 이상 쿼리 조건을 만족하지 않으면 반환되지는 않아요. LIMIT가 사용되면 한도를 충족할 만큼 충분한 행이 반환되면 잠금이 멈춰요 (다만 OFFSET이 건너뛴 행은 잠길 거란 점에 유의하세요). 마찬가지로 커서의 쿼리에서 잠금 절이 사용되면 커서가 실제로 가져오거나 지나친 행만 잠겨요.
잠금 절이 sub-SELECT에 나타나면 잠기는 행은 서브쿼리가 외부 쿼리에 반환한 행이에요. 외부 쿼리의 조건이 서브쿼리의 실행 최적화에 사용될 수 있으므로 이는 서브쿼리만 검사해서 암시되는 것보다 적은 행을 포함할 수 있어요. 예를 들어,
SELECT * FROM (SELECT * FROM mytable FOR UPDATE) ss WHERE col1 = 5;
는 그 조건이 서브쿼리 안에 텍스트로 있지 않아도 col1 = 5인 행만 잠가요.
이전 릴리스는 이후의 세이브포인트에 의해 업그레이드된 잠금을 보존하지 못했어요. 예를 들어 이 코드:
BEGIN;
SELECT * FROM mytable WHERE key = 1 FOR UPDATE;
SAVEPOINT s;
UPDATE mytable SET ... WHERE key = 1;
ROLLBACK TO s;
는 ROLLBACK TO 후에 FOR UPDATE 잠금을 보존하지 못했어요. 이는 9.3 릴리스에서 수정됐어요.
주의:
READ COMMITTED트랜잭션 격리 수준에서 실행되고ORDER BY와 잠금 절을 사용하는SELECT명령이 행을 순서 없이 반환할 수 있어요.ORDER BY가 먼저 적용되기 때문이에요. 명령이 결과를 정렬하지만, 그다음 한 행 이상에 잠금을 얻으려고 블록될 수 있어요.SELECT가 블록 해제되면 일부 정렬 컬럼 값이 수정됐을 수 있어, 그 행들이 순서 없이 나타나는 것처럼 보일 수 있어요 (원래 컬럼 값 기준으로는 순서대로지만요). 필요하면FOR UPDATE/SHARE절을 서브쿼리에 넣어 우회할 수 있어요, 예:
SELECT * FROM (SELECT * FROM mytable FOR UPDATE) ss ORDER BY column1;
이러면 mytable의 모든 행을 잠그게 되지만, FOR UPDATE를 최상위에 두면 실제로 반환된 행만 잠가요. 이는 특히 ORDER BY가 LIMIT나 다른 제한과 결합될 때 상당한 성능 차이를 만들 수 있어요. 따라서 이 기법은 정렬 컬럼의 동시 업데이트가 예상되고 엄격하게 정렬된 결과가 요구될 때만 권장돼요.
REPEATABLE READ 또는 SERIALIZABLE 트랜잭션 격리 수준에서는 이로 인해 serialization 실패(SQLSTATE '40001')가 발생하므로, 이 격리 수준 아래에서는 행이 순서 없이 받을 가능성이 없어요.
TABLE 명령
명령
TABLE name
은 다음의 동등합니다:
SELECT * FROM name
그것은 최상위 명령으로 또는 복잡한 쿼리의 일부에서 공간을 아끼는 구문 변형으로 사용될 수 있어요. TABLE에는 WITH, UNION, INTERSECT, EXCEPT, ORDER BY, LIMIT, OFFSET, FETCH, FOR 잠금 절만 사용할 수 있고 WHERE 절과 어떤 형태의 집계도 사용할 수 없어요.
예시 (Examples)
테이블 films를 테이블 distributors와 조인하려면:
SELECT f.title, f.did, d.name, f.date_prod, f.kind
FROM distributors d JOIN films f USING (did);
title | did | name | date_prod | kind
-------------------+-----+--------------+------------+----------
The Third Man | 101 | British Lion | 1949-12-23 | Drama
The African Queen | 101 | British Lion | 1951-08-11 | Romantic
...
모든 필름의 len 컬럼을 합산하고 결과를 kind별로 그룹화하려면:
SELECT kind, sum(len) AS total FROM films GROUP BY kind;
kind | total
----------+-------
Action | 07:34
Comedy | 02:58
Drama | 14:28
Musical | 06:42
Romantic | 04:38
모든 필름의 len 컬럼을 합산하고 kind별로 그룹화하며 5시간 미만인 그룹 합계만 보여주려면:
SELECT kind, sum(len) AS total
FROM films
GROUP BY kind
HAVING sum(len) < interval '5 hours';
kind | total
----------+-------
Comedy | 02:58
Romantic | 04:38
다음 두 예시는 개별 결과를 두 번째 컬럼(name)의 내용에 따라 정렬하는 동일한 방법이에요:
SELECT * FROM distributors ORDER BY name;
SELECT * FROM distributors ORDER BY 2;
did | name
-----+------------------
109 | 20th Century Fox
110 | Bavaria Atelier
101 | British Lion
107 | Columbia
102 | Jean Luc Godard
113 | Luso films
104 | Mosfilm
103 | Paramount
106 | Toho
105 | United Artists
111 | Walt Disney
112 | Warner Bros.
108 | Westward
다음 예시는 distributors와 actors 테이블의 합집합을 얻는 방법을 보여줘요. 각 테이블에서 W로 시작하는 결과만 제한해요. 고유 행만 원하므로 키워드 ALL은 생략돼요.
distributors: actors:
did | name id | name
-----+-------------- ----+----------------
108 | Westward 1 | Woody Allen
111 | Walt Disney 2 | Warren Beatty
112 | Warner Bros. 3 | Walter Matthau
... ...
SELECT distributors.name
FROM distributors
WHERE distributors.name LIKE 'W%'
UNION
SELECT actors.name
FROM actors
WHERE actors.name LIKE 'W%';
name
----------------
Walt Disney
Walter Matthau
Warner Bros.
Warren Beatty
Westward
Woody Allen
이 예시는 컬럼 정의 목록 유무와 관계없이 FROM 절에서 함수를 사용하는 방법을 보여줘요:
CREATE FUNCTION distributors(int) RETURNS SETOF distributors AS $$
SELECT * FROM distributors WHERE did = $1;
$$ LANGUAGE SQL;
SELECT * FROM distributors(111);
did | name
-----+-------------
111 | Walt Disney
CREATE FUNCTION distributors_2(int) RETURNS SETOF record AS $$
SELECT * FROM distributors WHERE did = $1;
$$ LANGUAGE SQL;
SELECT * FROM distributors_2(111) AS (f1 int, f2 text);
f1 | f2
-----+-------------
111 | Walt Disney
ordinality 컬럼이 추가된 함수의 예시는 다음과 같아요:
SELECT * FROM unnest(ARRAY['a','b','c','d','e','f']) WITH ORDINALITY;
unnest | ordinality
--------+----------
a | 1
b | 2
c | 3
d | 4
e | 5
f | 6
(6 rows)
이 예시는 단순 WITH 절 사용법을 보여줘요:
WITH t AS (
SELECT random() as x FROM generate_series(1, 3)
)
SELECT * FROM t
UNION ALL
SELECT * FROM t;
x
--------------------
0.534150459803641
0.520092216785997
0.0735620250925422
0.534150459803641
0.520092216785997
0.0735620250925422
WITH 쿼리는 한 번만 평가됐다는 점에 유의하세요. 그래서 세 개의 같은 임의 값 집합을 두 번 얻었죠.
이 예시는 WITH RECURSIVE를 사용해 직접 부하만 보여주는 테이블에서 직원 Mary의 모든 부하(직접 또는 간접)와 그 간접성 수준을 찾아요:
WITH RECURSIVE employee_recursive(distance, employee_name, manager_name) AS (
SELECT 1, employee_name, manager_name
FROM employee
WHERE manager_name = 'Mary'
UNION ALL
SELECT er.distance + 1, e.employee_name, e.manager_name
FROM employee_recursive er, employee e
WHERE er.employee_name = e.manager_name
)
SELECT distance, employee_name FROM employee_recursive;
재귀 쿼리의 전형적인 형태에 유의하세요: 초기 조건, 그 뒤에 UNION, 그 뒤에 쿼리의 재귀 부분. 재귀 부분이 결국 튜플을 반환하지 않도록 하세요. 그렇지 않으면 쿼리가 무한 루프를 돌 거예요. (더 많은 예시는 Section 7.8 참고.)
이 예시는 LATERAL을 사용해 manufacturers 테이블의 각 행에 대해 집합 반환 함수 get_product_names()를 적용해요:
SELECT m.name AS mname, pname
FROM manufacturers m, LATERAL get_product_names(m.id) pname;
현재 제품이 없는 제조업체는 inner join이므로 결과에 나타나지 않을 거예요. 그런 제조업체의 이름도 결과에 포함하려면 다음을 할 수 있어요:
SELECT m.name AS mname, pname
FROM manufacturers m LEFT JOIN LATERAL get_product_names(m.id) pname ON true;
호환성 (Compatibility)
물론 SELECT 문은 SQL 표준과 호환돼요. 하지만 몇 가지 확장과 누락된 기능이 있어요.
FROM 절 생략
PostgreSQL은 FROM 절을 생략할 수 있게 허용해요. 단순 표현식의 결과를 계산하는 간단한 용도가 있어요:
SELECT 2+2;
?column?
----------
4
일부 다른 SQL 데이터베이스는 더미 한 행 테이블을 도입해 그 테이블에서 SELECT를 해야만 이 작업을 할 수 있어요.
빈 SELECT 목록
SELECT 뒤의 출력 표현식 목록은 비어 있을 수 있어, 0-컬럼 결과 테이블을 만들어요. 이는 SQL 표준에 따르면 유효한 구문이 아니에요. PostgreSQL은 0-컬럼 테이블을 허용하는 것과 일관되도록 이것을 허용해요. 다만 DISTINCT가 사용될 때는 빈 목록이 허용되지 않아요.
AS 키워드 생략
SQL 표준에서 선택적 키워드 AS는 새 컬럼 이름이 유효한 컬럼 이름(즉 어떤 예약 키워드와도 같지 않은)일 때마다 출력 컬럼 이름 앞에서 생략할 수 있어요. PostgreSQL은 약간 더 제한적이에요: 새 컬럼 이름이 예약이든 아니든 어떤 키워드와도 일치하면 AS가 필요해요. 권장 관행은 미래 키워드 추가에 대한 어떤 잠재적 충돌도 막기 위해 AS를 사용하거나 출력 컬럼 이름을 큰따옴표로 묶는 거예요.
FROM 항목에서 표준과 PostgreSQL 모두 예약되지 않은 키워드인 alias 앞에서 AS를 생략할 수 있게 허용해요. 하지만 구문상 모호성 때문에 출력 컬럼 이름에는 이것이 비실용적이에요.
FROM에서 sub-SELECT alias 생략
SQL 표준에 따르면 FROM 목록의 sub-SELECT에는 alias가 있어야 해요. PostgreSQL에서는 이 alias를 생략할 수 있어요.
ONLY와 상속
SQL 표준은 ONLY를 쓸 때 테이블 이름 주위에 괄호를 요구해요, 예: SELECT * FROM ONLY (tab1), ONLY (tab2) WHERE .... PostgreSQL은 이 괄호를 선택 사항으로 간주해요.
PostgreSQL은 자식 테이블을 포함하는 non-ONLY 동작을 명시적으로 지정하기 위해 끝에 *를 쓸 수 있게 허용해요. 표준은 이를 허용하지 않아요.
(이 점들은 ONLY 옵션을 지원하는 모든 SQL 명령에 동일하게 적용돼요.)
TABLESAMPLE 절 제한
TABLESAMPLE 절은 현재 일반 테이블과 구체화된 뷰(materialized view)에서만 받아들여져요. SQL 표준에 따르면 어떤 FROM 항목에도 적용할 수 있어야 해요.
FROM에서의 함수 호출
PostgreSQL은 함수 호출을 FROM 목록의 멤버로 직접 쓸 수 있게 허용해요. SQL 표준에서는 그러한 함수 호출을 sub-SELECT로 감싸야 하는데, 즉 FROM func(...) alias 구문은 대략 FROM LATERAL (SELECT func(...)) *alias*와 동등해요. LATERAL이 암묵적인 것으로 간주된다는 점에 유의하세요. 표준이 FROM의 UNNEST() 항목에 LATERAL 의미론을 요구하기 때문이에요. PostgreSQL은 UNNEST()를 다른 집합 반환 함수와 동일하게 취급해요.
GROUP BY와 ORDER BY에 사용 가능한 네임스페이스
SQL-92 표준에서 ORDER BY 절은 출력 컬럼 이름이나 번호만 사용할 수 있고, GROUP BY 절은 입력 컬럼 이름에 기반한 표현식만 사용할 수 있어요. PostgreSQL은 각 절을 확장해 다른 선택도 허용해요 (하지만 모호함이 있으면 표준의 해석을 사용해요). PostgreSQL은 두 절 모두 임의의 표현식을 지정할 수도 있게 허용해요. 표현식에 나타나는 이름은 항상 출력 컬럼 이름이 아니라 입력 컬럼 이름으로 취급된다는 점에 유의하세요.
SQL:1999 이상은 SQL-92와 완전히 상위 호환되지 않는 약간 다른 정의를 사용해요. 다만 대부분의 경우 PostgreSQL은 ORDER BY나 GROUP BY 표현식을 SQL:1999와 같은 방식으로 해석할 거예요.
기능적 종속 (Functional Dependencies)
PostgreSQL은 테이블의 기본 키가 GROUP BY 목록에 포함될 때만 기능적 종속(GROUP BY에서 컬럼 생략 허용)을 인식해요. SQL 표준은 인식해야 할 추가 조건을 지정해요.
LIMIT와 OFFSET
LIMIT와 OFFSET 절은 PostgreSQL 특정 구문으로, MySQL에서도 사용돼요. SQL:2008 표준은 같은 기능을 위해 OFFSET ... FETCH {FIRST|NEXT} ... 절을 도입했는데, 위 LIMIT 절에서 보여준 것과 같아요. 이 구문은 IBM DB2에서도 사용돼요. (Oracle용으로 작성된 애플리케이션은 PostgreSQL에는 없는 자동 생성 rownum 컬럼을 포함하는 우회 방법을 사용해 이 절들의 효과를 구현하는 경우가 많아요.)
FOR NO KEY UPDATE, FOR UPDATE, FOR SHARE, FOR KEY SHARE
FOR UPDATE는 SQL 표준에 나타나지만, 표준은 이를 DECLARE CURSOR의 옵션으로만 허용해요. PostgreSQL은 어떤 SELECT 쿼리와 sub-SELECT에서도 허용하지만, 이는 확장이에요. FOR NO KEY UPDATE, FOR SHARE, FOR KEY SHARE 변형과 NOWAIT, SKIP LOCKED 옵션은 표준에 나타나지 않아요.
WITH에서의 데이터 수정 문
PostgreSQL은 INSERT, UPDATE, DELETE, MERGE를 WITH 쿼리로 사용할 수 있게 허용해요. 이는 SQL 표준에 없는 기능이에요.
비표준 절
DISTINCT ON ( ... )은 SQL 표준의 확장이에요.ROWS FROM( ... )은 SQL 표준의 확장이에요.WITH의MATERIALIZED와NOT MATERIALIZED옵션은 SQL 표준의 확장이에요.