Database
 sql >> Base de Dados >  >> RDS >> Database

5 dicas sem complicações para usar a instrução SQL UPDATE com JOIN


“Opa! Foi mal." Quantas vezes você disse isso depois que um SQL UPDATE deu errado? O problema é que, se você não for cuidadoso, uma atualização de tabela pode ter sérias consequências na forma da instrução DELETE. Pode ficar ainda pior se você complicar usando UPDATE com JOIN. É por isso que você precisa pensar antes de clicar em Executar ou pressionar CTRL-E.

Então, hoje você vai aprender como codificar seu SQL UPDATE com JOIN sem problemas e nunca dizer “Opa! Meu mal” novamente.

Mas antes de começarmos a praticar, começamos com a sintaxe. Também fará com que nossos novatos se sintam em casa com o SQL Server UPDATE com JOIN. Em seguida, prepararemos alguns dados e alguns exemplos. E, finalmente, examine as dicas de segurança.

[id do formulário de envio=”12968″]

SQL UPDATE JOIN Sintaxe

UPDATE table1
SET column1 = <expression1 | value1> [, column2 = <expression2 | value2>, <columnN = expression3 | value3>]
FROM table1
[INNER | OUTER LEFT | OUTER RIGHT] JOIN table2 on table1.keycolumn = table2.keycolumn
[WHERE condition]

Precisamos detalhar alguns pontos a partir disso.
  1. Podemos atualizar uma tabela por vez para pelo menos uma coluna ou algumas colunas.
  2. Precisamos da cláusula FROM para adicionar um JOIN. O objeto na cláusula FROM pode ou não ser o mesmo que o objeto que está sendo atualizado.
  3. Podemos usar INNER ou OUTER JOIN (veja exemplos mais adiante).
  4. Podemos atualizar apenas um subconjunto dos dados usando a cláusula WHERE.

Antes de termos nossos exemplos, vamos preparar os dados.

Nossos dados de teste


Pelo amor aos filmes, vamos criar um banco de dados de títulos de filmes com classificações de usuários.
CREATE DATABASE [Movies]
GO

USE [Movies]
GO

CREATE TABLE [dbo].[Titles](
	[TitleID] [int] IDENTITY(1,1) NOT NULL,
	[Title] [varchar](50) NOT NULL,
	[ReleaseDate] [date] NOT NULL,
	[OverallUserRating] [varchar](10) NULL,
 CONSTRAINT [PK_Titles] PRIMARY KEY CLUSTERED 
(
	[TitleID] ASC
))
GO

CREATE TABLE [dbo].[UserRatings](
	[UserRatingID] [int] IDENTITY(1,1) NOT NULL,
	[TitleID] [int] NOT NULL,
	[User] [varchar](50) NOT NULL,
	[Rating] [tinyint] NOT NULL,
 CONSTRAINT [PK_UserRatings] PRIMARY KEY CLUSTERED 
(
	[UserRatingID] ASC
))
GO

ALTER TABLE [dbo].[UserRatings]  WITH CHECK ADD  CONSTRAINT [FK_UserRatings_Titles] FOREIGN KEY([TitleID])
REFERENCES [dbo].[Titles] ([TitleID])
GO

ALTER TABLE [dbo].[UserRatings] CHECK CONSTRAINT [FK_UserRatings_Titles]
GO

ALTER TABLE [dbo].[UserRatings]  WITH CHECK ADD  CONSTRAINT [CK_UserRatings_Rating] CHECK  (([Rating]>=(1) AND [Rating]<=(5)))
GO

ALTER TABLE [dbo].[UserRatings] CHECK CONSTRAINT [CK_UserRatings_Rating]
GO

Agora que temos o banco de dados e as tabelas, vamos inserir alguns dados:
INSERT INTO Titles
(Title, ReleaseDate)
VALUES 
('The Avengers', '05/04/2012'),
('Avengers: Age of Ultron','5/1/2015'),
('Avengers: Infinity War','4/27/2018'),
('Avengers: Endgame','4/26/2019'),
('Captain America: Civil War','5/6/2016')
GO

