SQL Server w logistyce

Kolor z liczby dni w widoku: słownik zamiast CASE

Widok liczy liczbę dni do terminu funkcją DATEDIFF, a kolor bierze ze słownika po kluczu dni. Zmiana koloru to zwykły UPDATE, a luki w słowniku wykrywa zapytanie kontrolne.

sql.server.net.pl/sql-server/
Kobieta w czerwonych okularach analizuje na monitorze tabelę z kolorowymi wierszami
Kobieta w czerwonych okularach analizuje na monitorze tabelę z kolorowymi wierszami
W skrócie
  • Wzorzec: dwie tabele i widok, który liczy dni funkcją DATEDIFF i pobiera kolor podzapytaniem ze słownika po kluczu dni.
  • Zmiana koloru: zwykły UPDATE na tabeli słownika, bez zmiany definicji widoku.
  • Brak wpisu: dla dnia spoza słownika widok zwraca NULL bez błędu, więc luki wykrywa osobne zapytanie kontrolne.
  • Wartości ujemne: termin przekroczony obejmuje wariant z przedziałami, w którym słownik trzyma dolną granicę zamiast pojedynczego dnia.

Widok z liczbą dni do terminu w SQL Server można napisać z wyrażeniem CASE, w którym progi koloru leżą w definicji widoku. Wtedy każda zmiana koloru jest zmianą widoku. Ten artykuł pokazuje wzorzec, w którym kolor jest wierszem tabeli, a widok liczy dni i szuka wpisu po kluczu.

Kolor terminu trzyma się w tabeli słownika z kluczem głównym typu int, w którym kluczem jest liczba dni. Widok liczy dni funkcją DATEDIFF w CROSS APPLY i pobiera kolor podzapytaniem skalarnym po tym kluczu. Zmiana koloru to wtedy UPDATE, a dla dnia bez wpisu widok zwraca NULL.

Reguła koloru jako dane w tabeli, a nie jako CASE w widoku

Wzorzec składa się z dwóch tabel i widoku. Tabela słownika ma klucz główny typu int z liczbą dni oraz kolumnę z nazwą koloru. Tabela zgłoszeń trzyma numer i termin. Widok niczego nie koduje na stałe, bo progi koloru leżą w wierszach słownika.

Słownik z przykładu obejmuje dni od 0 do 10 i cztery kolory (red, orange, yellow, green). Zero dni ma kolor red, a green zaczyna się od szóstego dnia. Wpisy są przykładowe, więc każdy zespół ustali własne progi.

CREATE TABLE dbo.slownik_kolorow (
    dni   int         NOT NULL PRIMARY KEY,
    kolor varchar(20) NOT NULL
);
INSERT dbo.slownik_kolorow (dni, kolor) VALUES
    (0, 'red'), (1, 'orange'), (2, 'orange'),
    (3, 'yellow'), (4, 'yellow'), (5, 'yellow'),
    (6, 'green'), (7, 'green'), (8, 'green'), (9, 'green'), (10, 'green');

CREATE TABLE dbo.zgloszenia (
    id     int         NOT NULL PRIMARY KEY,
    numer  varchar(20) NOT NULL,
    termin date        NOT NULL
);
GO

Przykład ilustracyjny, dane wymyślone. Słownik kolorów z kluczem głównym typu int i tabela zgłoszeń z terminem.

W opisywanym wdrożeniu lista reklamacji pokazuje liczbę dni do terminu, a kolor komórki z tą liczbą pochodzi ze słownika, w którym kluczem jest liczba dni. Widok zwraca nazwę koloru, a kolorowaniem zajmuje się lista. Od strony użytkownika opisuje to strona o terminie reklamacji w kolorach, a to, co administrator zmienia sam w module reklamacji, wyjaśnia druga.

