Why did the database administrator leave his wife?
She had one-to-many relationships.

—unknown author

One-to-many relationships

In the next chapter, we introduce SQL queries that span multiple tables using JOINS. To practice, you will need a schema that consists of multiple tables. This chapter introduces you to a small schema with a one-to-many relationship between two tables. In one of the following lessons on modeling the theory behind this practical chapter is fully detailed. Here you will only find a brief introduction.

Two tables

In the previous chapters, the various components of a SELECT statement were worked out. As a basis, we used a table that was the translation of this conceptual model:

Conceptueel model van een OPO

Separate tables

This schema has one entity type with a number of attributes. One of those attributes is the lecturer code of the coordinator of the OPO. After all, each OPO has a unique coordinator. It makes sense to add a second entity type ‘Lecturer’ to the schema. After all, we have a lot of information about lecturers. This entity type contains information about all lecturers, not just the coordinators of an OPO. A naive attempt is the following figure:

Twee onafhankelijke entiteittypes: een slecht idee!

If you translate this to PostgreSQL you get two separate tables. However, this structure is a source of problems. We mention two possible errors:

Both operations will cause the database to contain errors. You can then no longer look up info from some courses about the coordinator of the OPO. This is not a good solution. You want your RDBMS (‘Relational DataBase Management System’) to protect you against operations that introduce errors into the schema.

Linked tables

The problem, of course, is that the two tables cannot exist separately. There is a relationship between the two. The conceptual model in the figure below makes this relationship visible:

tussen beide entiteittypes bestaat een relatie

From right to left you read the relationship as ‘is coordinator of’, from left to right it could then become something in the style of ‘has as coordinator’. Each course has at least one and at most one (i.e. just one) coordinator. Not every lecturer is coordinator of an course, but a lecturer could possibly be coordinator of several courses. The cardinality on lecturer's side is thus (0, N). This is an example of a one-to-many relationship.

Note that the attribute ‘Coordinator’ from an earlier figure is omitted. Indeed, it has been replaced by a relationship with a new entity type ‘Lecturer’ because we feel the need to do more than just keep track of a lecturer's number.

Translation to relational model

Now how do you translate this one-to-many relationship into a relational model? After all, PostgreSQL is an example of a relational database. If we translate the conceptual model from the previous section to a logical model, that logical model will have to be a relational model.

You can study all about logical models in detail in the chapter ‘Logical data model’. The sole purpose of this section is to briefly introduce you to this topic so that we can move on to the next part in our exploration of the SQL language (JOIN). The logical model looks like this :

Logisch model voor deze relatie

For now, it's enough that you can read this diagram, and it's not that difficult:

This logical model is finally converted to a physical model, where we specify further information that depends on the specific properties of the chosen RDBMS (in our case PostgreSQL). For example, an example would be the data types of the columns.

The physical model was drawn with DBSchema. We will cover this in detail later. This physical model corresponds to the logical model, in a slightly different notation (notice the blue departing arrow indicating that this field is an FK referring to the yellow-brown arriving arrow at lecturer_id). Also, each field has been given a data type (char, date, ...):

fysiek model met twee tabellen en één-op-veel relatie

Adding this one-to-many relationship in the database is done as follows:

  1. First create the table ‘Lecturer’.
  2. Search for the primary key of the ‘Lecturer’ table. This primary key is the ‘lecturer_id’ field. The data type of this primary key is a char(8), because a lecturer code always begins with a ‘u’ followed by seven digits. Note that we are actually using the designations ‘field’ and ‘column’ interchangeably.
  3. In the table ‘Course’ a column should now be created with exactly the same data type as the data type of the primary key in the lecturer table, so also a char(8). We call this ‘transferring the primary key’ and refer to it in the course table as an ‘foreign key’.
  4. Now link these two columns using a constraint (constraint).
  5. Then first enter the data in table ‘Lecturer’.
  6. After that you can fill the table ‘Course’ with rows.

Realization in your own schema

From here, you need to roll up your sleeves and effectively create the two tables . After all, we want you to soon be able to add and remove information in your own tables and that won't be possible if we let everyone work in the same table. So start pgAdmin and execute the following steps.

