sql >> Database >  >> RDS >> Sqlserver

Een if-voorwaarde gebruiken in een SQL Server-insert

Het patroon is (zonder foutafhandeling):

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;

U hoeft hier geen extra lezing van de #temp-tabel uit te voeren. Dat doe je al door de update te proberen. Om te beschermen tegen race-omstandigheden, doe je hetzelfde als je elk blok van twee of meer verklaringen die je wilt isoleren zou beschermen:je zou het in een transactie verpakken met een geschikt isolatieniveau (waarschijnlijk serialiseerbaar hier, hoewel dat allemaal alleen is logisch als we het niet hebben over een #temp-tabel, want die is per definitie geserialiseerd).

Je bent geen stap verder door een IF EXISTS . toe te voegen check (en je zou vergrendelingshints moeten toevoegen om dat toch veilig / serialiseerbaar te maken), maar je zou verder achter kunnen lopen, afhankelijk van hoe vaak je bestaande rijen bijwerkt versus nieuwe invoegt. Dat kan een hoop extra I/O opleveren.

Mensen zullen je waarschijnlijk vertellen om MERGE te gebruiken (wat eigenlijk meerdere operaties achter de schermen zijn, en die ook moeten worden beschermd met serializable), ik dring er bij je op aan dat niet te doen. Ik leg hier uit waarom:

  • Wees voorzichtig met de MERGE-instructie van SQL Server

Voor een patroon met meerdere rijen (zoals een TVP), zou ik dit op dezelfde manier aanpakken, maar er is geen praktische manier om de tweede lezing te vermijden, zoals je kunt met het geval met één rij. En nee, MERGE ontwijkt het ook niet.

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;

Nou, ik denk dat er een manier is om het te doen, maar ik heb dit niet grondig getest:

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;

In beide gevallen voert u eerst de update uit, anders werkt u alle rijen bij die u zojuist hebt ingevoegd, wat verspilling zou zijn.



  1. Hoe SQLCipher te implementeren bij gebruik van SQLiteOpenHelper

  2. oratop

  3. Hoe converteer je een volledige MySQL-databasekarakterset en sortering naar UTF-8?

  4. Oracle JDeveloper 12c gebruiken met Oracle Database, deel 2