SQL Server w logistyce

Rozbicie ilości na palety w T-SQL bez pętli i bez błędu zaokrągleń

Liczbę palet daje dzielenie w górę na liczbach całkowitych, a wiersze palet tabela liczbowa. Kontrola sumy porównuje wynik z ilością wejściową.

sql.server.net.pl/sql-server/
Abstrakcyjna chmura nad cyfrowym miastem, w oknach wież pomarańczowe i niebieskie światła
Abstrakcyjna chmura nad cyfrowym miastem, w oknach wież pomarańczowe i niebieskie światła
W skrócie
  • Liczba palet: dzielenie w górę na liczbach całkowitych, (q + z - 1) / z, bez CEILING i bez rzutowania.
  • Wiersze palet: tabela liczbowa złączona warunkiem k <= n zastępuje pętlę.
  • Reszta i kontrola: ostatnia paleta dostaje resztę, a kontrola sumy porównuje wynik z ilością.
  • Test: wynik jest taki sam na poziomach zgodności 130 i 160.

Rozbicie ilości na palety w T-SQL da się zapisać jednym zapytaniem, bez pętli: liczbę palet daje dzielenie w górę na liczbach całkowitych, a kolejne wiersze palet daje tabela liczbowa. W opisywanym wdrożeniu formularz przyjęcia zewnętrznego rozbija ilość na palety, gdy pole numeru palety jest puste, a regułę tego formularza opisuje strona o numerze palety w przyjęciu zewnętrznym. Ten artykuł pokazuje wzorzec, który da się przenieść do dowolnej bazy, a nie kod formularza.

Dla ilości q i zawartości palety z liczba palet to n = (q + z - 1) / z. Na liczbach całkowitych to dzielenie w górę bez CEILING i bez rzutowania, a wiersze palet daje tabela liczbowa złączona warunkiem k <= n. Ostatnia paleta dostaje resztę q - z * (n - 1), a kontrola porównuje sumę z ilością.

Wzór nie wnosi błędu zaokrągleń, gdy ilość i zawartość są liczbami całkowitymi: dzielenie w górę na kolumnach int daje wynik bez liczb dziesiętnych. Przykład dał identyczny wynik na poziomach zgodności 130 i 160. Dla ilości ułamkowych wzór działa inaczej: na kolumnach decimal liczy w dół, a kontrola sumy zwraca „BLAD”. Granice wzorca opisujemy w ostatniej sekcji.

Liczbę palet daje dzielenie ilości przez zawartość w górę

Problem jest prosty. Z dokumentu dostawy znamy ilość, a na jednej palecie mieści się określona zawartość, więc trzeba policzyć, ile palet powstanie i ile sztuk trafi na każdą z nich. Liczba palet to najmniejsza liczba całkowita nie mniejsza od ilorazu ilości przez zawartość.

Dla ilości 250 i zawartości 100 powstają trzy palety. Dla 1000 przy 48 jest ich dwadzieścia jeden, a dla 26 przy 24 dwie. Pełne palety mają ilość równą zawartości, a ostatnia dostaje resztę.

Tabela w dalszej części pokazuje sześć takich przykładów razem z wynikiem kontroli sumy. Każdy z nich dał identyczny wynik na obu poziomach zgodności.

