├── LICENSE
└── README.md
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Gorilla Logic, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Clean-code-sql
2 |
3 | The final guide to write best SQL queries.
4 |
5 | ## Table of Contents
6 |
7 | - [Clean-code-sql](#clean-code-sql)
8 | - [Table of Contents](#table-of-contents)
9 | - [Introduction](#introduction)
10 | - [Indenting](#indenting)
11 | - [Select](#select)
12 | - [Joins](#joins)
13 | - [Tables](#tables)
14 | - [Columns](#columns)
15 | - [Procedures](#procedures)
16 | - [Views](#views)
17 | - [Comments](#comments)
18 | - [Modularize](#modularize)
19 | - [Temporary tables](#temporary-tables)
20 | - [Looping](#looping)
21 | - [When troubleshooting](#when-troubleshooting)
22 |
23 | ## Introduction
24 |
25 | These are guidelignes about good practices to write cleaner and better SQL queries.
26 |
27 | ## Indenting
28 |
29 | Indenting makes SQL query visually structured and easier to follow it.
30 |
31 | Bad:
32 |
33 | ```sql
34 | SELECT d.DepartmentName,e.Name, e.LastName, e.Address, e.State, e.City, e.Zip FROM Departments AS d JOIN Employees AS e ON d.ID = e.DepartmentID WHERE d.DepartmentName != 'HR';
35 | ```
36 |
37 | Good:
38 |
39 | ```sql
40 | SELECT d.DepartmentName
41 | , e.Name
42 | , e.LastName
43 | , e.Address
44 | , e.State
45 | , e.City
46 | , e.Zip
47 | FROM Departments AS d
48 | JOIN Employees AS e ON d.ID = e.DepartmentID
49 | WHERE d.Name != 'HR';
50 | ```
51 |
52 | [Back to top](#table-of-contents)
53 |
54 | ## Select
55 |
56 | SELECT \*
57 |
58 | When using SELECT \*, all the columns will be returned in the same order as they are defined, so the data returned can change whenever the table definitions change.
59 |
60 | Bad:
61 |
62 | ```sql
63 | SELECT *
64 | FROM Departments
65 | ```
66 |
67 | Good:
68 |
69 | ```sql
70 | SELECT d.DepartmentName
71 | , d.Location
72 | FROM Departments AS d
73 | ```
74 |
75 | **Table Aliases**
76 |
77 | It is more readable to use aliases instead of writing columns with no table information.
78 |
79 | Bad:
80 |
81 | ```sql
82 | SELECT Name,
83 | DepartmentName
84 | FROM Departments
85 | JOIN Employees ON ID = DepartmentID;
86 | ```
87 |
88 | Good:
89 |
90 | ```sql
91 | SELECT e.Name
92 | , d.DepartmentName
93 | FROM Departments AS d
94 | JOIN Employees AS e ON d.ID = e.DepartmentID;
95 | ```
96 |
97 | **SELECT DISTINCT**
98 |
99 | SELECT DISTINCT is a practical way to remove duplicates from a query, however its use requires a high processing cost. Better select more fields to create unique results.
100 |
101 | Bad:
102 |
103 | ```sql
104 | SELECT DISTINCT Name,
105 | LastName,
106 | Address
107 | FROM Employees;
108 | ```
109 |
110 | Good:
111 |
112 | ```sql
113 | SELECT Name
114 | , LastName
115 | , Address
116 | , State
117 | , City
118 | , Zip
119 | FROM Employees;
120 | ```
121 |
122 | **EXISTS**
123 |
124 | Prefer EXISTS to IN. Exists process exits as soon it finds the value, whereas IN scan the entire table.
125 |
126 | Bad:
127 |
128 | ```sql
129 | SELECT e.name
130 | , e.salary
131 | FROM Employee AS e
132 | WHERE e.EmployeeID IN (SELECT p.IdNumber
133 | FROM Population AS p
134 | WHERE p.country = "Canada"
135 | AND p.city = "Toronto");
136 | ```
137 |
138 | Good:
139 |
140 | ```sql
141 | SELECT e.name
142 | , e.salary
143 | FROM Employee AS e
144 | WHERE e.EmployeeID EXISTS (SELECT p.IdNumber
145 | FROM Population AS p
146 | WHERE p.country = "Canada"
147 | AND p.city = "Toronto");
148 | ```
149 |
150 | [Back to top](#table-of-contents)
151 |
152 | ## Joins
153 |
154 | Use the ANSI-Standard Join clauses instead of the old style joins.
155 |
156 | Bad:
157 |
158 | ```sql
159 | SELECT e.Name,
160 | d.DepartmentName
161 | FROM Departments AS d,
162 | Employees AS e
163 | WHERE d.ID = e.DepartmentID;
164 | ```
165 |
166 | Good:
167 |
168 | ```sql
169 | SELECT e.Name,
170 | d.DepartmentName
171 | FROM Departments AS d
172 | JOIN Employees AS e ON d.ID = e.DepartmentID;
173 | ```
174 |
175 | [Back to top](#table-of-contents)
176 |
177 | ## Tables
178 |
179 | For naming tables take into consideration the following advices:
180 |
181 | - Use singular names
182 | - Use schema name prefix
183 | - Use Pascal case
184 |
185 | Bad:
186 |
187 | ```sql
188 | CREATE TABLE Addresses
189 | ```
190 |
191 | Good:
192 |
193 | ```sql
194 | CREATE TABLE [Person].[Address]
195 | ```
196 |
197 | [Back to top](#table-of-contents)
198 |
199 | ## Columns
200 |
201 | For naming columns take into consideration the following advices:
202 |
203 | - Use singular names
204 | - Use Pascal case
205 | - Name your primary keys using "[TableName]ID" format
206 | - Be descriptive
207 | - Be consistent
208 |
209 | Bad:
210 |
211 | ```sql
212 | CREATE TABLE [Person].[Address](
213 | [Address] [int] NOT NULL,
214 | [AddressLine1] [nvarchar](60) NOT NULL,
215 | [Address2] [nvarchar](60) NULL,
216 | [city] [nvarchar](30) NOT NULL,
217 | [State_ProvinceID] [int] NOT NULL,
218 | [postalCode] [nvarchar](15) NOT NULL,
219 | [ModifiedDate] [datetime] NOT NULL,
220 | CONSTRAINT [PK_Address_AddressID] PRIMARY KEY CLUSTERED ([Address])
221 | );
222 | ```
223 |
224 | Good:
225 |
226 | ```sql
227 | CREATE TABLE [Person].[Address](
228 | [AddressID] [int] NOT NULL,
229 | [AddressLine1] [nvarchar](60) NOT NULL,
230 | [AddressLine2] [nvarchar](60) NULL,
231 | [City] [nvarchar](30) NOT NULL,
232 | [StateProvinceID] [int] NOT NULL,
233 | [PostalCode] [nvarchar](15) NOT NULL,
234 | [ModifiedDate] [datetime] NOT NULL,
235 | CONSTRAINT [PK_Address_AddressID] PRIMARY KEY CLUSTERED ([AddressID])
236 | );
237 | ```
238 |
239 | [Back to top](#table-of-contents)
240 |
241 | ## Procedures
242 |
243 | To write incredible stored procedures, take into consideration the following advices:
244 |
245 | - Use the SET NOCOUNT ON option in the beginning of the stored procedure to prevent the server from sending row counts of data affected by some statement or stored procedure to the client.
246 | - Try to write the DECLARATION and initialization (SET) at the beginning of the stored procedure.
247 | - Write all the SQL Server keywords in the CAPS letter.
248 | - Avoid to use 'sp\_' at the begining of the stored procedure name.
249 | - Take into consideration the previous sections, [Indenting](#indenting) - [Select](#select) - [Joins](#joins).
250 |
251 | Bad:
252 |
253 | ```sql
254 | CREATE OR ALTER PROCEDURE [HumanResources].[sp_UpdateEmployeePersonalInfo]
255 | @BusinessEntityID [int],
256 | @NationalIDNumber [nvarchar](15),
257 | @BirthDate [datetime],
258 | @MaritalStatus [nchar](1),
259 | @Gender [nchar](1)
260 | AS
261 | BEGIN
262 | --some code
263 | END;
264 | ```
265 |
266 | Good:
267 |
268 | ```sql
269 | CREATE OR ALTER PROCEDURE [HumanResources].[UpdateEmployeePersonalInfo]
270 | @BusinessEntityID [int],
271 | @NationalIDNumber [nvarchar](15),
272 | @BirthDate [datetime],
273 | @MaritalStatus [nchar](1),
274 | @Gender [nchar](1)
275 | AS
276 | BEGIN
277 | SET NOCOUNT ON;
278 |
279 | DECLARE @messageLog nvarchar(100);
280 |
281 | SET @messageLog = 'Updating the employee personal info';
282 |
283 | --some code
284 | END;
285 | ```
286 |
287 | [Back to top](#table-of-contents)
288 |
289 | ## Views
290 |
291 | Don't use the word 'view' in the view's name.
292 |
293 | Bad:
294 |
295 | ```sql
296 | - ViewEmployeesDepartments
297 | - EmployeeDeparmentsView
298 | ```
299 |
300 | Good:
301 |
302 | ```sql
303 | - EmployeeDeparments
304 | ```
305 |
306 | Don't include Order by and Where conditions into the view.
307 |
308 | Bad:
309 |
310 | ```sql
311 | SELECT EmployeeId,
312 | Name,
313 | LastName,
314 | Address,
315 | State
316 | FROM Employees
317 | WHERE EmployeeId > 0 ORDER BY Name
318 | ```
319 |
320 | Good:
321 |
322 | ```sql
323 | SELECT Name,
324 | LastName,
325 | Address,
326 | State,
327 | City,
328 | Zip
329 | FROM Employees;
330 | ```
331 |
332 | Use Alias with table + column to specify when the values come from.
333 |
334 | Bad:
335 |
336 | ```sql
337 | SELECT e.Name,
338 | d.Name
339 | FROM Departments AS d
340 | JOIN Employees AS e ON d.ID = e.DepartmentID;
341 | ```
342 |
343 | Good:
344 |
345 | ```sql
346 | SELECT e.Name as EmployeeName,
347 | d.Name as DeparmentName
348 | FROM Departments AS d
349 | JOIN Employees AS e ON d.ID = e.DepartmentID;
350 | ```
351 |
352 | [Back to top](#table-of-contents)
353 |
354 | ## Comments
355 |
356 | Write helpful comments and only when necessary. Do not write comments trying to explain the code that we will understand by reading the code itself.
357 |
358 | Bad:
359 |
360 | ```sql
361 | CREATE OR ALTER PROCEDURE [dbo].[uspLogError]
362 | @ErrorLogID [int] = 0 OUTPUT
363 | AS
364 | BEGIN
365 | SET NOCOUNT ON;
366 | -- Setting ErrorLogID to 0
367 | SET @ErrorLogID = 0;
368 |
369 | //some code
370 | END;
371 | ```
372 |
373 | Good:
374 |
375 | ```sql
376 | CREATE OR ALTER PROCEDURE [dbo].[uspLogError]
377 | @ErrorLogID [int] = 0 OUTPUT
378 | AS
379 | BEGIN
380 | SET NOCOUNT ON;
381 |
382 | -- Output parameter value of 0 indicates that error
383 | -- information was not logged
384 | SET @ErrorLogID = 0;
385 |
386 | //some code
387 | END;
388 | ```
389 |
390 | [Back to top](#table-of-contents)
391 |
392 | ## Modularize
393 |
394 | When writing a long query, thinking about modularizing is a good idea. Using common table expressions (CTEs) you can break down your code and make it easy to understand.
395 |
396 | Bad:
397 |
398 | ```sql
399 | SELECT e.name
400 | , e.salary
401 | FROM Employee AS e
402 | WHERE e.EmployeeID EXISTS (SELECT p.IdNumber
403 | FROM Population AS p
404 | WHERE p.country = "Canada"
405 | AND p.city = "Toronto")
406 | AND e.salary >= (SELECT AVG(s.salary)
407 | FROM salaries AS s
408 | WHERE s.gender = "Female")
409 | ```
410 |
411 | Good:
412 |
413 | ```sql
414 | WITH toronto_ppl AS (
415 | SELECT p.IdNumber
416 | FROM Population AS p
417 | WHERE p.country = "Canada"
418 | AND p.city = "Toronto"
419 | )
420 | , avg_female_salary AS (
421 | SELECT AVG(s.salary) AS avgSalary
422 | FROM salaries AS s
423 | WHERE s.gender = "Female"
424 | )
425 | SELECT e.name
426 | , e.salary
427 | FROM Employee AS e
428 | WHERE e.EmployeeID EXISTS (SELECT IdNumber FROM toronto_ppl)
429 | AND e.salary >= (SELECT avgSalary FROM avg_female_salary)
430 | ```
431 |
432 | [Back to top](#table-of-contents)
433 |
434 | ## Temporary tables
435 |
436 | Prefer to use a Table variable when the result set is small instead of a Temporary table. A temp table will be created on the temp database and that will make your query slower.
437 |
438 | Bad:
439 |
440 | ```sql
441 | DECLARE TABLE #ListOWeekDays
442 | (
443 | DyNumber INT,
444 | DayAbb VARCHAR(40),
445 | WeekName VARCHAR(40)
446 | )
447 | ```
448 |
449 | Good:
450 |
451 | ```sql
452 | DECLARE @ListOWeekDays TABLE
453 | (
454 | DyNumber INT,
455 | DayAbb VARCHAR(40),
456 | WeekName VARCHAR(40)
457 | )
458 | ```
459 |
460 | [Back to top](#table-of-contents)
461 |
462 | ## Looping
463 |
464 | Avoid running queries using a loop. Coding SQL queries in loops slows down the entire sequence.
465 |
466 | Bad:
467 |
468 | ```sql
469 | WHILE @date <= @endMonth
470 | BEGIN
471 | SET @date = DATEADD(d, 1, @date)
472 | END
473 | ```
474 |
475 | Good:
476 |
477 | ```sql
478 | DECLARE @StartDate DateTime = '2021-06-01'
479 | DECLARE @EndDate DateTime = '2021-06-29'
480 |
481 | ;WITH populateDates (dates) AS (
482 |
483 | SELECT @StartDate as dates
484 | UNION ALL
485 | SELECT DATEADD(d, 1, dates)
486 | FROM populateDates
487 | WHERE DATEADD(d, 1, dates)<=@EndDate
488 |
489 | )
490 | SELECT *
491 | INTO dbo.SomeTable
492 | FROM populateDates
493 | ```
494 |
495 | [Back to top](#table-of-contents)
496 |
497 | ## When troubleshooting
498 |
499 | - Use the Execution Plan tool if you have it available, with it you can see the statistics and warnings, which can help you find improvement areas.
500 | - Use the comma before the column, it is useful when for some reason you have to comment out columns.
501 |
502 | [Back to top](#table-of-contents)
503 |
--------------------------------------------------------------------------------