sql >> Database teknologi >  >> RDS >> Sqlserver

Brug af en if-betingelse i en indsættelse af SQL Server

Mønsteret er (uden fejlhåndtering):

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

UPDATE #TProductSales SET StockQty = @StockQty, ETA1 = @ETA1
  WHERE ProductID = @ProductID;

IF @@ROWCOUNT = 0
BEGIN
  INSERT #TProductSales(ProductID, StockQTY, ETA1) 
    VALUES(@ProductID, @StockQTY, @ETA1);
END

COMMIT TRANSACTION;

Du behøver ikke at udføre en yderligere læsning af #temp-tabellen her. Det gør du allerede ved at prøve opdateringen. For at beskytte mod raceforhold gør du det samme, som du ville beskytte enhver blok af to eller flere udsagn, som du ønsker at isolere:du vil pakke det ind i en transaktion med et passende isolationsniveau (sandsynligvis kan serialiseres her, selvom det kun alle giver mening, når vi ikke taler om en #temp-tabel, da den per definition er serialiseret).

Du er ikke længere fremme ved at tilføje en IF EXISTS check (og du bliver nødt til at tilføje låsetip for at gøre det sikkert / serialiserbart alligevel), men du kan være længere bagud, afhængigt af hvor mange gange du opdaterer eksisterende rækker vs. indsæt nye. Det kan tilføje op til en masse ekstra I/O.

Folk vil sandsynligvis fortælle dig, at du skal bruge MERGE (som faktisk er flere operationer bag kulisserne og også skal beskyttes med serialiserbar), opfordrer jeg dig til at lade være. Jeg forklarer hvorfor her:

  • Vær forsigtig med SQL Servers MERGE-erklæring

For et mønster med flere rækker (som en TVP), ville jeg håndtere dette på nogenlunde samme måde, men der er ikke en praktisk måde at undgå den anden læsning, som du kan med enkeltrækkets sag. Og nej, MERGE undgår det heller ikke.

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

UPDATE t SET t.col = tvp.col
  FROM dbo.TargetTable AS t
  INNER JOIN @TVP AS tvp
  ON t.ProductID = tvp.ProductID;

INSERT dbo.TargetTable(ProductID, othercols)
  SELECT ProductID, othercols
  FROM @TVP AS tvp
  WHERE NOT EXISTS
  (
    SELECT 1 FROM dbo.TargetTable
    WHERE ProductID = tvp.ProductID
  );

COMMIT TRANSACTION;

Nå, jeg gætter på, at der er en måde at gøre det på, men jeg har ikke testet dette grundigt:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

DECLARE @exist TABLE(ProductID int PRIMARY KEY);

UPDATE t SET t.col = tvp.col
  OUTPUT deleted.ProductID INTO @exist
  FROM dbo.TargetTable AS t
  INNER JOIN @tvp AS tvp
  ON t.ProductID = tvp.ProductID;

INSERT dbo.TargetTable(ProductID, othercols) 
  SELECT ProductID, othercols 
  FROM @tvp AS t 
  WHERE NOT EXISTS 
  (
    SELECT 1 FROM @exist 
    WHERE ProductID = t.ProductID
  );

COMMIT TRANSACTION;

I begge tilfælde udfører du opdateringen først, ellers opdaterer du alle de rækker, du lige har indsat, hvilket ville være spild.



  1. Find overtrædelser af fremmednøgler i SQLite

  2. Sådan installeres PostgreSQL 12 på Ubuntu 20.04 DigitalOcean

  3. Sådan fungerer CHARACTER_LENGTH()-funktionen i MySQL

  4. Sådan installeres WordPress:Serversoftwaren