Z ilości i zawartości palety do wierszy paletSchemat z pięcioma polami połączonymi strzałkami: ilość i zawartość palety, liczba palet n = (q + z - 1) / z, tabela liczbowa k <= n, ostatnia paleta q - z * (n - 1), kontrola sumy z wynikiem OK albo BLAD. Pod polami dwie ramki: o suficie 1000 palet i o przykładzie q = 26, z = 24, n = 2, palety 24 i 2. Z ilości i zawartości palety do wierszy palet Zapytanie bez pętli; na końcu kontrola porównuje sumę po podziale z ilością. Ilość i zawartość palety q, z dwie liczby całkowite (int) Liczba palet n = (q + z - 1) / z dzielenie w górę, bez CEILING i bez rzutowania Tabela liczbowa k <= n jeden wiersz na każdą paletę, bez pętli Ostatnia paleta q - z * (n - 1) dostaje resztę; pozostałe palety są pełne (z) Kontrola sumy suma = q ? porównanie z ilością wejściową Trzy cyfry dają liczby 1-1000; kontrola wykryje więcej palet. Przykład: q = 26, z = 24 n = (26 + 24 - 1) / 24 = 2 palety: 24 i 2 TAK NIE OK BLAD Wynik przykładu jest taki sam na poziomach zgodności 130 i 160.
Schemat: z ilości i zawartości palety wynika liczba palet, a tabela liczbowa zamienia ją na wiersze sprawdzane kontrolą sumy.

Dzielenie w górę na liczbach całkowitych nie wymaga zaokrąglania

Wzór (ilosc + na_palecie - 1) / na_palecie na kolumnach int daje dzielenie w górę bez CEILING i bez rzutowania na liczby dziesiętne. Dzielenie dwóch liczb całkowitych obcina ułamek, więc dodanie na_palecie - 1 do ilości przesuwa wynik do następnej liczby całkowitej, gdy reszta jest większa od zera. Dla ilości 26 i zawartości 24 wzór daje (26 + 24 - 1) / 24, czyli 49 / 24, a po obcięciu ułamka dwie palety.

W teście 30000 par, z ilością od 1 do 500 i zawartością od 1 do 60 na kolumnach int, wzór dał tę samą liczbę palet co CEILING na ilorazie dziesiętnym.

  • Kolumny int: wzór daje dzielenie w górę bez CEILING i bez rzutowania; przykład użyty w artykule działa na kolumnach int.
  • Kolumny decimal: wzór liczy w dół: 10,5 przy 5 daje dwie palety i sumę 10, a kontrola sumy zwraca „BLAD”.

Tabela liczbowa zastępuje pętlę po paletach

Pierwszy blok kodu tworzy tabelę przyjecie_przyklad, w której każdy artykuł ma ilość i zawartość palety. Kolumny ilosc i na_palecie mają typ int i ograniczenie CHECK, które wymaga wartości większych od zera. Sześć wierszy danych odpowiada sześciu artykułom z tabeli wyników.

SET NOCOUNT ON;
CREATE TABLE dbo.przyjecie_przyklad (
    artykul    varchar(10) PRIMARY KEY,
    ilosc      int NOT NULL CHECK (ilosc > 0),
    na_palecie int NOT NULL CHECK (na_palecie > 0));
INSERT dbo.przyjecie_przyklad VALUES
    ('ART-01', 250, 100), ('ART-02', 1000, 48), ('ART-03', 26, 24),
    ('ART-04', 7, 3), ('ART-05', 5, 100), ('ART-06', 48, 48);
WITH cyfry AS (SELECT x FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) AS v(x)),
liczby AS (SELECT 100 * a.x + 10 * b.x + c.x + 1 AS k FROM cyfry a, cyfry b, cyfry c),
palety AS (
    SELECT p.artykul, p.ilosc, p.na_palecie, l.k AS paleta,
           (p.ilosc + p.na_palecie - 1) / p.na_palecie AS ile_palet
    FROM dbo.przyjecie_przyklad AS p
    JOIN liczby AS l ON l.k <= (p.ilosc + p.na_palecie - 1) / p.na_palecie)
SELECT artykul, ilosc, paleta, ile_palet,
       CASE WHEN paleta < ile_palet THEN na_palecie
            ELSE ilosc - na_palecie * (ile_palet - 1) END AS ilosc_na_palecie
INTO #rozbicie FROM palety;

Przykład ilustracyjny, nazwy wymyślone. Tabela z sześcioma artykułami i zapytanie, które zapisuje rozbicie do tabeli tymczasowej.

