SQL Server w logistyce

Dokumenty do wysyłki bez kolejki: znacznik „wysłano” w jednym UPDATE z OUTPUT

Dwa przebiegi zadania zabiorą ten sam dokument, jeśli odczyt i znacznik dzieli choćby sekunda. Jeden UPDATE z OUTPUT oddaje dokumenty tylko jednemu z nich.

sql.server.net.pl/sql-server/
Dłoń na klawiaturze przed dwoma monitorami z kodem
Dłoń na klawiaturze przed dwoma monitorami z kodem
W skrócie
  • Problem: procedura wydająca dokumenty do wysyłki, uruchomiona w dwóch przebiegach naraz, może zwrócić ten sam dokument dwa razy.
  • Wzorzec: jeden UPDATE z klauzulą OUTPUT INTO tabela zmienna oznacza dokumenty i zwraca ich numery, a zestaw wynikowy powstaje tylko z tej tabeli.
  • Pułapka: UPDATE bez warunku EXISTS oznacza także dokument, który nie wejdzie do zestawu, a OUTPUT bez INTO kończy się błędem 334 na tabeli z wyzwalaczem.
  • Kontrola: test dwóch sesji w odstępie sekundy: sesja A dostaje cztery wiersze, a sesja B zestaw pusty.

UPDATE z OUTPUT w T-SQL pozwala wydać dokumenty do wysyłki bez osobnej kolejki: jedno polecenie oznacza je jako wysłane i od razu zwraca ich numery. Dwa przebiegi zadania uruchomione prawie jednocześnie nie zabiorą wtedy tego samego dokumentu.

Procedura, która jednym poleceniem UPDATE z klauzulą OUTPUT oznacza dokumenty jako wysłane i zwraca ich numery, nie pozwala dwóm równoległym przebiegom zabrać tego samego dokumentu. Płacimy za to semantyką co najwyżej raz: gdy odbiorca dokumentu nie potwierdzi, dokument zostaje oznaczony i nie wróci.

To wzorzec z nazwami przykładowymi. Uruchomiliśmy go na SQL Server 2022 przy poziomach zgodności 130 i 160, także w teście dwóch równoległych sesji, i za każdym razem dostaliśmy ten sam wynik. Użyte konstrukcje są dostępne od SQL Server 2016 (według dokumentacji).

Dwa przebiegi zadania mogą zabrać ten sam dokument

Przykład: zadanie uruchamia cyklicznie procedurę, która pobiera dokumenty do wysyłki. Dwa przebiegi zadania uruchomiły się prawie jednocześnie, drugi sekundę po pierwszym. Gdy procedura najpierw wybiera dokumenty do tabeli tymczasowej, a dopiero potem oznacza je osobnym UPDATE, oba przebiegi zwracają te same dokumenty 1 i 2 i każdy z nich wychodzi dwa razy.

Gdy znacznik ustawia jedno polecenie UPDATE z OUTPUT, pierwszy przebieg dostaje dokumenty W-1 i P-1, a drugi zestaw pusty. Drugi UPDATE czeka na blokadę założoną przez pierwszy, a po jej zwolnieniu widzi już Wyslano = 1. Nic nie zostaje wysłane dwa razy.

Test dwóch sesji: odczyt i zapis osobno kontra jedno polecenie
SesjaSELECT, potem UPDATEUPDATE z OUTPUT
Sesja Azwraca dokumenty 1 i 2zwraca dokumenty 1 i 2 i przytrzymuje blokadę 3 s
Sesja B (sekundę później)zwraca dokumenty 1 i 2czeka na blokadę, potem zwraca zestaw pusty
Skutekkażdy dokument wysłany dwa razykażdy dokument wysłany raz

Przykład. Odstęp między odczytem a zapisem w wersji naiwnej odtworzono poleceniem WAITFOR DELAY na trzy sekundy.

Odczyt dokumentów do wysyłki i ich oznaczenie muszą być jednym poleceniem, bo każdy odstęp między nimi jest oknem dla drugiego przebiegu.

Dane próbne i procedura zwracająca płaski zestaw

