Mettre à jour une table à l'aide de JOIN dans SQL Server?

836

Je veux mettre à jour une colonne dans une table en faisant une jointure sur une autre table, par exemple:

UPDATE table1 a 
INNER JOIN table2 b ON a.commonfield = b.[common field] 
SET a.CalculatedColumn= b.[Calculated Column]
WHERE 
    b.[common field]= a.commonfield
AND a.BatchNO = '110'

Mais il se plaint:

Msg 170, niveau 15, état 1, ligne 2
ligne 2: syntaxe incorrecte près de «a».

Qu'est-ce qui ne va pas ici?

Manjot
la source

Réponses:

1599

Vous n'avez pas tout à fait la UPDATE FROMsyntaxe propriétaire de SQL Server vers le bas. Je ne sais pas non plus pourquoi vous deviez vous connecter sur le CommonFieldfiltre et le filtrer ensuite. Essaye ça:

UPDATE t1
  SET t1.CalculatedColumn = t2.[Calculated Column]
  FROM dbo.Table1 AS t1
  INNER JOIN dbo.Table2 AS t2
  ON t1.CommonField = t2.[Common Field]
  WHERE t1.BatchNo = '110';

Si vous faites quelque chose de vraiment stupide - comme essayer constamment de définir la valeur d'une colonne sur l'agrégat d'une autre colonne (ce qui viole le principe d'éviter de stocker des données redondantes), vous pouvez utiliser un CTE (expression de table commune) - voir ici et ici pour plus de détails:

;WITH t2 AS
(
  SELECT [key], CalculatedColumn = SUM(some_column)
    FROM dbo.table2
    GROUP BY [key]
)
UPDATE t1
  SET t1.CalculatedColumn = t2.CalculatedColumn
  FROM dbo.table1 AS t1
  INNER JOIN t2
  ON t1.[key] = t2.[key];

La raison pour laquelle cela est vraiment stupide, c'est que vous devrez réexécuter cette mise à jour entière à chaque fois que vous modifiez une ligne table2. A SUMest quelque chose que vous pouvez toujours calculer au moment de l'exécution et, ce faisant, ne vous inquiétez jamais que le résultat soit périmé.

Aaron Bertrand
la source
4
Quand j'essaye, ça n'aime pas UPDATE table1 a SET a.[field] = b.[field] - supprimer l'alias a fonctionne, doncUPDATE table1 a SET [field] = b.[field]
baldmosher
@baldmosher Je parie qu'il y a un autre problème, pourriez-vous poster une repro sur SQL fiddle?
Aaron Bertrand
1
N'a pas fonctionné pour moi sur MySQL. Je devais utiliser ce qui suit ( ce qui est plus logique): UPDATE t1 INNER JOIN t2 on t2.col = t1.col SET t1.field=value WHERE t2.col=something.
George
16
@GeorgeRappel bien sûr, ne fonctionnera probablement pas non plus sur de nombreuses autres plates-formes. La question concerne SQL Server.
Aaron Bertrand
Supposons que plusieurs enregistrements de t1 référencent le même enregistrement de t2, de sorte que la jointure entraîne le même enregistrement t2 renvoyé sur plusieurs lignes. Dans votre premier exemple, si vous mettiez à jour t2, mettrait-il à jour cet enregistrement plusieurs fois ou une seule fois?
xr280xr
46

Essayez-le comme ceci:

begin tran
    UPDATE a 
    SET a.CalculatedColumn= b.[Calculated Column]
    FROM table1 a INNER JOIN table2 b ON a.commonfield = b.[common field] 
    WHERE a.BatchNO = '110'
commit tran
RBarryYoung
la source
29

La réponse donnée ci-dessus par Aaron est parfaite:

UPDATE a
  SET a.CalculatedColumn = b.[Calculated Column]
  FROM Table1 AS a
  INNER JOIN Table2 AS b
  ON a.CommonField = b.[Common Field]
  WHERE a.BatchNo = '110';

Je veux juste ajouter pourquoi ce problème se produit dans SQL Server lorsque nous essayons d'utiliser l'alias d'une table lors de la mise à jour de cette table, la syntaxe de mention ci-dessous donnera toujours une erreur:

update tableName t 
set t.name = 'books new' 
where t.id = 1

la casse peut être quelconque si vous mettez à jour une seule table ou si vous mettez à jour en utilisant join.

Bien que la requête ci-dessus fonctionne correctement en PL / SQL mais pas dans SQL Server.

La façon correcte de mettre à jour une table tout en utilisant l'alias de table dans SQL Server est:

update t 
set t.name = 'books new' 
from tableName t 
where t.id = 1

J'espère que cela aidera tout le monde pourquoi l'erreur est venue ici.

Ankur Bhutani
la source
1
Bien merci. Votre réponse est la bonne pour cette question.
Ola Ström
4
MERGE table1 T
   USING table2 S
      ON T.CommonField = S."Common Field"
         AND T.BatchNo = '110'
WHEN MATCHED THEN
   UPDATE
      SET CalculatedColumn = S."Calculated Column";
un jour
la source
3