Zmiana koloru jest wtedy zmianą wiersza słownika, a nie definicji widoku. Przykład w tym artykule to wzorzec napisany od nowa, z nazwami przykładowymi. Sprawdziliśmy go na SQL Server 2022 Express z kolacją Polish_CI_AS w jednorazowej bazie. Składnia nie używa elementów nowszych niż SQL Server 2016 SP1, a starszych wersji nie sprawdzaliśmy.

Widok liczy dni raz i bierze kolor po kluczu

Widok łączy zgłoszenia z wyrażeniem CROSS APPLY, które oblicza kolumnę dni jako DATEDIFF(DAY, GETDATE(), z.termin). CROSS APPLY nadaje liczbie dni jedną nazwę, więc podzapytanie może ją wykorzystać bez powtarzania wyrażenia.

Kolor zwraca podzapytanie skalarne z tabeli słownika, w którym warunek s.dni = d.dni działa po kluczu głównym. Widok zwraca więc nazwę koloru jako zwykły tekst. Kolorowaniem zajmuje się lista, która ten widok odczytuje.

CREATE VIEW dbo.v_zgloszenia
AS
SELECT z.id, z.numer, z.termin, d.dni,
       (SELECT s.kolor
        FROM dbo.slownik_kolorow AS s
        WHERE s.dni = d.dni) AS kolor
FROM dbo.zgloszenia AS z
CROSS APPLY (SELECT DATEDIFF(DAY, GETDATE(), z.termin) AS dni) AS d;
GO

Przykład ilustracyjny. Widok liczy dni raz i bierze kolor ze słownika po kluczu dni.

Funkcja DATEDIFF z częścią DAY liczy przekroczone granice dób, więc godzina zwrócona przez GETDATE() nie zmienia wyniku. W teście wersja z GETDATE() i wersja z CAST(GETDATE() AS date) dały taki sam wynik dla każdego wiersza.

Widok liczy dni i szuka koloru w słowniku Widok liczy dni do terminu, szuka koloru w słowniku po kluczu dni i zwraca NULL, gdy wpisu brak. Wariant z przedziałami używa TOP (1) z ORDER BY i obejmuje dni ujemne. Kolor terminu ze słownika po kluczu dni Widok liczy dni raz, a kolor pobiera ze słownika zamiast z wyrażenia CASE. zgloszenia (termin) DATEDIFF(DAY, GETDATE(), termin) dni klucz dni slownik_kolorow (dni PK, kolor) jest wpis? tak kolor nie NULL widok v_zgloszenia id, numer, termin, dni, kolor Wariant z przedziałami - obejmuje dni ujemne slownik_przedzialow (dni_od) TOP (1), dni_od <= dni, ORDER BY dni_od DESC
Widok liczy dni do terminu, szuka koloru w słowniku po kluczu dni i zwraca NULL, gdy wpisu brak. Wariant z przedziałami obejmuje dni ujemne.

Wynik widoku zależy od daty uruchomienia, więc powtarzalny test buduje terminy względem bieżącej daty. Dane testowe używają DATEADD na CAST(GETDATE() AS date), a nie dat wpisanych na stałe. Pięć zgłoszeń daje dni od 14 do -3, a dwa z nich dostają kolor NULL: czternaście dni oraz minus trzy dni.

INSERT dbo.zgloszenia (id, numer, termin) VALUES
    (1, '2026-10-0001', DATEADD(DAY, 14, CAST(GETDATE() AS date))),
    (2, '2026-10-0002', DATEADD(DAY, 10, CAST(GETDATE() AS date))),
    (3, '2026-10-0003', DATEADD(DAY,  4, CAST(GETDATE() AS date))),
    (4, '2026-10-0004', CAST(GETDATE() AS date)),
    (5, '2026-10-0005', DATEADD(DAY, -3, CAST(GETDATE() AS date)));
SELECT id, numer, dni, kolor FROM dbo.v_zgloszenia ORDER BY id;

Przykład ilustracyjny. Terminy ustawione względem bieżącej daty i zapytanie o wynik widoku.

