Presumo que o banco de dados esteja sempre verificando os valores padrão, mesmo que eu forneça o valor correto, portanto, estou fazendo o mesmo trabalho duas vezes.
Hum, por que você assumiria isso? ;-). Dado que os padrões existem para fornecer um valor quando a coluna à qual eles estão anexados não está presente na INSERT
declaração, eu assumiria exatamente o oposto: que eles serão completamente ignorados se a coluna associada estiver presente na INSERT
declaração.
Felizmente, nenhum de nós precisa assumir algo devido a esta afirmação na pergunta:
Estou mais interessado em desempenho.
Perguntas sobre desempenho são quase sempre testáveis. Então, precisamos apenas fazer um teste para permitir que o SQL Server (a verdadeira autoridade aqui) responda a essa pergunta.
CONFIGURAÇÃO
Execute o seguinte uma vez:
SET NOCOUNT ON;
-- DROP TABLE #HasDefault;
CREATE TABLE #HasDefault
(
[HasDefaultID] INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
[SomeInt] INT NULL,
[SomeDate] DATETIME NOT NULL DEFAULT (GETDATE())
);
-- DROP TABLE #NoDefault;
CREATE TABLE #NoDefault
(
[NoDefaultID] INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
[SomeInt] INT NULL,
[SomeDate] DATETIME NOT NULL
);
-- make sure that data file and Tran Log file are grown, if need be, ahead of time:
INSERT INTO #HasDefault ([SomeInt])
SELECT TOP (2000000) NULL
FROM [master].sys.[all_columns] ac1
CROSS JOIN [master].sys.[all_columns] ac2;
Execute os testes 1A e 1B individualmente, não juntos, pois isso distorce o tempo. Execute cada uma delas várias vezes para ter uma noção do tempo médio de cada uma.
Teste 1A
TRUNCATE TABLE #HasDefault;
GO
PRINT '#HasDefault:';
SET STATISTICS TIME ON;
INSERT INTO #HasDefault ([SomeDate])
SELECT TOP (1000000) '2017-05-15 10:11:12.000'
FROM [master].sys.[all_columns] ac1
CROSS JOIN [master].sys.[all_columns] ac2;
SET STATISTICS TIME OFF;
GO
Teste 1B
TRUNCATE TABLE #NoDefault;
GO
PRINT '#NoDefault:';
SET STATISTICS TIME ON;
INSERT INTO #NoDefault ([SomeDate])
SELECT TOP (1000000) '2017-05-15 10:11:12.000'
FROM [master].sys.[all_columns] ac1
CROSS JOIN [master].sys.[all_columns] ac2;
SET STATISTICS TIME OFF;
GO
Execute os testes 2A e 2B individualmente, não juntos, pois isso distorce o tempo. Execute cada uma delas várias vezes para ter uma noção do tempo médio de cada uma.
Teste 2A
TRUNCATE TABLE #HasDefault;
GO
DECLARE @Counter INT = 0,
@StartTime DATETIME,
@EndTime DATETIME;
BEGIN TRAN;
--SET STATISTICS TIME ON;
SET @StartTime = GETDATE();
WHILE (@Counter < 100000)
BEGIN
INSERT INTO #HasDefault ([SomeDate]) VALUES ('2017-05-15 10:11:12.000');
SET @Counter = @Counter + 1;
END;
SET @EndTime = GETDATE();
--SET STATISTICS TIME OFF;
COMMIT TRAN;
PRINT DATEDIFF(MILLISECOND, @StartTime, @EndTime);
Teste 2B
TRUNCATE TABLE #NoDefault;
GO
DECLARE @Counter INT = 0,
@StartTime DATETIME,
@EndTime DATETIME;
BEGIN TRAN;
--SET STATISTICS TIME ON;
SET @StartTime = GETDATE();
WHILE (@Counter < 100000)
BEGIN
INSERT INTO #NoDefault ([SomeDate]) VALUES ('2017-05-15 10:11:12.000');
SET @Counter = @Counter + 1;
END;
SET @EndTime = GETDATE();
--SET STATISTICS TIME OFF;
COMMIT TRAN;
PRINT DATEDIFF(MILLISECOND, @StartTime, @EndTime);
Você deve ver que não há diferença real no tempo entre os testes 1A e 1B, ou entre os testes 2A e 2B. Portanto, não, não há penalidade de desempenho para ter um DEFAULT
definido, mas não usado.
Além disso, além de documentar apenas o comportamento pretendido, você deve ter em mente que é principalmente você quem se importa com as instruções DML sendo completamente contidas nos procedimentos armazenados. O pessoal de suporte não se importa. Os desenvolvedores futuros podem não estar cientes do seu desejo de ter todo o DML encapsulado nesses procedimentos armazenados ou se importar, mesmo que eles saibam. E quem mantiver esse banco de dados após a sua saída (outro projeto ou trabalho) pode não se importar, ou pode não ser capaz de impedir o uso de um ORM, não importa o quanto eles protestem. Portanto, os Padrões podem ajudar a dar uma folga às pessoas ao fazer um INSERT
, especialmente um ad-hoc INSERT
feito por um representante de suporte, pois essa é uma coluna que eles não precisam incluir (é por isso que eu sempre uso padrões na auditoria colunas de data).
E, ocorreu-me apenas que pode ser mostrado objetivamente se a DEFAULT
está marcada ou não quando a coluna associada está presente na INSERT
instrução: simplesmente forneça um valor inválido. O teste a seguir faz exatamente isso:
-- DROP TABLE #BadDefault;
CREATE TABLE #BadDefault
(
[BadDefaultID] INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
[SomeInt] INT NOT NULL DEFAULT (1 / 0)
);
INSERT INTO #BadDefault ([SomeInt]) VALUES (1234); -- Success!!!
SELECT * FROM #BadDefault; -- just to be sure ;-)
INSERT INTO #BadDefault ([SomeInt]) VALUES (DEFAULT); -- Error:
/*
Msg 8134, Level 16, State 1, Line xxxxx
Divide by zero error encountered.
The statement has been terminated.
*/
SELECT * FROM #BadDefault; -- just to be sure ;-)
GO
Como você pode ver, quando uma coluna (e um valor, não a palavra-chave DEFAULT
) é fornecida, o Padrão é 100% ignorado. Sabemos disso porque o INSERT
sucesso. Mas se o padrão for usado, ocorrerá um erro quando ele estiver sendo finalmente executado.
Existe uma maneira de evitar restrições DEFAULT em uma execução de acionador?
Embora a necessidade de evitar restrições padrão (pelo menos nesse contexto) seja completamente desnecessária, por uma questão de integridade, pode-se observar que só seria possível "evitar" uma restrição padrão dentro de um INSTEAD OF
gatilho, mas não dentro de um AFTER
gatilho. De acordo com a documentação para CREATE TRIGGER :
Se existirem restrições na tabela de acionadores, elas serão verificadas após a execução do acionador INSTEAD OF e antes da execução do acionador AFTER. Se as restrições forem violadas, as ações do gatilho INSTEAD OF serão revertidas e o gatilho AFTER não será acionado.
Obviamente, o uso de um INSTEAD OF
acionador exigiria:
- Desabilitando a restrição padrão
- Criando um
AFTER
gatilho que habilita a restrição
No entanto, eu não recomendaria exatamente isso.