1

Temat: Dziedziczenie tabel

W bazie mam 5 identycznych tabel, do których poszczególne jednostki organizacyjne wprowadzają swoje dane. Każda jednostka ma uprawnienia do edycji tylko na swojej tabeli, a do podglądu na wszystkich.
Aby zobaczyć wszystkie dane razem stworzony jest widok, który łączy te 5 tabel UNIONami.

Problem jest wtedy, gdy muszę zmienić strukturę, np. dodać kolumnę, bo zmian trzeba dokonać we wszystkich 5 tabelach i dodatkowo zmienić widok.
Dziś przez przypadek przeczytałem o dziedziczeniu tabel i wpadłem na taki pomysł: tworzę jedną tabelę 'wzorcową', a następnie 5 tabel, które dziedziczą po tej 'wzorcowej'; tabela 'wzorcowa' zastępuje mi widok z poprzedniego rozwiązania.

Zaleta jest taka, że zmiany w strukturze tabeli 'wzorcowej' przenoszą się na pozostałe tabele. Dodatkowo zauważyłem, że w tabeli 'wzorcowej' istnieje możliwość modyfikacji danych, które automatycznie przenoszą się do tabel źródłowych. W przypadku widoku trzeba było tworzyć reguły.

Moje pytanie jest takie: czy dobrym pomysłem jest zastąpienie rozwiązania z widokiem na ten z dziedziczeniem tabel? Nie wiem, czy gdzieś nie kryje się jakiś kruczek. A może są jeszcze inne rozwiązania takiego zagadnienia?

Pozdrawiam

2

Odp: Dziedziczenie tabel

No cóż aby odpowiedzieć na twoje pytanie trzeba trochę więcej wiedzieć na temat projektu, który realizujesz, np ile masz rekordów w poszczególnych tabelach. jeśli jest ich dużo, a tobie zależy na szybkim działaniu bazy to dziedziczenie daje przewagę nad innymi rozwiązaniami gdyż wprowadza dodatkowo partycjonowanie (czyli dzielenie dużej tabeli na mniejsze)

Jeśli rekordów nie jest dużo to najlepszym rozwiązaniem jest jedna tabela i jeden widok, który w zależności od tego który użytkownik się zaloguje pokarze mu tylko jego dane. Oczywiście widok musi mieć RULE, a do tego dobrze by było byś wykorzystał opcje klauzuli WITH "CHECK OPTION". Takie rozwiązanie upraszcza też budowę klienta bazy danych bo inserty i UPDATE trzeba wykonywać tylko na jednym widoku.

Reasumując każde rozwiązanie, które spełnia twoje wymogi jest dobre

3

Odp: Dziedziczenie tabel

Dziękuję za odpowiedź.
Projekt to przestrzenna baza danych, z której korzysta program Quantum GIS.
W GISie mam kilkanaście warstw, które są oparte o schemat jaki opisałem - 5 tabel + widok dla całości. W każdej tabeli jest góra 10000 rekordów.
Dane dla poszczególnych jednostek chcę koniecznie przechowywać w osobnych tabelach ze względu na bezpieczeństwo (nie ufam sobie na tyle  w kwestii Postgresa aby wrzucić to wszystko do jednej tabeli smile ).

W chwili obecnej w kliencie (QGIS) wyświetlam wszystkie tabele (tylko po to, aby użytkownicy mogli zmieniać swoje dane) i widoki, aby widoczna była całość. Optymalnie dobrze by było wyświetlać tylko dane zbiorcze i umożliwić edycję z uwzględnieniem uprawnień.
Dla jednej warstwy dorobiłem RULE do widoku, ale to dość skomplikowane (rekordy trafiają do różnych tabel w zależności od wprowadzonych danych lub użytkownika) i nie bardzo chce mi się to implementować do pozostałych tabel i widoków.
O ile dobrze zrozumiałem zasadę działania, to dziedziczenie załatwi za mnie stworzenie widoku i edycję danych. Trzeba będzie zrobić tylko RULE dla insertów, ale to dość proste.
Z Twojego postu wywnioskowałem, że nie ma przeciwskazań aby używać dziedziczenia w ten sposób. Spróbuję to zastosować w praktyce.

P.s.
Co daje WITH "CHECK OPTION"?

4

Odp: Dziedziczenie tabel