Dane próbne to trzy tabele opisane niżej oraz pięć dokumentów, z których każdy odtwarza jedną sytuację. Dokumenty W-1 i P-1 są zatwierdzone i niewysłane. Dokument W-2 nie jest zatwierdzony. Dokument P-2 jest już wysłany, a P-3 jest zatwierdzony i ma jedną pozycję o ilości 0.

Trzy tabele danych próbnych
TabelaZawartość
dbo.Dokumentnumer i typ (WZ albo PZ) oraz dwa znaczniki: zatwierdzenia i wysłano
dbo.Pozycjaartykuł i ilość pozycji dokumentu
dbo.RecepturaKompletuskładowe kompletu i ich ilości
CREATE TABLE dbo.Dokument (Id int IDENTITY PRIMARY KEY, Numer varchar(20), Typ char(2), Zatwierdzony bit NOT NULL, Wyslano bit NOT NULL DEFAULT 0);
CREATE TABLE dbo.Pozycja (Id int IDENTITY PRIMARY KEY, DokId int, Artykul varchar(10), Ilosc decimal(15,3));
CREATE TABLE dbo.RecepturaKompletu (Komplet varchar(10), Skladowa varchar(10), Ilosc int);
INSERT dbo.Dokument (Numer, Typ, Zatwierdzony, Wyslano) VALUES
 ('W-1', 'WZ', 1, 0), ('P-1', 'PZ', 1, 0), ('W-2', 'WZ', 0, 0), ('P-2', 'PZ', 1, 1), ('P-3', 'PZ', 1, 0);
INSERT dbo.Pozycja (DokId, Artykul, Ilosc) VALUES
 (1,'KOMPLET-1',2),(1,'ART-X',5),(2,'ART-Y',10),(3,'ART-X',1),(4,'ART-Y',3),(5,'ART-Y',0);
INSERT dbo.RecepturaKompletu VALUES ('KOMPLET-1','CZESC-A',2),('KOMPLET-1','CZESC-B',1);

Procedura oznacza dokumenty jednym poleceniem, a potem zwraca zestaw wyłącznie dla dokumentów z tabeli zmiennej. Wynik jest płaskim zestawem o sześciu kolumnach z kropką, które widać w tabeli wyniku poniżej. Dane dokumentu powtarzają się w każdym wierszu pozycji, a zagnieżdżenie w JSON zostaje po stronie programu odbierającego zestaw. Blok z CREATE PROCEDURE uruchamiamy jako osobną partię.

CREATE PROCEDURE dbo.DokumentyDoWysylki AS
BEGIN
  SET NOCOUNT ON;
  DECLARE @wydane TABLE (Id int PRIMARY KEY);

  UPDATE d SET Wyslano = 1
  OUTPUT inserted.Id INTO @wydane
  FROM dbo.Dokument d
  WHERE d.Wyslano = 0 AND d.Zatwierdzony = 1
    AND EXISTS (SELECT 1 FROM dbo.Pozycja p WHERE p.DokId = d.Id AND p.Ilosc > 0);

  SELECT d.Id AS [Dokument.Id], d.Numer AS [Dokument.Numer],
         CASE d.Typ WHEN 'WZ' THEN 'SA' WHEN 'PZ' THEN 'SB' ELSE d.Typ END AS [Dokument.Seria],
         ROW_NUMBER() OVER (PARTITION BY d.Id ORDER BY p.Id, x.Artykul) AS [Pozycje.Lp],
         x.Artykul AS [Pozycje.Artykul], x.Ilosc AS [Pozycje.Ilosc]
  FROM @wydane w
  JOIN dbo.Dokument d ON d.Id = w.Id
  JOIN dbo.Pozycja p ON p.DokId = d.Id AND p.Ilosc > 0
  LEFT JOIN dbo.RecepturaKompletu r ON r.Komplet = p.Artykul
  CROSS APPLY (SELECT COALESCE(r.Skladowa, p.Artykul) AS Artykul, p.Ilosc * COALESCE(r.Ilosc, 1) AS Ilosc) x
  ORDER BY d.Id, [Pozycje.Lp];
END;

