Partilhar via


Problemas de design do T-SQL

Aplica-se a:SQL ServerAzure SQL DatabaseAzure SQL Managed InstanceBase 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 no banco de dados podem quebrar aplicativos que dependem dele.
  • O código pode não produzir o resultado esperado.
  • O código pode quebrar 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 quebrar 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

Se você usar um caractere curinga em um procedimento armazenado, modo de 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á mudar se a tabela ou exibição subjacente for alterada. A forma de uma coluna é uma combinação do 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 porque esses consumidores esperarão um número diferente de colunas.

Como corrigir violações

Você pode proteger os consumidores do procedimento armazenado, da exibição ou da função com valor de tabela contra alterações de esquema substituindo o caractere curinga por uma lista totalmente qualificada de nomes de coluna.

Example

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 os gatilhos aninhados usados pela replicação, podem atualizar @@IDENTITY fora do seu 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 de usuário.

Example

No primeiro exemplo, @@IDENTITY é usado em um procedimento armazenado que insere dados em uma tabela. A tabela é então publicada para replicação por mesclagem, o 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 Sales.Customer tabela tem um valor máximo de identidade 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 insert na tabela do usuário, mas @@IDENTITY retorna o valor da operação insert 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 são tamanho 1 ou 2

Quando você usa tipos de dados de comprimento variável, como VARCHAR, NVARCHAR e VARBINARY, você incorre em um custo de armazenamento adicional para controlar o comprimento do valor armazenado no tipo de dados. Além disso, colunas de comprimento variável são armazenadas depois de todas as colunas de comprimento fixo, o que pode ter implicações no 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 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-o como um tipo de comprimento fixo, como CHAR, NCAR e BINÁRIO.

Example

Este exemplo mostra definições para duas tabelas. A primeira tabela declara que uma cadeia de caracteres de comprimento variável tem comprimento 2. 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 fisicamente armazenados 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 exterior: As junções externas retornam todas as linhas de pelo menos uma das tabelas ou exibições especificadas na cláusula FROM, desde que essas linhas atendam a qualquer condição de pesquisa WHERE ou HAVER. Se você usar = ou = para especificar uma associação externa, estará usando sintaxe obsoleta.

Como corrigir violações

Para corrigir uma violação numa 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:

  • JUNÇÃO À ESQUERDA EXTERNA ou JUNÇÃO À ESQUERDA
  • JUNÇÃO EXTERNA À DIREITA ou JUNÇÃO DIREITA

Exemplos da sintaxe preterida e da sintaxe atualizada são fornecidos nos exemplos a seguir. Mais informações sobre junções podem ser encontradas em Aderências.

Examples

Os seis exemplos demonstram as seguintes opções:

  1. O Exemplo 1 demonstra a sintaxe obsoleta para uma junção interna.
  2. O Exemplo 2 demonstra como você pode atualizar o Exemplo 1 para usar a sintaxe atual.
  3. O Exemplo 3 demonstra a sintaxe preterida para uma junção externa esquerda.
  4. O Exemplo 4 demonstra como você pode atualizar o Exemplo 2 para usar a sintaxe atual.
  5. O Exemplo 5 demonstra a sintaxe preterida para uma junção externa direita.
  6. 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 (parâmetro) não é preenchido em todos os caminhos de código

Esta 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 através do procedimento armazenado ou função. Esta 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 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.

Important

Especificar um valor dentro da 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.

Example

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 @Sum parâmetro 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: A perda de dados pode ocorrer ao transmitir de {Type1} para {Type2}

Se os tipos de dados forem atribuídos inconsistentemente 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 só 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 cada coluna em uma cláusula WHERE precisar ser convertida. 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:

  • Verifique o tipo de uma coluna computada, pois o tipo não é conhecido até ao tempo de execução.
  • Analise qualquer coisa dentro de uma declaração CASE. Ele também não analisa o valor de retorno de uma instrução CASE.
  • Analise os parâmetros de entrada ou o valor de retorno de uma chamada para ISNULL

Esta tabela resume as verificações cobertas pela regra SR0014:

Construção linguísticaO que é verificadoExample
Valor padrão dos parâmetrosTipo de dados do parâmetro
CREATE PROCEDURE p1(@p1 INT = 1)
AS
BEGIN
END
CRIAR PREDICADO DE ÍNDICEPredicado é booleano
CREATE INDEX index1 ON table1 (column1)
WHERE column1 > 10
Argumentos das funções ESQUERDA ou DIREITATipo e comprimento do argumento String
SET @v = LEFT('abc', 2)
Argumentos das funções CAST e CONVERTExpressão e tipos são válidos
SET @v = CAST('abc' AS CHAR(10))
Declaração SETO lado esquerdo e o lado direito têm tipos compatíveis
SET @v1 = 'xyz'
SELECT @v1 = c1 FROM t1
Predicado de IFPredicado é booleano
IF (@v > 10)
Predicado da declaração WHILEPredicado é booleano
WHILE (@v > 10)
INSERIR instruçãoOs valores e as colunas estão corretos
INSERT INTO t1(c1, c2) VALUES (99, 'xyz')
INSERT INTO t1 SELECT c1 FROM t2.
SELECIONAR ONDE predicadoPredicado é booleano
SELECT * FROM t1 WHERE c1 > 10
SELECIONAR EXPRESSÃO SUPERIORExpressão é de tipo inteiro ou flutuante.
SELECT TOP 4 * FROM t1
SELECT TOP 1.5 PERCENT * FROM t1
Declaração UPDATEExpressão e coluna têm tipos compatíveis
UPDATE t1 SET c1 = 100
Predicado UPDATEPredicado é booleano
UPDATE t1 SET c1 = 100
WHERE c1 > 100
ATUALIZAR EXPRESSÃO TOPExpressão é de tipo inteiro ou flutuante.
UPDATE TOP 4 table1
APAGAR PREDICADOPredicado é booleano
DELETE t1 WHERE c1 > 10
EXCLUIR expressão TOPExpressão é de tipo inteiro ou flutuante.
DELETE TOP 2 FROM t1
Declaração de variável DECLAREO valor inicial e o tipo de dados são compatíveis
DECLARE @v INT = 10
Argumentos de instrução EXECUTE e tipo de retornoParâmetros e argumentos
CREATE PROCEDURE p1 (@p1 INT) AS
GO
EXECUTE p1 100
EXECUTE @v1 = p1 100
Instrução RETURNA expressão RETURN tem um tipo de dados compatível
CREATE FUNCTION f1() RETURNS INT
AS
BEGIN
  RETURN 100
END
Condições da instrução MERGEA condição é booleana
MERGE t1 USING t2
ON t1.c1 = t2.c2
WHEN t1.c1 > 10 THEN DELETE

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 explicitamente tipos de dados, consulte esta página no site da Microsoft: CAST e CONVERT (Transact-SQL).

Example

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