- Para wierszy: przesunięcie to rozchód w źródle i przychód w celu, połączone wspólnym kluczem pary.
- Kierunek: wynika ze znaku ilości, bez osobnej kolumny.
- Czas ruchu: czas wiersza dodatniego, który funkcja okna MAX przypisuje obu wierszom pary.
- Test: wynik jest taki sam na poziomach zgodności 130 i 160.
Ruch zapisany w dwóch wierszach to operacja, której czas trzeba wybrać z jednego z nich; w opisanym wzorcu jest to czas wiersza dodatniego. W opisywanym wdrożeniu zmiana lokalizacji w programie zapisuje pozycję jako dwa wiersze o wspólnym numerze pary i różnych numerach pozycji. Pierwszy wiersz to rozchód w lokalizacji źródłowej, a drugi to przychód w lokalizacji docelowej. Mechanizm zmiany lokalizacji opisuje strona o zmianie lokalizacji całej palety, a ten artykuł pokazuje wzorzec odczytu takiej pary w T-SQL.
Gdy przesunięcie jest zapisane jako dwa wiersze o wspólnym kluczu pary, kierunek wynika ze znaku ilości, a czas ruchu to czas wiersza dodatniego, który funkcja okna MAX przypisuje obu wierszom bez podzapytania. Okno dat liczy ruchy po tym czasie, a kontrola kompletności zwraca pary bez drugiego wiersza.
Czas oznacza zakończenie ruchu, a nie czas pracy ani miarę wydajności. Para bez wiersza dodatniego ma czas pusty i trafia do kontroli kompletności. Przykład dał identyczny wynik na poziomach zgodności 130 i 160.
Ruch zapisany w dwóch wierszach ma wspólny klucz pary
Dziennik ruchów z przykładu ma jeden wiersz na każdą połowę ruchu. Obie połowy łączy kolumna id_pary, czyli klucz pary. Pierwszy wiersz pary opisuje zdjęcie towaru z lokalizacji źródłowej, a drugi złożenie go w lokalizacji docelowej. Przykład zawiera pięć par, z których cztery są pełne, a jedna ma tylko rozchód.
Kolumny skaner i login niosą informację o osobie, a kolumna kiedy czas zapisu każdego wiersza. W przykładzie czasy obu wierszy tej samej pary się różnią. Zapytanie z przykładu łączy te dwie informacje w jeden czas ruchu.
Kierunek wynika ze znaku ilości
Kierunek wynika ze znaku ilości: ilość nieujemna oznacza przychód, a ujemna rozchód. Wiersz z ilością -10 w lokalizacji A-01 jest więc rozchodem, a wiersz z ilością 10 w lokalizacji B-07 przychodem. Zapytanie nie potrzebuje osobnej kolumny z kierunkiem, bo znak ilości już go niesie.
Poniższa lista opisuje oba kierunki.
- Rozchód: wiersz z ilością ujemną, który opisuje zdjęcie towaru z miejsca źródłowego.
- Przychód: wiersz z ilością dodatnią, który opisuje złożenie towaru w miejscu docelowym.
Czas operacji pochodzi z wiersza dodatniego
Czas ruchu to czas wiersza dodatniego, który funkcja okna MAX przypisuje obu wierszom pary. Wyrażenie MAX z warunkiem na dodatnią ilość i klauzulą OVER z podziałem na klucz pary działa w jednym przebiegu, bez podzapytania. Dla pary 1 rozchód ma czas 08:00:05, a przychód 08:04:40; oba wiersze dostają czas ruchu 08:04:40. Podzapytanie z wyborem jednego wiersza jest innym podejściem, opisanym osobno na stronie o stanie ujemnym w SQL Server.
SET NOCOUNT ON;
CREATE TABLE dbo.ruch_przyklad (
id int IDENTITY PRIMARY KEY, id_pary int NOT NULL, ilosc int NOT NULL, kiedy datetime2(0) NOT NULL,
lokalizacja varchar(10) NOT NULL, skaner varchar(10) NULL, login varchar(10) NOT NULL);
INSERT dbo.ruch_przyklad (id_pary, ilosc, kiedy, lokalizacja, skaner, login) VALUES
(1, -10, '2026-03-02T08:00:05', 'A-01', 'S-1', 'osoba-1'), (1, 10, '2026-03-02T08:04:40', 'B-07', 'S-1', 'osoba-1'),
(2, -5, '2026-03-02T08:10:00', 'A-02', 'S-2', 'osoba-2'), (2, 5, '2026-03-02T08:15:30', 'B-08', 'S-2', 'osoba-2'),
(3, -20, '2026-03-02T09:00:10', 'A-03', NULL, 'osoba-3'), (3, 20, '2026-03-02T09:02:00', 'B-09', NULL, 'osoba-3'),
(4, -8, '2026-03-02T09:55:00', 'A-04', 'S-1', 'osoba-1'), (4, 8, '2026-03-02T10:08:15', 'B-10', 'S-1', 'osoba-1'),
(5, -3, '2026-03-02T10:05:00', 'A-05', 'S-2', 'osoba-2');
DECLARE @od datetime2(0) = '2026-03-02T08:00:00', @do datetime2(0) = '2026-03-02T10:00:00';
WITH ruch AS (
SELECT id_pary, ilosc, lokalizacja, COALESCE(skaner, login) AS osoba,
CASE WHEN ilosc >= 0 THEN 'przychod' ELSE 'rozchod' END AS kierunek,
MAX(CASE WHEN ilosc > 0 THEN kiedy END) OVER (PARTITION BY id_pary) AS czas_ruchu
FROM dbo.ruch_przyklad)
SELECT id_pary, kierunek, ilosc, lokalizacja, osoba, czas_ruchu FROM ruch ORDER BY id_pary, ilosc;
WITH ruch AS (
SELECT id_pary, ilosc, COALESCE(skaner, login) AS osoba,
MAX(CASE WHEN ilosc > 0 THEN kiedy END) OVER (PARTITION BY id_pary) AS czas_ruchu
FROM dbo.ruch_przyklad)
SELECT osoba, COUNT(*) AS ruchy, SUM(ilosc) AS sztuk_przeniesionych
FROM ruch WHERE ilosc > 0 AND czas_ruchu >= @od AND czas_ruchu < @do GROUP BY osoba ORDER BY osoba;
SELECT id_pary, SUM(ilosc) AS saldo, COUNT(*) AS wierszy FROM dbo.ruch_przyklad
GROUP BY id_pary HAVING SUM(ilosc) <> 0 OR COUNT(*) <> 2;
Przykład ilustracyjny, nazwy wymyślone. Dziennik pięciu par, odczyt kierunku i czasu, zliczanie ruchów w oknie dat i kontrola kompletności.
Pierwsze zapytanie zwraca dziewięć wierszy ruchów w sześciu kolumnach, od klucza pary po czas ruchu. Tabela pokazuje wynik z samym czasem, bez daty. Przy parze 5 czas ruchu jest pusty, bo para nie ma wiersza dodatniego.
| id_pary | kierunek | ilosc | lokalizacja | osoba | czas_ruchu |
|---|---|---|---|---|---|
| 1 | rozchod | -10 | A-01 | S-1 | 08:04:40 |
| 1 | przychod | 10 | B-07 | S-1 | 08:04:40 |
| 2 | rozchod | -5 | A-02 | S-2 | 08:15:30 |
| 2 | przychod | 5 | B-08 | S-2 | 08:15:30 |
| 3 | rozchod | -20 | A-03 | osoba-3 | 09:02:00 |
| 3 | przychod | 20 | B-09 | osoba-3 | 09:02:00 |
| 4 | rozchod | -8 | A-04 | S-1 | 10:08:15 |
| 4 | przychod | 8 | B-10 | S-1 | 10:08:15 |
| 5 | rozchod | -3 | A-05 | S-2 | NULL |
Wynik pierwszego zapytania z bloku kodu, sam czas bez daty.
Przyjęto, że ruch kończy się w miejscu docelowym, więc czasem ruchu jest czas wiersza dodatniego. Według dokumentacji wdrożenia czas ruchu to czas drugiej operacji pary.
„czas wykonania dla drugiej operacji” (dokumentacja wdrożenia).
Osobę wskazuje identyfikator skanera albo, gdy go brak, login
Osobę ruchu wyznacza wyrażenie COALESCE, które bierze identyfikator skanera, a gdy go brak, login. W przykładzie pary 1 i 4 mają skaner S-1, para 2 skaner S-2, a para 3 nie ma skanera, więc osobą jest login osoba-3. Każdy wiersz ma więc osobę, nawet gdy skaner nie został użyty.
Osoba ruchu to osoba z wiersza dodatniego, czyli osoba, która kończy ruch w miejscu docelowym. Zapytanie zlicza ruchy właśnie po tej osobie.
- Skaner: identyfikator skanera jest pierwszym źródłem osoby.
- Login: login jest drugim źródłem osoby, gdy skanera brak.
Liczba ruchów na osobę zależy od czasu z wiersza dodatniego
Drugie zapytanie zlicza ruchy na osobę w oknie dat od 08:00 do 10:00, po czasie wiersza dodatniego. Każda z trzech osób, czyli dwa skanery i osoba bez skanera rozpoznana po loginie, ma po jednym ruchu. Para 4 nie liczy się, bo rozchód o 09:55:00 mieści się w oknie, ale czas ruchu 10:08:15 leży po jego końcu. Para 5 ma czas pusty, więc też odpada.
Okno dat liczy więc ruchy według zakończenia, a nie rozpoczęcia. Ruch rozpoczęty przed końcem okna, a zakończony po nim, nie liczy się w tym oknie. Tabela podaje liczbę ruchów i sztuk na osobę.
| osoba | ruchy | sztuk_przeniesionych |
|---|---|---|
| osoba-3 | 1 | 20 |
| S-1 | 1 | 10 |
| S-2 | 1 | 5 |
Wynik drugiego zapytania z bloku kodu.
Niepełna para trafia do kontroli kompletności
Trzecie zapytanie grupuje wiersze po kluczu pary i zwraca pary, w których suma ilości jest różna od zera albo wierszy jest inaczej niż dwa. Zwróciło jedną parę: parę 5 z saldem -3 i jednym wierszem. Kontrola kompletności wskazuje więc parę, w której brakuje drugiego wiersza.
| id_pary | saldo | wierszy |
|---|---|---|
| 5 | -3 | 1 |
Wynik trzeciego zapytania z bloku kodu.
Wzorzec ma cztery granice, które opisuje poniższa lista.
- Czas to zakończenie ruchu: czas z wiersza dodatniego nie jest czasem pracy ani miarą wydajności osoby.
- Para bez wiersza dodatniego: ma czas pusty, odpada z okna dat i trafia do kontroli kompletności.
- Wiersz z ilością zero: nie jest ruchem: ma kierunek „przychod”, ale nie dostaje czasu ruchu i odpada z okna dat.
- Para z dwoma wierszami dodatnimi: liczy się jako dwa ruchy, a osobą ruchu jest osoba z wiersza dodatniego.
Ekrany opisywanego wdrożenia, na których palety zmieniają miejsce, opisują strony o składowaniu palety na terminalu i o wydaniu ze zlecenia na terminalu. Dokument zmiany lokalizacji w dwóch wierszach opisuje strona o korekcie wydania ruchem odwrotnym.
Przykład (dane wymyślone). Para 1 zapisuje rozchód 10 sztuk z A-01 o 08:00:05 i przychód 10 sztuk w B-07 o 08:04:40, oba wiersze ze skanera S-1. Zapytanie przypisuje obu wierszom czas 08:04:40, czyli czas wiersza dodatniego, a kierunki „przychod” i „rozchod” wynikają ze znaku ilości.
Para 4 zaczyna się o 09:55:00, ale kończy o 10:08:15, więc w oknie od 08:00 do 10:00 nie jest liczona. Para 5 ma tylko rozchód, nie ma czasu ruchu i trafia do kontroli kompletności. W oknie każda z trzech osób ma po jednym ruchu.
Pytania do analizy
Przed zastosowaniem wzorca odpowiedz na cztery pytania.
- Który wiersz pary w dzienniku opisuje koniec ruchu: rozchód czy przychód?
- Czy ruch ma się liczyć w oknie dat po czasie zakończenia, czy po czasie rozpoczęcia?
- Jak w dzienniku ma być traktowana para, która ma tylko jeden wiersz?
- Które pole identyfikuje osobę w dzienniku: identyfikator skanera czy login?