Zacznę od tego że niestety pomyliłem się co do opcji WITH CHECK OPTION, niestety w postgresql 9.3 nadal nie działa (lub może trzeba ją jakoś włączyć (ale wątpię) klauzula taka jest bardzo przydatna gdyż dokonuje dla widoku aktualizacji tylko wtedy gdy widok wyświetli modyfikowany/dodawany rekord. Opis trochę pokrętny więc opisze to tak:
Gdy wykonujesz na widoku insert lub update widok sprawdza czy dane w rekordzie spełniają warunki w where jeśli tak to dokonuje zmiany wartości lub dodania rekordu w przeciwnym wypadku zgłasza błąd (fajne jest to że może sprawdzać warunki tylko dla konkretnego widoku opcja with local check option lub też dla widoków od niego zależnych opcja with cascade check option).

Co do twojego przypadku to twoje myślenie o tabelach dziedziczonych jest jak najbardziej OK tyle, że jeśli masz w każdej z tabel po 10000 rekordów (czyli tyle co nic) to strasznie utrudniasz sobie sprawę (ale nie zrozum tego źle, jak przerobisz sobie skrypt na tabele dziedziczone tez będzie OK) poniżej załączam mały przykład (będzie działał pod warunkiem, że nie pracujesz na użytkowniku postgres ale z prawami superusera.

Założenie jest jedno mam jedną tabele i jeden widok wiec w kliencie bazy danych nie muszę budować 5 insertów/updat-ów w zależności od tego kto się zaloguje do bazy

create table t_user_warstwa(
user_name name not null
,warstwa integer not null check (warstwa > 0 and warstwa <=5)
,CONSTRAINT t_user_warstwa_pkey PRIMARY KEY (user_name,warstwa)
);

create table t_warstwa_dane(
id serial primary key
,warstwa integer not null check (warstwa > 0 and warstwa <=5)
,wartosc integer);

create or replace view v_warstwa_dane
as
select d.* from t_warstwa_dane d where exists (select 1 from t_user_warstwa uw where uw.user_name=current_user and uw.warstwa=d.warstwa);

CREATE OR REPLACE RULE "_INSERT" AS
ON INSERT TO v_warstwa_dane
DO INSTEAD insert into t_warstwa_dane (warstwa,wartosc) values ((select warstwa from t_user_warstwa where user_name = current_user and warstwa=new.warstwa),new.wartosc) returning *;

CREATE OR REPLACE RULE "_UPDATE" AS
ON UPDATE TO v_warstwa_dane
DO INSTEAD update t_warstwa_dane set warstwa=(select warstwa from t_user_warstwa where user_name = current_user and warstwa=new.warstwa),wartosc=new.wartosc where id=old.id returning *;

CREATE OR REPLACE RULE "_DELETE" AS
ON DELETE TO v_warstwa_dane
DO INSTEAD DELETE from t_warstwa_dane where warstwa=(select warstwa from t_user_warstwa where user_name = current_user and warstwa=old.warstwa) and id=old.id returning *;

insert into t_user_warstwa values(current_user,1);
insert into t_user_warstwa values('postgres',2);

a tu sprawdź sobie jak to rozwiązanie działa

insert into v_warstwa_dane (warstwa,wartosc) values (1,-1);
insert into v_warstwa_dane (warstwa,wartosc) values (2,-2);

update v_warstwa_dane set wartosc=-2 where warstwa=1 returning *
update v_warstwa_dane set wartosc=-3 where warstwa=2 returning *

delete from v_warstwa_dane where warstwa=2 returning *
delete from v_warstwa_dane where warstwa=1 returning *

5

Odp: Dziedziczenie tabel

Dzięki, działa. Musze jeszcze przeanalizować, który sposób wybrać. Ten, który obecnie stosuję jest najmniej przyjazny.

Do przykładu mam pytanie:

1. Przy wykonywaniu "niedozwolonego" DELETE nie pojawia się żaden komunikat, że nie usunięto rekordów (lub części rekordów)
2. Przy wykonywaniu "niedozwolonego" UPDATE lub INSERT pojawia się komunikat
      ERROR:  null value in column "warstwa" violates not-null constraint

W jaki najprostszy sposób dodać komunikat do przypadku 1 i zmienić treść komunikatu z przypadku 2 na zrozumiały dla zwykłego użytkownika?

6

Odp: Dziedziczenie tabel

Co do pytań:
Chyba nie ma innego sposobu jak napisać trigger, który obsłuży zdarzenie BEFORE INSERT/UPDATE/DELETE i w zależności od danych zareaguje odpowiednim błędem ale z tego co pamiętam triggery można tworzyć na widokach od wersji 9.1 (piszę na podstawie dokumentacji bo sam jeszcze nie robiłem triggerów na widokach).

Dodam, że nie było by tego problemu gdyby działała opcja WITH CHECK OPTION no ale podobno w wersji 9.4 ma działać.

Możesz też zrobić obsługę błędów po stronie klienta bazy danych sprawdzając czy polecenia INSERT/UPDATE/DELETE zwróciły dane po to dopisałem opcje RETURNING.

Ostatnio edytowany przez c_michal (2014-01-10 09:56:33)