If you follow the roadmap below (based on the steps in previous section) you get two linked tables filled with data. We'll make some small exercises on this schema. In the next chapter on JOIN we will use this schema as a basis.

  1. The following steps assume that everyone has their own schema with the name ‘rxxxxxxx’, using of course your own r-number. The easiest way is for you to use the pooling connection at port 62324.
  2. Check your schema to see if there may already be a table ‘Course’ or ‘Lecturer’ present. If so, remove this table with a DROP TABLE ... in a query tool. Remember: either use for each table name first the name of the schema, followed by a period and then the table name, or you define your search_path properly.
  3. Download the file ‘lecturer-course-SQL.txt’. The next steps use pieces of this SQL code to build everything and populate it with data. Each time, you can copy a piece from this text document, paste it into your query tool and execute.
  4. Create the table ‘Lecturer’ by executing the CREATE code for this table from the file you just downloaded. Nothing new to see here: two fields not required, some different data types, a primary key, ...
  5. Now create the table ‘Course’ by copy/paste from the txt file. This code needs a little bit of explanation:
    CREATE TABLE rxxxxxxx.course (
      code char(6) NOT NULL ,
    
      ...
    
      coordinator char(8) NOT NULL ,
      CONSTRAINT pk_course_code PRIMARY KEY ( code ),
      CONSTRAINT fk_course_lecturer FOREIGN KEY ( coordinator )
        REFERENCES rxxxxxxx.lecturer( lecturer_id )
    );

    This last CONSTRAINT is new. Loosely translated, this constraint says the following: “the ‘coordinator’ column of this table refers to the primary key ‘lecturer_id’ of the table ‘Lecturer’ and is thus a foreign key”. It is a good idea to use for each CONSTRAINT a sensible name. In this case: ‘fk’ (for ‘foreign key’) followed by the two names of the two tables involved. The tool DBSchema that we will use later for this, uses this naming scheme automatically.

  6. Think logically about the correct order of filling in the data in the two tables. The ‘Course’ table depends on the ‘Lecturer’ table. You can only add a coordinator for a course if this person already exists in the ‘Lecturer’ table. So first fill the lecturer table by a copy/paste operation of the INSERT statements from the txt file.
    INSERT INTO rxxxxxxx.lecturer( lecturer_id, last_name, first_name, email, street, house_number,
      municipality, birthdate, start_date, end_date, sex ) VALUES ( 'u0042352', 'Adriaens',
      'Gerben', 'gerben.adriaens@ucll.be', 'Ankerstraat', '12', '1330 Rixensart',
      '1984-02-16', '2017-09-01', null, 'M');
    INSERT INTO rxxxxxxx.lecturer( lecturer_id, last_name, first_name, email, street, house_number,
      municipality, birthdate, start_date, end_date, sex ) VALUES ( 'u0057764', 'Bogers',
      'Goedele', 'goedele.bogers@ucll.be', 'Elzasgang', null, '2830 Willebroek',
      '1979-09-09', '2006-11-20', null, 'V');
    INSERT INTO ...
  7. Finally, now that the ‘Lecturer’ table is populated, you can also populate the ‘Course’ table with data. This time we'll do it with a CSV list. Copy these lines from the list and put them in a new .csv file. Import this .csv file like (see section ‘CSV file’):
    code,credits,name,start_date,end_date,language,semester,coordinator
    MBI71A,3,Probleemoplossend denken,2016-09-15,2022-09-14,nl,1,u0012047
    MBI65X,4,Webontwikkeling 1,2017-09-15,2022-09-14,nl,1,u0012047
    MBI66X,4,Webontwikkeling 2,2017-09-15,2022-09-14,nl,2,u0015529
    MBI20X,3,MobieleToepassingen,2018-09-15,2023-09-14,nl,5,u0057764
    MBI26A,6,Computersystemen,2013-09-15,2022-09-14,nl,6,u0041234
    ...
  8. Done! Run a SELECT * FROM ... on both tables to view the data.

Response of the RDBMS to errors

Now make the following small exercises. Sometimes the purpose of an exercise is to let you make an error so that you learn to properly interpret error messages from our RDBMS.

