Está SELECT * ok en un Trigger. ¿O estoy pidiendo problemas?

8

Estoy atrapado en un debate en el trabajo, y necesito algunos consejos sobre posibles trampas que podría pasar por alto.

Imagine un escenario en el que se utiliza un disparador para copiar registros eliminados a una tabla de auditoría. El activador usa SELECT *. Todos señalan y gritan y nos dicen lo malo que es esto.

Sin embargo, si se realiza una modificación en la Estructura de la Tabla principal, y se pasa por alto la Tabla de auditoría, entonces el Disparador generará un error para que las personas sepan que la tabla de Auditoría también necesita modificación.

El error se detectará durante las pruebas en nuestros servidores DEV. Pero debemos asegurarnos de que los Matches de producción sean DEV, por lo que permitimos SELECCIONAR * en los Sistemas de producción (solo disparadores).

Entonces, mi pregunta es: me están presionando para eliminar SELECT *, pero no estoy seguro de qué otra manera asegurarme de que estamos capturando automáticamente errores de desarrollo de esta naturaleza, ¿alguna idea o es esta mejor práctica?

He reunido un ejemplo a continuación:

--Create Test Table
CREATE TABLE dbo.Test(ID INT IDENTITY(1,1), Person VARCHAR(255))
--Create Test Audit Table
CREATE TABLE dbo.TestAudit(AuditID INT IDENTITY(1,1),ID INT, Person VARCHAR(255))

--Create Trigger on Test
CREATE TRIGGER [dbo].[trTestDelete] ON [dbo].[Test] AFTER DELETE
NOT FOR REPLICATION
AS
BEGIN
    SET NOCOUNT ON;
    INSERT  dbo.TestAudit([ID], [Person])
    SELECT  *
    FROM    deleted
END

--Insert Test Data into Test
INSERT INTO dbo.Test VALUES
('Scooby')
,('Fred')
,('Shaggy')

--Perform a delete
DELETE dbo.Test WHERE Person = 'Scooby'

ACTUALIZACIÓN (reformulación de la pregunta):

Soy un DBA y necesito asegurarme de que los Desarrolladores no proporcionen scripts de implementación mal pensados ​​al contribuir a nuestra Documentación de mejores prácticas. SELECT * provoca un error en DEV cuando el desarrollador pasa por alto la tabla de auditoría (esta es una red de seguridad), por lo que el error se detecta al principio del proceso de desarrollo. Pero en algún lugar de la Constitución de SQL: la segunda enmienda dice "No usarás SELECT *". Así que ahora hay un impulso para deshacerse de la red de seguridad.

¿Cómo reemplazaría la red de seguridad, o debería considerar que esta es la mejor práctica para los activadores?

ACTUALIZACIÓN 2: (solución)

Gracias por todos sus comentarios, no estoy seguro si tengo una respuesta clara porque parece ser un tema muy gris. Pero colectivamente, ha proporcionado puntos de discusión que pueden ayudar a nuestros desarrolladores a avanzar en la definición de sus mejores prácticas.

Gracias Daevinpor su contribución, su respuesta proporciona la base para algunos mecanismos de prueba que nuestros Desarrolladores pueden implementar. +1

Gracias CM_Dayton, sus sugerencias que contribuyen a las mejores prácticas pueden ser beneficiosas para cualquier persona que esté desarrollando Desencadenadores de auditoría. +1

Muchas gracias a ypercube, has pensado mucho sobre los problemas relacionados con las tablas que experimentan diferentes formas de cambios de definición. +1

En conclusión:

Is Select * ok in a tigger? Sí, es un área gris, no siga ciegamente la ideología "Seleccionar * es malo".

Am I asking for Trouble? Sí, hacemos más que solo agregar nuevas columnas a las tablas.

