1

I have problems with setting the reference on database table. I have following structure:

CREATE TABLE club(
    id INTEGER NOT NULL,
    name_short VARCHAR(30),
    name_full VARCHAR(70) NOT NULL
);
CREATE UNIQUE INDEX club_uix ON club(id);
ALTER TABLE club ADD CONSTRAINT club_pk PRIMARY KEY (id);

CREATE TABLE team(
    id INTEGER NOT NULL,
    club_id INTEGER NOT NULL,
    team_name VARCHAR(30)
);
CREATE UNIQUE INDEX team_uix ON team(id, club_id);
ALTER TABLE team ADD CONSTRAINT team_pk PRIMARY KEY (id, club_id);
ALTER TABLE team ADD FOREIGN KEY (club_id) REFERENCES club(id);

CREATE TABLE person(
    id INTEGER NOT NULL,
    first_name VARCHAR(20),
    last_name VARCHAR(20) NOT NULL
);
CREATE UNIQUE INDEX person_uix ON person(id);
ALTER TABLE person ADD PRIMARY KEY (id);

CREATE TABLE contract(
    person_id INTEGER NOT NULL,
    club_id INTEGER NOT NULL,
    wage INTEGER
);
CREATE UNIQUE INDEX contract_uix on contract(person_id);
ALTER TABLE contract ADD CONSTRAINT contract_pk PRIMARY KEY (person_id);
ALTER TABLE contract ADD FOREIGN KEY (club_id) REFERENCES club(id);
ALTER TABLE contract ADD FOREIGN KEY (person_id) REFERENCES person(id);

CREATE TABLE player(
    person_id INTEGER NOT NULL,
    team_id INTEGER,
    height SMALLINT,
    weight SMALLINT

);
CREATE UNIQUE INDEX player_uix on player(person_id);
ALTER TABLE player ADD CONSTRAINT player_pk PRIMARY KEY (person_id);
ALTER TABLE player ADD FOREIGN KEY (person_id) REFERENCES person(id);
-- ALTER TABLE player ADD FOREIGN KEY (team_id) REFERENCES team(id); --this is not working

It gives me this error:

Error code -5529, SQL state 42529: a UNIQUE constraint does not exist on referenced columns: TEAM in statement [ALTER TABLE player ADD FOREIGN KEY (team_id) REFERENCES team(id)]

As you can see, team table has composite primary key (club_id + id), the person references club through contract. Person has some common attributes for player and other staff types.

One club can have multiple teams. Employed person has to have a contract with a club. Player (is the specification of person) - if emplyed - can be assigned to one of the club's teams.

Is there better way to design my structure? I thought about excluding the club_id from team's primary key, but I would like to know if this is the only way. Thanks.

UPDATE 1

I would like to have the id as team identification only within the club, so multiple teams can have equal id as long as they belong to different clubs. Is it possible?

UPDATE 2 updated the naming convention as adviced by philip

Some business rules to better understand the structure:

  • One club can have 1..n teams (Main squad, Reserve squad, Youth squad or Team A, Team B... only team can play match, not club)
  • One team belongs to one club only
  • A player is type of person (other types (staff) are scouts, coaches etc so they do not need to belong to specific team, just to the club, if employed)
  • Person can have 0..1 contract with 1 club (that means he is employed or unemployed)
  • Player (if employed) belongs to one team of the club

Now thinking about it - moving team_id from player to contract would solve my problem, and it could hold the condition "Player (if employed) belongs to one team of the club", but it would be redundant for other staff types. What do you think?