Il semble que SQL Server 2012 puisse également gérer l'ancienne syntaxe de mise à jour de Teradata:

UPDATE a
SET a.CalculatedColumn= b.[Calculated Column]
FROM table1 a, table2 b 
WHERE 
    b.[common field]= a.commonfield
AND a.BatchNO = '110'

Si je me souviens bien, 2008R2 donnait une erreur lorsque j'ai essayé une requête similaire.

nyunyu
la source
2

Je trouve utile de transformer un UPDATE en un SELECT pour obtenir les lignes que je veux mettre à jour en tant que test avant la mise à jour. Si je peux sélectionner les lignes exactes que je veux, je peux mettre à jour uniquement les lignes que je veux mettre à jour.

DECLARE @expense_report_id AS INT
SET @expense_report_id = 1027

--UPDATE expense_report_detail_distribution
--SET service_bill_id = 9

SELECT *
FROM expense_report_detail_distribution erdd
INNER JOIN expense_report_detail erd
INNER JOIN expense_report er 
    ON er.expense_report_id = erd.expense_report_id 
    ON erdd.expense_report_detail_id = erd.expense_report_detail_id
WHERE er.expense_report_id = @expense_report_id
CW1255
la source
2
    UPDATE mytable
         SET myfield = CASE other_field
             WHEN 1 THEN 'value'
             WHEN 2 THEN 'value'
             WHEN 3 THEN 'value'
         END
    From mytable
    Join otherTable on otherTable.id = mytable.id
    Where othertable.somecolumn = '1234'

Plus d'alternatives ici .

wut
la source
0

Une autre approche consisterait à utiliser MERGE

  ;WITH cteTable1(CalculatedColumn, CommonField)
  AS
  (
    select CalculatedColumn, CommonField from Table1 Where BatchNo = '110'
  )
  MERGE cteTable1 AS target
    USING (select "Calculated Column", "Common Field" FROM dbo.Table2) AS source ("Calculated Column", "Common Field")
    ON (target.CommonField = source."Common Field")
    WHEN MATCHED THEN 
        UPDATE SET target.CalculatedColumn = source."Calculated Column";

-Merge fait partie de la norme SQL

-Je suis également sûr que les mises à jour des jointures internes ne sont pas déterministes .. Question similaire ici où la réponse en parle http://ask.sqlservercentral.com/questions/19089/updating-two-tables-using-single-query. html

Shane Neuville
la source
4
Bien qu'ils puissent être standard, je serais très prudent avecMERGE .
Aaron Bertrand
1
Ce qui est assez drôle car littéralement 5 minutes après avoir posté cela, je suis tombé sur des mises à jour problématiques non déterministes dans les sprocs dont j'ai hérité :-) trucs amusants
Shane Neuville
Cela ne rend pas la fusion meilleure, cela signifie simplement que vous avez de mauvaises mises à jour.
Aaron Bertrand
1
Oui, j'étais juste anecdotique :-) J'avais ça sur le cerveau quand j'ai replongé dans le sproc et c'était la première chose que j'ai vue.
Shane Neuville
3
Les CTE sont standard; les crochets pour échapper aux noms stupides ne le sont pas (les guillemets doubles le sont).
jour,
0

J'ai eu le même problème .. et vous n'avez pas besoin d'ajouter une colonne physique .. cuz maintenant vous devrez la maintenir .. ce que vous pouvez faire est d'ajouter une colonne générique dans la requête de sélection:

EX:

select tb1.col1, tb1.col2, tb1.col3 ,
( 
select 'Match' from table2 as tbl2
where tbl1.col1 = tbl2.col1 and tab1.col2 = tbl2.col2
)  
from myTable as tbl1
Mahmoud Sayed
la source
0

L'approche d'Aaron ci-dessus a parfaitement fonctionné pour moi. Mon instruction de mise à jour était légèrement différente car j'avais besoin de me joindre sur la base de deux champs concaténés dans une table pour correspondre à un champ dans une autre table.

 --update clients table cell field from custom table containing mobile numbers

update clients
set cell = m.Phone
from clients as c
inner join [dbo].[COSStaffMobileNumbers] as m 
on c.Last_Name + c.First_Name = m.Name
Jeremy Boutot
la source
-3

Essayer:

UPDATE table1
SET CalculatedColumn = ( SELECT [Calculated Column] 
                         FROM table2 
                         WHERE table1.commonfield = [common field])
WHERE  BatchNO = '110'
user140301
la source
7
Je vote en aval, car cela mettra à jour chaque ligne table1, pas seulement les lignes où il y a une correspondance sur le champ commun entre les deux tables (en fait une jointure gauche et non une jointure interne).
Cᴏʀʏ
@ Cᴏʀʏ: Vous voulez dire qu'il mettra à jour chaque correspondance de ligne BatchNo = '110', non? Est-ce que tous les votes négatifs sont le résultat de cet effet, ou est-ce que d'autres avaient d'autres raisons de voter contre?
palswim
Je demande parce que certains peuvent accepter que l'opération UPDATE définisse certaines lignes sur NULL, et ce formulaire peut être une solution moins spécifique à T-SQL.
palswim