Jednym z rozwiązań jest posiadanie drugiej tabeli do wykrywania kolizji i wypełnienie jej wyzwalaczem. Korzystając ze schematu dodanego do pytania:
CREATE TABLE medicinal_product_date_map(
aic_code char(9) NOT NULL,
applicable_date date NOT NULL,
UNIQUE(aic_code, applicable_date));
(uwaga:to druga próba z powodu błędnego odczytania wymagań za pierwszym razem. Mam nadzieję, że tym razem to prawda).
Niektóre funkcje do obsługi tej tabeli:
CREATE FUNCTION add_medicinal_product_date_range(aic_code_in char(9), start_date date, end_date date)
RETURNS void STRICT VOLATILE LANGUAGE sql AS $$
INSERT INTO medicinal_product_date_map
SELECT $1, $2 + offset
FROM generate_series(0, $3 - $2)
$$;
CREATE FUNCTION clr_medicinal_product_date_range(aic_code_in char(9), start_date date, end_date date)
RETURNS void STRICT VOLATILE LANGUAGE sql AS $$
DELETE FROM medicinal_product_date_map
WHERE aic_code = $1 AND applicable_date BETWEEN $2 AND $3
$$;
I zapełnij tabelę za pierwszym razem:
SELECT count(add_medicinal_product_date_range(aic_code, vs, ve))
FROM medicinal_products;
Teraz utwórz wyzwalacze, aby wypełnić mapę dat po zmianach w produktach medycznych:po wstawieniu wywołań add_, po aktualizacji wywołań clr_ (stare wartości) i add_ (nowe wartości), po usunięciu wywołań clr_.
CREATE FUNCTION sync_medicinal_product_date_map()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
IF TG_OP = 'UPDATE' OR TG_OP = 'DELETE' THEN
PERFORM clr_medicinal_product_date_range(OLD.aic_code, OLD.vs, OLD.ve);
END IF;
IF TG_OP = 'UPDATE' OR TG_OP = 'INSERT' THEN
PERFORM add_medicinal_product_date_range(NEW.aic_code, NEW.vs, NEW.ve);
END IF;
RETURN NULL;
END;
$$;
CREATE TRIGGER sync_date_map
AFTER INSERT OR UPDATE OR DELETE ON medicinal_products
FOR EACH ROW EXECUTE PROCEDURE sync_medicinal_product_date_map();
Ograniczenie unikatowości na medical_product_date_map spowoduje uwięzienie wszelkich produktów dodanych z tym samym kodem tego samego dnia:
[email protected]@[local] =# INSERT INTO medicinal_products VALUES ('1','A','2010-01-01','2010-04-01');
INSERT 0 1
[email protected]@[local] =# INSERT INTO medicinal_products VALUES ('1','A','2010-03-01','2010-06-01');
ERROR: duplicate key value violates unique constraint "medicinal_product_date_map_aic_code_applicable_date_key"
DETAIL: Key (aic_code, applicable_date)=(1 , 2010-03-01) already exists.
CONTEXT: SQL function "add_medicinal_product_date_range" statement 1
SQL statement "SELECT add_medicinal_product_date_range(NEW.aic_code, NEW.vs, NEW.ve)"
PL/pgSQL function "sync_medicinal_product_date_map" line 6 at PERFORM
Zależy to od sprawdzanych wartości pod kątem posiadania dyskretnej przestrzeni - dlatego zapytałem o daty i znaczniki czasu. Chociaż sygnatury czasowe są technicznie dyskretne, ponieważ Postgresql przechowuje tylko rozdzielczość mikrosekundową, dodawanie wpisu do tabeli mapy dla każdej mikrosekundy, w której produkt ma zastosowanie, nie jest praktyczne.
Powiedziawszy to, prawdopodobnie możesz również uciec z czymś lepszym niż skanowanie pełnej tabeli, aby sprawdzić nakładające się interwały znaczników czasu, z pewnymi sztuczkami polegającymi na wyszukiwaniu tylko pierwszego interwału nie po lub nie przed ... jednak dla łatwych dyskretnych spacji Wolę to podejście, które IME może być również przydatne w przypadku innych rzeczy (np. raportów, które muszą szybko znaleźć produkty, które mają zastosowanie w określonym dniu).
Podoba mi się również to podejście, ponieważ wydaje mi się, że w ten sposób można wykorzystać mechanizm ograniczania unikalności bazy danych. Uważam również, że będzie to bardziej niezawodne w kontekście współbieżnych aktualizacji tabeli głównej:bez blokowania tabeli przed współbieżnymi aktualizacjami, możliwe byłoby, aby wyzwalacz walidacji nie widział konfliktu i zezwalał na wstawianie w dwóch równoczesnych sesjach, które są następnie widać konflikt, gdy widoczne są efekty obu transakcji.