Wynik widoku z kolorem terminu Makieta poglądowa, dane przykładowe. Siatka wyniku widoku z kolumnami id, numer, termin, dni i kolor oraz pięcioma wierszami przykładowymi, w tym dwoma z kolorem NULL. Wynik zapytania wynik z dnia 2026-10-14 (przykład) SELECT id, numer, termin, dni, kolor FROM dbo.v_zgloszenia ORDER BY id id numer termin dni kolor 1 2026-10-0001 2026-10-28 14 NULL 2 2026-10-0002 2026-10-24 10 green 3 2026-10-0003 2026-10-18 4 yellow 4 2026-10-0004 2026-10-14 0 red 5 2026-10-0005 2026-10-11 -3 NULL 5 wierszy Makieta poglądowa, dane przykładowe
Makieta poglądowa, dane przykładowe: wynik widoku dla pięciu zgłoszeń, w tym dwóch z kolorem NULL.

Dni spoza słownika dają kolor NULL

Słownik obejmuje dni od 0 do 10, więc zgłoszenie z terminem za 14 dni i zgłoszenie 3 dni po terminie nie mają w nim wiersza. Widok zwraca dla nich kolor NULL i nie zgłasza błędu ani ostrzeżenia.

Brak koloru jest więc cichy. Sam widok go nie sygnalizuje, a lista pokazuje po prostu komórkę bez koloru. Luki w słowniku wykrywa dopiero osobne zapytanie kontrolne, które opisujemy w ostatniej sekcji z kodem.

Klucz główny i typ klucza chronią widok przed błędami

Klucz główny na kolumnie dni pilnuje, żeby dla jednego dnia istniał jeden kolor. Próba dopisania drugiego koloru dla dnia 0 kończy się błędem 2627 w chwili zapisu słownika. Błąd pojawia się więc tam, gdzie powstaje duplikat.

Bez klucza ten sam duplikat psuje odczyt. W teście widok zwrócił wiersze do pierwszego dnia ze zdublowanym wpisem i zakończył się błędem 512, bo podzapytanie zwróciło więcej niż jedną wartość. Błąd ujawnia się w odczycie widoku, a nie tam, gdzie duplikat powstał.

Klucz słownika musi być liczbą bez duplikatów, bo inaczej widok zawiedzie dopiero przy odczycie, a nie przy zapisie słownika.

Znaczenie ma także typ klucza. Gdy kolumna słownika jest tekstowa, a porównuje się ją z liczbą z DATEDIFF, SQL Server konwertuje wartości słownika na int, a wpis niebędący liczbą (w teście abc) kończy zapytanie błędem 245. Klucz typu int unika tej konwersji. Kolejność konwersji opisuje dokumentacja o pierwszeństwie typów danych.

Plan zależy od liczby wierszy. Dla jednego wiersza wyniku optymalizator szukał koloru po kluczu (Nested Loops i Clustered Index Seek na słowniku). Dla 20 tysięcy wierszy wybrał Hash Match z jednokrotnym przeglądem małego słownika. Oba plany są poprawne, a klucz jest potrzebny przede wszystkim dla jednoznaczności i typu. Pomiarów czasu nie wykonywaliśmy, więc nie porównujemy kosztu z wyrażeniem CASE.

Wariant z przedziałami obejmuje także wartości ujemne

Wartość ujemna oznacza termin przekroczony, a zero oznacza termin dzisiaj. Słownik z pojedynczymi dniami nie obejmuje liczb ujemnych, bo wymagałby osobnego wiersza dla każdej z nich.

Wariant z przedziałami trzyma w słowniku dolną granicę w kolumnie dni_od. Widok używa OUTER APPLY z TOP (1), wybiera wiersz o największej granicy nieprzekraczającej liczby dni i sortuje malejąco po dni_od. W tym wariancie ORDER BY jest obowiązkowy, bo bez niego TOP (1) wybiera dowolny wiersz. Skutki takiego wyboru pokazuje artykuł o TOP bez ORDER BY w WMS.

