一個比較清晰的SQL編寫風格

前言


SQL (Structured Query Language) 是具備數據操縱和數據定義等多種功能的數據庫語言,這種語言具備交互性特色,能爲用戶提供極大的便利,數據庫管理系統應充分利用SQL語言提升計算機應用系統的工做質量與效率。git

身邊不少人工做中常常和SQL打交道, 但是每人的編寫SQL風格都獨樹一幟。github

恰好在githup上看到一個不錯的編碼風格, 在這裏給你們推薦一下。sql

mattm/sql-style-guide數據庫

介紹


使用小寫的SQL

小寫SQL比大寫SQL易讀,並且沒必要一直按住shift鍵。express

-- Good
select * from users

-- Bad
SELECT * FROM users

-- Bad
Select * From users
複製代碼

單行查詢vs多行查詢

如下狀況最好將SQL寫在同一行中:dom

  • 查詢全部列(*)或者只查詢1列或者兩列
  • 查詢語句沒有額外複雜性
-- Good
select * from users

-- Good
select id from users

-- Good
select id, email from users

-- Good
select count(*) from users
複製代碼

這樣作的緣由很簡單,當全部內容都在一行時,仍然很容易閱讀。但一旦你開始添加更多的列或更復雜的代碼,若是是多行代碼就更容易閱讀:ide

-- Good
select
    id,
    email,
    created_at
from users

-- Good
select *
from users
where email = 'example@domain.com 複製代碼

對於具備1或2列的查詢,能夠將這些列放在同一行上。對於3+列,將每一個列名放在它本身的行上,包括第一項:函數

-- Good
select id, email
from users
where email like '%@gmail.com'

-- Good
select user_id, count(*) as total_charges
from charges
group by user_id

-- Good
select
    id,
    email,
    created_at
from users

-- Bad
select id, email, created_at
from users

-- Bad
select id,
    email
from users
複製代碼

向左對齊

-- Good
select id, email
from users
where email like '%@gmail.com'

-- Bad
select id, email
  from users
 where email like '%@gmail.com'
複製代碼

使用單引號

一些SQL方言,如BigQuery支持使用雙引號,可是對於大多數方言,雙引號最終將引用列名。所以,單引號更可取:post

-- Good
select *
from users
where email = 'example@domain.com'

-- Bad
select *
from users
where email = "example@domain.com"
複製代碼

使用 != 而不用 <>

由於!=讀起來像「不等於」,更接近咱們大聲說出來的方式。ui

-- Good
select count(*) as paying_users_count
from users
where plan_name != 'free'

-- Bad
select count(*) as paying_users_count
from users
where plan_name <> 'free'
複製代碼

逗號應該放在行尾

-- Good
select
    id,
    email
from users

-- Bad
select
    id
    , email
from users
複製代碼

縮進條件

當只有一個where條件時,將它保留在與where相同的行上:

select email
from users
where id = 1234
複製代碼

當有多個縮進時,將每一個縮進比where更深一層。將邏輯運算符放在前一個條件的末尾:

select id, email
from users
where 
    created_at >= '2019-03-01' and 
    vertical = 'work'
複製代碼

避免括號內的空格

-- Good
select *
from users
where id in (1, 2)

-- Bad
select *
from users
where id in ( 1, 2 )
複製代碼

in值的長列表分紅多個縮進行

-- Good
select *
from users
where email in (
    'user-1@example.com',
    'user-2@example.com',
    'user-3@example.com',
    'user-4@example.com'
)
複製代碼

表名應該是名詞的複數形式

-- Good
select * from users
select * from visit_logs

-- Bad
select * from user
select * from visitLog
複製代碼

列名應該是xxx_xxx格式

-- Good select id, email, timestamp_trunc(created_at, month) as signup_month from users

-- Bad select id, email, timestamp_trunc(created_at, month) as SignupMonth from users

列名的約定

  • 布爾字段的前綴應該是is_、has_或does_。例如,is_customer、has_unsubscribe等。
  • 僅限日期的字段應該以_date做爲後綴。例如,report_date。
  • Date+time字段應該以_at做爲後綴。例如,created_at、posted_at等。

列排序約定

首先放置主鍵,而後是外鍵,而後是全部其餘列。若是表中有任何系統列(created_at、updated_at、is_deleted等),那麼將它們放在最後。

-- Good
select
    id,
    name,
    created_at
from users

-- Bad
select
    created_at,
    name,
    id,
from users
複製代碼

inner join 處理

最好顯式,以便鏈接類型很是清楚:

-- Good
select
    email,
    sum(amount) as total_revenue
from users
inner join charges on users.id = charges.user_id