pacreely
fuente
te respondes en la pregunta. select * se romperá si se cambia la tabla de origen. Para asegurarse de que dev y prod sean iguales, use alguna forma de control de código fuente.
Bob Klimes
pregunta un poco más amplia, ¿con qué frecuencia elimina registros y cuántos como porcentaje total de la tabla? una alternativa a los desencadenantes sería tener un indicador de bits que marque las filas como eliminadas y un trabajo de agente que se ejecute en una programación para moverlos a una tabla de registro. Puede incorporar las comprobaciones de trabajo del agente para ver si el esquema de la tabla coincide y el trabajo simplemente fallará si hay un problema con ese paso hasta que se solucione.
Tanner
Por lo general, estoy de acuerdo con SELECT *ser perezoso, pero como tienes una razón legítima para usarlo, es más gris que blanco y negro. Lo que debe intentar hacer es algo como esto , pero ajústelo para que no solo tenga el mismo recuento de columnas, sino que los nombres de columna y los tipos de datos sean los mismos (ya que alguien podría cambiar los tipos de datos y aún causar problemas en la base de datos que normalmente no se detectan) con su SELECT *'red de seguridad'.
Daevin
3
Me gusta la idea de usar SELECT *como red de seguridad, pero no atrapará todos los casos. Por ejemplo, si suelta una columna y la agrega nuevamente. Esto cambiará el orden de las columnas y (a menos que todas las columnas sean del mismo tipo) las inserciones en la tabla de auditoría fallarán o provocarán la pérdida de datos debido a las conversiones de tipo implícitas.
ypercubeᵀᴹ
2
También me pregunto cómo funcionará su diseño de auditoría cuando se elimine una columna de una tabla. ¿También elimina la columna de la tabla de auditoría (y pierde todos los datos de auditoría anteriores)?
ypercubeᵀᴹ

Respuestas:

10

Típicamente, se considera programación "perezosa".

Dado que está insertando específicamente dos valores en su TestAudittabla aquí, tendré cuidado de asegurarme de que su selección también obtenga exactamente dos valores. Porque si, por alguna razón, esa Testtabla tiene o alguna vez obtiene una tercera columna, este disparador fallará.

No está directamente relacionado con su pregunta, pero si está configurando una tabla de auditoría, también agregaría algunas columnas adicionales a su TestAudittabla para ...

  • realizar un seguimiento de la acción que está auditando (eliminar en este caso, inserciones o actualizaciones)
  • columna de fecha / hora para rastrear cuándo ocurrió el evento de auditoría
  • columna de ID de usuario para rastrear quién llevó a cabo la acción que está auditando.

Entonces eso resulta en una consulta como:

INSERT dbo.TestAudit([ID], [Person], [AuditAction], [ChangedOn], [ChangedBy])
SELECT [ID], [Person], 
   'Delete', -- or a 'D' or a numeric lookup to an audit actions table...
   GetDate(), -- or SYSDATETIME() for greater precision
   SYSTEM_USER -- or some other value for WHO made the deletion
FROM deleted

De esa forma, obtiene las columnas exactas que necesita y audita de qué / cuándo / por qué / de quién trata el evento de auditoría.

Leva
fuente
"ID de usuario" Este es complicado con la auditoría. Por lo general, las cuentas de la base de datos no corresponden a usuarios reales. Con mucha más frecuencia, corresponden a una sola aplicación web u otro tipo de componente, con un único conjunto de credenciales utilizadas por ese componente. (Y a veces, los componentes también compartirán credenciales). Por lo tanto, las credenciales de la base de datos son bastante inútiles como un identificador de quién hizo qué, a menos que solo esté interesado en qué componente lo hizo. Pero transmitir datos de aplicaciones que identifiquen al "quién" no es exactamente fácil con una función de disparo, que yo sepa.
jpmc26
ver actualización de la pregunta.
Pacreely
Otro problema que podría surgir con SELECT * en general (aunque probablemente no en su ejemplo) es que si las columnas de la tabla subyacente no están en el mismo orden que las columnas de inserción, la inserción fallará.
CaM
3

Comenté esto en su pregunta, pero pensé que intentaría presentar una solución de código.

Por lo general, estoy de acuerdo con SELECT *ser flojo, pero como tienes una razón legítima para usarlo, es más gris que blanco y negro.