CREATE TABLE dbo.slownik_przedzialow (dni_od int NOT NULL PRIMARY KEY, kolor varchar(20) NOT NULL);
INSERT dbo.slownik_przedzialow VALUES (-1000, 'darkred'), (0, 'red'), (1, 'orange'), (3, 'yellow'), (6, 'green');
GO
CREATE VIEW dbo.v_zgloszenia_przedzialy
AS
SELECT z.id, z.numer, d.dni, k.kolor
FROM dbo.zgloszenia AS z
CROSS APPLY (SELECT DATEDIFF(DAY, GETDATE(), z.termin) AS dni) AS d
OUTER APPLY (SELECT TOP (1) s.kolor
             FROM dbo.slownik_przedzialow AS s
             WHERE s.dni_od <= d.dni
             ORDER BY s.dni_od DESC) AS k;
GO

Przykład ilustracyjny. Słownik z dolnymi granicami przedziałów i widok z OUTER APPLY.

Przykładowy słownik zaczyna się od wiersza -1000 z kolorem darkred, więc zaległe zgłoszenia dostają kolor bez dopisywania wiersza dla każdej liczby dni. Czternaście dni trafia do przedziału od 6 dni w górę i dostaje green. Dla liczby dni poniżej -1000 nie byłoby żadnego wiersza z dolną granicą, więc kolor też byłby NULL.

Kolor terminu w słowniku dni i w wariancie z przedziałami
Dni do terminuKolor ze słownika 0-10Kolor z wariantu z przedziałami
14NULLgreen
10greengreen
4yellowyellow
0redred
-3NULLdarkred

Pięć zgłoszeń z przykładu: słownik dni daje NULL dla 14 i -3, a wariant z przedziałami koloruje oba terminy.

Zapytanie kontrolne pokazuje luki w słowniku

Zapytanie kontrolne grupuje wiersze widoku z kolorem NULL po liczbie dni i podaje, ile zgłoszeń przypada na każdy dzień bez wpisu. W przykładzie zwraca dwa wiersze: -3 oraz 14, po jednym zgłoszeniu.

SELECT dni, COUNT(*) AS ile
FROM dbo.v_zgloszenia
WHERE kolor IS NULL
GROUP BY dni
ORDER BY dni;

Przykład ilustracyjny. Zapytanie kontrolne pokazuje dni, dla których słownik nie ma koloru.

Przykład: dział reklamacji prowadzi listę zgłoszeń z kolumną dni do terminu. Dla zgłoszenia 2026-10-0003 termin mija za 4 dni, więc widok zwraca kolor yellow z wpisu słownika. Kierownik zmienia kolor yellow na orange jednym UPDATE na tabeli słownika, a widok zostaje bez zmian.

Zgłoszenie 2026-10-0005 jest 3 dni po terminie i ma NULL, bo słownik nie zawiera wartości ujemnych, co ujawnia zapytanie kontrolne. Po przejściu na wariant z przedziałami zaległe zgłoszenia dostają ciemnoczerwony kolor bez dopisywania wiersza dla każdej liczby dni.

Podobny pomysł słownika w tabeli, tam dla kategorii błędów, pokazuje artykuł o klasyfikacji błędów i ponowieniach wysyłki. Walidację dat w procedurze i błąd zwracany jako zwykły wynik opisuje artykuł o API na procedurach SQL i JSON, a termin zwrotu liczony z ostatniego dokumentu artykuł o odczycie stanu narzędzi przez API. Tę samą ideę danych w tabeli stosuje wąska lista z zapasowym zbiorem.

Cztery pytania przed budową własnego słownika kolorów

Przed wdrożeniem takiego słownika w innym systemie ustal cztery rzeczy.

  • Zmiana reguły: czy reguła koloru ma się zmieniać bez wdrożenia, czy ma być częścią wersjonowanego kodu widoku.
  • Termin przekroczony: jaki kolor ma dostać i czy ma się różnić od terminu przypadającego dzisiaj.
  • Rodzaje terminów: ile potrzeba osobnych słowników, jeśli terminów jest kilka, na przykład termin zgłoszenia i termin płatności.
  • Zmiany słownika: kto może go zmieniać i czy zmiana ma zostawiać ślad.