INSERT INTO UserRatings(TitleID, [User], Rating) 
VALUES 
(1,'Natasha',5),
(1,'Bruce',3),
(1,'Tony',4),
(1,'Bucky',5),
(2,'Steve',4),
(2,'Wanda',3),
(2,'Pietro',2),
(2,'Clint',5),
(3,'Hope',5),
(3,'Sam',5),
(3,'Nick',3),
(3,'James',5),
(4,'Scott',5),
(4,'Wong',5),
(4,'Peter',5),
(4,'Carol',4),
(4,'Shuri',5)
GO

ATUALIZAÇÃO do SQL Server com PARTICIPE Exemplo


Examinaremos diferentes exemplos com o mesmo objetivo de atualizar o OverallUserRating nos Títulos tabela. As classificações podem ser de 1 a 5. Classificação geral do usuário é a média de todas as classificações de um título de filme.

Aqui está o estado inicial da tabela:

Exemplo de ATUALIZAR LEFT JOIN

-- SQL UPDATE with LEFT OUTER JOIN
SELECT
 a.TitleID
,CAST(CAST(AVG(CAST(b.Rating AS DECIMAL(3,2))) AS DECIMAL(3,2)) AS varchar(9)) AS AverageRating
INTO #ComputedRatings
FROM titles a
INNER JOIN UserRatings b ON a.TitleID = b.TitleID
GROUP BY a.TitleID

-- mark 'No Rating' if there are no existing ratings
UPDATE Titles
SET OverallUserRating = ISNULL(b.AverageRating,'No Rating') 
FROM Titles a
LEFT JOIN #ComputedRatings b ON a.TitleID = b.TitleID

A primeira instrução SELECT calcula a classificação média por título de filme com base nas UserRatings tabela. O resultado é despejado em uma tabela temporária chamada #ComputedRatings . Como usamos INNER JOIN, os títulos de filmes sem classificação são descartados.

Na instrução UPDATE, os Títulos A tabela foi atualizada usando um LEFT JOIN do #ComputedRatings tabela temporária. Se a classificação média for null , o valor se torna Sem classificação . Em nossa amostra, Capitão América:Guerra Civil ainda não tem classificações de usuários – veja a Figura 2.

Exemplo de SQL UPDATE INNER JOIN


Veja como vai:
-- SQL UPDATE with INNER JOIN
SELECT
 a.TitleID
,ISNULL(CAST(CAST(AVG(CAST(b.Rating AS DECIMAL(3,2))) AS DECIMAL(3,2)) AS varchar(9)),'No Rating')  AS AverageRating
INTO #ComputedRatings
FROM titles a
LEFT JOIN UserRatings b ON a.TitleID = b.TitleID
GROUP BY a.TitleID


UPDATE Titles
SET OverallUserRating = b.AverageRating
FROM Titles a
INNER JOIN #ComputedRatings b ON a.TitleID = b.TitleID

Ao executar o código acima, o resultado será o mesmo da Figura 2. Mas qual é a diferença entre os dois códigos?
  • A primeira instrução SELECT considera a classificação do usuário NULL, diferentemente do nosso exemplo LEFT JOIN anterior. Ele não descarta o título do filme sem classificações do usuário. Então, desta vez, o Sem classificação valor para Capitão América:Guerra Civil já é considerado.
  • Um INNER JOIN com a atribuição direta do AverageRating value é mais apropriado, pois todos os TitleIDs são contabilizados.

Agora, em vez de uma tabela temporária, a expressão de tabela comum (CTE) também pode ser usada. Segue o código modificado:
-- SQL UPDATE with JOIN using INNER JOIN from a CTE
;WITH ComputedRatings AS
(SELECT
 a.TitleID
,ISNULL(CAST(CAST(AVG(CAST(b.Rating AS DECIMAL(3,2))) AS DECIMAL(3,2)) AS varchar(9)),'No Rating')  AS AverageRating
FROM Titles a
LEFT JOIN UserRatings b ON a.TitleID = b.TitleID
GROUP BY a.TitleID)
UPDATE Titles
SET OverallUserRating = b.AverageRating
FROM Titles t
INNER JOIN ComputedRatings cr ON t.TitleID = cr.TitleID

Mais informações
  • Seu guia definitivo para SQL JOIN:INNER JOIN – Parte 1
  • Seu guia definitivo para SQL JOIN:OUTER JOIN – Parte 2

Usando a atualização Comando com Participar Com segurança (5 dicas)


