Buenas Practicas Desarrollo SQL Server
Buenas Practicas Desarrollo SQL Server
Objetivo de la Guía
El objetivo de esta guía es tener un documento en el cual se enumeren las malas prácticas de
trabajo sobre SQL Server las cuales afectarían algunas características como la performance,
funcionales y mantenimiento.
Esta guía deberá ser actualizada con cada nueva versión de SQL Server que Sancor migre,
actualmente como SQL server de plataforma está la versión 2008.
Diseño de tablas
Utilizar el tipo de dato correcto ayuda a que luego en las consultas y operaciones no se deban
aplicar conversiones o bien funciones que puedan afectar la performance y lo funcional.
Como dominio si utilizamos otro tipo de datos a los adecuados para cada situación se debería
agregar constraint de Check para hacer las validaciones, por ejemplo si para una fecha se usara
char(8) sería necesario usar los check para evitar datos no validos como una fecha (por ejemplo
‘ABCD’)
Tipos de datos a deprecar (no se usaran más en versiones futuras de SQL Server)
Los tipos de datos a deprecar no se deberían utilizar en nuevos desarrollos ya que los mismos
serán removidos en futuras versión de SQL Server.
Este es un listado de los tipos de datos que no se deberían usar en nuevos desarrollos.
Esta buena práctica permite no tener tablas donde no se pueda identificar registros como así
también que se generara de forma automática el índice CLuster (ver luego notas adjuntas)
● Usar claves naturales a menos que la PK sea mayor a 4 campos , en este caso utilizar claves
artificiales como pueden ser los Identity y GUID.
● Analizar en cada caso si la PK debe ser el índice Cluster de la tabla
o Por ejemplo en una tabla de movimientos la PK es muy probable que sea el
numero o ID pero las consultas se buscan por Fecha, si dejamos el Default el
Cluster será el ID y estará desaprovechado
● Evitar que dentro de la PK existan campos actualizables
Clustered Index (Amarillo)
● Toda tabla de usuario debe contener un índice Clustered, el mismo ayudara no solo en el
ordenamiento sino que además en varios puntos de performance.
http://msdn.microsoft.com/en-us/library/cc917672.aspx
http://www.sql-server-performance.com/tips/clustered_indexes_p1.aspx
Las relaciones entre tablas deben estar definidas dentro del motor relacional y no fuera de este
mismo.
La buena práctica de armar las reglas de integridad referencial en el motor es que desde cualquier
sistema donde se quiera manipular datos el esquema es consistente.
Por lo cual no se van a poder armar relaciones de integridad referencial en los sistemas.
if exists (select id
from inserted
where ID
not in
(
select ID from titles
)
begin
raiserror 20001 "Invalid title: title_id
does not exist on titles table"
rollback transaction
return
end
return
● Evitar que dentro del nombre del campo se haga mención al tipo de dato, esta técnica no
permite luego tener un modelo ordenado cuando cambian los tipos de datos.
● Utilizar los comentarios de los campos para poner especificaciones y descripción del
mismo así queda en todo el SQL Server el catalogo lo más detallado posible.
● No usar caracteres no alfanuméricos (&%/(,etc) , espacios y acentos, esto luego dificulta
las querys. (si es valido usar _ y - )
Nomenclatura de Tablas (Verde)
● No usar caracteres no estándar (&%/(,etc) , espacios y acentos, esto luego dificulta las
querys, (si es valido usar _ y - )
● No usar en el nombre nada que tenga que ver con el modulo del sistema, para ello utilizar
Schemas
● Se deberían armar índices para las condiciones de WHERE y JOIN como mínimo.
● Evitar la superposición de índices (esto genera mayor costo de mantenimiento y tiene un
fuerte impacto en la performance)
● No armar índices sobre campos con mucha selectividad (por ejemplo Sexo), esto hará que
en muchas de las querys se haga un Scan ya que el índice termina no siendo eficiente por
la selectividad de datos
● Utilizar índices con filtro para los Flag (Leído / No Leído, Procesado / No Procesado /
ENVIOSPDF)
o La utilización de índices con filtro en estos casos hace ahorrar recursos de espacio
en disco.
SELECT A.PostalCode
FROM #Address2 AS a
WHERE A.StateProvinceID = 42
De optar por almacenar este tipo dentro de la base de datos, será necesario armar una tabla
adicional donde solo se contendrá el ID del registro identifícate y su información BLOB.
Esto ayuda a los efectos de poder tener mayor control sobre la tabla como así también poder
aplicar distintas políticas administrativas , como así también forzar un JOIN para traer los objetos y
así proteger del uso inadecuado de SELECT * sobre la tabla principal.
CREATE TABLE
DBO.ARTICULOS (ID INT PRIMARY KEY,
NOMBRE VARCHAR(100),
STOCK DECIMAL (18,2))
GO
UPDATE DBO.ARTICULOS
SET STOCK = STOCK + T.PARCIAL
FROM DBO.ARTICULOS A
INNER JOIN
( SELECT ARTICULO_ID,
SUM(CASE WHEN TIPO='I' THEN CANTIDAD ELSE -CANTIDAD END)
AS PARCIAL FROM INSERTED
GROUP BY ARTICULO_ID
) T
ON
A.ID = T.ARTICULO_ID
END
GO
UPDATE DBO.ARTICULOS
SET STOCK = STOCK - T.PARCIAL
FROM DBO.ARTICULOS A
INNER JOIN
( SELECT ARTICULO_ID,
SUM(CASE WHEN TIPO='I' THEN CANTIDAD ELSE -CANTIDAD END)
AS PARCIAL FROM DELETED
GROUP BY ARTICULO_ID
) T
ON
A.ID = T.ARTICULO_ID
END
GO
● Tratar de utilizar Schemas , estos permiten agrupar los objetos de distintas formas
Programabilidad
En los distintos querys solamente llevar a la aplicación los campos y registros necesarios para
evitar una sobrecarga al motor relacional.
Por ejemplo si se quiere hacer un filtro por un campo que se aplique sobre la base y no sobre el
resulset de dicha aplicación.
Esto mejorara la performance no solo en el servidor de base de datos sino que entre la aplicación y
el SQL Server
La utilización del SELECT * no es una buena práctica de trabajo ya que traerá no solo que todos los
campos (por ejemplo si hay un campo image ) sino que además de existir algún índice Cover no
será aprovechado y esto tendrá efectos negativos en la performance.
El motor de base de datos esta optimizado para código orientado a teoría de conjuntos y no
iterativa.
Cualquier utilización de código iterativo (Cursores / While) hará que la performance se degrade de
forma importante, aumentando también así los loqueos
Se define entonces como practica que toda necesidad de aplicar un cursor / while deberá ser
analizado por distintos responsables en el área de desarrollo.
Estos son algunos casos donde se podrían aplicar cursores / whiles ya que no hay alternativas, en
el resto de los casos deberá ser analizado.
● Renovación de Pólizas
● Impresiones masivas
● Emisión de pólizas automáticas
La utilización de los alias asegura que se seleccionen los campos adecuados cuando hay más de un
conjunto.
Utilizar [ ] para objetos (tablas / vistas / funciones / Stores Procedures) y campos. (Verde)
Dentro del motor de base de datos van apareciendo distintas palabras reservadas, si nuestra
codificación esta en ingles seremos más propensos a poder caer en alguna palabra reservada.
Utilizando [] evitamos cualquier problema con este punto como así también si existen caracteres
no alfanuméricos en la nomenclatura.
En la utilización de la instrucción INSERT será necesario que se declaren los campos a ser
insertados.
Si no se realiza esta declaración se tomaran todos los campos de la tabla, lo que implica que ante
un nuevo campo hay que modificar si o si la sentencia ya que de lo contrario emitirá un error.
La utilización de OLD Style JOIN esta deprecada en SQL Server 2005 y superior.
● JOIN
● LEFT JOIN
● RIGHT JOIN
● FULL JOIN
● CROSS JOIN
● APPLY
-- ANSI SQL-92
-- ANSI SQL-89
-- ANSI SQL-89
SELECT E.empid, E.firstname, E.lastname, O.orderid
FROM HR.Employees AS E, Sales.Orders AS O
WHERE E.empid = O.empid; -- MAL
GO
Utilizar Exist en lugar de COUNT para cuando es necesario encontrar registro y aplicar algún
condicional (Amarillo)
En varios de nuestros procesos nos podemos encontrar ante la necesidad de buscar si existen
registros con determinada condición y luego seguir en el proceso con el resto de los pasos.
Para hacer esta operación se debe utilizar Exist en lugar de Count ya que es mucho más eficiente
DECLARE @n INT
SELECT @n = COUNT(*)
FROM Sales.SalesOrderDetail AS sod
WHERE sod.OrderQty = 1
IF @n > 0
PRINT 'Record Exists' NO USAR
Los índices por más que estén creados en nuestras tablas, dependiendo de cómo escribamos las
querys podemos lograr que no se utilicen de forma eficiente, esto quiere decir que se hará una
operación de SCAN en lugar de un SEEK.
Las querys deben ser SARG’s compatibles para que los índices sean usados de forma eficiente.
La manipulación del campo indexado en una query dentro del WHERE, JOIN, Order By, Group BY o
función de agregación hará que la misma no sea eficiente.
SELECT * FROM
Purchasing.PurchaseOrderHeader
WHERE PurchaseOrderID * 2 = 3400 MAL
SELECT * FROM
Purchasing.PurchaseOrderHeader
WHERE PurchaseOrderID = 3400 / 2 ok
SELECT ORDERDATE FROM
Sales.SalesOrderHeader
WHERE YEAR(OrderDate) = 2001 MAL
Select theDate
from aTable
where datediff(day, theDate, getdate()) < 3 MAL
Select theDate
from aTable
where theDate < @afterDate
HINT (Rojo)
Los HINT permiten cambiar el comportamiento de una query e indicarle a SQL Server que en lugar
que el analice cual es el mejor camino (mas rápido) lo hagamos nosotros por él.
Esto puede implicar grandes perjuicios de performance a futuro ya que los sistemas no son
estáticos.
Como regla general no está permitido la utilización de ningún tipo de HINT y solo se podrán aplicar
en casos donde se haya estudiado con los DBA que para esa query era la mejor alternativa.
SELECT * FROM
Purchasing.PurchaseOrderHeader
WITH (INDEX (PK_PURCHASEORDERHEADER_PURCHASEORDERID))
WHERE PurchaseOrderID * 2 = 3400
Se deberá indicar el OWNER del objeto tanto en los FROM como en los JOIN.
Esto permite ser más eficiente la búsqueda del objeto dentro SQL Server ya que puede existir un
mismo nombre de objeto para distinto schema
Order BY (Amarillo)
En esta sección se indicaran los tips a tener en cuenta para que el uso de esta sintaxis no
perjudique la performance.
La necesidad de aplicar ordenamiento en la mayoría de los casos está relacionado con una capa de
presentación más que con la de datos.
● Si se utiliza la cláusula TOP (no 100 Percent) es necesario usar Order BY para determinar
los registros
● Si no hay índices sobre los campos del ORDER BY entonces analizar el query plan , si la
operación SORT supera el 20% entonces evitar la utilización de ORDER BY en donde no se
use TOP, y en donde se utilice analizar los índices necesarios a crear o bien ordenar desde
la aplicación con el resulset ya procesado.
Stores Procedures (Amarillo)
La utilización de Stores procedures es una buena práctica de desarrollo contra el motor ya que
beneficia a los siguientes ítems
No se podrá utilizar como Prefijo SP_ ya que es utilizado por SQL Server como de sistema lo que
implica doble comprobación a la hora de ejecución (primero en la master y luego en la base que
estamos posicionados) degradando así los tiempos de ejecución.
Como primer sintaxis dentro del código utilizar SET NOCOUNT ON para no enviar información
de registros afectados hacia la aplicación y así generar una sobrecarga adicional.
En muchos casos es necesario la utilización de WHERE en forma dinámica, por ejemplo dentro de
un SP con varios parámetros que el usuario puede completar, algunos ejemplos donde es
necesario esta utilidad seria: (Impresión de caratula, impresión masiva, lectura de tramites).
Para poder hacer dicha operación se deberá realizar con SQL Dinámico SP_EXECUTESQL para
armar la concatenación del WHERE solamente.
Al Store Procedure habrá que cambiarle el contexto de ejecución distinto de CALLER (por ejemplo
un usuario definido) ya que de lo contrario habrá que darle permisos al que ejecute el Store no
solo de EXECUTE sino que además a los objetos que se utilicen dentro del SQL Dinámico.
http://www.sommarskog.se/dyn-search-2008.html
Utilizar parámetros con nombres para los campos y luego armar el SQL dentro del SP y no tener un
solo parámetro @sql y pasarlo directamente, esto es a los efectos de disminuir la injection de
código.
Dentro de la cláusula WHERE y JOIN se deberán respetar el orden de los campos del índice a
utilizar.
Por ejemplo si tenemos un índice conformado por CIUDAD y luego ZONA nuestra consulta debería
estar armada de la siguiente manera
La utilización de vistas solamente está permitido en casos donde se necesite hacer una interfaz
entre base de datos (por ejemplo Extrared)
No está permitido la utilización de vistas que llaman a su vez a otras vistas, esta técnica además de
ser poco legible a la hora de buscar el funcionamiento, atenta contra la performance ya que
muchas veces no es necesario para la query que deseamos obtener tener acceso a todas las tablas
que hacen referencias cada una de estas vistas.
Las vistas deberán tener una nomenclatura un prefijo en su nomenclatura que permita
identificarlas dentro del código.
Al utilizar los campos identity es necesario en alguno de los procesos determinar qué valor se
insertó para luego poderlo utilizar.
Por ejemplo si se dispone de un maestro / Detalle y es necesario para insertar los detalles saber el
valor identity que se generó en el maestro.
Para hacer esta operación se debe utilizar la función SCOPE_IDENTITY ().Esta función
determina el ultimo valor identity del Scope (por ejemplo el Store Procedure que estoy
ejecutando)
Si se utiliza Para @@identity Si se utiliza esta función se retornara el ultimo valor de la sesión
pero no del Scope.
SELECT SCOPE_IDENTITY () OK
http://blog.sqlauthority.com/2007/03/25/sql-server-identity-vs-scope_identity-vs-ident_current-
retrieve-last-inserted-identity-of-record/
Consultar fechas: (Rojo)
Las fechas en los tipos de datos dedicados a ella no se guardan en ningún formato (dd-mm-yyyy o
mm-dd-yyyy) ya que se almacena en Bytes.
El formato de las fechas depende del lenguaje definido en el login de SQL Server.
Para evitar problemas con los distintos formatos es necesario utilizar el estándar ANSI
(YYYYMMDD HH:mm:ss)
Los siguientes ejemplos ilustran la forma de manejar las fechas en este formato
SELECT * FROM
Production.TransactionHistory
WHERE TRANSACTIONDATE >='09-01-2003'
AND TRANSACTIONDATE < '09-03-2003' mal
SELECT * FROM
Production.TransactionHistory
WHERE TRANSACTIONDATE >='20030901'
AND TRANSACTIONDATE < '20030903'
Para consultar un rango de fechas no se debe utilizar el BETWEEN cuando el tipo de dato no sea
Date ya que el mismo representa >= and <= y los otros tipos de datos guardan fecha y hora.
Los siguientes ejemplos ilustran como se debe escribir la query para traer los registro del
1/12/2010
En su lugar trate de utilizar funciones más avanzadas de TSQL (CTE, Tablas derivadas, etc).
La utilización de tablas temporales deberá debidamente ser justificada ya que en SQL 2008 con las
diferentes funcionalidades nuevas al lenguaje son menos necesarias.
Algunas situaciones donde se podrían utilizar cuando un resulset es necesario trabajarlo en varias
partes dentro del Scope (Store Procedure / Funcion / Trigger)
Si el resultado final es muy inferior a la cantidad de registros insertados en una tabla temporal
existirá una pérdida de performance en esos casos y deberán ser analizados
Los subquerys a nivel campo son aquellas consultas que se generan para mostrar una query
dentro de un campo.
Si dicha query requiere el registro externo para determinar el valor entonces se estará evaluando
la misma por cada uno y esto en lo general afectara la performance, por lo cual su utilización debe
ser reducida y tratar de utilizar otras funciones como puede ser APPLY
SELECT O1.articulo,O1.nrofac,O1.precio
FROM #auxi AS O1
WHERE O1.precio =
(SELECT MAX(O2.precio)
FROM #auxi AS O2
WHERE O2.nrofac = O1.nrofac )
order by O1.nrofac ok
select venta,
(select SUM(venta) from ventas) as total_ventas
FROM VENTAS OK- SOLO SE EJECUTA UNA VEZ YA QUE EL VALOR
VENTA_TOTAL NO DEPENDE DE CADA REGISTRO
(
SELECT SUM(val) AS sumval, AVG(val) AS avgval
FROM dbo.MyOrderValues
)
SELECT orderid, custid, val,
CAST(val / sumval * 100. AS NUMERIC(5, 2)) AS pct,
CAST(val - avgval AS NUMERIC(12, 2)) AS diff
FROM dbo.MyOrderValues
CROSS JOIN Aggs; ok
Las transacciones deberán ser controladas pos las sintaxis BEGIN TRAN / COMMIT TRAN /
ROLLBACK TRAN
● Deberán ser lo más corta posibles ya que el tiempo afectara a los loqueos que se verán en
problemas de performance.
o Esto implica que dentro del código de la transacción solo deba ir lo necesario.
● Si hay anidamiento de transacciones no hacer evaluaciones del estado de la transacción ya
que estas cosas estirarían el tiempo de la misma, definir bien al ACID adecuado.
● No usar funciones Select dentro de una transacción
En el siguiente ejemplo se muestra como el ACID no está bien definido, ya que si la auditoria sale
mal igual se hace la transacción, en este caso sería conveniente que primero se haga el insert y
luego la auditoria en otra transacción separada y no anidada ya que por más que de error se ha
definido como regla que se continúe.
Al estar el código escrito así lo que se lograra es un mayor tiempo de duración en la transacción
BEGIN TRAN
EXEC AUDITAR
IF @@ERROR <> OR @@ERROR = 0 -- si hay o no errores en auditar
BEGIN
COMMIT TRAN -- hacemos igual el COMMIT
END
Triggers (Rojo)
Los triggers forman parte de cada una de las transacciones (sean explicitas o implícitas) lo cual el
código que deben contener debe estar con las mejores prácticas para así evitar un mayor tiempo
de duración de las transacciones y que esto impacte en mayores loqueos y a su vez en
performance.
La siguiente lista son las consideraciones que se deben hacer en los Triggers
El control de errores dentro del TSQL es fundamental para que nuestros procesos sean
consistentes.
begin try
SELECT 1/0 AS DIVISION
end try
begin catch
raiserror('Se genero un error en la operacion',16,1)
end catch
Si dentro del código TSQL es necesario acceder a objetos que no están dentro de la misma base de
datos se deberán utilizar o vistas o Sinónimos (este no solo es útil para tablas sino que también
para el resto de los objetos)
SELECT ID
FROM RRHH.HUMANRESOURCES.EMPLOYEE -MAL
Las funciones a deprecar son aquellas que no estarán más soportadas en nuevas versión de SQL
Server y que deben ser reemplazadas.
No se deberán usar ninguna de estas funciones para nuevos desarrollos (incluyendo reingeniería)
http://msdn.microsoft.com/en-us/library/ms143729.aspx