If this is your first visit, be sure to check out the FAQ by clicking the link above. You may have to register before you can post: click the register link above to proceed. To start viewing messages, select the forum that you want to visit from the selection below.

 
Go Back  dBforums > General > Database Concepts & Design > Symmetric Self Referencing Many to Many Relationship

Reply
 
LinkBack Thread Tools Search this Thread Display Modes
  #1 (permalink)  
Old 04-21-10, 12:20
jx12345 jx12345 is offline
Registered User
 
Join Date: Apr 2010
Posts: 51
Symmetric Self Referencing Many to Many Relationship

I've got this problem i've been mulling over for ages and can't seem to come up with an answer for it:

I want to create a db that stores country information so i'll probably have a country tables as follows:

countryid countryname etc
1 france
2 germany
3 spain


I also want to store information about what country borders another country. To my mind this is a self referencing many to many relationship so i create a borders table to resolve the many to many as follows:

countrya countryb
1 2
1 3

the problem with this is the relationship borders is symmetrical ie. if france borders germany then germany should also border france, yet with this design france can border germany without germany bordering france.

any thoughts, ideas, comments, etc. would be most welcome.

thanks

j
Reply With Quote
  #2 (permalink)  
Old 04-21-10, 14:27
r937 r937 is offline
SQL Consultant
 
Join Date: Apr 2002
Location: Toronto, Canada
Posts: 19,524
your "self referencing many to many relationship borders table" is fine

all you have to do is make sure that whenever you store a pair of country FKs, the lower one goes first

this lets you avoid storing the relationship twice
__________________
r937.com | rudy.ca
please visit Simply SQL and buy my book
Reply With Quote
  #3 (permalink)  
Old 04-21-10, 14:44
jx12345 jx12345 is offline
Registered User
 
Join Date: Apr 2010
Posts: 51
hi rudy,

many thanks for your quick reply... i'm afraid i'm going to show my ignorance here:

how does having the lower fk go first help me?
Reply With Quote
  #4 (permalink)  
Old 04-21-10, 14:47
r937 r937 is offline
SQL Consultant
 
Join Date: Apr 2002
Location: Toronto, Canada
Posts: 19,524
Quote:
Originally Posted by jx12345 View Post
how does having the lower fk go first help me?
i'd be happy to tell you right after you explain in a bit more detail what your problem was again?
__________________
r937.com | rudy.ca
please visit Simply SQL and buy my book
Reply With Quote
  #5 (permalink)  
Old 04-21-10, 14:57
dportas dportas is offline
Registered User
 
Join Date: Dec 2007
Location: London, UK
Posts: 732
Another way to do this is to name or number each border and treat the border itself as a set (ie. the set of adjacent countries):

CREATE TABLE Borders (BorderNum INTEGER NOT NULL, CountryNum INTEGER NOT NULL, PRIMARY KEY (BorderNum, CountryNum));

This has the possible advantage that there is only one column in which to look for a country.
Reply With Quote
  #6 (permalink)  
Old 04-21-10, 15:24
blindman blindman is offline
World Class Flame Warrior
 
Join Date: Jun 2003
Location: Ohio
Posts: 11,726
In your join table you will have columns for CountryA and CountryB.
Which table comes first can be arbitrary, though if you use Rudy's suggestion of enforcing a constraint against CountryB ever being a lower value than CountryA you can prevent duplicate relationships such as:

Code:
Record	CountryA	CountryB
1	USA		Canada
2	Canada		USA
3	USA		Mexico
...not that such duplicates would be a huge issue, as long as you UNION the table to itself to get the entire set of relationships from either direction, and use this UNION view in all your code joins:

Code:
select	CountryA, CountryB from CountryNeighbors
UNION
select	CountryB as CountryA, CountryA as CountryB from CountryNeighbors
Yields:
Code:
CountryA	CountryB
USA		Canada
Canada		USA
USA		Mexico
Mexico		USA
This result set would effectively show you all bordering countries for every country.
__________________
If it's not practically useful, then it's practically useless.

blindman
www.chess.com: "sqlblindman"
Reply With Quote
  #7 (permalink)  
Old 04-21-10, 16:45
dportas dportas is offline
Registered User
 
Join Date: Dec 2007
Location: London, UK
Posts: 732
If you just want to enforce the constraint that borders must be symmetrical then you could do this:

CREATE TABLE Borders
(countrya INTEGER NOT NULL,
countryb INTEGER NOT NULL,
PRIMARY KEY (countrya,countryb),
FOREIGN KEY (countryb,countrya) REFERENCES Borders (countrya,countryb));

INSERT INTO Borders VALUES (1,2),(2,1);
Reply With Quote
  #8 (permalink)  
Old 04-21-10, 16:51
r937 r937 is offline
SQL Consultant
 
Join Date: Apr 2002
Location: Toronto, Canada
Posts: 19,524
Quote:
Originally Posted by dportas View Post
If you just want to enforce the constraint that borders must be symmetrical ...
that's a cute trick, but it more or less locks you in to double entries

