天天看點

SQL最佳實踐

作者:聞數起舞

在這篇文章中,我将與大家分享我在過去20年中所學習和使用的一些格式化SQL的最佳實踐。這些技巧中有許多是我在繼承代碼、需要進行更新、追尋bug以及為他人進行代碼審查的過程中,出于挫折感而一路收集的。另外,當涉及到教育訓練新員工時,我發現在處理寫得不好的SQL時,人們要了解領域和代碼要困難得多。

在本文的每一節中,我都會集中讨論一個領域的實踐。我将為你提供一個壞做法的例子和最佳做法的例子,以及我以特定方式編寫代碼的原因。當你在閱讀時,你可能會想,"這不是一個高性能的代碼",有時可能是這樣的情況,因為我試圖在一個特定的點上磨練,避免多餘的潛在混亂。另外,有時我甯願選擇性能稍差的代碼(取決于它的使用頻率,代碼的目的,以及性能沖擊),如果它能使代碼更容易閱讀和維護。

我猜想,有些人讀到這裡可能會說:"我的代碼執行得很好,是以我不關心。我明白它的作用。"對此(因為多年來我已經聽過很多次這樣的評論),我想對這種說法提出質疑。與使用幹淨的代碼相比,發現和閱讀馬虎的 "壞做法 "的代碼要花費明顯更多的時間。這些做法也将有助于減少錯誤,你的代碼将被每個閱讀它的人所欣賞。

當我們談論程式設計語言時,你幾乎可以按照你認為合适的方式編寫語句,代碼就會執行。顯然,這有一些例外(比如Python中的縮進),但這是另一個問題。這意味着,當我在談論SQL的最佳實踐時,人們可以提出這樣的論點:這隻是一個觀點和寫作風格的問題。

你如何寫它很重要。比如說,想想一本書。當然,你可以寫一整本書,沒有段落、換行、句子之間的标準間距等等。是的,它仍然是一本書,是的,讀者可能會了解它。然而,如果沒有段落,你會有多容易找到一個特定的章節?如果你被要求編輯一些東西,你會有多大的信心知道你确實在正确的章節裡?由于這些原因,我在這裡概述了一些最佳做法。

請注意,雖然所有這些做法都可以被認為是 "意見",但它們是我在20年裡犯過的錯誤,在有效閱讀、編輯和了解代碼方面遇到的挑戰,以及看着我的團隊成員面臨同樣的挑戰之後所采用的做法。我會盡力告訴你這些做法和使用它們背後的邏輯。我将讓你考慮這些做法,看看什麼對你有用,什麼對你的團隊有用。

格式化

我想談的第一件事是格式化。代碼應該有良好的格式和視覺上的吸引力,這使得它非常容易閱讀。在調試、排除故障和修改你的代碼時,擁有正确格式化的代碼我們會得到回報。

說到格式化,有許多事情需要考慮,如意圖、對齊、逗号位置和文本大小寫。如果你能讀到這裡,你可能已經注意到我的代碼是如何對齊的,而且大多數項目都在一行中。這對可讀性有很大的影響。

下面是一個已經寫好的、相當難讀的代碼例子。在這個例子中,你會注意到有很多東西違背了最佳實踐,比如。

  • 每行項目數
  • 對齊問題
  • 尾部逗号
  • 不良或缺乏混疊
  • 缺少評論
  • 按數字而不是名稱分組
  • 聚合函數在選擇語句中的位置
  • 多個難以檢測的錯誤

在這篇文章中,我将讨論這些做法以及更多的做法,以幫助你寫出幹淨的、沒有錯誤的代碼,讓你感到自豪。

不良做法--許多問題

SQL最佳實踐

最佳實踐

請看下面的代碼,并與上面的代碼進行比較。哪一個更容易閱讀?哪一個提供了關于代碼的意圖和一些條件值的含義的最佳背景?哪一個版本提供了一個更幹淨的使用者界面,可以讓你快速發現bug或完全避免bug?我希望你已經同意,下面的代碼有助于克服上述代碼中發現的所有挑戰。

我聽到人們的反駁是,"好吧,你不得不寫更多的代碼行。"這是不相關的。計算機并不關心這個問題,隻需按幾下鍵就可以實作回行和空格或制表符。

在以下章節中,我将逐一讨論這些問題,并提供好的和壞的做法的例子。

SQL最佳實踐

統一口徑

如果你看一下上面的 "最佳實踐 "代碼,注意到所有東西都是向左對齊的。所有的逗号、空格和縮進使代碼非常容易閱讀。

每行一個項目