Typ dokumentu jest mapowany na serię wyrażeniem CASE: w przykładzie WZ daje serię SA, a PZ serię SB (wartości wymyślone). Pierwsze wywołanie zwraca dokumenty W-1 i P-1, czyli cztery wiersze zestawu, a drugie wywołanie zestaw pusty. Po obu wywołaniach wysłane są dokumenty 1, 2 i 4, a dokumenty 3 i 5 mają Wyslano = 0.

EXEC dbo.DokumentyDoWysylki;
EXEC dbo.DokumentyDoWysylki;
SELECT 'KONTROLA' AS Sekcja, Id, Numer, Zatwierdzony, Wyslano FROM dbo.Dokument ORDER BY Id;
Wynik pierwszego wywołania procedury
Dokument.IdDokument.NumerDokument.SeriaPozycje.LpPozycje.ArtykulPozycje.Ilosc
1W-1SA1CZESC-A4.000
1W-1SA2CZESC-B2.000
1W-1SA3ART-X5.000
2P-1SB1ART-Y10.000

Przykład. Drugie wywołanie zwraca zestaw pusty.

Stan dokumentów po dwóch wywołaniach
IdNumerZatwierdzonyWyslano
1W-111
2P-111
3W-200
4P-211
5P-310

Przykład. Dokument P-3 zostaje niewysłany, bo żadna jego pozycja nie ma ilości większej od zera.

Komplet rozbijamy na składowe w tym samym zapytaniu

Komplet jest rozbijany w tym samym zapytaniu. Złączenie LEFT JOIN do receptury zostawia pozycje, które nie są kompletami, a wyrażenie COALESCE wybiera składową z receptury albo artykuł pozycji. Ilość to ilość pozycji razy ilość z receptury albo razy 1.

Pozycja KOMPLET-1 w ilości 2 przy recepturze 2 x CZESC-A i 1 x CZESC-B daje wiersze CZESC-A w ilości 4 i CZESC-B w ilości 2, a ART-X w ilości 5 przechodzi bez zmian. Dlatego numer pozycji w zestawie liczy ROW_NUMBER z podziałem na dokument, a nie identyfikator pozycji: z jednej pozycji powstaje kilka wierszy i identyfikator przestaje być jednoznaczny. W dokumencie W-1 numery biegną od 1 do 3.

Wynik procedury: komplet rozbity na składoweMakieta poglądowa, dane przykładowe. Okno wyniku zapytania: płaski zestaw o sześciu kolumnach i czterech wierszach. Dokument W-1 ma trzy pozycje, z czego dwie (CZESC-A, CZESC-B) pochodzą z jednej pozycji kompletu, dokument P-1 ma jedną. Pod spodem osobny wynik z FOR JSON PATH z tablicą Pozycje.Wynikzestaw płaskiPierwsze wywołanie proceduryEXEC dbo.DokumentyDoWysylki;Drugie wywołanie zwraca zestaw pusty.Dokument.IdDokument.NumerDokument.SeriaPozycje.LpPozycje.ArtykulPozycje.Ilosc1W-1SA1CZESC-A4.0001W-1SA2CZESC-B2.0001W-1SA3ART-X5.0002P-1SB1ART-Y10.000z jednej pozycjikompletuNumer pozycji liczy ROW_NUMBER w obrębie dokumentu, bo z jednej pozycji komplet daje kilka wierszy.Osobne zapytanie: podzapytanie z FOR JSON PATH daje tablicę Pozycje[{"Dokument":{"Id":1},"Numer":"W-1","Pozycje":[  {"Artykul":"KOMPLET-1","Ilosc":2.000},  {"Artykul":"ART-X","Ilosc":5.000}]}]Makieta poglądowa, dane przykładowe
Makieta poglądowa, dane przykładowe. Wynik procedury w siatce zapytania: komplet rozbity na dwie składowe i numer pozycji liczony w dokumencie.

Znacznik w jednym UPDATE z OUTPUT zamyka okno dla drugiej sesji

W poleceniu UPDATE klauzula OUTPUT zwraca wiersze, które polecenie właśnie zmieniło. Zapis OUTPUT inserted.Id INTO @wydane odkłada numery zmienionych dokumentów do tabeli zmiennej, a zestaw wynikowy powstaje tylko z niej.