Zavael
  • 2,383
  • 1
  • 32
  • 44
  • Yes, there is a better way to design your structure. Have `team_id` be the primary key for `team`, preferably auo-incrementing. I see no reason why the same `team_id` would be used in different clubs. If there is a "club_team_id", then make that a different column. – Gordon Linoff Jun 01 '14 at 16:06
  • maybe I could use the knowledge that in any club there is for sure one team with id 0 and it is the main team, any other team could be optional, I see no reason to have every team with unique ID if the team is partly already identified by club – Zavael Jun 01 '14 at 19:26
  • 3
    Explain in the question the meaning of team_id: a unique value identifying teams (which is proper use of "id") or merely team-within-club. (Aparently the latter.) The database users (including question answerers) need to know this. – philipxy Jun 01 '14 at 20:15
  • 1
    @Zavael . . . I think you are confusing entities and attributes of those entities. To the outside observer, a club is an attribute of a team, as is the club's numbering system for the team. – Gordon Linoff Jun 02 '14 at 01:38
  • @philip yeah, i should but i didnt have any specific meaning of it, just tried to minimalise the table column by reusing other relations – Zavael Jun 02 '14 at 08:13
  • @GordonLinoff maybe i am looking on it in too OOP way, as that is where i am more at home, not in the DB design – Zavael Jun 02 '14 at 08:14
  • @philip as I stated i do not have any meaning for team_id other than additional identification of table... forced to think about it one more time, my comment about team_id as hidden club's numbering system now sounds wrong, ok adding it to question too – Zavael Jun 02 '14 at 11:07
  • I actually upvoted this question because I think this is a recurrent problem for beginners and once in a while deserves to be (again) the center of attention, for a short while. – Sebas Jun 03 '14 at 04:17
  • BTW, there is no column `team.team_id`. You wrote: `...REFERENCES team(team_id)`, did you actually mean `...REFERENCES team(id)`? – Branko Dimitrijevic Jun 03 '14 at 09:38
  • @BrankoDimitrijevic yes, i am sorry, i refactored the name convention and forget to change it in exception, thanks! I hope its correct now – Zavael Jun 03 '14 at 10:42
  • @Zavael See my answer: each table is rows satisfying a statement using its columns--give them! – philipxy Jun 03 '14 at 12:17
  • A table doesn't have a "condition", it has a _statement_. If the statement is true for a row then that row is in, if the statement is false a row then that row is out. So you want for contract "Player [player_id] is on the [team_id] team of club [club_id] with wage $[wage]". – philipxy Jun 05 '14 at 03:09
  • @philip yes, my terminology is not accurate, i meant the table design will reflect the business rule "Only if player is employed, it will belong to team of employing club" – Zavael Jun 05 '14 at 09:06

2 Answers2

2

When a subrow in one table has to be a subrow in another (referenced) table, that is an inclusion dependency (IND). Eg player team_id referencing team id (not a key in team). When there is an IND and the referenced subrow is a key, that is a foreign key (FK). Eg player person_id referencing person id (a key in person). In SQL a FOREIGN KEY declaration says that there is an IND and the referenced columns are unique. (Ie declared by either PRIMARY KEY or UNIQUE). (So it actually means "foreign superkey".) In SQL we would ideally declare an IND (when there isn't also a FK) by a CHECK constraint. (But DBMSes do not support CHECK well). Ie check for player that team_id is in team projected on id. But you tried to declare an SQL FK. It fails because, as the error message says, team id is not unique.


(Notice here the distinction commented between teams and their team ids. Because a team id doesn't identify a team, you should only speak of a team identified by a team id and club. OOP must distinguish not only between teams in the world and team ids (some kind of string) in the world but also "team_id" pointer/reference values of a programming language. It was commented that id" is a bad name because a team id alone doesn't identify a team. We could just use the term used in the world.)

But you actually want something stronger than both your player table team_id IND and player_id FK. Ie check for player that team_id is paired in club with a club_id that is paired in contract with a person_id that is person_id.

Such complicated constraints are actually unnecessary if your design involved, say, roster(club_id,team_id,player_id) and didn't have team_id in player and probably didn't have contract either.

No, we cannot "see" from a schema that "person references club through contract". (And that phrase is a poor way of expressing what you mean.) We cannot tell that from the tables and keys. Every table holds rows that satisfy some statement parameterized by its column names; you must give these statements. Eg for player: person [person_id] plays on a team identified by [team_id] & some club. You must also give all business rules that restrict what situations can arise. Eg: A person can be contracted to at most one club. A team belongs to at most one club. A user (nor we) cannot use the database without the statements and you (nor we) cannot determine the constraints without the statements and business rules. Write them out.

philipxy
  • 14,867
  • 6
  • 39
  • 83
  • thank you for the answer, I am fighting a bit with understanding the first paragraph, need more time. Dont want to be pesky, but do you think a suggestion i made in last update could be a good step? – Zavael Jun 03 '14 at 11:01
  • thank you for showing me the diff between CHECK and IND, didnt know about this. I decided to move the team_id from player to contract and know I can promote the IND to FK :) – Zavael Jun 05 '14 at 09:17
  • @Zavael I hope you mean the diffs between FK (=IND+key), FS(=IND+superkey), arbitrary IND and arbitrary constraint & the diffs in SQL between FK for FS, CHECK for IND & CHECK for arbitrary constraint. (And yes your new design is my suggestion but my roster is your contract.) – philipxy Jun 07 '14 at 01:43
