SQL Server w logistyce

Rejestr blokad w T-SQL: jednostka zatrzymana bez zmiany ilości i bez kasowania śladów

Rejestr blokad w T-SQL zatrzymuje jednostkę bez zmiany ilości i bez kasowania śladów: blokada jest osobnym wierszem rejestru, a nie zmianą stanu. Widok z NOT EXISTS wylicza z rejestru jednostki dostępne, więc zablokowana jednostka znika z widoku, a ilość razem w tabeli jednostek zostaje bez zmian.

sql.server.net.pl/sql-server/
Abstrakcyjny walec bazy danych otoczony panelami wykresów i ikonami kłódek
Abstrakcyjny walec bazy danych otoczony panelami wykresów i ikonami kłódek
W skrócie
  • Rejestr: blokada jest osobnym wierszem, a ilość jednostki zostaje bez zmian.
  • Widok: warunek NOT EXISTS wylicza z rejestru jednostki dostępne.
  • Indeks: indeks filtrowany pozwala na najwyżej jedną aktywną blokadę na jednostkę.
  • Test: pięć kroków na poziomach zgodności 130 i 160 daje ten sam wynik.

Rejestr blokad w T-SQL zatrzymuje jednostkę bez zmiany ilości i bez kasowania śladów: blokada jest osobnym wierszem rejestru, a nie zmianą stanu. Programista albo administrator bazy magazynowej ma zatrzymać jednostkę, czyli pozycję o zapisanej ilości, i chce mieć historię blokad oraz widok jednostek dostępnych. Rejestr ze stanem blokady i śladem jej założenia oraz zdjęcia razem z widokiem z NOT EXISTS daje obie rzeczy bez dotykania ilości.

Blokada zapisana jako wiersz rejestru nie zmienia ilości i zostawia ślad: kto i kiedy ją założył i zdjął. Widok z NOT EXISTS wylicza z rejestru jednostki dostępne, więc zablokowana jednostka znika z widoku, a ilość razem w tabeli jednostek zostaje bez zmian. Ekran, który czyta tabelę jednostek bezpośrednio, pokaże także jednostkę zablokowaną.

W opisywanym wdrożeniu blokada jest wierszem osobnego rejestru, a nie zmianą ilości, a rejestr nie ma kolumny ilości. Wiersz rejestru ma stan oraz pola z loginem i czasem założenia blokady oraz z loginem i czasem jej zdjęcia. Pokazany wzorzec da się przenieść do innej bazy i nie jest kodem tego wdrożenia. O regule wspólnej dla przycisku i zapisu mówi artykuł o funkcji strażnika z kodem odmowy.

Blokada zapisana w rejestrze zostawia ślad i nie zmienia ilości

Rejestr zapisuje blokadę jako osobny wiersz, więc ilość jednostki zostaje taka, jaka była, a po blokadzie pozostaje ślad. Rejestr z przykładu nie ma kolumny ilości. Ma za to stan blokady oraz dwie pary pól: login i czas założenia oraz login i czas zdjęcia. Stan przyjmuje w przykładzie dwie wartości, aktywna i zdjeta, a ograniczenie CHECK nie pozwala wpisać innej. Pola zdjęcia są puste, dopóki blokada jest aktywna, a w kolumnie powod można zapisać powód blokady.

Blokada zapisana jako wiersz rejestru nie zmienia ilości i zostawia ślad po założeniu i zdjęciu.

Założenia przykładu. Pierwszy z trzech bloków kodu tworzy tabelę jednostek z kolumnami id, numer, miejsce i ilosc oraz pięć jednostek o łącznej ilości 175. Drugi blok tworzy rejestr blokad, indeks filtrowany i widok dostępnych jednostek, a trzeci zawiera instrukcje założenia i zdjęcia blokady.

