Estas son recomendaciones sobre buenas prácticas para escribir consultas SQL más limpias y con mejor rendimiento.
Indenting
Indenting hace que la consulta SQL esté visualmente estructurada y sea más fácil de seguir.
Malo:
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';
Bien:
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.Name != 'HR';
Select
SELECT*
Al utilizar SELECT *, todas las columnas se devolverán en el mismo orden en que están definidas, por lo que los datos devueltos pueden cambiar cada vez que cambien las definiciones de la tabla.
Malo:
SELECT *
FROM Departments
Bien:
SELECT d.DepartmentName
, d.Location
FROM Departments AS d
Bien:
SELECT d.DepartmentName
, d.Location
FROM Departments AS d
Table Aliases
Es más legible usar alias en lugar de escribir columnas sin información de tabla.
Malo:
SELECT Name,
DepartmentName
FROM Departments
JOIN Employees ON ID = DepartmentID;
Bien:
SELECT e.Name
, d.DepartmentName
FROM Departments AS d
JOIN Employees AS e ON d.ID = e.DepartmentID;
select distinct
SELECT DISTINCT es una forma práctica de eliminar duplicados de una consulta, sin embargo, su uso requiere un alto costo de procesamiento. En lugar de eso seleccionemos más campos para crear resultados únicos.
Malo:
SELECT DISTINCT Name,
LastName,
Address
FROM Employees;
Bien:
SELECT Name
, LastName
, Address
, State
, City
, Zip
FROM Employees;
Exists
Preferir EXISTS a IN. Existe el proceso sale tan pronto como encuentra el valor, mientras que IN escanea toda la tabla.
Malo:
SELECT e.name
, e.salary
FROM Employee AS e
WHERE e.EmployeeID IN (SELECT p.IdNumber
FROM Population AS p
WHERE p.country = "Canada"
AND p.city = "Toronto");
Bien:
SELECT e.name
, e.salary
FROM Employee AS e
WHERE e.EmployeeID EXISTS (SELECT p.IdNumber
FROM Population AS p
WHERE p.country = "Canada"
AND p.city = "Toronto");
Joins
Utilice las cláusulas ANSI-Standard Join en lugar de las uniones de estilo antiguo.
Malo:
SELECT e.Name,
d.DepartmentName
FROM Departments AS d,
Employees AS e
WHERE d.ID = e.DepartmentID;
Bien:
SELECT e.Name,
d.DepartmentName
FROM Departments AS d
JOIN Employees AS e ON d.ID = e.DepartmentID;
Tables
Para la nomenclatura de las tablas, tengamos en cuenta los siguientes consejos:
- Usar nombres singulares
- Usar prefijo de nombre de esquema
- Usar PascalCase
Malo:
CREATE TABLE Addresses
Bien:
CREATE TABLE [Person].[Address]
Columns
Para nombrar columnas, tengamos en cuenta los siguientes consejos:
- Usar nombres singulares
- Usar PascalCase
- Asignemos un nombre a las claves principales con el formato “[TableName]ID”
- Seamos descriptivos
- Seamos consistentes
Malo:
CREATE TABLE [Person].[Address](
[Address] [int] NOT NULL,
[AddressLine1] [nvarchar](60) NOT NULL,
[Address2] [nvarchar](60) NULL,
[city] [nvarchar](30) NOT NULL,
[State_ProvinceID] [int] NOT NULL,
[postalCode] [nvarchar](15) NOT NULL,
[ModifiedDate] [datetime] NOT NULL,
CONSTRAINT [PK_Address_AddressID] PRIMARY KEY CLUSTERED ([Address])
);
Bien:
CREATE TABLE [Person].[Address](
[AddressID] [int] NOT NULL,
[AddressLine1] [nvarchar](60) NOT NULL,
[AddressLine2] [nvarchar](60) NULL,
[City] [nvarchar](30) NOT NULL,
[StateProvinceID] [int] NOT NULL,
[PostalCode] [nvarchar](15) NOT NULL,
[ModifiedDate] [datetime] NOT NULL,
CONSTRAINT [PK_Address_AddressID] PRIMARY KEY CLUSTERED ([AddressID])
);
Procedures
Para escribir buenos procedimientos almacenados, tengamos en cuenta los siguientes consejos:
- Utilicemos la opción SET NOCOUNT ON al principio del procedimiento almacenado para evitar que el servidor envíe al cliente recuentos de filas de datos afectados por alguna instrucción o procedimiento almacenado.
- Intentemos escribir la DECLARACIÓN y la inicialización (SET) al principio del procedimiento almacenado.
- Escribamos todas las palabras clave de SQL Server en la letra CAPS.
- Evitemos usar ‘sp_’ al principio del nombre del procedimiento almacenado.
- Tengamos en cuenta las secciones anteriores, indenting – Select- joins.
Malo:
CREATE OR ALTER PROCEDURE [HumanResources].[sp_UpdateEmployeePersonalInfo]
@BusinessEntityID [int],
@NationalIDNumber [nvarchar](15),
@BirthDate [datetime],
@MaritalStatus [nchar](1),
@Gender [nchar](1)
AS
BEGIN
--some code
END;
Bien:
@BusinessEntityID [int],
@NationalIDNumber [nvarchar](15),
@BirthDate [datetime],
@MaritalStatus [nchar](1),
@Gender [nchar](1)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @messageLog nvarchar(100);
SET @messageLog = 'Updating the employee personal info';
--some code
END;
Views
No usemos la palabra ‘View’ en el nombre de la vista.
Malo:
– ViewEmployeesDepartments
– EmployeeDeparmentsView
Bien:
– EmployeeDeparments
No incluyamos las condiciones OrderBy y Where en la vista.
Malo:
SELECT EmployeeId,
Name,
LastName,
Address,
State
FROM Employees
WHERE EmployeeId > 0 ORDER BY Name
Bien:
SELECT Name,
LastName,
Address,
State,
City,
Zip
FROM Employees;
Utilicemos Alias con tabla + columna para especificar de dónde provienen los valores.
Malo:
SELECT e.Name,
d.Name
FROM Departments AS d
JOIN Employees AS e ON d.ID = e.DepartmentID;
Bien:
SELECT e.Name as EmployeeName,
d.Name as DeparmentName
FROM Departments AS d
JOIN Employees AS e ON d.ID = e.DepartmentID;
Comments
Escribamos comentarios útiles y solo cuando sea necesario. No escribamos comentarios intentando explicar el código que deberíamos entender leyendo el propio código.
Malo:
CREATE OR ALTER PROCEDURE [dbo].[uspLogError]
@ErrorLogID [int] = 0 OUTPUT
AS
BEGIN
SET NOCOUNT ON;
-- Setting ErrorLogID to 0
SET @ErrorLogID = 0;
//some code
END;
Bien:
CREATE OR ALTER PROCEDURE [dbo].[uspLogError]
@ErrorLogID [int] = 0 OUTPUT
AS
BEGIN
SET NOCOUNT ON;
-- Output parameter value of 0 indicates that error
-- information was not logged
SET @ErrorLogID = 0;
//some code
END;
Modularize
Al escribir una consulta larga, pensemos en modularizar. Con las expresiones de tabla comunes (CTEs) podemos desglosar el código y facilitar su comprensión.
Malo:
SELECT e.name
, e.salary
FROM Employee AS e
WHERE e.EmployeeID EXISTS (SELECT p.IdNumber
FROM Population AS p
WHERE p.country = "Canada"
AND p.city = "Toronto")
AND e.salary >= (SELECT AVG(s.salary)
FROM salaries AS s
WHERE s.gender = "Female")
Bien:
WITH toronto_ppl AS (
SELECT p.IdNumber
FROM Population AS p
WHERE p.country = "Canada"
AND p.city = "Toronto"
)
, avg_female_salary AS (
SELECT AVG(s.salary) AS avgSalary
FROM salaries AS s
WHERE s.gender = "Female"
)
SELECT e.name
, e.salary
FROM Employee AS e
WHERE e.EmployeeID EXISTS (SELECT IdNumber FROM toronto_ppl)
AND e.salary >= (SELECT avgSalary FROM avg_female_salary)
Temporary tables
Prioricemos usar una variable Table cuando el conjunto de resultados es pequeño en lugar de una tabla Temporal. Se creará una tabla temporal en la memoria temporal de la base de datos y esto hará que su consulta sea más lenta.
Malo:
DECLARE TABLE #ListOWeekDays
(
DyNumber INT,
DayAbb VARCHAR(40),
WeekName VARCHAR(40)
)
Bien:
DECLARE @ListOWeekDays TABLE
(
DyNumber INT,
DayAbb VARCHAR(40),
WeekName VARCHAR(40)
)
Looping
Evitemos ejecutar consultas mediante un bucle. La codificación de consultas SQL en bucles ralentiza toda la ejecución.
Malo:
WHILE @date <= @endMonth
BEGIN
SET @date = DATEADD(d, 1, @date)
END
Bien:
DECLARE @StartDate DateTime = '2021-06-01'
DECLARE @EndDate DateTime = '2021-06-29'
;WITH populateDates (dates) AS (
SELECT @StartDate as dates
UNION ALL
SELECT DATEADD(d, 1, dates)
FROM populateDates
WHERE DATEADD(d, 1, dates)<=@EndDate
)
SELECT *
INTO dbo.SomeTable
FROM populateDates
Al solucionar problemas
- Utilicemos la herramienta Plan de Ejecución si la tenemos disponible, con ella podríamos ver las estadísticas y advertencias, que pueden ayudarnos a encontrar áreas de mejora.
- Utilicemos la coma antes de la columna, es útil cuando por alguna razón tenemos que comentar columnas.
Referencia: