2014-02-22 62 views
1

我正在創建一個PostgreSQL數據庫:Country - Province - City。 一個城市必須屬於一個國家,可以屬於一個省份。 一個省必須屬於一個國家。 一個城市可以是一個國家的資本:在PostgreSQL中實現沒有觸發器的複雜引用

CREATE TABLE country (
    id serial NOT NULL PRIMARY KEY, 
    name varchar(100) NOT NULL 
); 

CREATE TABLE province (
    id serial NOT NULL PRIMARY KEY, 
    name varchar(100) NOT NULL, 
    country_id integer NOT NULL, 
    CONSTRAINT fk_province_country FOREIGN KEY (country_id) REFERENCES country(id) 
); 

CREATE TABLE city (
    id serial NOT NULL PRIMARY KEY, 
    name varchar(100) NOT NULL, 
    province_id integer, 
    country_id integer, 
    CONSTRAINT ck_city_provinceid_xor_countryid 
     CHECK ((province_id is null and country_id is not null) or 
       (province_id is not null and country_id is null)), 
    CONSTRAINT fk_city_province FOREIGN KEY (province_id) REFERENCES province(id), 
    CONSTRAINT fk_city_country FOREIGN KEY (country_id) REFERENCES country(id) 
); 

CREATE TABLE public.capital ( 
    country_id integer NOT NULL, 
    city_id integer NOT NULL, 
    CONSTRAINT pk_capital PRIMARY KEY (country_id, city_id), 
    CONSTRAINT fk_capital_country FOREIGN KEY (country_id) REFERENCES country(id), 
    CONSTRAINT fk_capital_city FOREIGN KEY (city_id) REFERENCES city(id) 
); 

對於一些(但不是全部)的國家,我將有省的數據,所以一個城市將屬於一個省,全省的國家。其餘的,我只知道這個城市屬於一個國家。

問題1:關於我所在的國家有省數據,我一直在尋找一種解決方案,它將不允許一個城市屬於一個國家,同時又屬於不同國家的一個省份。

我傾向於通過檢查約束強制執行,即省或國家(但不是兩個)在城市中不爲空。看起來像一個整潔的解決方案。

另一種方法是在城市內保留省和國家的信息,並通過觸發器強制執行一致性。

問題#2:我想禁止一個城市是一個不屬於它的國家的首都。在我的問題#1的解決方案之後,這似乎是不可能的,因爲沒有辦法直接引用一個城市所屬的國家。

也許問題1的替代解決方案更好,它也簡化了未來的查詢。

+0

城市,省份和國家(以及縣也應該是抽象地緣政治區的凝結物)。使用表繼承。 –

回答

2

我會大大簡化設計:

CREATE TABLE country (
    country_id serial PRIMARY KEY -- pk is not null automatically 
    ,country text NOT NULL   -- just use text 
    ,capital int REFERENCES city -- simplified 
); 

CREATE TABLE province (   -- never use "id" as name 
    province_id serial PRIMARY KEY 
    ,province text NOT NULL   -- never use "name" as name 
    ,country_id integer NOT NULL REFERENCES country -- references pk per default 
); 

CREATE TABLE city (
    city_id serial PRIMARY KEY 
    ,city text NOT NULL 
    ,province_id integer NOT NULL REFERENCES province, 
); 
  • 由於一個國家只能有一個國會,沒有N:需要M表中。

  • 切勿使用「name」或「id」作爲列名。這是一些ORM的反模式。一旦你加入了幾個表(你在關係型數據庫中做了很多),你最終會得到多列相同的非描述性名稱,從而導致各種問題。

  • 只需使用text。沒有點varchar(n)Avoid problem like this.

  • PRIMARY KEY子句自動創建一列NOT NULL。 (NOT NULL棒,即使你以後刪除的PK約束。)

而最重要的

  • 一個城市只引用一個省所有箱子。沒有直接引用country。因此,不匹配是不可能的,磁盤存儲更小,並且您的整個設計更容易更簡單。查詢更簡單。

    對於國家以空字符串作爲名稱('')輸入一個虛擬省份,代表該國「整體」。 (可能即使使用相同的ID,你也可以讓省份和國家從相同的序列中抽取...)。在觸發器中自動執行此操作。不過,這個觸發器是可選的。

    我選擇了一個空字符串而不是NULL,因此該列仍可以是NOT NULL,並且(country_id, province)上的唯一索引完成其工作。您可以輕鬆識別代表全國的該省,並在您的申請中酌情處理。

我在多個實例中成功地使用了類似的設計。

+1

謝謝!在主要和次要方面的答案非常有用。我想避免進入虛擬省份,但如果它簡化了設計和查詢這麼多,那麼這似乎是一個好主意。是的,可插入的每個國家進入虛擬省份的可選觸發器將使未來的國家插入更容易。只有一個反對意見,一些國家確實有不止一個首都(例如荷蘭有阿姆斯特丹官方,海牙作爲政府所在地,也見南非,玻利維亞等)。然後當然這是一個設計決定,如果有人想要存儲這個額外的i – Manolis

1

我想你可以在不使用觸發器的情況下實現所有這些約束。它確實需要對數據進行一些重構。

開始通過強制的關係(使用外鍵):

city --> province --> country 

對於國家,沒有一個省份的信息,創造一個省 - 或許與該國的名字,或許有些怪異的默認名稱(「CountryProvince」 )。這允許你在這三個實體之間只有一組關係。它會自動確保城市和省份位於正確的國家,因爲您將通過該省獲得該國家。

最後一個問題是關於首都。有一種方法可以在沒有觸發器的情況下實現和執行唯一性。保持一個國旗在cities表,並採用獨特的過濾索引來保證唯一性:

create unique index on cities_capitalflag on cities(capitalflag) where capitalflag = 'Y'; 

編輯:

你說得對有關過濾索引需要的國家。但是,這要求將該國儲存在該表中,這反過來又要求保持各省和各城市在國家方面的一致性。所以,這個解決方案接近於不需要觸發器,但它不在那裏。

+0

我不想插入這種「發明」的數據,這個解決方案已經出來了。 我喜歡獨特的過濾索引的想法!但它當然應該包括國家,否則,所有城市中只有一個可以成爲首都。 – Manolis