Add a new lecturer: ‘Anouk De Ridder’ with u-number ‘u0012047’. She lives at ‘Kerkstraat 23, 3053 Haasrode’, was born on 5 May 1994 and joins UCLL today. Mail addresses at UCLL are simple: firstname.lastname@ucll.be. Explain what happens when you run an INSERT with the data above.

You get an error message that a lecturer with this lecturer code already exists. Since the lecturer code is the primary key (and thus unique) the RDBMS returns an error message and the INSERT is not executed. Again, you'll notice that it's useful to use a meaningful name for your CONSTRAINT: something is wrong with primary key ‘lecturer_id’ in the table ‘Lecturer’:

ERROR: duplicate key value violates unique constraint "pk_lecturer_lecturer_id"
DETAIL: Key (lecturer_id)="(u0012047)" already exists.
SQL state: 23505

Stupid typo of course above. The u-number of Anouk is not ‘u0012047’ but rather ‘u0099999’. Now add this lecturer to the table ‘Lecturer’.

No problem this time if your query was something in this style:

INSERT INTO rxxxxxxx.lecturer( lecturer_id, last_name, first_name, email, street, house_number,
  municipality, birthdate, start_date, end_date, sex ) VALUES ( 'u0099999', 'De Ridder',
  'Anouk', 'anouk.deridder@ucll.be', 'Kerkstraat', '23', '3053 Haasrode',
  '1994-07-05', '2022-10-15', null, 'V');

The server replies that one row was successfully added:

INSERT 0 1

Query returned successfully in 85 msec.

The same day she is added to the database, Anouk De Ridder decides that she does not want to become a lecturer after all. Remove her from the table ‘Lecturer’.

DELETE
FROM rxxxxxxx.lecturer
WHERE lecturer_id = 'u0099999';

You can also use the following code, but then you delete everyone with this name. Who knows, maybe there is more than one person with the name ‘Anouk De Ridder’ and then these persons will also be removed from the table:

DELETE
FROM rxxxxxxx.lecturer
WHERE last_name = 'De Ridder' AND first_name = 'Anouk'; -- dangerous!

The server replies that one row was successfully deleted:

DELETE 1

Query returned successfully in 53 msec

Remove ‘Bram De Smet’ of Leuven from the ‘Lecturer’ table. Explain what happens.

With this code you delete the row:

DELETE
FROM rxxxxxxx.lecturer
WHERE first_name = 'Bram' AND last_name = 'De Smet' AND municipality LIKE '%Leuven';

However, the server gives this error message:

ERROR: update or delete on table "lecturer" violates foreign key constraint
  "fk_course_lecturer" on table "course"
DETAIL: Key (lecturer_id)=(u0012047) is still referenced from table "course".
SQL state: 23503

You read this error message as follows: “The table ‘Course’ contains at least one row that refers to this lecturer. This lecturer is thus coordinator of a course. Therefore, it should not be removed.”. In the case of Anouk De Ridder, there was no problem because her u-number does not appear anywhere in the course table and so she could safely be removed from that table.

Finally, add a new course: ‘Trees and graphs’ with code ‘MBI39A’. This course was given in the second semester, stopped this year and ran from September 15, 2017 to September 14, 2022. The course was given in Dutch and had 3 credits. Coordinator of the course was the lecturer with u number ‘u0099998’. Write the INSERT, execute and interpret the result.

With this code, you do the INSERT:

INSERT INTO rxxxxxxx.course( code, credits, name, start_date,
  end_date, language, semester, coordinator ) VALUES ( 'MBI39A', 3, 'Trees and graphs',
  '2017-09-15', '2022-09-14', 'nl', 2, 'u0099998');

The database server refuses to execute this INSERT and returns the following error message:

ERROR: insert or update on table "course" violates
  foreign key constraint "fk_course_lecturer"
DETAIL: Key (coordinator)=(u0099998) is not present in table "lecturer".
SQL state: 23503

Since no lecturer with this lecturer code is already present in the ‘Lecturer’ table, you cannot add a new course with this lecturer as coordinator. Only after you have added this lecturer in the ‘Lecturer’ table, can you add the course.