0

What should be unique is the team_id column in your team table. Currently what you've done means a teamid can be repeated as long as it goes with different clubids, which is incorrect.

The FK to club takes care already of the 0-n relationship you mention.

Edit: going on with the relationship issue.

Since a team may belong to only 1 club,

  1. the relationship should be represented by a foreign key (club_id pointing at the club table)
  2. the primary key should not include the club_id

Why that? Including the club_id in the primary key would mean that if the team changes club, it would fondamentally change what team it is. Apart from the fact that I don´t see why a team would change a club, if that ever happened, the team would be exactly the same object, perfectly independant from the club.

It would be a terrible conception error to do otherwise.

Sebas
  • 21,192
  • 9
  • 55
  • 109
  • thanks for answer, yes and the club_id is already uniqe and indexed so why is it bad to use it as part of the identifier? Do I get no advantage with this? is it bad if team_id would be repeated as long as it belongs to different clubs? – Zavael Jun 01 '14 at 19:22
  • @Sebas It's perfectly reaonable to have team_id plus club identify teams. What he wants is not incorrect. He's constraining it incorrectly, speicifically by writing a nonsense declaration [sic]. – philipxy Jun 02 '14 at 13:25
  • @philip, I don't agree with you. `team_id` is by definition an ID. IDs are used to identify business objects. He is confused between a 0.n relationship and a n.n relationship. A n.n relationship would indeed have to be reprented by a table with the `team_id` and the `club_id` columns, each being a FK to the respective table [...] – Sebas Jun 03 '14 at 04:15
  • @Zavael it is not *normal*. Could a team belong to different clubs though? In that case you want a n.n relationship represented by another table, apart from the `team` and the `club` tables. – Sebas Jun 03 '14 at 04:16
  • @Sebas His edit 1 says team_id plus club identifies a team. So it does. Just as a player number plus team typically identifies a player. A string out in the world. _12" high on a jersey._ At most you can complain he broke a naming/terminology convention for "id". (As I said above.) – philipxy Jun 03 '14 at 05:29
  • @Sebas (after your edit) My last comment still applies; club+team_id is like club+player_no: a (natural) key. Your "since" is specious: maybe a team has multiple clubs, or multiple ids, maybe shared with other clubs, maybe even on the same club; if club+id gives one team then it's a key. Players get traded; the Astros changed leagues: new things for old keys. You confuse application design with internal design, and whether something is a key with whether we want it (or something else) for some purpose (like, as surrogate identifier and/or permanent identifier). – philipxy Jun 04 '14 at 08:07
  • @philip I disapprove. If team_id is a candidate key, team_id + anything is a mistaken conception. – Sebas Jun 05 '14 at 02:18
  • @Sebas In his code team id is not a declared a candidate key (PK/UNIQUE) of team (id+club_id is) and team_id is not a candidate key of any table. His English makes clear team id and the team_ids are not candidate keys. His attempt to define team team_id as a FK is a _mistate_ not consistent with everything else and is explained in my answer; he's _trying_ to say there is an IND, not FK, and _should_ be declaring a bigger CHECK. As explained in my answer. You cannot infer from his asking for the FK that he wants table id to be a CK/UNIQUE because _that FK request is the mistake he's making_. – philipxy Jun 05 '14 at 02:52
  • @philip I´m sorry, we misunderstand each other and this conversation is going nowhere. – Sebas Jun 05 '14 at 05:16
  • @Sebas thanks for the input, maybe my Englhish is not sufficient to explain me correctly :) the team is strongly coupled with club, it cannot change the club, club is the owner of the team and club can manage its teams, therefore team_id as standalone identification is not needed, from all the discussions i realized the best will be to move the team_id to contract table, thanks again – Zavael Jun 05 '14 at 09:15