-- Bad
select
    email,
    sum(amount) as total_revenue
from users
join charges on users.id = charges.user_id
複製代碼

對於join條件,將首先引用的表放在on以後

-- Good
select
    ...
from users
left join charges on users.id = charges.user_id
-- primary_key = foreign_key --> one-to-many --> fanout
  
select
    ...
from charges
left join users on charges.user_id = users.id
-- foreign_key = primary_key --> many-to-one --> no fanout

-- Bad
select
    ...
from users
left join charges on charges.user_id = users.id
複製代碼

單個鏈接條件應該與鏈接位於同一行

-- Good
select
    email,
    sum(amount) as total_revenue
from users
inner join charges on users.id = charges.user_id
group by email

-- Bad
select
    email,
    sum(amount) as total_revenue
from users
inner join charges
on users.id = charges.user_id
group by email
複製代碼

當你有多個鏈接條件時,把它們放在各自的縮進行:

-- Good
select
    email,
    sum(amount) as total_revenue
from users
inner join charges on 
    users.id = charges.user_id and
    refunded = false
group by email
複製代碼

除非必要,不然不要包含表名

-- Good
select
    id,
    name
from companies

-- Bad
select
    companies.id,
    companies.name
from companies
複製代碼

始終重命名聚合和函數包裝的參數

-- Good
select count(*) as total_users
from users

-- Bad
select count(*)
from users

-- Good
select timestamp_millis(property_beacon_interest) as expressed_interest_at
from hubspot.contact
where property_beacon_interest is not null

-- Bad
select timestamp_millis(property_beacon_interest)
from hubspot.contact
where property_beacon_interest is not null
複製代碼

在布爾條件需顯示寫出

-- Good
select * from customers where is_cancelled = true
select * from customers where is_cancelled = false

-- Bad
select * from customers where is_cancelled
select * from customers where not is_cancelled
複製代碼

as 重命名列名

-- Good
select
    id,
    email,
    timestamp_trunc(created_at, month) as signup_month
from users

-- Bad
select
    id,
    email,
    timestamp_trunc(created_at, month) signup_month
from users
複製代碼

group by 時寫出列名而不要用序號

-- Good
select user_id, count(*) as total_charges
from charges
group by user_id

-- Bad
select
    user_id,
    count(*) as total_charges
from charges
group by 1
複製代碼

分組的列首先顯示出來

-- Good
select
  timestamp_trunc(com_created_at, year) as signup_year,
  count(*) as total_companies
from companies
group by signup_year

-- Bad
select
  count(*) as total_companies,
  timestamp_trunc(com_created_at, year) as signup_year
from companies
group by signup_year
複製代碼

調整對齊case-when語句

每一個when應該在它本身的行上(case行上沒有任何內容),而且應該比case行縮進更深一層。then能夠在同一條線上,也能夠在它下面的本身的線上。

-- Good
select
    case
        when event_name = 'viewed_homepage' then 'Homepage'
        when event_name = 'viewed_editor' then 'Editor'
    end as page_name
from events

-- Good too
select
    case
        when event_name = 'viewed_homepage'
            then 'Homepage'
        when event_name = 'viewed_editor'
            then 'Editor'
    end as page_name
from events

-- Bad 
select
    case when event_name = 'viewed_homepage' then 'Homepage'
        when event_name = 'viewed_editor' then 'Editor'
    end as page_name
from events
複製代碼

儘可能使用CTEs, 避免使用子查詢

-- Good
with ordered_details as (

    select
        user_id,
        name,
        row_number() over (partition by user_id order by date_updated desc) as details_rank
    from billingdaddy.billing_stored_details

),

final as (

    select user_id, name
    from ordered_details
    where details_rank = 1

)

select * from final

-- Bad
select user_id, name
from (
    select
        user_id,
        name,
        row_number() over (partition by user_id order by date_updated desc) as details_rank
    from billingdaddy.billing_stored_details
) ranked
where details_rank = 1
複製代碼

使用有意義的CTE名稱

-- Good
with ordered_details as (

-- Bad
with d1 as (
複製代碼

使用over()函數

你能夠把它放在同一行上,也能夠根據它的長度把它分紅多行:

-- Good
select
    user_id,
    name,
    row_number() over (partition by user_id order by date_updated desc) as details_rank
from billingdaddy.billing_stored_details

-- Good
select
    user_id,
    name,
    row_number() over (
        partition by user_id
        order by date_updated desc
    ) as details_rank
from billingdaddy.billing_stored_details
複製代碼

喜歡能夠關注公衆號: 終身幼稚園

終身幼稚園
相關文章
相關標籤/搜索