FAQ

Najczęstsze pytania o kolor z liczby dni w widoku

01

Czy kolor ze słownika jest wolniejszy niż wyrażenie CASE?

Nie mierzyliśmy czasu, więc nie wskazujemy zwycięzcy. Sprawdziliśmy plany. Dla jednego wiersza optymalizator szukał koloru przez Clustered Index Seek, a dla 20 tysięcy wierszy wybrał Hash Match z jednokrotnym przeglądem małego słownika. Oba plany są poprawne.

02

Co zwraca widok dla liczby dni, której nie ma w słowniku?

Kolor NULL, bez błędu i bez ostrzeżenia. W przykładzie słownik obejmuje dni od 0 do 10, więc NULL dostały 14 i -3. Wynik zależy od daty uruchomienia, bo widok używa GETDATE().

03

Dlaczego klucz słownika powinien być typu int?

Klucz główny pilnuje jednego koloru na dzień i zgłasza błąd 2627 przy zapisie duplikatu, a bez niego duplikat kończy odczyt widoku błędem 512. Klucz tekstowy porównany z liczbą z DATEDIFF wymusza konwersję wartości słownika na int, a wpis niebędący liczbą kończy zapytanie błędem 245.

04

Jak objąć kolorem terminy przekroczone?

Wariantem z przedziałami. Słownik ma kolumnę dni_od, a widok wybiera wiersz przez OUTER APPLY z TOP (1) i ORDER BY dni_od DESC. Wiersz -1000 daje darkred zaległym zgłoszeniom z przykładu. Bez ORDER BY TOP (1) wybrałby dowolny wiersz.

05

Jak sprawdzić, które dni nie mają jeszcze koloru?

Zapytaniem, które grupuje wiersze widoku z kolorem NULL po liczbie dni. W przykładzie zwraca dwa wiersze: -3 oraz 14, po jednym zgłoszeniu.

Słownik pojęć

Słownik pojęć

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

DDATEDIFF
Funkcja T-SQL zwracająca liczbę przekroczonych granic wskazanej części daty między dwiema datami. Z częścią DAY liczy granice dób, więc godzina nie zmienia wyniku.
SSłownik kolorów
Tabela, w której kluczem jest liczba dni, a wartością nazwa koloru. Zmiana koloru to UPDATE jednego wiersza, bez zmiany definicji widoku.
PPodzapytanie skalarne
Zapytanie w liście kolumn, które zwraca jedną wartość. W przykładzie pobiera kolor ze słownika po kluczu dni, a dwa wiersze o tym samym kluczu kończą odczyt błędem 512.
CCROSS APPLY
Operator łączący każdy wiersz tabeli z wynikiem wyrażenia tabelowego. W przykładzie nadaje liczbie dni jedną nazwę, z której korzysta podzapytanie.
KKlucz główny
Ograniczenie, które pilnuje unikalności klucza. W słowniku kolorów gwarantuje jeden kolor na dzień i zgłasza błąd 2627 przy próbie dopisania duplikatu.
WWariant z przedziałami
Słownik z dolną granicą dni zamiast pojedynczego dnia. Widok wybiera najwyższą granicę nieprzekraczającą liczby dni, więc obejmuje także wartości ujemne.
GGETDATE
Funkcja zwracająca bieżącą datę i godzinę serwera. Widok z GETDATE() zmienia wynik z dnia na dzień, więc powtarzalny test buduje terminy względem bieżącej daty.
TTOP (1) z ORDER BY
Konstrukcja wybierająca jeden wiersz w określonej kolejności. W wariancie z przedziałami ORDER BY jest obowiązkowy, bo bez niego wybrany zostałby dowolny wiersz.

Zobacz to w praktyce

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