我的一般規則是每行一個項目。這可以是你的選擇語句中的一個元素,或連接配接語句中的一個條件,或一個案例語句。再一次,看看上面關于格式化的代碼,看看每行寫一個項目時的可讀性差異。這裡的關鍵是要保持一緻。我見過那些每行寫一個項目的代碼,但每隔一段時間就會有一個*連接配接*子句,其中有一個and和一個or語句在同一行。在閱讀和調試時,這可能會讓人感到非常沮喪,因為很容易就會忽略添加條件,因為它是寫在同一行的。

不好的做法--在一行中設定多個案例條件

在這裡,我們看到一個全部在一行的case語句。這是一種不好的做法,因為它使代碼難以閱讀,也難以迅速發現所有正在評估的條件。另外,如果不是不可能的話,正确注釋代碼也是非常具有挑戰性的。我知道在這個例子中,'main_reporting'不是描述性的,而且看起來與其他的值不一緻,但是,有時候你被告知要這樣輸出值,而不能與其他的值邏輯一緻。

SQL最佳實踐

最佳實踐--多條線路上的多個案例條件

在這裡,我們看到一個寫在多行上的案例聲明,并有注釋以幫助提供清晰度。

SQL最佳實踐

注釋代碼

請注釋你的代碼。我将來會再寫一篇關于代碼注釋的文章,但注釋很重要。我覺得我每天都會在LinkedIn或其他網站上看到一個文章,有人說:"你不需要代碼注釋。代碼就是一個注釋。這有什麼關系?你不知道如何閱讀代碼嗎?" 說真的,這種話我已經聽了很多年了。但現實是這樣的。雖然代碼是一種語言,如果精通這種語言,讀者可以了解代碼在做什麼。但是,代碼從來沒有告訴讀者為什麼有人想讓代碼以這種方式運作。至于為什麼有人想用某種方式來編碼,可能性是無窮的。有時,你可以圍繞後端資料中的一個錯誤進行編碼,或者也許有商業邏輯決定了代碼應該如何運作。

雖然你确實可以閱讀代碼,并有可能查閱某些表格的文檔,但這比輸入幾個字元要費勁得多。下面是一些好的和壞的注釋做法的例子。

糟糕的注釋 -

請看下面的代碼。我們可以看到,該代碼隻想傳回u.id>1000的結果。在這個非常簡單的例子中,這是很明顯的。但更重要的問題是為什麼有人要這樣做?

也許他們是u.id=1000之前的測試使用者。或者,代碼正在過濾掉所有來自密歇根州的使用者,因為出于某種原因,有人認為所有小于u.id 1000的使用者都來自密歇根州。這聽起來可能是一個可怕的想法,實際上有代碼會這樣寫,但它執行起來都是一樣的。這裡的重點是,作為新使用者,我們不知道,有可能六個月後你也不會知道。

SQL最佳實踐

更好的注釋--行内注釋

這裡我們有一個行内評論,告訴我們更多關于為什麼我們添加了u.id>1000的條件。很明顯,我們有一些測試使用者應該被從結果集中清除掉。

SQL最佳實踐

糟糕的注釋--沒有塊注釋

請看下面的代碼。我們可以看到,這個查詢将傳回被認為是非測試使用者的使用者。行内注釋幫助我們了解,我們的願望是将這些測試使用者從結果集中清除掉。但是我們不得不讀幾行代碼。你可能會說,好吧,這不值得在頂部設定注釋塊。這隻是8行代碼,發生了什麼是顯而易見的。無論是否簡單,讀者都不知道這段代碼背後的原因。但如果代碼不是那麼簡單呢?你肯定會欣賞一些評論。由于這些原因,以及為了在開始寫代碼之前有更好的規劃(後面會有更多的介紹)。

SQL最佳實踐

更好的注釋 - 塊注釋

下面是我們剛才看到的同樣的代碼,現在我們要告訴使用者為什麼我們要

想運作這段代碼和需要注意的事情。

SQL最佳實踐

通用表表達式(CTE)

通用表表達式或CTE是一種建立查詢結果的記憶體表的方法。這個表可以在你的SQL腳本的其餘部分使用。使用CTE的好處是,你可以減少代碼的重複,使你的代碼更具可讀性,并提高你對結果進行QA檢查的能力。

還注意到真正的好塊頭評論。

不使用CTE的代碼執行個體

在下面的代碼中,我們可以看到,有兩個子查詢正在傳回結果。這兩個子查詢然後被連接配接在一起,産生最終的結果集。雖然這段代碼會執行,但有幾個問題。

1.要對子查詢進行QA并檢查結果真的很難。例如,如果我們想對預設螢幕有多條記錄的使用者數量進行統計呢?我們不能簡單地對子查詢執行一些sql。我們必須複制/粘貼子查詢,然後修改它來執行這個問題。如果我們在QA過程中能避免改變代碼,那就更好了。

2.如果我們需要在代碼的其他地方利用這個使用者子查詢,我們就必須重新寫或複制/粘貼這塊代碼到我們腳本的其他地方。這将不是一個DRY(不要重複自己)的做法。