Warunek EXISTS w UPDATE chroni przed cichą utratą. Bez niego UPDATE oznaczyłby też dokument P-3 jako wysłany, choć żaden jego wiersz nie wchodzi do zestawu, bo filtr wymaga ilości większej od zera, więc dokument zniknąłby z kolejki bez śladu. W teście bez warunku oznaczone zostało Id 5.

UPDATE dbo.Dokument SET Wyslano = 0 WHERE Numer = 'P-3';
DECLARE @bez TABLE (Id int);
UPDATE dbo.Dokument SET Wyslano = 1 OUTPUT inserted.Id INTO @bez WHERE Wyslano = 0 AND Zatwierdzony = 1;
SELECT 'BEZ_STRAZNIKA_oznaczone_Id' AS Sekcja, Id FROM @bez;

Wyścig sprawdziliśmy testem dwóch sesji uruchomionych w odstępie jednej sekundy. Wersja naiwna, czyli SELECT do tabeli tymczasowej i UPDATE rozdzielone trzysekundową przerwą, zwróciła w obu sesjach te same dokumenty 1 i 2.

CREATE PROCEDURE dbo.DokumentyDoWysylki_Naiwna AS
BEGIN
  SET NOCOUNT ON;
  SELECT Id INTO #do_wyslania FROM dbo.Dokument d WHERE d.Wyslano = 0 AND d.Zatwierdzony = 1
     AND EXISTS (SELECT 1 FROM dbo.Pozycja p WHERE p.DokId = d.Id AND p.Ilosc > 0);
  WAITFOR DELAY '00:00:03';
  UPDATE dbo.Dokument SET Wyslano = 1 WHERE Id IN (SELECT Id FROM #do_wyslania);
  SELECT Id AS [Dokument.Id] FROM #do_wyslania ORDER BY Id;
END;

Przykład ilustracyjny. Wersja naiwna użyta w teście wyścigu; WAITFOR odtwarza odstęp między odczytem a zapisem.

Wzorzec z UPDATE i OUTPUT zachował się inaczej. Sesja A, z transakcją przytrzymaną trzy sekundy, dostała zestaw z czterema wierszami, a sesja B zestaw pusty, bo jej UPDATE czekał na blokadę i po jej zwolnieniu zobaczył Wyslano = 1.

BEGIN TRAN;
EXEC dbo.DokumentyDoWysylki;
WAITFOR DELAY '00:00:03';
COMMIT;
EXEC dbo.DokumentyDoWysylki;
Schemat: SELECT, potem UPDATE kontra UPDATE z OUTPUTSchemat: dwie sesje biorą dokumenty do wysyłki, sesja B startuje sekundę po sesji A. W wersji SELECT, potem UPDATE obie sesje dostają dokumenty 1 i 2. W wersji UPDATE z OUTPUT sesja A trzyma blokadę do zatwierdzenia i dostaje dokumenty 1 i 2, a sesja B czeka i dostaje zestaw pusty. Po prawej wariant z potwierdzeniem o statusach 0, 1, 2.Dwie sesje, jeden dokument: odczyt i zapis osobno kontra jedno polecenieOdstęp między odczytem a zapisem jest oknem dla drugiej sesji. Jedno polecenie UPDATE z OUTPUT oddaje dokumenty tylko jednej z nich.SELECT, potem UPDATE0 s1 s3 sSesja ASesja BSELECTprzerwaUPDATESELECTUPDATEte same dokumenty trafiają do obu sesjidokumenty 1 i 2UPDATE z OUTPUT0 s1 s3 sSesja ASesja BUPDATE z OUTPUTblokada wiersza do zatwierdzeniazestaw z czterema wierszamidokumenty 1 i 2czeka na blokadęzestaw pustywariant z potwierdzeniem:status 0, 1, 2Wzorzec z OUTPUT daje semantykę co najwyżej raz: znacznik jest ustawiany przed odpowiedzią odbiorcy.
Schemat: dwie sesje biorą dokumenty, naiwny SELECT z UPDATE zwraca je obu, a UPDATE z OUTPUT oddaje je tylko jednej sesji.

Wynik testu był taki sam po włączeniu opcji bazy READ_COMMITTED_SNAPSHOT, którą opisuje dokumentacja ALTER DATABASE SET. Sprawdziliśmy jeden UPDATE przy dwóch poziomach izolacji, domyślnym i z tą opcją, więc nie twierdzimy, że OUTPUT rozwiązuje każdy wyścig.

OUTPUT bez INTO zatrzymuje UPDATE na tabeli z wyzwalaczem

Klauzula OUTPUT bez INTO zwraca wiersze klientowi, i tu jest pułapka. Dla tabeli z włączonym wyzwalaczem UPDATE z samym OUTPUT kończy się błędem 334, a z OUTPUT ... INTO tabela zmienna dostaje 1 wiersz bez błędu. Wyzwalacz dopisuje CREATE TRIGGER, który musi być pierwszą instrukcją partii, więc polecenia z tego bloku uruchamiamy w osobnych partiach rozdzielonych poleceniem GO narzędzia klienckiego.

CREATE TABLE dbo.DokumentTrg (Id int IDENTITY PRIMARY KEY, Wyslano bit NOT NULL DEFAULT 0);
INSERT dbo.DokumentTrg DEFAULT VALUES;
GO
CREATE TRIGGER dbo.trg_DokumentTrg ON dbo.DokumentTrg AFTER UPDATE AS BEGIN SET NOCOUNT ON; END;
GO
EXEC (N'UPDATE dbo.DokumentTrg SET Wyslano = 1 OUTPUT inserted.Id WHERE Wyslano = 0');

Przykład ilustracyjny. GO jest separatorem partii narzędzia, a nie instrukcją T-SQL. Ostatnie polecenie kończy się błędem 334.

Komunikat błędu brzmi: „The target table 'dbo.DokumentTrg' of the DML statement cannot have any enabled triggers if the statement contains an OUTPUT clause without INTO clause.”.

Płaski zestaw z kolumnami z kropką różni się od FOR JSON PATH

Zestaw z kolumnami z kropką jest płaski: dokument powtarza się w każdym wierszu pozycji. FOR JSON PATH na takim zestawie z aliasami Dokument.Id i Pozycje.Artykul daje po jednym obiekcie na wiersz, z zagnieżdżonymi obiektami Dokument i Pozycje, a nie tablicę pozycji. Opisuje to dokumentacja FOR JSON.

SELECT d.Id AS [Dokument.Id], p.Artykul AS [Pozycje.Artykul], p.Ilosc AS [Pozycje.Ilosc]
FROM dbo.Dokument d JOIN dbo.Pozycja p ON p.DokId = d.Id WHERE d.Id = 1 FOR JSON PATH;
[{"Dokument":{"Id":1},"Pozycje":{"Artykul":"KOMPLET-1","Ilosc":2.000}},{"Dokument":{"Id":1},"Pozycje":{"Artykul":"ART-X","Ilosc":5.000}}]

Przykład. Wynik: po jednym obiekcie na wiersz, bez tablicy pozycji.

Tablicę pozycji daje dopiero podzapytanie z FOR JSON PATH w kolumnie Pozycje.

SELECT d.Id AS [Dokument.Id], d.Numer,
       (SELECT p.Artykul, p.Ilosc FROM dbo.Pozycja p WHERE p.DokId = d.Id FOR JSON PATH) AS Pozycje
FROM dbo.Dokument d WHERE d.Id = 1 FOR JSON PATH;
[{"Dokument":{"Id":1},"Numer":"W-1","Pozycje":[{"Artykul":"KOMPLET-1","Ilosc":2.000},{"Artykul":"ART-X","Ilosc":5.000}]}]

Przykład. Wynik: jeden obiekt dokumentu z tablicą Pozycje.

Odpowiedź API zbudowaną z FOR JSON PATH opisuje artykuł o API na procedurach SQL i JSON.

Kompromis wzorca i wariant z potwierdzeniem

Znacznik jest ustawiany przed odpowiedzią odbiorcy, więc to semantyka co najwyżej raz. Gdy odbiorca zestawu nie przyjmie dokumentu, dokument pozostaje oznaczony, a procedura nie zwróci go ponownie.

Wariant z potwierdzeniem trzyma kolejkę ze statusem 0 (czeka), 1 (pobrane) i 2 (potwierdzone) oraz czasem pobrania. Ten sam UPDATE z OUTPUT zmienia 0 na 1 i bierze także status 1 starszy niż 15 minut. Dokument, którego odbiorca nie potwierdził, wraca więc do zestawu po upływie tego czasu.

CREATE TABLE dbo.Kolejka (Id int PRIMARY KEY, Status tinyint NOT NULL DEFAULT 0, Pobrano datetime2 NULL);
INSERT dbo.Kolejka (Id) VALUES (1),(2);
DECLARE @pob TABLE (Id int PRIMARY KEY);
UPDATE dbo.Kolejka SET Status = 1, Pobrano = SYSUTCDATETIME()
OUTPUT inserted.Id INTO @pob
WHERE Status = 0 OR (Status = 1 AND Pobrano < DATEADD(MINUTE, -15, SYSUTCDATETIME()));

Przykład ilustracyjny. Pobranie uruchamiamy trzy razy, każdy raz w osobnej partii.

UPDATE dbo.Kolejka SET Status = 2 WHERE Id = 1;
UPDATE dbo.Kolejka SET Pobrano = DATEADD(MINUTE, -20, Pobrano) WHERE Id = 2;

Przykład ilustracyjny. Potwierdzenie dokumentu 1 i cofnięcie czasu dokumentu 2 o 20 minut; wykonujemy je między drugim a trzecim pobraniem.

Wyniki trzech pobrań z kolejki
PobranieZwrócone IdUwaga
11 i 2oba dokumenty mają status 0
2, zaraz po pierwszymbrak (0 wierszy)oba dokumenty są pobrane i młodsze niż 15 minut
3, po potwierdzeniu dokumentu 1 i cofnięciu czasu dokumentu 2 o 20 minut2dokument 1 ma status 2, dokument 2 status 1

Przykład. Stan końcowy: dokument 1 ma status 2, a dokument 2 status 1.

To semantyka co najmniej raz, więc odbiorca musi rozpoznawać powtórkę po stałym kluczu dokumentu. Ponowienia po nieudanej wysyłce, z osobną tabelą ponowień, opisuje artykuł o klasyfikacji błędów i ponowieniach wysyłki, a szukanie przyczyn zatoru w kolejce integracji artykuł o diagnostyce kolejki integracji. Inny sposób uniknięcia powtórki, przez unikalny indeks i blokadę aplikacyjną, pokazuje artykuł o jednym mailu na zdarzenie.

Pytania przed napisaniem własnej procedury wydającej dokumenty

Zanim przeniesiesz wzorzec do własnej bazy, odpowiedz sobie na cztery pytania.

  • Przebiegi: czy procedura pobierająca dokumenty do wysyłki może być wywołana przez dwa przebiegi naraz, także przypadkiem.
  • Klucz: czy odbiorca potrafi rozpoznać ten sam dokument wysłany drugi raz po stałym kluczu.
  • Nieprzyjęcie: kto dowie się, że dokument został oznaczony, a odbiorca go nie przyjął.
  • Kształt zestawu: czy zestaw wynikowy ma być płaski, czy baza ma sama składać JSON.

Warunek zamknięcia zlecenia po liczbie otwartych linii opisuje artykuł o zamykaniu zlecenia w T-SQL, a liczenie kompletów ze zebranych składowych artykuł o alokacji zachłannej kompletów. Szersze omówienie sposobów wymiany dokumentów z programem ERP znajdziesz w artykule o integracji systemu magazynowego z ERP. Wszystkie artykuły sekcji zebraliśmy w spisie artykułów o SQL Server.

FAQ

Najczęstsze pytania o UPDATE z OUTPUT w T-SQL

01

Dlaczego SELECT, a potem UPDATE, pozwala wysłać dokument dwa razy?

Między odczytem a zapisem znacznika jest odstęp, w którym druga sesja czyta te same dokumenty. W teście dwóch sesji w odstępie sekundy wersja naiwna, z trzysekundową przerwą, zwróciła w obu sesjach dokumenty 1 i 2. Każdy dokument wyszedł więc dwa razy.

02

Co daje klauzula OUTPUT i po co INTO?

OUTPUT zwraca wiersze zmienione przez UPDATE, więc znacznik i lista dokumentów powstają w jednym kroku. Postać OUTPUT INTO odkłada numery do tabeli zmiennej. Sam OUTPUT na tabeli z włączonym wyzwalaczem kończy się błędem 334, a z INTO tabela zmienna dostaje 1 wiersz bez błędu.

03

Jak zwrócić dokument z pozycjami w jednym zestawie i rozbić komplet?

Zestaw jest płaski, z kolumnami z kropką, a dane dokumentu powtarzają się w każdym wierszu pozycji. Komplet rozbija LEFT JOIN do receptury z COALESCE składowej i artykułu oraz mnożeniem ilości przez ilość z receptury. Numer pozycji liczy ROW_NUMBER w obrębie dokumentu, bo z jednej pozycji powstaje kilka wierszy.

04

Czym kolumny z kropką różnią się od FOR JSON PATH?

FOR JSON PATH na płaskim zestawie z aliasami Dokument.Id i Pozycje.Artykul daje po jednym obiekcie na wiersz z zagnieżdżonymi obiektami, a nie tablicę pozycji. Tablicę daje dopiero podzapytanie z FOR JSON PATH w kolumnie Pozycje. Przy płaskim zestawie zagnieżdżenie robi program odbierający.

05

Co zrobić, gdy odbiorca nie potwierdzi dokumentu?

Przy jednym UPDATE dokument pozostaje oznaczony i nie wróci, bo to semantyka co najwyżej raz. Wariant z potwierdzeniem trzyma status 0, 1 i 2 oraz czas pobrania i bierze ponownie status 1 starszy niż 15 minut. To semantyka co najmniej raz, więc odbiorca rozpoznaje powtórkę po stałym kluczu dokumentu.

Słownik pojęć

Słownik pojęć

Pojęcia użyte we wzorcu znacznika wysłano w jednym UPDATE z OUTPUT.

KKlauzula OUTPUT
Część polecenia UPDATE, INSERT lub DELETE zwracająca zmienione wiersze. We wzorcu OUTPUT inserted.Id INTO @wydane odkłada numery oznaczonych dokumentów w tym samym kroku, w którym ustawiany jest znacznik.
WWyścig
Sytuacja, w której wynik zależy od kolejności zdarzeń w dwóch równoległych przebiegach. We wzorcu dwa przebiegi pobierają te same dokumenty, gdy odczyt i znacznik rozdziela odstęp.
TTabela zmienna
Zmienna typu tabela, istniejąca do końca partii lub procedury. We wzorcu przyjmuje numery z klauzuli OUTPUT INTO, a zestaw wynikowy jest składany tylko z niej.
KKolumna z kropką
Alias kolumny z kropką w nazwie, na przykład Dokument.Id. We wzorcu zestaw jest płaski, a kropka oddziela dokument od pozycji, gdy zestaw trafia do FOR JSON PATH.
FFOR JSON PATH
Klauzula SELECT zwracająca wynik jako JSON. Alias z kropką daje zagnieżdżony obiekt, a podzapytanie z FOR JSON PATH w kolumnie daje tablicę, jak kolumna Pozycje w przykładzie.
SSemantyka co najwyżej raz
Gwarancja, że dokument zostanie zwrócony najwyżej jednemu odbiorcy. We wzorcu znacznik ustawiany przed odpowiedzią odbiorcy oznacza, że niepotwierdzony dokument nie wróci.
SSemantyka co najmniej raz
Gwarancja, że dokument zostanie zwrócony przynajmniej raz, nawet kosztem powtórek. We wzorcu z potwierdzeniem dokument pobrany i niepotwierdzony wraca po 15 minutach, więc odbiorca rozpoznaje go po kluczu.
WWyzwalacz
Procedura wykonywana automatycznie po zmianie danych w tabeli. We wzorcu jego obecność powoduje błąd 334, gdy UPDATE ma OUTPUT bez INTO.

Zobacz to w praktyce

Sprawdź, jak systemy SoftwareStudio oparte na MS SQL Server wspierają ten obszar Twojej firmy.