Lo que debería (en mi opinión) intentar hacer es algo como esto , pero ajústelo para asegurarse de que los nombres de columna y los tipos de datos sean los mismos (ya que alguien podría cambiar los tipos de datos y aún causar problemas en la base de datos que normalmente no se detectan con su SELECT *seguridad red'.

Incluso podría crear una función que le permita verificar rápidamente si la versión de auditoría de la tabla coincide con la versión que no es de auditoría:

-- The lengths are, I believe, max values for the corresponding db objects. If I'm wrong, someone please correct me
CREATE FUNCTION TableMappingComparer(
    @TableCatalog VARCHAR(85) = NULL,
    @TableSchema VARCHAR(32) = NULL,
    @TableName VARCHAR(128) = NULL) RETURNS BIT
AS
BEGIN
    DECLARE @ReturnValue BIT = NULL;
    DECLARE @VaryingColumns INT = NULL;

    IF (@TableCatalog IS NOT NULL
            AND @TableSchema IS NOT NULL
            AND @TableName IS NOT NULL)
        SELECT @VaryingColumns = COUNT(COLUMN_NAME)
            FROM (SELECT COLUMN_NAME,
                        DATA_TYPE -- Add more columns that you want to ensure are identical
                    FROM INFORMATION_SCHEMA.COLUMNS
                    WHERE TABLE_CATALOG = @TableCatalog
                        AND TABLE_SCHEMA = @TableSchema
                        AND TABLE_NAME = @TableName
                EXCEPT
                    SELECT COLUMN_NAME,
                            DATA_TYPE -- Add more columns that you want to ensure are identical
                        FROM INFORMATION_SCHEMA.COLUMNS
                        WHERE (TABLE_CATALOG = @TableCatalog
                            AND TABLE_SCHEMA = @TableSchema
                            AND TABLE_NAME = @TableName + 'Audit')
                            AND (COLUMN_NAME != 'exclude your audit table specific columns')) adt;
    IF @VaryingColumns = 0
        SET @ReturnValue = 1
    ELSE IF @VaryingColumns > 0
        SET @ReturnValue = 0

    RETURN @ReturnValue;
END;

El SELECT ... EXCEPT SELECT ...Auditle mostrará qué columnas en la tabla no están en la tabla de Auditoría. Incluso podría cambiar la función para devolver el nombre de columnas que no son iguales en lugar de solo si se asignan o no, o incluso generar una excepción.

A continuación, puede ejecutar este antes de pasar de DEVa PRODUCTIONlos servidores para cada tabla en el PP, usando un cursor sobre:

SELECT TABLE_NAME
    FROM INFORMATION_SCHEMA.TABLES
    WHERE NOT (TABLE_NAME LIKE '%Audit')
Daevin
fuente
1
ver actualización de la pregunta
secreto
Me alegro de poder ayudar. Gracias por leer todas las respuestas y traerlas de vuelta a su equipo para sugerencias; ¡La adaptabilidad y la disposición a mejorar son las formas en que los departamentos de tecnología mantienen a las empresas funcionando y funcionando sin problemas! : D
Daevin
0

La declaración que indicará el activador fallará y el activador fallará. Sería una mejor práctica documentar el desencadenante y el seguimiento de auditoría para que sepa modificar la consulta para agregar las columnas en lugar de especificar el *.

Como mínimo, debe modificar el desencadenador para que pueda fallar con gracia al registrar errores en una tabla y quizás poner una alerta en la tabla en la que el desencadenador registra los errores.

Esto también recuerda, puede poner un activador o alerta cuando alguien altera la tabla y agrega más columnas o elimina columnas, para notificarle que agregue el activador.

En cuanto al rendimiento, creo que * no cambia nada, solo aumenta las posibilidades de fallas en el futuro cuando las cosas cambian y también puede causar latencia de red cuando extrae más información a través de la red cuando lo necesita. Hay un momento y un lugar para *, pero creo que, como se describió anteriormente, tiene mejores soluciones y herramientas para probar.

Shaulinator
fuente
0

Si las estructuras de su tabla de auditoría o original cambian, está garantizando que tendrá un problema con su select *.

INSERT INTO [AuditTable]
(Col1,Col2,Col3)
SELECT * 
FROM [OrigTable] or [deleted];

Si cualquiera de los dos cambia, el disparador producirá un error.

Podrías hacerlo:

INSERT INTO [AuditTable]
SELECT * 
FROM [OrigTable];

Pero como dice CM_Dayton, esa es una programación perezosa y abre la puerta a otras inconsistencias. Para que este escenario funcione, debe asegurarse absolutamente de que está actualizando la estructura de ambas tablas al mismo tiempo.

MguerraTorres
fuente