可維護、可閱讀的SQL代碼的十個優秀實踐
沒有正確的指導,它很容易混淆SQL。由于團隊中的每個人都可能有自己的寫作SQL習慣,因此您可以快速結束一個沒有人理解的令人困惑的代碼。
您可能意識到遵循一套良好實踐的重要性..這篇文章為您提供了您正在尋找的指導!
1. 使用大寫的關鍵字
讓我們從一個基本開始:使用大寫的SQL關鍵字,以及小寫的表和列。使用SQL函數的大寫(First_Value(),date_trunc()等)也是一個很好的做法。
避免:
- select id, name from company.customers
而是:
- SELECT id, name FROM company.customers
2. 使用Snake Case進行Schema,表,列的編寫
編程語言在案例類型時具有最佳實踐:Camelcase,Pascalcase,Kebabuic和Snake_Case是最常見的。
涉及SQL,Snake Case(有時稱為下劃線Case)是最廣泛使用的約定。
避免:
- SELECT Customers.id,
- Customers.name,
- COUNT(WebVisit.id) as nbVisit
- FROM COMPANY.Customers
- JOIN COMPANY.WebVisit ON Customers.id = WebVisit.customerId
- WHERE Customers.age <= 30
- GROUP BY Customers.id, Customers.name
而是:
- SELECT customers.id,
- customers.name,
- COUNT(web_visit.id) as nb_visit
- FROM company.customers
- JOIN company.web_visit ON customers.id = web_visit.customer_id
- WHERE customers.age <= 30
- GROUP BY customers.id, customers.name
雖然有些人喜歡包括區分Schema,表和列的變體,但我建議使用Snake Case。
3. 在提高可讀性時使用別名
眾所周知,別名是重命名表或列沒有意義的表格或列的便捷方式。當他們的名字并不有意義時,請隨時向您的表和列提供別名,并別名。
避免:
- SELECT customers.id,
- customers.name,
- customers.context_col1,
- nested.f0_
- FROM company.customers
- JOIN (
- SELECT customer_id,
- MIN(date)
- FROM company.purchases
- GROUP BY customer_id
- ) ON customer_id = customers.id
而是:
- SELECT customers.id,
- customers.name,
- customers.context_col1 as ip_address,
- first_purchase.date as first_purchase_date
- FROM company.customers
- JOIN (
- SELECT customer_id,
- MIN(date) as date
- FROM company.purchases
- GROUP BY customer_id
- ) AS first_purchase
- ON first_purchase.customer_id = customers.id
我通常用小寫的列別名,以及具有大寫的表。
4. 格式化:仔細使用縮進和空格
即使它是一個基本的原則,使您的代碼更具可讀性是一個快速的勝利。正如您與Python一樣,您應該標識您的SQL代碼。
關鍵字后,以及使用子查詢或派生表后。
避免:
- SELECT customers.id, customers.name, customers.age, customers.gender, customers.salary, first_purchase.date
- FROM company.customers
- LEFT JOIN ( SELECT customer_id, MIN(date) as date FROM company.purchases GROUP BY customer_id ) AS first_purchase
- ON first_purchase.customer_id = customers.id
- WHERE customers.age<=30
而是:
- SELECT customers.id,
- customers.name,
- customers.age,
- customers.gender,
- customers.salary,
- first_purchase.date
- FROM company.customers
- LEFT JOIN (
- SELECT customer_id,
- MIN(date) as date
- FROM company.purchases
- GROUP BY customer_id
- ) AS first_purchase
- ON first_purchase.customer_id = customers.id
- WHERE customers.age <= 30
此外,請注意我們如何在where子句中使用白空格。
避免:
- SELECT id WHERE customers.age<=30
而是:
- SELECT id WHERE customers.age <= 30
5. 避免 Select *
值得提醒這種良好的做法。您應該明確關于要選擇的內容,因此避免使用SELECT *。
選擇使您的請求不清楚,因為它隱藏了查詢背后的意圖。另外,請記住,您的表可能會發展和影響選擇。這就是為什么我不是除()教學的除外粉絲。
避免:
- SELECT * EXCEPT(id) FROM company.customers
更喜歡:
- SELECT name,
- age,
- salary
- FROM company.customers
6. 使用ANSI-92 Join 語法
…而不是Join表的SQL where子句。雖然您可以使用where子句和join子句來聯結表,但它是使用Join / ansi-92語法的最佳實踐。
雖然性能方面沒有差異,但是Join子句將關系邏輯與過濾器分開并提高可讀性。
避免:
- SELECT customers.id,
- customers.name,
- COUNT(transactions.id) as nb_transaction
- FROM company.customers, company.transactions
- WHERE customers.id = transactions.customer_id
- AND customers.age <= 30
- GROUP BY customers.id, customers.name
而是:
- SELECT customers.id,
- customers.name,
- COUNT(transactions.id) as nb_transaction
- FROM company.customers
- JOIN company.transactions ON customers.id = transactions.customer_id
- WHERE customers.age <= 30
- GROUP BY customers.id, customers.name
“Where基于條款”語法 - 也稱為ANSI-89 - 比新的ANSI-92大,這就是為什么它仍然很常見。如今,大多數開發人員和數據分析師都使用Join語法。
7. 使用公共表表達式(CTE)
CTE允許您定義和執行查詢,其中結果暫時存在,并且可以在更大的查詢中使用。CTE可在大多數現代數據庫上獲得。
它類似于派生表,具有2個優點:
- 使用CTE提高了查詢的可讀性
- CTE是定義一次,然后可以多次參考
您用 WITH … AS:
- WITH my_cte AS( SELECT col1, col2 FROM table)SELECT * FROM my_cte
避免:
- SELECT customers.id,
- customers.name,
- customers.age,
- customers.gender,
- customers.salary,
- persona_salary.avg_salary as persona_avg_salary,
- first_purchase.date
- FROM company.customers
- JOIN (
- SELECT customer_id,
- MIN(date) as date
- FROM company.purchases
- GROUP BY customer_id
- ) AS first_purchase
- ON first_purchase.customer_id = customers.id
- JOIN (
- SELECT age,
- gender,
- AVG(salary) as avg_salary
- FROM company.customers
- GROUP BY age, gender
- ) AS persona_salary
- ON persona_salary.age = customers.age
- AND persona_salary.gender = customers.gender
- WHERE customers.age <= 30
而是:
- WITH first_purchase AS
- (
- SELECT customer_id,
- MIN(date) as date
- FROM company.purchases
- GROUP BY customer_id
- ),
- persona_salary AS
- (
- SELECT age,
- gender,
- AVG(salary) as avg_salary
- FROM company.customers
- GROUP BY age, gender
- )
- SELECT customers.id,
- customers.name,
- customers.age,
- customers.gender,
- customers.salary,
- persona_salary.avg_salary as persona_avg_salary,
- first_purchase.date
- FROM company.customers
- JOIN first_purchase ON first_purchase.customer_id = customers.id
- JOIN persona_salary ON persona_salary.age = customers.age
- AND persona_salary.gender = customers.gender
- WHERE customers.age <= 30
8. 有時,它可能值得分成多個查詢
小心這個。讓我們給出一些背景:
我經常在BigQuery上使用Airflow,轉換數據和準備數據可視化上執行SQL查詢。我們有一個工作流Orchestrator(氣流),以定義的順序執行請求。在某些情況下,我們選擇將復雜查詢拆分為多個較小的查詢。
代替:
- CREATE TABLE customers_infos AS
- SELECT customers.id,
- customers.salary,
- traffic_info.weeks_since_last_visit,
- category_info.most_visited_category_id,
- purchase_info.highest_purchase_value
- FROM company.customers
- LEFT JOIN ([..]) AS traffic_info
- LEFT JOIN ([..]) AS category_info
- LEFT JOIN ([..]) AS purchase_info
你可以使用:
- ## STEP1: Create initial table
- CREATE TABLE public.customers_infos AS
- SELECT customers.id,
- customers.salary,
- 0 as weeks_since_last_visit,
- 0 as most_visited_category_id,
- 0 as highest_purchase_value
- FROM company.customers
- ## STEP2: Update traffic infos
- UPDATE public.customers_infos
- SET weeks_since_last_visit = DATE_DIFF(CURRENT_DATE,
- last_visit.date, WEEK)
- FROM (
- SELECT customer_id, max(visit_date) as date
- FROM web.traffic_info
- GROUP BY customer_id
- ) AS last_visit
- WHERE last_visit.customer_id = customers_infos.id
- ## STEP3: Update category infos
- UPDATE public.customers_infos
- SET most_visited_category_id = [...]
- WHERE [...]
- ## STEP4: Update purchase infos
- UPDATE public.customers_infos
- SET highest_purchase_value = [...]
- WHERE [...]
警告:即使這種方法在簡化復雜查詢時,它可能會帶來可讀性/性能權衡。
如果您使用OLAP或任何面向列的數據庫,則尤其如此,針對聚合和分析查詢(選擇,AVG,MIN,MAX,…)進行優化,但在交談時更少的性能(更新)。
雖然在某些情況下,它也可能提高您的表現。即使使用現代面向列的數據庫,也會導致內存或性能問題太多。在這些情況下,拆分您的請求通常有助于性能和內存。
此外,值得一提的是,您需要某種程序或Orchestrator以定義的順序執行查詢。
9. 基于您自己的約定的有意義的名稱
正確地命名你的模式和表格很難。使用哪些命名約定是值得難得的,但選擇一個并堅持下來并非。您應該定義自己的慣例,并通過您的團隊通過它。
計算機科學中只有兩個難題:緩存失效和命名的東西。 - 菲爾卡爾頓 |
以下是我使用的約定示例:
(1) Schema
如果您使用多種目的的分析數據庫,則是在有意義的模式下組織表的良好做法。
在我們的BigQuery數據庫中,我們每個數據源的一個架構。更重要的是,我們根據其目的輸出不同模式的結果。
- 任何應由第三方工具可訪問的任何表都在公共架構中奠定。Dataviz工具,如DataSudio或Tableau從這里獲取他們的數據。
- 自從我們使用BQML使用機器學習以來,我們有一個專用的Machine_Learning架構。
(2) 表
表格本身應該根據慣例,我們有幾個數據可視化的儀表板,每個儀表板都有自己的目的:營銷儀表板,一個產品儀表板,一個行政儀表板,名稱為幾個。
我們的公共模式中的每個表都由儀表板的名稱前綴。有些例子可能包括:
- product_inbox_usage
- product_addon_competitor_stats
- marketing_acquisition_agencies
- executive_funnel_overview
在與團隊合作時,值得花時間定義您的約定。談到命名一個新表時,永遠不要使用快速和骯臟的名稱,你會“更改”:你可能不會。
隨意使用這些示例來定義您的約定。
10. 最后,寫下有用的注釋……但不是太多
我同意良好的寫作和理所當然地命名的代碼不應需要評論的想法。讀取代碼的人甚至應該在代碼本身之前了解邏輯和意圖。
仍然,評論可能在某些情況下很有用。但你肯定應該避免評論的陷阱太多。
避免:
- WITH fp AS
- (
- SELECT c_id, # customer id
- MIN(date) as dt # date of first purchase
- FROM company.purchases
- GROUP BY c_id
- ),
- ps AS
- (
- SELECT age,
- gender,
- AVG(salary) as avg
- FROM company.customers
- GROUP BY age, gender
- )
- SELECT customers.id,
- ct.name,
- ct.c_age, # customer age
- ct.gender,
- ct.salary,
- ps.avg, # average salary of a similar persona
- fp.dt # date of first purchase for this client
- FROM company.customers ct
- # join the first purchase on client id
- JOIN fp ON c_id = ct.id
- # match persona based on same age and genre
- JOIN ps ON ps.age = c_age
- AND ps.gender = ct.gender
- WHERE c_age <= 30
而是:
- WITH first_purchase AS
- (
- SELECT customer_id,
- MIN(date) as date
- FROM company.purchases
- GROUP BY customer_id
- ),
- persona_salary AS
- (
- SELECT age,
- gender,
- AVG(salary) as avg_salary
- FROM company.customers
- GROUP BY age, gender
- )
- SELECT customers.id,
- customers.name,
- customers.age,
- customers.gender,
- customers.salary,
- persona_salary.avg_salary as persona_avg_salary,
- first_purchase.date
- FROM company.customers
- JOIN first_purchase ON first_purchase.customer_id = customers.id
- JOIN persona_salary ON persona_salary.age = customers.age
- AND persona_salary.gender = customers.gender
- WHERE customers.age <= 30
結論
SQL很棒。它是數據分析,數據科學,數據工程甚至軟件開發的基礎之一:它不會等待。它的靈活性是一種力量,但可以是陷阱。
您可能不會最初意識到這一點,特別是如果您是唯一負責自己代碼的人。但在某些時候,在與團隊合作或者有人繼續工作時,沒有一系列最佳實踐的SQL代碼將成為負擔。
在本文中,我總結了編寫SQL的最常見的優秀實踐。當然,有些是爭辯或基于個人意見:您可能希望從這里獲得靈感,并定義與您的團隊不同的東西。
我希望它能幫助您將您的SQL質量帶到一個下一個高度!
原文鏈接:
https://towardsdatascience.com/10-best-practices-to-write-readable-and-maintainable-sql-code-427f6bb98208