SQL Server w logistyce

Ruch zapisany w dwóch wierszach - kierunek i czas operacji w jednym zapytaniu

Kierunek ruchu wynika ze znaku ilości, a czas ruchu z wiersza dodatniego. Funkcja okna MAX przypisuje go obu wierszom pary bez podzapytania.

sql.server.net.pl/sql-server/
Abstrakcyjna mozaika kolorowych kafelków z wykresami na ciemnoniebieskim tle w odcieniach pomarańczu
Abstrakcyjna mozaika kolorowych kafelków z wykresami na ciemnoniebieskim tle w odcieniach pomarańczu
W skrócie
  • 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.

Ruch zapisany w dwóch wierszachSchemat pary wierszy o wspólnym kluczu pary. Rozchód ma ilość -10 w lokalizacji A-01 o 08:00:05, przychód ilość 10 w lokalizacji B-07 o 08:04:40; oba wiersze dostają czas ruchu 08:04:40 z wiersza dodatniego. Osobno wiersz bez pary: rozchód -3 w A-05 z czasem ruchu NULL, który wychwytuje kontrola kompletności z warunkiem HAVING. Ruch zapisany w dwóch wierszach Wspólny klucz łączy wiersze pary; kierunek daje znak ilości, a czas ruchu pochodzi z wiersza dodatniego. czas ruchu 08:04:40 rozchod ilosc -10 lokalizacja A-01 kiedy 08:00:05 przychod ilosc 10 lokalizacja B-07 kiedy 08:04:40 klucz pary z wiersza dodatniego Kierunek wynika ze znaku ilości: minus to rozchód, plus to przychód. Wiersz bez pary rozchod ilosc -3 lokalizacja A-05 czas ruchu NULL Kontrola kompletności HAVING SUM(ilosc) <> 0 OR COUNT(*) <> 2 Wynik przykładu jest taki sam na poziomach zgodności 130 i 160.
Schemat: dwa wiersze pary mają wspólny klucz; kierunek wynika ze znaku ilości, a czas z wiersza dodatniego.

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.

Wynik pierwszego zapytania: dziewięć wierszy ruchów
id_parykierunekilosclokalizacjaosobaczas_ruchu
1rozchod-10A-01S-108:04:40
1przychod10B-07S-108:04:40
2rozchod-5A-02S-208:15:30
2przychod5B-08S-208:15:30
3rozchod-20A-03osoba-309:02:00
3przychod20B-09osoba-309:02:00
4rozchod-8A-04S-110:08:15
4przychod8B-10S-110:08:15
5rozchod-3A-05S-2NULL

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ę.

Liczba ruchów i sztuk na osobę w oknie dat od 08:00 do 10:00
osobaruchysztuk_przeniesionych
osoba-3120
S-1110
S-215

Wynik drugiego zapytania z bloku kodu.

Makieta okna wyników odczytu ruchówMakieta poglądowa z danymi przykładowymi: okno wyników z dziewięcioma wierszami ruchów, tabelą liczby ruchów na osobę w oknie godzin od 08:00 do 10:00 i tabelą kontroli kompletności z parą 5. Wyniki zapytania Ruchy z czasem z 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 Ruchy na osobę w oknie 08:00-10:00 osoba ruchy sztuk_przeniesionych osoba-3 1 20 S-1 1 10 S-2 1 5 Kontrola kompletności id_pary saldo wierszy 5 -3 1 Makieta poglądowa, dane przykładowe
Makieta poglądowa, dane przykładowe. Okno wyników zapytania z dziewięcioma wierszami ruchów, czasem ruchu i osobą.

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.

Wynik kontroli kompletności
id_parysaldowierszy
5-31

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.

  1. Który wiersz pary w dzienniku opisuje koniec ruchu: rozchód czy przychód?
  2. Czy ruch ma się liczyć w oknie dat po czasie zakończenia, czy po czasie rozpoczęcia?
  3. Jak w dzienniku ma być traktowana para, która ma tylko jeden wiersz?
  4. Które pole identyfikuje osobę w dzienniku: identyfikator skanera czy login?
FAQ

Najczęstsze pytania o ruch zapisany w dwóch wierszach

01

Dlaczego czas ruchu bierze się z wiersza dodatniego?

Przyjęto, że ruch kończy się w miejscu docelowym. Według dokumentacji wdrożenia czas ruchu to czas drugiej operacji pary.

02

Co się dzieje z parą bez wiersza dodatniego?

Czas ruchu jest pusty (NULL), para nie wchodzi do okna dat i trafia do kontroli kompletności.

03

Czy potrzebne jest podzapytanie dla czasu z drugiego wiersza?

Nie. Wyrażenie MAX z warunkiem na dodatnią ilość i klauzulą OVER z podziałem na klucz przypisuje czas obu wierszom w jednym przebiegu. Wynik był identyczny na poziomach zgodności 130 i 160.

04

Jak traktować wiersz z ilością zero?

Przykład nie zawiera takiego wiersza: kierunek zalicza zero do przychodu, a czas ruchu bierze tylko wiersze z ilością większą od zera.

05

Kto jest osobą ruchu?

W przykładzie identyfikator skanera, a gdy go brak, login.

Słownik pojęć

Słownik pojęć

Pojęcia z zapytań i przykładu opisanych w artykule.

PPara ruchu
Dwa wiersze jednego przesunięcia, rozchód w źródle i przychód w celu.
KKlucz pary
Wartość wspólna dla obu wierszy ruchu.
RRozchód
Wiersz z ilością ujemną, opisujący zdjęcie towaru z miejsca źródłowego.
PPrzychód
Wiersz z ilością dodatnią, opisujący złożenie towaru w miejscu docelowym.
FFunkcja okna
Funkcja z klauzulą OVER, tu MAX w podziale na klucz pary.
OOkno dat
Przedział czasu, w którym zlicza się ruchy według czasu z wiersza dodatniego.

Zobacz to w praktyce

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