過程,并暴露出更多潛在的錯誤。怎麼說呢?暫時假設你在腳本中的5個地方使用了使用者子查詢。還假設你正在處理的代碼不容易閱讀,因為它沒有遵循最佳實踐。如果你被要求更新代碼,添加另一個條件來清除更多的測試使用者,那麼你很有可能錯過添加這個條件的機會

到子查詢的5種用途中的至少一種。

3.在資料庫上有更多的周期。每次執行子查詢時,都要進行表掃描以傳回結果。由于我們的使用者子查詢包含通配符條件,資料庫将有相當多的工作要做。執行一次子查詢,将其存儲在記憶體中,然後在你的代碼中根據需要重新使用結果集,這要便宜得多(CPU周期和美元,如果你使用的是雲資料庫)。

4.閱讀整個代碼塊并了解正在執行的内容和原因更加複雜。雖然可以滾動閱讀代碼,但可能很難輕松了解正在發生的事情。一般來說,如果你必須在顯示器上垂直滾動你的代碼,你的代碼就太長了,應該重構為更小的元件。

SQL最佳實踐

使用CTE的例子

下面我們看到一個使用CTE的例子。雖然CTE可以很好地幫助克服我們之前指出的一些挑戰,但CTE在顯示最終結果集後通常不會留在記憶體中。

例如,如果你要運作這整個代碼塊,它就會執行。但是,如果你想在幾分鐘後從使用者的CTE中選擇所有的結果,這些資料就不能被查詢了。為了解決這個問題,你可以使用易失性表或臨時記憶體表,這些表通常在你的會話(資料庫連接配接)保持活動時就一直存在。以後會有更多關于這個主題的内容。

SQL最佳實踐

使用 "SELECT *"

你永遠不應該用 "select *"來寫查詢。我認為這條規則的唯一例外是,如果你試圖檢查一個表,在這種情況下,你應該始終限制傳回的結果數量。這樣寫查詢是個壞主意,原因很多。

1.資料庫性能。傳回不需要的列比隻查詢你關心的列更昂貴。

2.調試的挑戰。假設你使用的是上一節中描述的CTE,那麼追蹤某些屬性的來源是非常具有挑戰性的。

3.表會改變。即使你真的需要選擇所有的列,也不能保證你的表不會随着時間而改變。随着表的變化,你将會查詢到新的資料,而這些資料并不是有意的,這可能會破壞其他地方的代碼,造成混亂,或者影響資料庫的性能和成本。

壞的做法 - 使用 SELECT *

SQL最佳實踐

最佳實踐--隻選擇所需元素

SQL最佳實踐

别名

别名是非常重要的,它可以幫助讀者了解元素所在的位置和正在使用的表格。當不使用别名或使用不良的命名規則時,複雜性就會增加,而代碼的閱讀/了解就會減少。

不好的做法 - 字段上沒有使用别名

下面你可以看到,這些表有一個'u'和'p'的别名,但所選元素沒有利用這個别名。這可能是非常令人沮喪的,如果不止一個表含有相同名稱的字段,就會導緻運作時錯誤。例如,user_id在'users'表和'preferences'表中都能找到。

SQL最佳實踐

最佳實踐--字段上使用的别名

下面你可以看到,表格和標明的元素利用了表格的别名。這使得代碼對終端使用者來說非常可讀。即使你隻有一個表,在表和字段名上使用别名也是一個好的做法。好的習慣造就了好的代碼。

SQL最佳實踐

不好的做法 - CTE上的通用别名<

下面我們有一個已經建立的CTE,但被配置設定的表名叫做 "cte"。這是一個非常普通的名字,完全沒有告訴終端使用者關于表中資料的資訊。如果你是一個使用者,在閱讀CTE後面的選擇語句時,你不會有任何迹象表明正在使用什麼表。

SQL最佳實踐

最佳實踐--CTE上的特定别名

下面我們有一個已經建立的CTE,有一個更具描述性的名字。這個名字給了使用者一些訓示,讓他們知道其中包含什麼資料。

SQL最佳實踐

逗号

在選擇語句中,我更喜歡使用前導逗号,而不是尾随逗号,這是我個人意見發揮作用的一種情況。我見過很多人在寫選擇元素時使用尾部逗号,在其他語言中,使用尾部逗号是常見的做法。然而,在其他語言中,在一個函數中加入大量的參數并不常見,而在SQL中,有大量的元素被選擇(并在代碼中聲明)是很常見的。雖然你可能認為我對這種用法的看法過于偏激,但以下是我認為前導逗号有好處的幾個原因。

1.看起來很幹淨的使用者界面。當你看最佳做法的例子時,看看逗号是如何很好地排列的。與使用尾部逗号相比,很容易看出缺少一個逗号,并避免運作時的錯誤。

