-
SQL 스타일 가이드DB 2022. 1. 16. 17:27
개요
SQL 스타일을 통일시켜 직관적인 SQL 코드를 짜고 읽을 수 있도록 합니다.
mordern sql style guide를 기반으로 깃랩, 모질라 문서를 참고하여 정리했습니다.
‼️가 붙어 있는 부분은 lint가 적용되지 않아 개개인이 주의해야 되는 부분입니다.
Case
네이밍에는 문자, 숫자, _ 만 사용합니다.
네이밍의 시작은 문자로 시작하고 _로 끝나지 않습니다.
테이블 명의 최대 길이는 20자입니다.
한 줄에 표현할 수 있는 쿼리의 길이는 100자입니다.
탭은 사용하지 않고 공백만 사용합니다.
- jetbrains 제품을 사용한다면 탭을 공백으로 변환하도록 합니다.
쿼리 내 들여쓰기는 4칸 들여쓰기로 합니다.
필드 네이밍
필드 이름은 모두 소문자로 제공하며 스네이크 케이스를 지향합니다. (카멜케이스 X)
-- GOOD SELECT item_no -- BAD SELECT ItemId
‼️ keyword, type, function
키워드, 타입, 함수들은 모두 대문자로 제공합니다.
SELECT item_id, SUBSTR(description, 0, 6) AS item_description, FROM item WHERE margin_rate > 50%
function(count, substr 등)과 같은 함수는 jetbrain에서 linter로 지원을 하지 않음
‼️ 필드의 의미 명시
id, name, type과 같이 SQL에 예약어이거나 애매한 필드일 경우, 테이블 명이나 해당 컬럼의 의미를 접두사로 붙여 명확하게 표현합니다.
-- Good SELECT id AS account_id, name AS account_name, type AS account_type, ... -- Bad SELECT id, name, type, ...
‼️ boolean type
boolean 타입일 경우, (true/false) 접두사는 반드시
has_
,is_
또는does_
와 같이 시작해야 합니다.- 정확하게 is, 조동사(can, does), has 로 시작합니다.
-- Good SELECT deleted AS is_deleted, sla AS has_sla FROM table -- Bad SELECT deleted, sla FROM table
세미콜론은 쿼리의 마지막 줄에 배치
세미콜론을 마지막 줄에 추가하면 쿼리 수정이 수월합니다.
SELECT 1 ;
Queries
공통
절의 첫 단어 정렬
한 줄의 첫 단어는 왼쪽 정렬을 합니다.
-- GOOD SELECT * FROM item -- BAD SELECT * FROM item
절의 요소 배치
키워드 또는 문장은 항상 다음 새로운 줄에서 시작합니다.
-- GOOD SELECT max(q4) FROM my_staff_size; WHERE year BETWEEN 1996 AND 2017 -- BAD SELECT max(q4) FROM my_staff_size; WHERE year BETWEEN 1996 AND 2017
콤마 배치
콤마는 항상 왼쪽에 배치합니다.
-- GOOD SELECT x - y AS z -- the delta between X and Y , p * q AS pq -- the pq , tau * mu AS intermediate_result -- another expression , something_else -- something else again FROM third_table; -- BAD SELECT x - y AS z, p * q AS pq, tau * mu AS intermediate_result, something_else FROM third_table;
짧은 문장은 생략
subquery일 경우에만 짧은 문장이면 위의 규칙을 어기고 1줄로 표현합니다.
-- GOOD SELECT (SELECT 1 FROM dual WHERE 1 IS NOT NULL) x FROM (SELECT 1 FROM dual WHERE 1 IS NOT NULL) y -- BAD SELECT (SELECT 1 FROM dual WHERE 1 IS NOT NULL) x
섹션 요소 정렬
들여쓰기 (4칸)의 규칙을 무시하고 상위 필드와 동일한 라인에 맞도록 정렬합니다.
-- GOOD SELECT id , name , note -- BAD SELECT id , name , note
‼️ <> 보다 !=를 사용
!=가 <>보다 프로그래밍 언어에서 더 일반적이고
같지 않음
이라고 직관적으로 이해할 수 있습니다.-- GOOD item != 'text' -- BAD item <> 'text'
UPDATE문
= 정렬
SET에 포함되는
=
는 동일한 위치에 있도록 정렬합니다.-- GOOD UPDATE my_staff_size SET id = id + 1 , name = name || '.duplicate' , note = note || ' (another one)' , additional_note = 'Processed.' , status = -1 -- BAD UPDATE my_staff_size SET id = id + 1 , name = name || '.duplicate' , note = note || ' (another one)' , additional_note = 'Processed.' , status = -1
WITH절
with문은 쿼리의 상단에 배치합니다.
with문의 이름은 명확하고 간결하게 짓습니다.
= table 별칭과 동일
replace_sfdc_account_id_with_master_record_id
와 같은 이름은 피하도록 합니다.
혼란을 줄 수 있거나 헷갈릴만한 with문은 주석을 추가합니다.
WITH events AS ( -- think of these select statements as your import statements. ... ) , filtered_events AS ( -- CTE comments go here ... ) SELECT * FROM filtered_events
요소 배치
WITH
절 다음에 나오는 이름은 새로운 줄에 쓰지 않고 붙여씁니다.-- GOOD WITH something AS ( ... ) -- BAD WITH something AS ( ... )
WITH 절 내의 쿼리는 새로운 줄에서 4칸 들여쓰기를 적용하여 진행합니다.
--GOOD WITH something AS ( SELECT 26 AS x FROM (SELECT dep_id FROM department WHERE actual IS NOT NULL) WHERE 1 IS NOT NULL ) -- BAD WITH something AS (SELECT 26 AS x FROM (SELECT dep_id FROM department WHERE actual IS NOT NULL) WHERE 1 IS NOT NULL )
여러 with 절
with 절이 여러개가 등록될 경우, 공통의 섹션 요소 정렬 과 같이 정렬합니다.
-- GOOD WITH something AS ( SELECT 26 AS x FROM (SELECT dep_id FROM department WHERE actual IS NOT NULL) WHERE 1 IS NOT NULL ) , another AS ( SELECT 26 AS x FROM dual WHERE 1 IS NOT NULL ) -- BAD WITH something AS ( SELECT 26 AS x FROM (SELECT dep_id FROM department WHERE actual IS NOT NULL) WHERE 1 IS NOT NULL ) , another AS ( SELECT 26 AS x FROM dual WHERE 1 IS NOT NULL )
‼️ 중첩쿼리 지양
중첩 쿼리 (inner query 등등)은 가독성을 떨어트리기 때문에 사용을 지양합니다.
-- Good WITH sample AS ( SELECT client_id, submission_date FROM main_summary WHERE sample_id = '42' ) SELECT * FROM sample LIMIT 10 -- Bad SELECT * FROM ( SELECT client_id, submission_date FROM main_summary WHERE sample_id = '42' ) LIMIT 10
SELECT문
요소 배치
공통의 절의 요소 배치 속성을 그대로 적용하지만 *, distinct만 예외로 한 줄로 표현합니다.
-- GOOD SELECT a.* FROM small_table_1 a -- GOOD SELECT * FROM small_table_1 a -- GOOD SELECT DISTINCT d.dep_id , p.per_id , count(*) AS per_cnt , max(m.actual) AS has_actual_template FROM -- BAD SELECT * FROM small_table_1 a
별칭 사용
별칭에는 항상
AS
를 붙입니다.-- GOOD SELECT (SELECT 1 FROM dual WHERE 1 IS NOT NULL) AS x -- BAD SELECT (SELECT 1 FROM dual WHERE 1 IS NOT NULL) x
별칭 정렬
별칭은 항상 동등한 위치에 정렬합니다.
-- GOOD SELECT x - y AS z , p * q AS pq , tau * mu AS intermediate_result -- BAD SELECT x - y AS z , p * q AS pq , tau * mu AS intermediate_result
FROM절
요소 배치
공통의 절의 요소배치 속성과 동일합니다.
-- GOOD SELECT * FROM item INNER JOIN options -- BAD SELECT * FROM item INNER JOIN options
join indent
join은 from 절의 첫 테이블과 동일한 인덴트를 갖습니다.
-- GOOD SELECT * FROM item INNER JOIN options -- BAD SELECT * FROM item INNER JOIN options
별칭 사용
별칭에는 항상
AS
를 붙입니다.-- GOOD SELECT * FROM item AS i INNER JOIN options AS o -- BAD -- GOOD SELECT * FROM item i INNER JOIN options o
별칭 정렬
별칭은 항상 동등한 위치에 정렬합니다.
-- GOOD SELECT * FROM item AS i INNER JOIN options AS o -- BAD SELECT * FROM item AS i INNER JOIN options AS o
‼️ JOIN 명시
조인을 사용할 땐, 어떤 조인인지 명시하도록 합니다.
-- GOOD SELECT submission_date , experiment.key AS experiment_id , experiment.value AS experiment_branch , count(*) AS count FROM telemetry.clients_daily CROSS JOIN UNNEST(experiments.key_value) AS experiment -- BAD SELECT * FROM item AS i JOIN options AS o -- BAD SELECT submission_date , experiment.key AS experiment_id , experiment.value AS experiment_branch , count(*) AS count FROM clients_daily , UNNEST(experiments.key_value) AS experiment -- Implicit JOIN
‼️ JOIN 후 네이밍
테이블을 조인할 때, 접두사로 테이블 명을 명시해 줍니다.
-- GOOD SELECT sfdc_account.account_id AS sfdc_account_id , zuora_account.account_id AS zuora_account_id FROM sfdc_account LEFT JOIN zuora_account -- BAD SELECT sfdc_account.account_id , zuora_account.account_id AS zuora_id FROM sfdc_account LEFT JOIN zuora_account
테이블을 조인하고 양 테이블의 컬럼들을 참조할 때 alias 대신 전체 테이블 이름을 참조하는 것이 좋습니다.
- 테이블 이름이 20자가 넘는다면 해당 테이블을 잘 표현하도록 영작을 잘해봅시다.
-- Good SELECT budget_forecast_cogs_opex.account_id, date_details.fiscal_year, date_details.fiscal_quarter, date_details.fiscal_quarter_name, cost_category.cost_category_level_1, cost_category.cost_category_level_2 FROM budget_forecast_cogs_opex LEFT JOIN date_details ON date_details.first_day_of_month = budget_forecast_cogs_opex.accounting_period LEFT JOIN cost_category ON budget_forecast_cogs_opex.unique_account_name = cost_category.unique_account_name -- 괜찮긴 하지만 별칭이 함축되어 있어 의미가 모호함. 별칭은 해당 테이블의 성격을 잘 나타내도록 지어야한다. SELECT bfcopex.account_id, -- 15 more columns date_details.fiscal_year, date_details.fiscal_quarter, date_details.fiscal_quarter_name, cost_category.cost_category_level_1, cost_category.cost_category_level_2 FROM budget_forecast_cogs_opex AS bfcopex LEFT JOIN date_details ON date_details.first_day_of_month = bfcopex.accounting_period LEFT JOIN cost_category ON bfcopex.unique_account_name = cost_category.unique_account_name -- Bad SELECT a.*, -- 15 more columns b.fiscal_year, b.fiscal_quarter, b.fiscal_quarter_name, c.cost_category_level_1, c.cost_category_level_2 FROM budget_forecast_cogs_opex a LEFT JOIN date_details b ON b.first_day_of_month = a.accounting_period LEFT JOIN cost_category c ON b.unique_account_name = c.unique_account_name
ON절 정렬
on절은 join에서 4칸 들여쓰기를 하며 항상 새로운 줄에 작성합니다.
SELECT * FROM small_table_1 a JOIN medium_table_4 d USING (id) LEFT OUTER JOIN medium_table_5 e ON d.x = e.x LEFT OUTER JOIN medium_table_6 f ON e.y = f.y INNER JOIN medium_table_7 g ON f.z = g.z;
‼️ USING 사용 지양
using은 snowflake에서 부정확한 결과를 생성하므로 사용을 지양합니다.
-- BETTER SELECT * FROM small_table_1 JOIN medium_table_4 ON small_table_1.id = medium_table_4.id -- OK SELECT * FROM small_table_1 JOIN medium_table_4 USING (id)
WHERE와 HAVING절
요소 배치
공통의 절의 요소배치 속성과 동일합니다.
-- GOOD WHERE 1 IS NOT NULL -- BAD WHERE 1 IS NOT NULL
AND/OR 위치
and와 or는 콤마와 같이 항상 왼쪽에 배치합니다.
-- GOOD WHERE year BETWEEN 1996 AND 2017 AND (q1 > 0 OR q4 = 20) AND actual IS NOT NULL; --BAD WHERE year BETWEEN 1996 AND 2017 AND (q1 > 0 OR q4 = 20) AND actual IS NOT NULL;
‼️ AND/OR 혼합 사용
and와 or를 섞어서 사용할 때, 순서에 의존하지 말고 괄호로 묶어줍니다.
SELECT * FROM production.product AS prd WHERE (prd.weight > 10.0 AND color IN ('Red','Silver')) OR color IS NULL
GROUP BY와 ORDER BY절
숫자 사용 금지
숫자로 사용할 시, ALL(*)과 같은 경우에 어떤 컬럼인지 알 수가 없으므로 필드를 명확하게 명시합니다.
만약, select 절에 복잡한 수식이 있다면 WITH절로 사전에 처리하도록 합니다.
-- GOOD WITH result AS ( SELECT submission_date , normalized_channel IN ('nightly', 'aurora', 'beta') AS is_prerelease , COUNT(*) AS count FROM telemetry.clients_daily WHERE submission_date > '20190701' ) SELECT submission_date , normalized_channel IN ('nightly', 'aurora', 'beta') AS is_prerelease , COUNT(*) AS count FROM result GROUP BY submission_date, is_prerelease ORDER BY submission_date -- Bad SELECT submission_date, normalized_channel IN ('nightly', 'aurora', 'beta') AS is_prerelease, count(*) AS count FROM telemetry.clients_daily WHERE submission_date > '20190701' GROUP BY 1, 2 -- Implicit grouping avoids repeating expressions ORDER BY 1
요소 배치
공통의 절의 요소 배치속성과 동일합니다.
-- GOOD GROUP BY 1, 2 -- BAD GROUP BY 1, 2
요소 정렬 기준
요소는 100자가 넘지 않으면 한 줄로 표현합니다.
-- GOOD GROUP BY 1, 2 -- BAD GROUP BY 1 , 2
Subquery (괄호)
여는 괄호는 해당 줄의 가장 마지막에 위치해야 합니다.
괄호 안의 쿼리는 4칸 들여쓰기여야 합니다.
닫는 괄호는 해당 줄의 가장 첫 번째에 위치해야 합니다.
-- GOOD WITH o AS ( SELECT id AS org_id , name , description , another_field FROM all_organizations NATURAL JOIN all_names WHERE status >= 1 AND item_no IN ( SELECT no FROM item WHERE item_no IN (1, 2, 3, 4, 5) ) ) -- GOOD SELECT ( SELECT 1 FROM dual WHERE 1 IS NOT NULL ) AS x FROM ( SELECT 1 FROM dual WHERE 1 IS NOT NULL ) y WHERE ( SELECT TRUE FROM dual WHERE 1 IS NOT NULL )
표현식
괄호와 콤마
(
괄호 앞에 띄어쓰기괄호 앞에는 띄어쓰기를 추가해 줍니다.
-- GOOD SELECT (item) -- BAD SELECT(item)
콤마 뒤에 띄어쓰기
콤마 뒤에는 띄어쓰기를 추가해 줍니다.
-- GOOD SELECT (cell_1, cell_2, cell_3, cell_4) -- BAD SELECT (cell_1,cell_2,cell_3,cell_4)
연산자
연산자 앞,뒤 띄어쓰기
연산자 앞, 뒤는 띄어쓰기를 추가해 줍니다.
-- GOOD SELECT (1 + 2 + 3 + 4) -- BAD SELECT (1+2+3+4)
CASE문
WHEN 정렬
WHEN은 CASE문 다음 줄에 표현하며 4칸 들여쓰기를 적용합니다.
-- GOOD , CASE WHEN kind = 'O' AND item = 1 -- BAD , CASE WHEN kind = 'O'
THEN 정렬
THEN은 WHEN 다음 줄에 표현하면 4칸 들여쓰기를 적용합니다.
-- GOOD , CASE WHEN kind = 'O' AND item = 1 THEN 'organization' -- BAD , CASE WHEN kind = 'O' THEN 'organization'
ELSE 정렬
ELSE는 WHEN과 동일한 위치에 정렬합니다.
-- GOOD WHEN kind = '' THEN 'alias' ELSE 'unknown subject' -- BAD WHEN kind = '' THEN 'alias' ELSE 'unknown subject'
END 정렬
END는 CASE와 동일한 위치에 정렬합니다.
-- GOOD CASE WHEN kind = 'O' THEN 'organization' ELSE 'unknown subject' END -- BAD CASE WHEN kind = 'O' THEN 'organization' ELSE 'unknown subject' END
짧은 case문은 생략
만약 case 문이 짧다면 위의 규칙을 생략합니다.
SELECT id , name , kind , CASE WHEN id < 0 THEN 'C' ELSE NULL END AS was_converted , CASE WHEN kind = 'O' THEN 'organization' WHEN kind = 'P' THEN 'person' WHEN kind = 'XX' THEN 'something gone wrong here, looks like' + 'this code should be unreachable' WHEN kind = '' THEN 'alias' ELSE 'unknown subject' END AS kind_name_1
‼️ CASE문 단순화
단순화 할 수 있으면 단순화 하도록 합니다.
-- OK CASE WHEN field_id = 1 THEN 'date' WHEN field_id = 2 THEN 'integer' WHEN field_id = 3 THEN 'currency' WHEN field_id = 4 THEN 'boolean' WHEN field_id = 5 THEN 'variant' WHEN field_id = 6 THEN 'text' END AS field_type -- Better CASE field_id WHEN 1 THEN 'date' WHEN 2 THEN 'integer' WHEN 3 THEN 'currency' WHEN 4 THEN 'boolean' WHEN 5 THEN 'variant' WHEN 6 THEN 'text' END AS field_type
‼️ Window Function
각 절마다 하나씩 여러줄로 분할하여 정렬합니다.
SELECT post_no , ROW_NUMBER() OVER ( PARTITION BY p.productline , LEFT(p.productnumber, 2) ORDER BY RIGHT(p.productnumber, 4) DESC ) AS sequencenum , FIRST_VALUE(CONCAT('https://img.co.kr', image_url)) OVER ( PARTITION BY cart_tb.user_no, item.item_no ORDER BY item_image_no ) AS image_url_first FROM production.product
문서