(by the way, in which database systems would inserting the (1,2) fail because the (2,1) isn't there yet? does this technique require suspending FK checking until after the transaction? this is getting messier by the minute...)
__________________
r937.com | rudy.ca
please visit Simply SQL and buy my book
Reply With Quote
  #9 (permalink)  
Old 04-21-10, 17:25
jx12345 jx12345 is offline
Registered User
 
Join Date: Apr 2010
Posts: 51
wow, this is some forum, thanks for all your replies... i think i'm getting left behind here :-)

Quote:
Originally Posted by r937 View Post
i'd be happy to tell you right after you explain in a bit more detail what your problem was again?
sorry, rudy, yes i think your previous reply has sunk in.. keeping the lower id first prevents duplicate entries, right??

my problem then is how do i successfully query the db to tell me who borders germany when france borders germany but germany doesnt border france... if you see what i mean??

i guess blindman's union qry does this for me??
Reply With Quote
  #10 (permalink)  
Old 04-21-10, 17:32
jx12345 jx12345 is offline
Registered User
 
Join Date: Apr 2010
Posts: 51
Quote:
Originally Posted by dportas View Post
Another way to do this is to name or number each border and treat the border itself as a set (ie. the set of adjacent countries):

CREATE TABLE Borders (BorderNum INTEGER NOT NULL, CountryNum INTEGER NOT NULL, PRIMARY KEY (BorderNum, CountryNum));

This has the possible advantage that there is only one column in which to look for a country.
i can see that this would solve the problem but it kind of feels a bit wrong - a bit too border centric - i'm probably mixing the db design with the end application here - but this design feels like it would force me to be adding countries to borders, whereas i'd envisaged adding a country neighbours to countries, if you see what i mean?
Reply With Quote
  #11 (permalink)  
Old 04-21-10, 17:41
r937 r937 is offline
SQL Consultant
 
Join Date: Apr 2002
Location: Toronto, Canada
Posts: 19,524
Quote:
Originally Posted by jx12345 View Post
my problem then is how do i successfully query the db to tell me who borders germany
SELECT countrya FROM borders WHERE countryb = 'germany'
UNION ALL
SELECT countryb FROM borders WHERE countrya = 'germany'

__________________
r937.com | rudy.ca
please visit Simply SQL and buy my book
Reply With Quote
  #12 (permalink)  
Old 04-21-10, 17:58
jx12345 jx12345 is offline
Registered User
 
Join Date: Apr 2010
Posts: 51
Quote:
Originally Posted by r937 View Post
SELECT countrya FROM borders WHERE countryb = 'germany'
UNION ALL
SELECT countryb FROM borders WHERE countrya = 'germany'

yeah,

in your/anyone's opinion is it better to, or what are the advantages/disadvantates of :

a) using a union qry for the data retrieval, & storing lower fk first in the borders table to prevent duplication.

compared with

b) automatically adding duplicate entries to the border table by adding the opposite entry ie. (2,1) & (1,2) or (4,3) & (3,4) every an entry is made

any thoughts, comments, etc. much appreciated,

cheers

j
Reply With Quote
  #13 (permalink)  
Old 04-21-10, 18:27
loquin loquin is offline
Super Moderator
 
Join Date: Jun 2004
Location: Arizona, USA
Posts: 1,797
One of the goals of normalization in database design is to reduce or eliminate duplicate data. (If you have duplicate data, what happens when you update/delete the first row, but forget to update/delete the second??? After the change, if you don't know the answer already, how do you then know which row is correct?)

After all, it's very easy to add a check constraint to the table, so that CountryA must be less than CountryB. (and, in a case like this, where once entered, the table data will change rarely, it's not as if there would be a performance hit...)
__________________
Lou
使大吃一惊
"Lisa, in this house, we obey the laws of thermodynamics!" - Homer Simpson
"I have my standards. They may be low, but I have them!" - Bette Middler
"It's a book about a Spanish guy named Manual. You should read it." - Dilbert


Last edited by loquin; 04-21-10 at 18:32.
Reply With Quote
  #14 (permalink)  
Old 04-22-10, 13:03
blindman blindman is offline
World Class Flame Warrior
 
Join Date: Jun 2003
Location: Ohio
Posts: 11,726
Quote:
Originally Posted by r937 View Post
that's a cute trick, but it more or less locks you in to double entries
Agreed. I'd never implement something like this, though it is interesting.
__________________
If it's not practically useful, then it's practically useless.

blindman
www.chess.com: "sqlblindman"
Reply With Quote
  #15 (permalink)  
Old 04-22-10, 13:05
blindman blindman is offline
World Class Flame Warrior
 
Join Date: Jun 2003
Location: Ohio
Posts: 11,726
Quote:
Originally Posted by jx12345 View Post
how do i successfully query the db to tell me who borders germany when france borders germany but germany doesnt border france... if you see what i mean??

i guess blindman's union qry does this for me??
Yes. Create my union query as a view, and you won't have to think about it again.

Select * from UNIONVIEW where CountryA = 'Germany'

That is all you would need.
__________________
If it's not practically useful, then it's practically useless.

blindman
www.chess.com: "sqlblindman"
Reply With Quote
Reply

Tags
database design, many-to-many, relationships, self-referencing

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are On