I would radically simplify your design:
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,
);
Since a country can only have one capitol, no n:m table is needed.
Never use "name" or "id" as column names. That's an anti-pattern of some ORMs. Once you join a couple of tables (which you do a lot in relational databases) you end up with multiple columns of the same non-descriptive name, causing all kinds of problems.
Just use
text
. No point invarchar(n)
. Avoid problem like this.The
PRIMARY KEY
clause makes a columnNOT NULL
automatically. (NOT NULL
sticks, even if you later remove the pk constraint.)
And most importantly:
A city only references one province in all cases. No direct reference to
country
. Therefore mismatches are impossible, on-disk storage is smaller and your whole design is much simpler. Queries are simpler.For every country enter a single dummy-province with an empty string as name (
''
), representing the country "as a whole". (Possibly even with the same id, you could have provinces and countries draw from the same sequence ...). Do this automatically in a trigger. This trigger is optional, though.I chose an empty string instead of
NULL
, so the column can still beNOT NULL
and a unique index over(country_id, province)
does its job. You can easily identify this province representing the whole country and deal with it as appropriate in your application.
I am using a similar design successfully in multiple instances.