Zapytanie składa się z czterech kroków, które opisuje poniższa lista.

  1. Wyrażenie tabelowe cyfry zawiera cyfry od 0 do 9.
  2. Wyrażenie liczby składa z nich trzy cyfry, czyli liczby od 1 do 1000.
  3. Złączenie z warunkiem k <= liczba palet daje po jednym wierszu na każdą paletę każdego artykułu.
  4. Wynik trafia do tabeli tymczasowej #rozbicie, z której korzysta kolejny blok.

Nie ma więc pętli ani kursora: o liczbie wierszy decyduje warunek złączenia.

Ostatnia paleta dostaje resztę

Wyrażenie warunkowe w ostatnim zapytaniu pierwszego bloku decyduje, ile sztuk dostaje dana paleta. Paleta o numerze mniejszym niż liczba palet dostaje pełną zawartość. Ostatnia dostaje ilość pomniejszoną o pełne palety, czyli ilosc - na_palecie * (ile_palet - 1). Dla 250 przy 100 są to dwie palety po 100 i ostatnia z resztą 50. Dla 1000 przy 48 jest dwadzieścia pełnych palet po 48 i ostatnia z ilością 40.

Gdy ilość jest mniejsza od zawartości, powstaje jedna paleta z całą ilością: 5 przy 100 daje paletę 5. Gdy ilość równa się zawartości, też powstaje jedna paleta: 48 przy 48 daje paletę 48. W obu przypadkach kolumna z ilością pełnej palety ma wartość NULL, bo pełnych palet nie ma. Dla 7 przy 3 powstają trzy palety: dwie po 3 i ostatnia z ilością 1.

Wynik rozbicia sześciu artykułów z kontrolą sumy
artykuliloscpaletpelnaostatniasumakontrola
ART-01250310050250OK
ART-0210002148401000OK
ART-0326224226OK
ART-0473317OK
ART-0551NULL55OK
ART-06481NULL4848OK

Wynik drugiego zapytania dla sześciu artykułów z przykładu.

Makieta okna wyników rozbicia ilości na paletyMakieta poglądowa z danymi przykładowymi: okno wyników zapytania z siatką sześciu artykułów i kolumnami od ilości do kontroli, a pod siatką trzy fragmenty zapytania. Wyniki zapytania artykul ilosc palet pelna ostatnia suma kontrola ART-01 250 3 100 50 250 OK ART-02 1000 21 48 40 1000 OK ART-03 26 2 24 2 26 OK ART-04 7 3 3 1 7 OK ART-05 5 1 NULL 5 5 OK ART-06 48 1 NULL 48 48 OK Fragment zapytania (p.ilosc + p.na_palecie - 1) / p.na_palecie AS ile_palet JOIN liczby AS l ON l.k <= (p.ilosc + p.na_palecie - 1) / p.na_palecie CASE WHEN paleta < ile_palet THEN na_palecie ELSE ilosc - na_palecie * (ile_palet - 1) END Makieta poglądowa, dane przykładowe
Makieta poglądowa, dane przykładowe. Okno wyników zapytania z sześcioma wierszami rozbicia ilości na palety i kolumną kontroli.

Kontrola sumy porównuje wynik z ilością wejściową

Drugi blok liczy z tabeli #rozbicie podsumowanie dla każdego artykułu, w siedmiu kolumnach, od ilości po kontrolę. Kontrola ma wartość „OK”, gdy suma równa się ilości, a w przeciwnym razie „BLAD”. W przykładzie wszystkie sześć wierszy ma „OK”.

SELECT artykul, MAX(ilosc) AS ilosc, MAX(ile_palet) AS palet,
       MAX(CASE WHEN paleta < ile_palet THEN ilosc_na_palecie END) AS pelna,
       MAX(CASE WHEN paleta = ile_palet THEN ilosc_na_palecie END) AS ostatnia,
       SUM(ilosc_na_palecie) AS suma,
       CASE WHEN SUM(ilosc_na_palecie) = MAX(ilosc) THEN 'OK' ELSE 'BLAD' END AS kontrola