Segurança refere-se à atualização dos registros pretendidos. Além disso, trata-se de NÃO tocar nos registros que não pretendemos atualizar. Aqui, vamos lidar com 5 cenários:

Visualize os registros primeiro com a instrução SELECT


Essa dica é perfeita para alguns registros. Portanto, antes de afetar os registros para atualização, tente isto:

Isso é fácil de fazer. E se parecer bom, descomente as cláusulas UPDATE e SET. Marque a cláusula SELECT como um comentário. Então, você está pronto para ir. Dessa forma, você minimiza o controle de danos por ser proativo.

Faça um teste usando tabelas temporárias


Não tem certeza se um erro pode ocorrer? Em seguida, tente despejar os registros da tabela que deseja atualizar em uma tabela temporária. Depois disso, faça um teste a partir daí.

Não há erros de tempo de execução? Os resultados parecem bons? Em seguida, substitua a tabela temporária pela tabela original. Dessa forma, você sabe que não haverá erros de tempo de execução ao longo do caminho.

Além disso, observe que as tabelas temporárias são uma das maneiras de armazenar as cópias das tabelas originais. Você também pode usar tabelas com otimização de memória ou um backup de banco de dados. Com os backups de banco de dados, você tem mais liberdade para brincar com os registros que precisa atualizar. Mas a desvantagem disso é o espaço de armazenamento.

Mais informações
  • CREATE TABLE (Transact-SQL) – Tabelas temporárias

Tente adicionar a cláusula OUTPUT em UPDATE


Deseja voltar no tempo antes de executar a atualização? Se você é tão cético, pode usar a cláusula OUTPUT e ver o passado e o presente. No exemplo abaixo, uma variável de tabela serve para despejar os valores anteriores e atuais após a atualização. Você pode então SELECT a variável da tabela para ver os resultados:
DECLARE @tvTitles AS TABLE(TitleID INT NOT NULL,
                           OldOverallRatings VARCHAR(10) NULL,
			    NewOverAllRatings varchar(10) NOT NULL)

;WITH ComputedRatings AS
(
	SELECT
	a.TitleID
	,ISNULL(CAST(CAST(AVG(CAST(b.Rating AS DECIMAL(3,2))) AS DECIMAL(3,2)) AS varchar(9)),'No Rating')  AS AverageRating
	FROM titles a
	LEFT JOIN UserRatings b ON a.TitleID = b.TitleID
	GROUP BY a.TitleID
)
UPDATE #tmpTitles
SET OverallUserRating = cr.AverageRating
OUTPUT INSERTED.TitleID, DELETED.OverallUserRating, INSERTED.OverallUserRating
INTO @tvTitles
FROM #tmpTitles t
INNER JOIN ComputedRatings cr ON t.TitleID = cr.TitleID

Alguns pontos a serem observados sobre este código:
  • A variável de tabela funciona como um contêiner dos valores anteriores e atuais.
  • A atualização usual com o CTE está em ordem. Ainda usamos a mesa temporária para jogar pelo seguro.
  • A cláusula OUTPUT se aplica para despejar os valores anteriores e atuais na variável da tabela. INSERTED contém novos valores, enquanto DELETED contém valores antigos.

Se você emitir um SELECT da variável de tabela, aqui está o que você pode esperar:

O resultado é como o da Figura 3, mas olha para o futuro. Este olha para o passado. Se for diferente, algo deu errado no meio.

A boa notícia é que você pode usar valores antigos na variável de tabela para restaurá-la ao seu estado anterior. Mas se for o mesmo, então, novamente, você está pronto para ir. Você pode marcar a cláusula OUTPUT como um comentário ou removê-la e, em seguida, substituir a tabela temporária pela tabela original.

Mais informações
  • Cláusula OUTPUT (Transact-SQL)

Use TRY…CATCH para lidar com erros futuros


As 3 dicas anteriores são úteis quando você está criando e testando seu código. Mas todos sabemos que não podemos antecipar tudo. Assim, precisamos adicionar mais redes de segurança ao código.

Falando em redes de segurança, o T-SQL tem os blocos de tratamento de erros TRY…CATCH como C# e C++.