Przykład wymaga silnika z poziomem zgodności 100 lub wyższym, bo korzysta z indeksu filtrowanego i typu datetime2, i nie używa CREATE OR ALTER. Test wykonano na jednej instancji, na poziomach zgodności 130 i 160; starszych wersji silnika nie testowano. Poniżej dwa pierwsze bloki kodu w kolejności uruchamiania, a trzeci blok stoi w części o zdjęciu blokady. Bloki zawierają separator GO, który jest separatorem narzędzia, a nie instrukcją T-SQL.

-- jednostki ze stanem. Ilość jest zapisana w jednostce i blokada jej nie zmienia.
SET NOCOUNT ON;
IF OBJECT_ID(N'dbo.jednostka_dostepna_przyklad') IS NOT NULL DROP VIEW dbo.jednostka_dostepna_przyklad;
IF OBJECT_ID(N'dbo.blokada_przyklad') IS NOT NULL DROP TABLE dbo.blokada_przyklad;
IF OBJECT_ID(N'dbo.jednostka_przyklad') IS NOT NULL DROP TABLE dbo.jednostka_przyklad;
GO
CREATE TABLE dbo.jednostka_przyklad (
    id      int          NOT NULL PRIMARY KEY,
    numer   varchar(20)  NOT NULL UNIQUE,
    miejsce varchar(10)  NOT NULL,
    ilosc   decimal(15,3) NOT NULL
);
INSERT dbo.jednostka_przyklad (id, numer, miejsce, ilosc) VALUES
    (1, 'J-001', 'M-01', 30),
    (2, 'J-002', 'M-01', 40),
    (3, 'J-003', 'M-02', 35),
    (4, 'J-004', 'M-02', 20),
    (5, 'J-005', 'M-03', 50);
GO

Tabela jednostek z pięcioma jednostkami o łącznej ilości 175.

CREATE TABLE dbo.blokada_przyklad (
    id           int IDENTITY(1,1) PRIMARY KEY,
    jednostka_id int           NOT NULL REFERENCES dbo.jednostka_przyklad (id),
    stan         varchar(10)   NOT NULL CHECK (stan IN ('aktywna', 'zdjeta')),
    zalozyl      varchar(30)   NOT NULL,
    zalozono     datetime2(0)  NOT NULL DEFAULT SYSDATETIME(),
    zdjal        varchar(30)   NULL,
    zdjeto       datetime2(0)  NULL,
    powod        nvarchar(200) NULL
);
-- najwyżej jedna aktywna blokada na jednostkę; ten sam indeks obsługuje NOT EXISTS
CREATE UNIQUE INDEX ux_blokada_aktywna ON dbo.blokada_przyklad (jednostka_id)
    WHERE stan = 'aktywna';
GO
CREATE VIEW dbo.jednostka_dostepna_przyklad
AS
SELECT j.id, j.numer, j.miejsce, j.ilosc
FROM dbo.jednostka_przyklad AS j
WHERE NOT EXISTS (SELECT 1 FROM dbo.blokada_przyklad AS b
                  WHERE b.jednostka_id = j.id AND b.stan = 'aktywna');
GO

Rejestr blokad z indeksem filtrowanym i widok dostępnych jednostek.

Jedna aktywna blokada na jednostkę wynika z indeksu filtrowanego

Pod tabelą rejestru stoi unikalny indeks filtrowany po kolumnie jednostka_id, który obejmuje tylko wiersze o stanie aktywna. Filtr na stanie sprawia, że wiersze zdjęte nie wchodzą do indeksu, więc jednostka może mieć najwyżej jeden wiersz aktywny, a wierszy zdjętych tyle, ile było zdjęć. Komentarz w kodzie mówi, że ten sam indeks obsługuje także warunek NOT EXISTS w widoku.

  • Pierwsza blokada jednostki. Wiersz o stanie aktywna powstaje, a w rejestrze jest jedna aktywna blokada tej jednostki.
  • Druga blokada tej samej jednostki. Zostaje odrzucona błędem 2601, a w rejestrze nadal jest jedna aktywna blokada.