FROM #rozbicie GROUP BY artykul ORDER BY artykul;
SELECT CAST(26 AS decimal(19,10)) / 24 * 24 AS iloczyn_dla_literalu,
       CASE WHEN CAST(26 AS decimal(19,10)) / 24 * 24 < 26 THEN 'test q > n*z doliczy palete' ELSE 'brak' END AS wynik_dla_literalu;
SELECT CAST(ilosc AS decimal(19,10)) / na_palecie * na_palecie AS iloczyn_dla_kolumn_int,
       CASE WHEN CAST(ilosc AS decimal(19,10)) / na_palecie * na_palecie < ilosc THEN 'test q > n*z doliczy palete' ELSE 'brak' END AS wynik_dla_kolumn_int
FROM dbo.przyjecie_przyklad WHERE artykul = 'ART-03';
DECLARE @iloraz decimal(19,10) = CAST(26 AS decimal(19,10)) / 24;
SELECT @iloraz AS iloraz_zapisany, @iloraz * 24 AS iloczyn_z_zapisanego_ilorazu,
       CASE WHEN @iloraz * 24 < 26 THEN 'test q > n*z doliczy palete' ELSE 'brak' END AS wynik_dla_zapisanego_ilorazu;

Przykład ilustracyjny. Podsumowanie z kontrolą sumy i trzy zapytania z kontrprzykładem dziesiętnym.

Kontrola działa na wierszach wyniku. Artykuł, który nie ma w wyniku żadnego wiersza, nie pojawi się też w kontroli. Kontrola sumy sprawdza więc to, co rozbicie zapisało, a nie to, czy każdy artykuł z dokumentu został rozbity.

Pozostałe zapytania drugiego bloku pokazują neutralny kontrprzykład dziesiętny. Poniższa tabela zestawia ich wyniki. Wynik ilorazu dziesiętnego zależy więc od jego zaokrąglenia.

Iloczyn ilorazu i zawartości w trzech wariantach liczenia
WariantIloczynWynik testu
CAST(26 AS decimal(19,10)) / 24 * 24, literał 2425,9999999999992test q > n*z doliczy palete
iloraz zapisany w decimal(19,10), pomnożony przez 2425,9999999992test q > n*z doliczy palete
to samo wyrażenie na kolumnach int26brak

Wyniki trzech zapytań z kontrprzykładem dziesiętnym.

Zawartość zero i bardzo duża ilość wymagają osobnych zabezpieczeń

Wzór ma cztery granice, które opisuje poniższa lista. Ograniczenie CHECK odrzuca zawartość równą zero komunikatem Msg 547. Bez niego dzielenie przez zero zatrzymałoby zapytanie.

The INSERT statement conflicted with the CHECK constraint.

  • Zawartość zero: ograniczenie CHECK odrzuca wiersz komunikatem Msg 547, na obu poziomach zgodności.
  • Sufit tabeli liczbowej: tabela z trzech cyfr daje liczby od 1 do 1000, więc wystarcza dla co najwyżej 1000 palet na artykuł; przy ilości 1500 i zawartości 1 wynik miał 1000 wierszy i sumę 1000, a kontrola zwróciła „BLAD”, czyli kontrola sumy wykrywa przekroczenie sufitu, ale go nie usuwa.
  • Ilości ułamkowe: na kolumnach decimal wzór liczy w dół: 10,5 przy 5 daje dwie palety i sumę 10, a kontrola sumy zwraca „BLAD”.
  • Wartość bliska granicy int: ilość 2147483647 przy zawartości 2 daje komunikat Msg 8115 o przepełnieniu przy konwersji na typ int.

