Problemas de design do T-SQL
Aplica-se a: SQL Server Banco de Dados SQL do Azure Instância Gerenciada de SQL do Azure Banco de Dados SQL no Microsoft Fabric
Quando você analisa o código T-SQL em seu projeto de banco de dados, um ou mais avisos podem ser categorizados como problemas de design. Você deve resolver problemas de design para evitar as seguintes situações:
- Alterações subsequentes em seu banco de dados podem causar problemas nos aplicativos que dependem dele.
- O código pode não produzir o resultado esperado.
- O código pode ser desfeito se você executá-lo com versões futuras do SQL Server.
Em geral, você não deve suprimir um problema de design porque ele pode causar problemas em seu aplicativo, agora ou no futuro.
As regras fornecidas identificam os seguintes problemas de design:
- SR0001: evite SELECT * em procedimentos armazenados, exibições e funções com valor de tabela
- SR0008: considere usar SCOPE_IDENTITY em vez de @@IDENTITY
- SR0009: evite usar tipos de comprimento variável que sejam de tamanho 1 ou 2
- SR0010: evite usar sintaxe preterida ao unir tabelas ou exibições
- SR0013: o parâmetro de saída (parameter) não é preenchido em todos os caminhos de código
- SR0014: pode ocorrer perda de dados ao converter de {Type1} para {Type2}
SR0001: evite SELECT * em procedimentos armazenados, exibições e funções com valor de tabela
Se você usar um caractere curinga em um procedimento armazenado, exibição ou função com valor de tabela para selecionar todas as colunas em uma tabela ou exibição, o número ou a forma das colunas retornadas poderá ser alterado se a tabela ou exibição subjacente for alterada. A forma de uma coluna é uma combinação de seu tipo e tamanho. Essa variação pode causar problemas em aplicativos que consomem o procedimento armazenado, a exibição ou a função com valor de tabela, pois esses consumidores esperam um número diferente de colunas.
Como corrigir violações
Você pode proteger os consumidores do procedimento armazenado, exibição ou função com valor de tabela contra alterações de esquema substituindo o caractere curinga por uma lista totalmente qualificada de nomes de coluna.
Exemplo
O exemplo a seguir primeiro define uma tabela chamada [Table2] e, em seguida, define dois procedimentos armazenados. O primeiro procedimento contém um SELECT *
, que viola a regra SR0001. O segundo procedimento evita SELECT *
e lista explicitamente as colunas na instrução SELECT.
CREATE TABLE [dbo].[Table2]
(
[ID] INT NOT NULL IDENTITY(0, 1),
[c1] INT NOT NULL,
[Comment] NVARCHAR (50)
)
ON [PRIMARY]
CREATE PROCEDURE [dbo].[procWithWarning]
AS
BEGIN
-- Contains code that breaks rule SR0001
SELECT *
FROM [dbo].[Table2]
END
CREATE PROCEDURE [dbo].[procFixed]
AS
BEGIN
-- Explicitly lists the column names in a SELECT statement
SELECT [dbo].[Table2].[ID], [dbo].[Table2].[c1], [dbo].[Table2].[Comment]
FROM [dbo].[Table2]
END
SR0008: considere usar SCOPE_IDENTITY em vez de @@IDENTITY
Como @@IDENTITY é um valor de identidade global, ele pode ter sido atualizado fora do escopo atual e obtido um valor inesperado. Os gatilhos, incluindo gatilhos aninhados usados pela replicação, podem atualizar @@IDENTITY fora do escopo atual.
Como corrigir violações
Para resolver esse problema, você deve substituir as referências a @@IDENTITY por SCOPE_IDENTITY, que retorna o valor de identidade mais recente no escopo da instrução do usuário.
Exemplo
No primeiro exemplo, @@IDENTITY é usado em um procedimento armazenado que insere dados em uma tabela. Em seguida, a tabela é publicada para replicação de mesclagem, que adiciona gatilhos às tabelas publicadas. Portanto, @@IDENTITY pode retornar o valor da operação de inserção em uma tabela do sistema de replicação em vez da operação de inserção em uma tabela de usuário.
A tabela Sales.Customer
tem um valor de identidade máximo de 29483. Se você inserir uma linha na tabela, @@IDENTITY e SCOPE_IDENTITY() retornarão valores diferentes. SCOPE_IDENTITY() retorna o valor da operação de inserção em uma tabela de usuário, mas @@IDENTITY retorna o valor da operação de inserção na tabela do sistema de replicação.
O segundo exemplo mostra como você pode usar SCOPE_IDENTITY() para acessar o valor de identidade inserido e resolver o aviso.
CREATE PROCEDURE [dbo].[ProcedureWithWarning]
@param1 INT,
@param2 NCHAR(1),
@Param3 INT OUTPUT
AS
BEGIN
INSERT INTO Sales.Customer ([TerritoryID],[CustomerType]) VALUES (@param1,@param2);
SELECT @Param3 = @@IDENTITY
END
CREATE PROCEDURE [dbo].[ProcedureFixed]
@param1 INT,
@param2 NCHAR(1),
@param3 INT OUTPUT
AS
BEGIN
INSERT INTO Sales.Customer ([TerritoryID],[CustomerType]) VALUES (@param1,@param2);
SELECT @Param3 = SCOPE_IDENTITY()
END
SR0009: evite usar tipos de comprimento variável que sejam de tamanho 1 ou 2
Ao usar tipos de dados de comprimento variável, como VARCHAR, NVARCHAR e VARBINARY, você incorre em um custo de armazenamento adicional para rastrear o comprimento do valor armazenado no tipo de dados. Além disso, as colunas de comprimento variável são armazenadas após todas as colunas de comprimento fixo, o que pode ter implicações de desempenho. Você também receberá um aviso se declarar um tipo de comprimento variável, como VARCHAR, mas não especificar nenhum comprimento. Esse aviso ocorre porque, se não for especificado, o comprimento padrão é 1.
Como corrigir violações
Se o comprimento do tipo for muito pequeno (tamanho 1 ou 2) e consistente, declare-os como um tipo de comprimento fixo, como CHAR, NCHAR e BINARY.
Exemplo
Este exemplo mostra definições para duas tabelas. A primeira tabela declara uma cadeia de caracteres de comprimento variável como tendo comprimento 2. Em vez disso, a segunda tabela declara uma cadeia de caracteres de comprimento fixo, o que evita o aviso.
CREATE TABLE [dbo].[TableWithWarning]
(
[ID] INT NOT NULL IDENTITY(0, 1),
[c1] INT NOT NULL PRIMARY KEY,
[c2] INT,
[c3] INT,
[SmallString] VARCHAR(2)
)
ON [PRIMARY]
CREATE TABLE [dbo].[FixedTable]
(
[ID] INT NOT NULL IDENTITY(0, 1),
[c1] INT NOT NULL PRIMARY KEY,
[c2] INT,
[c3] INT,
[SmallString] CHAR(2)
)
ON [PRIMARY]
Os dados para tipos de comprimento variável são armazenados fisicamente após os dados para tipos de comprimento fixo. Portanto, você causará movimentação de dados se alterar uma coluna de variável para comprimento fixo em uma tabela que não esteja vazia.
SR0010: evite usar sintaxe preterida ao unir tabelas ou exibições
As junções que usam a sintaxe preterida se enquadram em duas categorias:
- Junção interna: para uma junção interna, os valores nas colunas que estão sendo unidas são comparados usando um operador de comparação como =, <, >= e assim por diante. As junções internas retornam linhas somente se pelo menos uma linha de cada tabela corresponder à condição de junção.
- Junção externa: as junções externas retornam todas as linhas de pelo menos uma das tabelas ou exibições especificadas na cláusula FROM, contanto que essas linhas atendam algum critério de pesquisa WHERE ou HAVING. Se você usar = ou = para especificar uma junção externa, estará usando a sintaxe preterida.
Como corrigir violações
Para corrigir uma violação em uma junção interna, use a sintaxe INNER JOIN
.
Para corrigir uma violação em uma junção externa, use a sintaxe apropriada OUTER JOIN
. Você tem as seguintes opções:
- LEFT OUTER JOIN ou LEFT JOIN
- RIGHT OUTER JOIN ou RIGHT JOIN
Exemplos da sintaxe preterida e da sintaxe atualizada são fornecidos nos exemplos a seguir. Encontre mais informações sobre junções em Junções.
Exemplos
Os seis exemplos demonstram como usar as seguintes opções:
- O exemplo 1 demonstra a sintaxe preterida para uma junção interna.
- O exemplo 2 demonstra como você pode atualizar o exemplo 1 para usar a sintaxe atual.
- O exemplo 3 demonstra a sintaxe preterida para uma junção externa esquerda.
- O exemplo 4 demonstra como você pode atualizar o exemplo 2 para usar a sintaxe atual.
- O exemplo 5 demonstra a sintaxe preterida para uma junção externa direita.
- O exemplo 6 demonstra como você pode atualizar o exemplo 5 para usar a sintaxe atual.
-- Example 1: Deprecated syntax for an inner join
SELECT [T2].[c3], [T1].[c3]
FROM [dbo].[Table2] T2, [dbo].[Table1] T1
WHERE [T1].[ID] = [T2].[ID]
-- Example 2: Current syntax for an inner join
SELECT [T2].[c3], [T1].[c3]
FROM [dbo].[Table2] AS T2
INNER JOIN [dbo].[Table1] as T1
ON [T2].[ID] = [T2].[ID]
-- Example 3: Deprecated syntax for a left outer join
SELECT [T2].[c3], [T1].[c3]
FROM [dbo].[Table2] T2, [dbo].[Table1] T1
WHERE [T1].[ID] *= [T2].[ID]
-- Example 4: Fixed syntax for a left outer join
SELECT [T2].[c3], [T1].[c3]
FROM [dbo].[Table2] AS T2
LEFT OUTER JOIN [dbo].[Table1] as T1
ON [T2].[ID] = [T2].[ID]
-- Example 5: Deprecated syntax for a right outer join
SELECT [T2].[c3], [T1].[c3]
FROM [dbo].[Table2] T2, [dbo].[Table1] T1
WHERE [T1].[ID] =* [T2].[ID]
-- Example 6: Fixed syntax for a right outer join
SELECT [T2].[c3], [T1].[c3]
FROM [dbo].[Table2] AS T2
RIGHT OUTER JOIN [dbo].[Table1] as T1
ON [T2].[ID] = [T2].[ID]
SR0013: o parâmetro de saída (parameter) não é preenchido em todos os caminhos de código
Essa regra identifica o código no qual o parâmetro de saída não está definido como um valor em um ou mais caminhos de código por meio do procedimento armazenado ou da função. Essa regra não identifica em quais caminhos o parâmetro de saída deve ser definido. Se vários parâmetros de saída tiverem esse problema, um aviso será exibido para cada parâmetro.
Como corrigir violações
Você pode corrigir esse problema de uma das duas maneiras. Você pode corrigir esse problema mais facilmente se inicializar os parâmetros de saída para um valor padrão no início do corpo do procedimento. Como alternativa, você também pode definir o parâmetro de saída como um valor nos caminhos de código específicos nos quais o parâmetro não está definido. No entanto, você pode ignorar um caminho de código incomum em um procedimento complexo.
Importante
Especificar um valor na declaração de procedimento, como CREATE PROC MyProcedure (@param1 INT = 10 OUTPUT)
não resolverá o problema. Você deve atribuir um valor ao parâmetro de saída dentro do corpo do procedimento.
Exemplo
O exemplo a seguir mostra dois procedimentos simples. O primeiro procedimento não define o valor do parâmetro de saída, @Sum
. O segundo procedimento inicializa o parâmetro @Sum
no início do procedimento, o que garante que o valor será definido em todos os caminhos de código.
CREATE PROCEDURE [dbo].[procedureHasWarning]
(
@Value1 BIGINT,
@Value2 INT,
@Value3 INT,
@Sum INT OUTPUT
)
AS
BEGIN
-- No initialization of the output parameter
--
-- Additional statements here.
--
RETURN 0;
END
--
CREATE PROCEDURE [dbo].[procedureFixed]
(
@Value1 BIGINT,
@Value2 INT,
@Value3 INT,
@Sum INT OUTPUT
)
AS
BEGIN
-- Initialize the out parameter
SET @Sum = 0;
--
-- Additional statements here
--
RETURN 0;
END
SR0014: pode ocorrer perda de dados ao converter de {Type1} para {Type2}
Se os tipos de dados forem atribuídos de forma inconsistente a colunas, variáveis ou parâmetros, eles serão convertidos implicitamente quando o código Transact-SQL que contém esses objetos for executado. Esse tipo de conversão não apenas reduz o desempenho, mas também, em alguns casos, causa perda sutil de dados. Por exemplo, uma verificação de tabela pode ser executada se todas as colunas em uma cláusula WHERE precisarem ser convertidas. Pior, os dados podem ser perdidos se uma cadeia de caracteres Unicode for convertida em uma cadeia de caracteres ASCII que usa uma página de código diferente.
Esta regra NÃO:
- Verifica o tipo de uma coluna computada porque o tipo não é conhecido até o tempo de execução.
- Analisa qualquer coisa dentro de uma instrução CASE. Ele também não analisa o valor retornado de uma instrução CASE.
- Analisa os parâmetros de entrada ou o valor retornado de uma chamada para ISNULL
Esta tabela resume as verificações cobertas pela regra SR0014:
Constructo de linguagem | O que é verificado | Exemplo |
---|---|---|
O valor padrão de parâmetros | Tipo de dados de parâmetro |
|
O predicado CREATE INDEX | O predicado é booliano |
|
Os argumentos das funções LEFT ou RIGHT | O tipo e o comprimento do argumento de cadeia de caracteres |
|
Os argumentos das funções CAST e CONVERT | A expressão e os tipos são válidos |
|
Instrução SET | O lado esquerdo e o lado direito têm tipos compatíveis |
|
O predicado de instrução IF | O predicado é booliano |
|
O predicado de instrução WHILE | O predicado é booliano |
|
Instrução INSERT | Os valores e as colunas estão corretos |
|
O predicado SELECT WHERE | O predicado é booliano |
|
A expressão SELECT TOP | A expressão é um tipo Integer ou Float |
|
Instrução UPDATE | A expressão e a coluna têm tipos compatíveis |
|
O predicado UPDATE | O predicado é booliano |
|
A expressão UPDATE TOP | A expressão é um tipo Integer ou Float |
|
DELETE PREDICATE | O predicado é booliano |
|
A expressão DELETE TOP | A expressão é um tipo Integer ou Float |
|
A declaração da variável DECLARE | O valor inicial e o tipo de dados são compatíveis |
|
Os argumentos da instrução EXECUTE e o tipo de retorno | Parâmetros e argumentos |
|
Instrução RETURN | A expressão RETURN tem um tipo de dados compatível |
|
As condições da instrução MERGE | A condição é booliana |
|
Como corrigir violações
Você pode evitar e resolver esses problemas atribuindo tipos de dados de forma consistente e convertendo explicitamente os tipos onde eles são necessários. Para obter mais informações sobre como converter tipos de dados explicitamente, consulte esta página no site da Microsoft: CAST e CONVERT (Transact-SQL).
Exemplo
Este exemplo mostra dois procedimentos armazenados que inserem dados em uma tabela. O primeiro procedimento, procWithWarning, causará uma conversão implícita de um tipo de dados. O segundo procedimento, procFixed, mostra como você pode adicionar uma conversão explícita para maximizar o desempenho e reter todos os dados.
CREATE TABLE [dbo].[Table2]
(
[ID] INT NOT NULL IDENTITY(0, 1),
[c1] INT NOT NULL,
[c2] INT NOT NULL,
[c3] BIGINT NOT NULL,
[Comment] VARCHAR (25)
)
ON [PRIMARY]
CREATE PROCEDURE [dbo].[procWithWarning]
(
@Value1 INT,
@Value2 INT,
@Value3 BIGINT,
@Comment CHAR(30)
)
AS
BEGIN
INSERT INTO [Table2] ([c1], [c2], [c3], Comment)
VALUES (@Value1, @Value2, @Value3, @Comment)
END
CREATE PROCEDURE [dbo].[procFixed]
(
@Value1 INT,
@Value2 INT,
@Value3 BIGINT,
@Comment CHAR(10)
)
AS
BEGIN
INSERT INTO [Table2] ([c1], [c2], [c3], Comment)
VALUES (@Value1, @Value2, @Value3, CAST(@Comment AS VARCHAR(25)))
END