W teście dwóch procesów, które 300 razy próbowały założyć blokadę tej samej jednostki, żadna jednostka nie miała więcej niż jednej aktywnej blokady. Nadmiarowe założenia kończyły się błędem 2601, bez innych błędów. Indeks filtrowany w innym zastosowaniu, dla numeru w dokumencie z miękkim usuwaniem, opisuje artykuł o numerze w dokumencie z miękkim usuwaniem.

Widok z NOT EXISTS wylicza z rejestru jednostki dostępne

Widok jednostka_dostepna_przyklad zwraca jednostki, dla których w rejestrze nie ma wiersza aktywnej blokady: warunek NOT EXISTS łączy jednostkę z rejestrem po jednostka_id i stanie aktywna. Widok liczy więc dostępność z rejestru, a sama tabela jednostek i jej ilości zostają nietknięte. W stanie początkowym pięć jednostek ma łączną ilość 175, a widok pokazuje pięć jednostek i 175.

Po blokadzie jednostki o ilości 40 widok pokazuje cztery jednostki i ilość 135, a ilość razem w tabeli jednostek nadal wynosi 175, przy jednej aktywnej blokadzie. Saldo lokalizacji liczone z ruchów magazynowych opisuje artykuł o saldzie lokalizacji z ruchów. Tabela zbiera wyniki pięciu kroków, a na poziomach zgodności 130 i 160 są one identyczne.

Wynik pięciu kroków testu rejestru blokad
KrokW widokuIlość w widokuIlość razemBlokad aktywnych
1 stan początkowy51751750
2 blokada jednostki 241351751
3 druga blokada tej samej jednostki (błąd 2601)41351751
4 zdjęcie blokady51751750
5 ponowna blokada41351751

Wynik pięciu kroków, taki sam na poziomach zgodności 130 i 160.

Makieta wyniku testu rejestru blokadMakieta poglądowa, dane przykładowe: siatka z pięcioma krokami testu (jednostki w widoku, ilość w widoku i razem, blokady aktywne) oraz kod indeksu i widoku z instrukcjami blokady. Wynik testu rejestru blokad poziomy zgodności 130 i 160, ten sam wynik Krok W widoku Ilość w widoku Ilość razem Blokad aktywnych 1 stan początkowy 5 175 175 0 2 blokada jednostki 2 4 135 175 1 3 druga blokada tej samej jednostki (błąd 2601) 4 135 175 1 4 zdjęcie blokady 5 175 175 0 5 ponowna blokada 4 135 175 1 Indeks filtrowany i widok (nazwy przykładowe) -- najwyżej jedna aktywna blokada na jednostkę; ten sam indeks obsługuje NOT EXISTS CREATE UNIQUE INDEX ux_blokada_aktywna ON dbo.blokada_przyklad (jednostka_id) WHERE stan = 'aktywna'; GO CREATE VIEW dbo.jednostka_dostepna_przyklad AS SELECT j.id, j.numer, j.miejsce, j.ilosc FROM dbo.jednostka_przyklad AS j WHERE NOT EXISTS (SELECT 1 FROM dbo.blokada_przyklad AS b WHERE b.jednostka_id = j.id AND b.stan = 'aktywna'); Założenie i zdjęcie blokady INSERT dbo.blokada_przyklad (jednostka_id, stan, zalozyl, powod) VALUES (2, 'aktywna', 'operator_1', N'kontrola jakości'); UPDATE dbo.blokada_przyklad SET stan = 'zdjeta', zdjal = 'operator_2', zdjeto = SYSDATETIME() WHERE jednostka_id = 2 AND stan = 'aktywna'; Makieta poglądowa, dane przykładowe
Makieta poglądowa, dane przykładowe. Siatka z pięcioma krokami i blok kodu z instrukcjami blokady.

Zdjęcie blokady zmienia stan wiersza i zostawia historię

Zdjęcie blokady jest instrukcją UPDATE, która ustawia stan zdjeta oraz wpisuje login i czas zdjęcia, a wiersz zostaje w rejestrze. Po zdjęciu jednostka wraca do widoku: widok pokazuje znowu pięć jednostek i 175. Ponowna blokada tej samej jednostki tworzy nowy wiersz, a poprzedni zostaje w historii. Historia jednostki ma wtedy dwa wiersze: zdjęty z czasem zdjęcia oraz aktywny bez niego. Zdjęcie nie kasuje więc śladu założenia, bo zmienia tylko stan i pola zdjęcia w tym samym wierszu.