2.在處理包裹着行的較長的case語句時,不會出現混亂。看下面的例子,很難分辨行的末尾是一個元素或語句的末尾,還是指定一個被傳入函數的參數的末尾。

為了增加一些額外的挫折感,BigQuery的查詢格式化器實際上是對你的代碼進行重新處理,以顯示所有的尾部逗号。

錯誤的做法--尾部逗号

在這個例子中,我們可以看到沒有使用前導逗号,這使得我們很難發現缺少的逗号。

SQL最佳實踐

最佳實踐--開始處的逗号

在這個例子中,我們可以看到,所有的逗号都是對齊的,這就很容易保證沒有一個逗号被遺漏。

SQL最佳實踐

錯誤的做法--尾部逗号的混淆

在這個例子中,我們可以看到前導逗号沒有被使用。我們有一個以逗号結尾的換行符,是以很難分辨這個 "最大 "行是否真的是一個單獨的語句,或者它是一個較長語句的一部分。有人會說,你不應該在你的代碼中使用這樣的換行符,雖然我支援在正确的地方使用換行符(因為它使代碼更容易閱讀),但當你在編輯器中或在Git差異比較中檢視你的文字包裝的代碼時,你仍然會遇到同樣的問題。

SQL最佳實踐

最佳實踐--帶縮進的前導逗号在包裹的文本上的應用

在下面的例子中,很容易看到缺少一個逗号。你可能會立即懷疑是否有人忘記了逗号,但由于閱讀帶有前導逗号的代碼非常容易,是以更有可能的是,缺少前導逗号是設計好的,實際上在'cast'語句前面不應該有一個逗号。另外,通過在cast語句中加入縮進,代碼看起來有一個更明顯的意圖,即為什麼不需要逗号。

SQL最佳實踐

如果有人在第3行的 "desc) "之後執行了回車,你現在就會在第4行有一個共同的開頭。通常情況下,我們希望所有的逗号都作為前導字元。但這一邏輯隻适用于我們談論的是被傳回的屬性(列)。在我們的例子中,第3行末尾的逗号是一個case語句的一部分,是以事情會變得非常混亂。

大寫

在過去的很多SQL代碼中,這種做法非常普遍,這可能與SQL已經存在很長時間的事實有關,可能比大多數帶有文法高亮的文本編輯器還要長。今天,大多數人都在他們的編輯器中使用(或應該使用)文法高亮,是以大寫字母不應該成為發現保留字的必要條件。

雖然我有個人意見,不使用大寫字母,但你可能不同意,有不同的意見。這是我的理由。

1.我不喜歡我的代碼對我大喊大叫。在社會環境和書面交流中,使用大寫字母是吼叫的同義詞,是以我盡量避免這樣寫。

2.代碼讀起來不那麼流暢。心理學研究表明,小寫的單詞比大寫的單詞更容易識别。這是因為與大寫字母相比,小寫字母有更多的形狀變化。如果全部使用大寫字母,閱讀速度會下降13-20%。此外,當你混合使用大寫和小寫的單詞時

3.在我打字時按住shift鍵或鎖定/解鎖大寫字母鍵是額外的擊鍵動作。我知道,這不是一個好的論據。

陋習--大寫字母

在這個例子中,我們可以看到,保留字是大寫的,所有其他的字都是小寫的。

SQL最佳實踐

最佳做法--小寫

SQL最佳實踐

分組 - 數字與顯式字段

我幾乎總是用明确的字段名進行分組,而不是用選擇語句中的位置号。雖然這對結果沒有任何影響,但我發現當涉及到打字和調試時,它可以節省我的時間。通常情況下,我更喜歡使用數字,因為這樣可以減少頁面上的文字,但是這已經引起了太多的問題,并且花了很多時間去追尋運作時的錯誤。

不良做法 - 按職位編号分組

在這裡你可以看到,我們正在根據所選的項目用位置号進行分組。我不喜歡這種做法的地方是。

1.如果有人在選擇語句中除了第一個或最後一個項目之外的任何地方放置一個聚合函數,那麼你就必須跳過group by中的一個位置号。如果你後來決定重新排列你所選擇的元素,這就會産生挫敗感。

2.你必須計算出你有多少個元素,減去聚集的項目,然後手動輸入位置号。

SQL最佳實踐

最佳實踐--按字段名稱分組

在這裡你可以看到,我們正在用明确的字段名來執行一個分組。雖然看起來這比使用位置數字要多打很多字和工作,但實際上它比使用數字打字要快。怎麼說呢?因為你所要做的就是複制選擇語句中的内容(減去聚合字段),并将這些值粘貼到分組中。當你使用數字時,你實際上必須輸入數字。

SQL最佳實踐