Vamos dar uma olhada no código modificado do exemplo anterior:
BEGIN TRY						  
  ;WITH ComputedRatings AS
  (
    SELECT
     a.TitleID
    ,ISNULL(CAST(CAST(AVG(CAST(b.Rating AS DECIMAL(3,2))) AS DECIMAL(3,2)) AS varchar(20)),
            'No User Ratings Yet') AS AverageRating
    FROM titles a
    LEFT JOIN UserRatings b ON a.TitleID = b.TitleID
    GROUP BY a.TitleID
  )
  UPDATE Titles
  SET OverallUserRating = cr.AverageRating
  FROM Titles t
  INNER JOIN ComputedRatings cr ON t.TitleID = cr.TitleID

END TRY
BEGIN CATCH
 SELECT  
  ERROR_NUMBER() AS ErrorNumber  
 ,ERROR_SEVERITY() AS ErrorSeverity  
 ,ERROR_STATE() AS ErrorState  
 ,ERROR_PROCEDURE() AS ErrorProcedure  
 ,ERROR_LINE() AS ErrorLine  
 ,ERROR_MESSAGE() AS ErrorMessage;  

END CATCH

Alterei o código acima para forçar o erro de truncamento de string. A Classificação Geral do Usuário coluna em Títulos tabela pode acomodar até 10 caracteres apenas. No CTE, mudamos para 20 caracteres. Isso não vai caber. O bloco CATCH assumirá o momento da ocorrência do erro e fornecerá as informações do erro.

Aqui está o resultado:

Acionamos o erro. Se você precisar detectar erros imprevistos durante o tempo de execução, essa é uma maneira de lidar com isso.

Mais informações
  • TRY…CATCH (Transact-SQL)

Usar manipulação de transações


Por fim, as transações. Ele garante a restauração de tudo para seu estado anterior antes da ocorrência do erro, incluindo UPDATE com JOIN e quaisquer outras instruções DML. Este é um bom complemento para a dica nº 4 acima.

Vamos alterar o código novamente para incluir transações:
BEGIN TRANSACTION

BEGIN TRY						  
  ;WITH ComputedRatings AS
  (
    SELECT
     a.TitleID
    ,ISNULL(CAST(CAST(AVG(CAST(b.Rating AS DECIMAL(3,2))) AS DECIMAL(3,2)) AS varchar(20)),
            'No User Ratings Yet') AS AverageRating
    FROM titles a
    LEFT JOIN UserRatings b ON a.TitleID = b.TitleID
    GROUP BY a.TitleID
  )
  UPDATE Titles
  SET OverallUserRating = cr.AverageRating
  FROM Titles t
  INNER JOIN ComputedRatings cr ON t.TitleID = cr.TitleID

  COMMIT TRANSACTION
END TRY
BEGIN CATCH
 SELECT  
  ERROR_NUMBER() AS ErrorNumber  
 ,ERROR_SEVERITY() AS ErrorSeverity  
 ,ERROR_STATE() AS ErrorState  
 ,ERROR_PROCEDURE() AS ErrorProcedure  
 ,ERROR_LINE() AS ErrorLine  
 ,ERROR_MESSAGE() AS ErrorMessage;  

 ROLLBACK TRANSACTION
END CATCH

É o mesmo que no exemplo anterior, exceto para transações. Assim, ele forçará um erro de truncamento de string. Ele não passará de COMMIT TRANSACTION, mas sim no bloco CATCH com o ROLLBACK TRANSACTION para reverter os valores para seus estados anteriores.

Este é o caminho se quisermos jogar pelo seguro com atualizações, inserções e exclusões.

Observação :Você pode projetar qualquer consulta visualmente em um diagrama usando o recurso Construtor de Consultas do dbForge Studio para SQL Server.

Mais informações
  • Práticas recomendadas do T-SQL
  • Como escrever consultas T-SQL como um profissional

Conclusão


Você viu a sintaxe do SQL UPDATE com JOIN. Exemplos e 5 dicas sem complicações o esclareceram ainda mais. Os requisitos podem diferir do que os exemplos apresentam, mas você entendeu. Você ainda pode cometer erros. No entanto, é possível reduzi-los a quase zero.

Por que não aplicar essas idéias à sua situação?

Se este post foi útil, sinta-se à vontade para espalhar a palavra em suas plataformas de mídia social favoritas. E se você quiser adicionar algumas ótimas ideias, seja bem-vindo à seção de comentários.