Poniższy fragment testu ustawia blokadę jednostki 2 z powodem wpisanym w kolumnie powod, a następnie ją zdejmuje.

INSERT dbo.blokada_przyklad (jednostka_id, stan, zalozyl, powod) VALUES (2, 'aktywna', 'operator_1', N'kontrola jakości');
UPDATE dbo.blokada_przyklad SET stan = 'zdjeta', zdjal = 'operator_2', zdjeto = SYSDATETIME()
WHERE jednostka_id = 2 AND stan = 'aktywna';

Założenie i zdjęcie blokady jednostki 2.

Przykład (dane wymyślone). Pięć jednostek ma łącznie ilość 175. Kontrola jakości blokuje jednostkę o ilości 40, więc widok pokazuje cztery jednostki i ilość 135, a suma ilości w tabeli jednostek nadal wynosi 175.

Druga próba zablokowania tej samej jednostki kończy się błędem. Po zdjęciu blokady jednostka wraca do widoku, a rejestr zachowuje, kto założył i zdjął blokadę oraz kiedy to zrobił. Ekran, który czyta tabelę jednostek bez widoku, nadal pokazuje zablokowaną jednostkę.

Ekran, który czyta tabelę jednostek, pokaże zablokowaną jednostkę

Reguła dostępności jest zapisana w widoku, więc obowiązuje ekrany, które czytają z widoku. W teście ekran czytający tabelę jednostek z własnym filtrem miejsca pokazał 2 jednostki w miejscu, w którym jedna jest zablokowana. Ekran czytający widok pokazał w tym samym miejscu 1 jednostkę.

Na schemacie poniżej ekran A czyta tabelę jednostek bezpośrednio i pokazuje pięć jednostek, a ekran B czyta widok i pokazuje cztery. To pułapka rozproszonej reguły: warunek trzeba powtórzyć w zapytaniach, które czytają tabelę. W opisywanym wdrożeniu ten sam warunek stoi w kilku zapytaniach wyboru.

Rejestr blokad i widok jednostek dostępnychSchemat rejestru blokad i tabeli jednostek połączonych widokiem z NOT EXISTS. Ekran czytający widok pokazuje cztery jednostki, a ekran czytający tabelę pięć, także zablokowaną. Blokada w rejestrze: widok pomija jednostkę, tabela ją pokazuje Rejestr nie zmienia ilości; reguła dostępności działa tylko dla ekranów, które czytają widok. Rejestr blokad Blokada to osobny wiersz ze stanem: kto i kiedy ją założył i zdjął. Indeks filtrowany: najwyżej jedna aktywna blokada na jednostkę. Tabela jednostek 5 jednostek, ilość razem 175. Blokada nie zmienia tych liczb. Widok jednostek dostępnych Jednostka wchodzi do widoku, gdy warunek NOT EXISTS nie znajduje aktywnej blokady. Wynik: 4 jednostki, ilość 135 Ekran B czyta widok Pokazuje 4 jednostki: zablokowana jest pominięta. bez reguły Ekran A czyta tabelę bezpośrednio Pokazuje 5 jednostek, także zablokowaną. Ekrany i raporty czytające tabelę jednostek bez widoku wymagają osobnego przeglądu: reguła nie obejmuje ich sama.
Schemat: ekran czytający widok pomija zablokowaną jednostkę, a ekran czytający tabelę ją pokazuje.

Lista zapytań, które czytają tabelę jednostek bezpośrednio, wymaga przeglądu i jest osobnym zadaniem.

Plan zapytania czyta indeks filtrowany zamiast całego rejestru