Przykład uruchomiono w jednorazowej bazie na poziomach zgodności 130 i 160 i dał identyczny wynik; starszej wersji serwera nie testowano. Przedstawiony wzorzec dotyczy ilości całkowitych, a ułamki wymagałyby innego wzoru. Wzorzec, który sprawdza wsad z pliku przed zapisem, opisuje artykuł o walidacji wsadu w tabeli roboczej. Dzienny stan palet w T-SQL opisuje strona o dziennym stanie palet w T-SQL, a podział dostaw sumą narastającą strona o alokacji wydania na dostawy.

Przykład (dane wymyślone). Artykuł A przychodzi w ilości 26 sztuk, a na palecie mieści się 24. Liczba palet to (26 + 24 - 1) / 24, czyli 2 na liczbach całkowitych. Tabela liczbowa daje numery 1 i 2; paleta 1 dostaje 24, a ostatnia 26 - 24 * 1, czyli 2. Kontrola sumy daje 26 = 26 i status „OK”.

Test typu „iloczyn mniejszy od ilości” zawodzi na zaokrąglonym ilorazie dziesiętnym, bo literał 24 dał 25,9999999999992. To samo wyrażenie na kolumnach int daje dokładnie 26.

Pytania do analizy

Przed zastosowaniem wzorca odpowiedz na cztery pytania.

  1. Czy w bazie ilości i zawartości palet są zapisane jako liczby całkowite?
  2. Jaki największy rozmiar rozbicia na jeden artykuł dopuszcza tabela liczbowa?
  3. Gdzie w schemacie ma zostać odrzucona zawartość równa zero: w definicji tabeli czy w zapytaniu?
  4. Kto sprawdza wynik kontroli sumy po rozbiciu i co robi z wierszem oznaczonym „BLAD”?
FAQ

Najczęstsze pytania o rozbicie ilości na palety

01

Czy przykład działa na starszym poziomie zgodności?

Przykład uruchomiono na poziomach zgodności 130 i 160 i wynik był identyczny; starszej wersji serwera nie testowano. Tabela liczbowa jest zbudowana ze stałych wartości, bez funkcji dostępnych tylko na nowszym poziomie.

02

Dlaczego nie CEILING?

Wzór (q + z - 1) / z na kolumnach int daje dzielenie w górę bez CEILING i bez rzutowania. CEILING na ilorazie dziesiętnym dał tę samą liczbę palet w 30000 testowanych kombinacjach. CEILING(q / z) na dwóch liczbach całkowitych dzieli z obcięciem ułamka, więc daje inną liczbę palet niż wzór w 27687 z 30000 kombinacji.

03

Co jeśli zawartość palety to zero?

Ograniczenie CHECK odrzuca wiersz komunikatem Msg 547; bez niego dzielenie przez zero zatrzymałoby zapytanie.

04

Co jeśli ilość jest ułamkowa?

Nie na tym wzorze. Na kolumnach decimal wzór liczy w dół: 10,5 przy 5 daje dwie palety i sumę 10, a kontrola sumy zwraca „BLAD”. Dla ilości ułamkowych potrzebny jest inny wzór, którego nie testowano.

05

Czy to jest kod formularza z opisywanego wdrożenia?

Nie. Artykuł opisuje wzorzec, który da się przenieść do dowolnej bazy. Formularz przyjęcia opisuje osobna strona.

Słownik pojęć

Słownik pojęć

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

DDzielenie w górę
Najmniejsza liczba całkowita nie mniejsza od ilorazu; dla int to (q + z - 1) / z.
TTabela liczbowa
Zbiór kolejnych liczb używany do wygenerowania wierszy bez pętli.
ZZawartość palety
Liczba sztuk mieszcząca się na jednej palecie, zapisana w kolumnie przykładu.
OOstatnia paleta
Paleta z resztą, równą ilości pomniejszonej o pełne palety.
KKontrola sumy
Porównanie sumy ilości po podziale z ilością wejściową.
PPoziom zgodności
Ustawienie bazy decydujące o dostępnych funkcjach języka; tu 130 i 160.

Zobacz to w praktyce

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