Plan zapytania do widoku sprawdzono przy 20005 jednostkach, około 50 blokadach aktywnych i około dwóch tysiącach blokad zdjętych. Anty-złączenie, w planie Left Anti Semi Join, czyta wtedy indeks filtrowany, a nie całą tabelę rejestru. Kształt planu jest taki sam na poziomach zgodności 130 i 160. Bez indeksu filtrowanego, przy tych samych danych, plan nadal zawiera anty-złączenie, ale nad Sort (DISTINCT) po skanie klucza głównego rejestru. Odczyty rejestru wynoszą wtedy 22 zamiast 2 przy około dwóch tysiącach pięćdziesięciu wierszach rejestru, a czasu nie mierzono.

Granice wzorca. Cztery granice wynikają z testu.

  • Zdjęcie blokady, której nie ma. Instrukcja zdjęcia zmienia 0 wierszy i nie zgłasza błędu.
  • Brak jednostek dostępnych. Gdy wszystkie jednostki są zablokowane, widok zwraca zero wierszy, a suma ilości w widoku jest NULL, nie zero.
  • Ekrany poza widokiem. Reguła w widoku działa tylko dla ekranów, które z niego czytają.
  • Indeks filtrowany. Nie jest konieczny dla poprawności widoku: w teście bez niego widok zwracał ten sam wynik.

Pozostałe teksty o wzorcach T-SQL zbiera przegląd artykułów o SQL Server.

Pytania do analizy

Przed zastosowaniem wzorca odpowiedz na cztery pytania.

  1. Które ekrany i raporty w systemie czytają stan bezpośrednio z tabel, a które przez widok dostępności?
  2. Czy blokada ma dotyczyć pojedynczej jednostki, czy większego zbioru jednostek i kto o tym decyduje?
  3. Kto ma prawo zdjąć blokadę założoną przez kogoś innego?
  4. Jak długo blokada może być aktywna, zanim ktoś ma obowiązek ją przejrzeć?
FAQ

Najczęstsze pytania o rejestr blokad

01

Czy blokada zmienia stan magazynowy?

Nie. W teście suma ilości jednostek pozostaje 175; zmienia się tylko to, co widok uznaje za dostępne.

02

Czy widok z NOT EXISTS jest wolniejszy niż kolumna z flagą?

Porównania czasu nie mierzono, więc artykuł nie twierdzi, że któreś rozwiązanie jest szybsze. Plan z testu czyta indeks filtrowany, a nie całą tabelę blokad; bez tego indeksu plan skanuje klucz główny rejestru i sortuje.

03

Czy indeks filtrowany jest konieczny?

Dla poprawności widoku nie. Daje jedną aktywną blokadę na jednostkę i wąską ścieżkę odczytu; w teście bez indeksu widok zwracał ten sam wynik, a odczyty rejestru wynosiły 22 zamiast 2.

04

Czy po zdjęciu można zablokować tę samą jednostkę ponownie?

Tak. Ponowna blokada to nowy wiersz, a poprzedni zostaje w historii.

05

Co z ekranami, które nie czytają z widoku?

Reguła w widoku działa tylko dla tych, które z niego czytają. Lista zapytań czytających tabelę jednostek bezpośrednio wymaga przeglądu i jest osobnym zadaniem. Gdy wszystkie jednostki są zablokowane, widok zwraca zero wierszy, a suma ilości w widoku jest NULL, nie zero.

Słownik pojęć

Słownik pojęć

Pojęcia z rejestru i testu opisanych w artykule.

RRejestr blokad
tabela, w której blokada jest osobnym wierszem ze stanem i śladem założenia oraz zdjęcia.
SStan dostępny
ilość jednostek, które nie mają aktywnej blokady.
NNOT EXISTS
warunek, który przepuszcza wiersz, gdy podzapytanie nie znajduje powiązanego wiersza.
IIndeks filtrowany
indeks tylko dla wierszy spełniających warunek, tu blokad ze stanem aktywna.
AAnty-złączenie
sposób wykonania NOT EXISTS, który odrzuca wiersze mające dopasowanie w rejestrze.
JJednostka
pozycja magazynowa o zapisanej ilości, którą można zablokować.

Zobacz to w praktyce

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