I am creating a database of films, where you can insert Films(id, director,the year of publishing-integer...), Directors, Actors(id, fate of birth, date of death-dates)... I have a problem with triggers in it, because in the table actors i want to ensure, that when you you want to add a new actor his year of birth cannot be bigger that the year of film publishing. But with the trigger I have written, i cannot insert any new actor, because the date of birth cannot be compared to the films publishing year, because the film does not exist yet. And i cannot add any new film too, because i have the same trigger on directors-which is not working too, and the film has a foreign mandatory key-id director. It is a bit complicated, so I hope you will get what I mean
create or replace
trigger "XVIKD00"."DATUM_NARODENIA_HEREC"
BEFORE INSERT OR UPDATE
ON HERCI
FOR EACH ROW
DECLARE
l_ROK_VYDANIA filmy.ROK_VYD%TYPE;
BEGIN
SELECT FILMY.ROK_VYD
INTO l_ROK_VYDANIA
FROM FILMY
WHERE FILMY.ID_FILM = :new.ID_HEREC;
IF( (:new.dat_umr_her is not null) and( FILMY.ROK_VYD is not null)
extract( year from :new.dat_nar_her ) > l_ROK_VYDANIA )
THEN
RAISE_APPLICATION_ERROR(-2009,'Dátumy nie su v správnom časovom
slede');
END IF;
END;
You have a circular dependence between your tables, and this way you can't insert any records, because the others don't exist yet.
To handle this in a reasonable fashion you should have:
Movies
id
name
Publishing_Year
Director
id
name
birthDate
Actor
id
name
birthDate
This way, you can always create each one of those, without having any dependencies from the others.
Then when you want to say that some Director directed a Movie you would insert into a table like this:
movie_director
id
movie_id
director_id
When you want to say that some actor stared in a Movie you insert a record in a table like this:
movie_actor
id
movie_id
actor_id
And if you wanted to validate if the director or the actor is old enough to be in said movie, you would put the triggers that validate that in these two tables.
Related
This question already has answers here:
How can you represent inheritance in a database?
(7 answers)
Closed 4 months ago.
I am designing a DVD rental database. There are two types of Users, Members and Staff, who all have a membership card. The MEMBERSHIP_CARD could be my supertype and MEMBERS and STAFF would be my subtypes.
I am trying to implement a limit for renting DVD's. Staff can rent 5 DVD's at a time while Members can only rent 3.
MEMBERSHIP_CARD: Card_id (PK), Fname, Lname, Email, Status
DVD: DVD_id (PK), Title, Duration, Publisher (FK)
RENTALS: Rental_id (PK), Issue_date, Return_date, DVD_id (FK), Card_id (FK)
Which option should I use?
Each MEMBERSHIP_CARD has a maximum loans attribute. But that is redundant--many people have the same value.
When a rental is created a trigger checks if it's for a member or staff and checks how many rentals a member already has. But then I am hard coding these values, which is not flexible.
We add a MEMBERSHIP_TYPE entity with a name and maximum rentals. But I don't know whether this is overkill if there are only 2 membership types.
MEMBERSHIP_TYPE: Membership_type_id (PK), Membership_name, Rental_max
Example row: 1, STAFF, 5.
MEMBERSHIP_CARD adds column Membership_type_id (FK).
I see three tables involved here:
user_type with two rows MEMBER and STAFF, contains the rental limit
users with one row per user, each user is tagged with a user_type
rentals with the users' rentals and a return date, so we see when a rental is ended
Then I'd create a compound trigger to check whether reantal inserts violate a user's rental limit.
CREATE OR REPLACE TRIGGER trg_rental_limit_check
FOR INSERT ON rentals COMPOUND TRIGGER
-- -----------------------------------------------------------
-- DECLARATION SECTION
-- -----------------------------------------------------------
v_user_ids sys.ora_mining_number_nt := sys.ora_mining_number_nt();
-- -----------------------------------------------------------
-- AFTER EACH ROW SECTION
-- -----------------------------------------------------------
AFTER EACH ROW IS
BEGIN
v_user_ids.extend(1);
v_user_ids(v_user_ids.count) := :new.user_id;
END AFTER EACH ROW;
-- -----------------------------------------------------------
-- AFTER STATEMENT SECTION
-- -----------------------------------------------------------
AFTER STATEMENT IS
BEGIN
FOR rec IN
(
select listagg(user_id, ', ') within group (order by user_id) as users
from
(
select user_id
from rentals r
where user_id in (select * from table(v_user_ids))
and return_date is null
group by user_id
having count(*) > (select ut.rental_limit
from users u
join user_type ut on ut.user_type_id = u.user_type_id
where u.user_id = r.user_id
)
)
)
LOOP
RAISE_APPLICATION_ERROR(-20001, 'Too many rentals for user(s) ' || rec.users);
END LOOP;
END AFTER STATEMENT;
END trg_rental_limit_check;
(Of course you can call the users table MEMBERSHIP_CARD and the user_type table MEMBERSHIP_TYPE, if you like that better :-)
As to
But is this overkill if there are only 2 membership types..?
No, this is just the proper way to establish the relation in the database. Even without a rental limit there are two types of users, so the least thing you'd want for this is a flag, and Oracle doesn't even have a boolean data type for this. So, if it were just for MEMBER and STAFF, you could already have a separate table indicating the user type. And well, as there is a rental limit depending on the user type, it would be inappropriate not to have this table.
And the trigger guarantees the desired consistency. You don't want to allow rentals exceeding the limit, and you may want your app to check this and prevent this from happening. With the trigger in place, even a faulty app cannot insert reantals that are violating the rule.
A member_type coumn which defines if as user is staff or not. Also a loan column where you keepo track howmany loan a user has
the rest is a simple CHECK constraint will help keep always track that a user will nocht get more dvd as it is allowed
CREATE TABLE member ( id int, member_type int, loan int
,
CONSTRAINT check_loan
CHECK ((member_type = 1 AND loan <= 5) OR (member_type = 2 AND loan <= 2)))
INSERT INTO member VALUEs (1,1,0)
1 rows affected
UPDATE member SET loan = 2 WHERE id = 1
1 rows affected
UPDATE member SET loan = loan + 4 WHERE id = 1
ORA-02290: check constraint (FIDDLE_IHFVIILOHPJRJZLFVGTY.CHECK_LOAN) violated
fiddle
I am learning SQL (postgres) and am trying to insert a record into a table that references records from two other tables, as foreign keys.
Below is the syntax I am using for creating the tables and records:
-- Create a person table + insert single row
CREATE TABLE person (
pname VARCHAR(255) NOT NULL,
PRIMARY KEY (pname)
);
INSERT INTO person VALUES ('personOne');
-- Create a city table + insert single row
CREATE TABLE city (
cname VARCHAR(255) NOT NULL,
PRIMARY KEY (cname)
);
INSERT INTO city VALUES ('cityOne');
-- Create a employee table w/ForeignKey reference
CREATE TABLE employee (
ename VARCHAR(255) REFERENCES person(pname) NOT NULL,
ecity VARCHAR(255) REFERENCES city(cname) NOT NULL,
PRIMARY KEY(ename, ecity)
);
-- create employee entry referencing existing records
INSERT INTO employee VALUES(
SELECT pname FROM person
WHERE pname='personOne' AND <-- ISSUE
SELECT cname FROM city
WHERE cname='cityOne
);
Notice in the last block of code, where I'm doing an INSERT into the employee table, I don't know how to string together multiple SELECT sub-queries to get both the existing records from the person and city table such that I can create a new employee entry with attributes as such:
ename='personOne'
ecity='cityOne'
The textbook I have for class doesn't dive into sub-queries like this and I can't find any examples similar enough to mine such that I can understand how to adapt them for this use case.
Insight will be much appreciated.
There doesn’t appear to be any obvious relationship between city and person which will make your life hard
The general pattern for turning a select that has two base tables giving info, into an insert is:
INSERT INTO table(column,list,here)
SELECT column,list,here
FROM
a
JOIN b ON a.x = b.y
In your case there isn’t really anything to join on because your one-column tables have no column in common. Provide eg a cityname in Person (because it seems more likely that one city has many person) then you can do
INSERT INTO employee(personname,cityname)
SELECT p.pname, c.cname
FROM
person p
JOIN city c ON p.cityname = c.cname
But even then, the tables are related between themselves and don’t need the third table so it’s perhaps something of an academic exercise only, not something you’d do in the real world
If you just want to mix every person with every city you can do:
INSERT INTO employee(personname,cityname)
SELECT pname, cname
FROM
person p
CROSS JOIN city c
But be warned, two people and two cities will cause 4 rows to be inserted, and so on (20 people and 40 cities, 800 rows. Fairly useless imho)
However, I trust that the general pattern shown first will suffice for your learning; write a SELECT that shows the data you want to insert, then simply write INSERT INTO table(columns) above it. The number of columns inserted to must match the number of columns selected. Don’t forget that you can select fixed values if no column from the query has the info (INSERT INTO X(p,c,age) SELECT personname, cityname, 23 FROM ...)
The following will work for you:
INSERT INTO employee
SELECT pname, cname FROM person, city
WHERE pname='personOne' AND cname='cityOne';
This is a cross join producing a cartesian product of the two tables (since there is nothing to link the two). It reads slightly oddly, given that you could just as easily have inserted the values directly. But I assume this is because it is a learning exercise.
Please note that there is a typo in your create employee. You are missing a comma before the primary key.
My task is to make a table that records the placement won by race car drivers competing in Race events.
The given schema is:
CREATE TABLE RaceEvent
(
Name text,
Year int,
);
CREATE TABLE Driver
(
Name text,
Date_of_birth date,
Gender char,
Nationality,
);
I then added the following constraints :
CREATE TABLE RaceEvent
(
RaceName text NOT NULL PRIMARY KEY,
Year int NOT NULL,
Description text NOT NULL
);
CREATE TABLE Driver
(
Name text NOT NULL,
Date_of_birth date NOT NULL PRIMARY KEY,
Gender char(1) NOT NULL,
Nationality text NOT NULL
);
The table I created looks like this :
CREATE TABLE Races
(
Medal char(6) CHECK (Medal = 'Gold' or Medal = 'Silver' or Medal =
'Bronze'),
Event text NOT NULL REFERENCES RaceEvent (Name),
DriverDOB date NOT NULL REFERENCES Driver (Date_of_birth)
);
I know using the date of birth as a primary key is very silly but for some reason that was part of the task.
I need to ensure a driver cannot gain multiple medals in the same race, can anybody give insight on a good way of doing this? I thought about using some sort of check but can't quite work it out.
After that, I need to write a query that can return the nationalities of drivers that won at least 2 gold medals in certain years, to figure out which nationalities seem to produce the best drivers. 2 versions of the same query, one using aggregation and one not.
I know I have to do something along these lines :
SELECT Nationality from Driver JOIN Races ON Driver.Date_of_Birth = Races.DriverDOB WHERE ....?
Not sure on what the best way of figuring out how to link the nationalities to the medals?
All feedback much appreciated
The "best" way to do it would be to restructure your schema, as right now it's pretty crap. I'm assuming you can't, so here's one way to prevent multiple drivers from gaining multiple medals in the same race: add a primary key on DriverDOB and Event to the Races table.
Try it out here: http://sqlfiddle.com/#!17/dc8a9/1
As for the query to get the nationalities with multiple golds in a given year, here's one way to do it:
SELECT d.nationality, COUNT(*) AS golds
FROM races r
JOIN driver d
ON r.driverdob = d.date_of_birth
JOIN raceevent e
ON r.event = e.racename
AND e.year = 1999
WHERE r.medal = 'Gold'
GROUP BY d.nationality
HAVING COUNT(*) > 1;
Output:
nationality golds
NatA 3
NatB 2
And you can test it here: http://sqlfiddle.com/#!17/dc8a9/9
I know I am sounding dumb but I really need help on this.
I have a Table (let's say Meeting) which Contains a column Participants.
The Participants dataType is varchar(Max) and it stores Participant's Ids in comma separated form like 1,2.
Now my problem is I am passing a parameter called #ParticipantsID in my Stored Procedure and want to do something like this:
Select Participants from Meeting where Participants in (#ParticipantsID)
Unfortunately I am missing something crucial here.
Can some one point that out?
I've been there before... I changed the DB design to have one record contain a single reference to the other table. If you can't change your DB structures and you have to live with this, I found this solution on CodeProject.
New Function
IF EXISTS(SELECT * FROM sysobjects WHERE ID = OBJECT_ID(’UF_CSVToTable’))
DROP FUNCTION UF_CSVToTable
GO
CREATE FUNCTION UF_CSVToTable
(
#psCSString VARCHAR(8000)
)
RETURNS #otTemp TABLE(sID VARCHAR(20))
AS
BEGIN
DECLARE #sTemp VARCHAR(10)
WHILE LEN(#psCSString) > 0
BEGIN
SET #sTemp = LEFT(#psCSString, ISNULL(NULLIF(CHARINDEX(',', #psCSString) - 1, -1),
LEN(#psCSString)))
SET #psCSString = SUBSTRING(#psCSString,ISNULL(NULLIF(CHARINDEX(',', #psCSString), 0),
LEN(#psCSString)) + 1, LEN(#psCSString))
INSERT INTO #otTemp VALUES (#sTemp)
END
RETURN
END
Go
New Sproc
SELECT *
FROM
TblJobs
WHERE
iCategoryID IN (SELECT * FROM UF_CSVToTable(#sCategoryID))
You would not typically organise your SQL database in quite this way. What you are describing are two entities (Meeting & Participant) that have a one-to-many relationship. i.e. a meeting can have zero or more participants. To model this in SQL you would use three tables: a meeting table, a participant table and a MeetingParticipant table. The MeetingParticipant table holds the links between meetings & participants. So, you might have something like this (excuse any sql syntax errors)
create table Meeting
(
MeetingID int,
Name varchar(50),
Location varchar(100)
)
create table Participant
(
ParticipantID int,
FirstName varchar(50),
LastName varchar(50)
)
create table MeetingParticipant
(
MeetingID int,
ParticipantID int
)
To populate these tables you would first create some Participants:
insert into Participant(ParticipantID, FirstName, LastName) values(1, 'Tom', 'Jones')
insert into Participant(ParticipantID, FirstName, LastName) values(2, 'Dick', 'Smith')
insert into Participant(ParticipantID, FirstName, LastName) values(3, 'Harry', 'Windsor')
and create a Meeting or two
insert into Meeting(MeetingID, Name, Location) values(10, 'SQL Training', 'Room 1')
insert into Meeting(MeetingID, Name, Location) values(11, 'SQL Training', 'Room 2')
and now add some participants to the meetings
insert into MeetingParticipant(MeetingID, ParticipantID) values(10, 1)
insert into MeetingParticipant(MeetingID, ParticipantID) values(10, 2)
insert into MeetingParticipant(MeetingID, ParticipantID) values(11, 2)
insert into MeetingParticipant(MeetingID, ParticipantID) values(11, 3)
Now you can select all the meetings and the participants for each meeting with
select m.MeetingID, p.ParticipantID, m.Location, p.FirstName, p.LastName
from Meeting m
join MeetingParticipant mp on m.MeetingID=mp.MeetingID
join Participant p on mp.ParticipantID=p.ParticipantID
the above should produce
MeetingID ParticipantID Location FirstName LastName
10 1 Room 1 Tom Jones
10 2 Room 1 Dick Smith
11 2 Room 2 Dick Smith
11 3 Room 2 Harry Windsor
If you want to find out all the meetings that "Dick Smith" is in you would write something like this
select m.MeetingID, m.Location
from Meeting m join MeetingParticipant mp on m.MeetingID=mp.ParticipantID
where
mp.ParticipantID=2
and get
MeetingID Location
10 Room 1
11 Room 2
I have omitted important things like indexes, primary keys and missing attributes such as meeting dates, but it is clearer without all the goo.
Your table is not normalized. If you want to query for individual participants, they should be split into their own table, along the lines of:
Meeting
MeetingId primary key
Other stuff
Persons
PersonId primary key
Other stuff
Participants
MeetingId foreign key Meeting(MeetingId)
PersonId foreign key Persons(PersonId)
primary key MeetingId,PersonId
Otherwise, you have to resort to all sorts of trickery (what I call SQL gymnastics) to find out what you want. That trickery never scales well - your queries become slow very quickly as the table grows.
With a properly normalized database, the queries can remain fast well into the multi-millions of records (I work with DB2/z where we are used to truly huge tables).
There are valid reasons for sometimes reverting to second normal form (or even first) for performance but that should be a very hard thought out decision (and based on actual performance data). All databases should initially start of in 3NF.
SELECT * FROM Meeting WHERE Participants LIKE '%,12,%' OR Participants LIKE '12,%' OR Participants LIKE '%,12'
where 12 is the ID you are looking for....
Ugly, what a nasty model.
If I understand your question correctly, you are trying to pass in a comma separated list of participant ids and see if it is in your list. This link lists several ways to do such a thing"
[http://vyaskn.tripod.com/passing_arrays_to_stored_procedures.htm][1]
codezy.blogspot.com
If you store the participant ids in a comma-separated list (as text) in the database, you cannot easily query it (as a list) using SQL. You would have to resort to string-operations.
You should consider changing your schema to use another table to map meetings to participants:
create table meeting_participants (
meeting_id integer not null , -- foreign key
participant_id integer not null
);
That table would have multiple rows per meeting (one for each participant).
You can then query that table for individual participants, or number of participants, and such.
If participants is a separate data type you should be storing it as a child table of your meeting table. e.g.
MEETING
PARTICIPANT 1
PARTICIPANT 2
PARTICIPANT 3
Each participant would hold the meeting ID so you can do a query
SELECT * FROM participants WHERE meeting_id = 1
However, if you must store a comma separated list (for some external reason) then you can do a string search to find the appropriate record. This would be a very inefficient way to do a query though.
That is not the best way to store the information you have.
If it is all you have got then you need to be doing a contains (not an IN). The best answer is to have another table that links Participants to Meetings.
Try SELECT Meeting, Participants FROM Meeting CONTAINS(Participants, #ParticipantId)
Hypothetical example:
I have Cars and Owners. Each Car belongs to one (and only one) Owner at a given time, but ownership may be transferred. Owners may, at any time, own zero or more cars. What I want is to store the historical relationships in a MySQL database such that, given an arbitrary time, I can look up the current assignment of Cars to Owners.
I.e. At time X (where X can be now or anytime in the past):
Who owns car Y?
Which cars (if any) does owner Z own?
Creating an M:N table in SQL (with a timestamp) is simple enough, but I'd like to avoid a correlated sub-query as this table will get large (and, hence, performance will suffer). Any ideas? I have a feeling that there's a way to do this by JOINing such a table with itself, but I'm not terribly experienced with databases.
UPDATE: I would like to avoid using both a "start_date" and "end_date" field per row as this would necessitate a (potentially) expensive look-up each time a new row is inserted. (Also, it's redundant).
Make a third table called CarOwners with a field for carid, ownerid and start_date and end_date.
When a car is bought fill in the first three and check the table to make sure no one else is listed as the owner. If there is then update the record with that data as the end_date.
To find current owner:
select carid, ownerid from CarOwner where end_date is null
To find owner at a point in time:
select carid, ownerid from CarOwner where start_date < getdate()
and end_date > getdate()
getdate() is MS SQL Server specific, but every database has some function that returns the current date - just substitute.
Of course if you also want additional info from the other tables, you would join to them as well.
select co.carid, co.ownerid, o.owner_name, c.make, c.Model, c.year
from CarOwner co
JOIN Car c on co.carid = c.carid
JOIN Owner o on o.ownerid = co.ownerid
where co.end_date is null
I've found that the best way to handle this sort of requirement is to just maintain a log of VehicleEvents, one of which would be ChangeOwner. In practice, you can derive the answers to all the questions posed here - at least as accurately as you are collecting the events.
Each record would have a timestamp indicating when the event occurred.
One benefit of doing it this way is that the minimum amount of data can be added in each event, but the information about the Vehicle can accumulate and evolve.
Also, with the timestamp, events can be added after the fact (as long as the timestamp accurately reflects when the event occurred.
Trying to maintain historical state for something like this in any other way I've tried leads to madness. (Maybe I'm still recovering. :D)
BTW, the distinguishing characteristic here is probably that it's a Time Series or Event Log, not that it's 1:m.
Given your business rule that each car belongs to at least one owner (ie. owners exist before they are assigned to a a car) and your operational constraint that the table may grow large, I'd design the schema as follows:
(generic sql 92 syntax:)
CREATE TABLE Cars
(
CarID integer not null default autoincrement,
OwnerID integer not null,
CarDescription varchar(100) not null,
CreatedOn timestamp not null default current timestamp,
Primary key (CarID),
FOREIGN KEY (OwnerID ) REFERENCES Owners(OwnerID )
)
CREATE TABLE Owners
(
OwnerID integer not null default autoincrement,
OwnerName varchar(100) not null,
Primary key(OwnerID )
)
CREATE TABLE HistoricalCarOwners
(
CarID integer not null,
OwnerID integer not null,
OwnedFrom timestamp null,
Owneduntil timestamp null,
primary key (cardid, ownerid),
FOREIGN KEY (OwnerID ) REFERENCES Owners(OwnerID ),
FOREIGN KEY (CarID ) REFERENCES Cars(CarID )
)
I personally would not touch the third table from my client application but would simply let the database do the work - and maintain data integrity - with ON UPDATE AND ON DELETE triggers on the Cars table to populate the HistoricalCarOwners table whenever a car changes owners (i.e whenever an UPDATE is committed on the OwnerId column) or a car is deleted.
With the above schema, selecting the current car owner is trivial and selecting historical car owners is a simple as
select ownerid, ownername from owners o inner join historicalcarowners hco
on hco.ownerid = o.ownerid
where hco.carid = :arg_id and
:arg_timestamp between ownedfrom and owneduntil
order by ...
HTH, Vince
If you really do not want to have a start and end date you can use just a single date and do a query like the following.
SELECT * FROM CarOwner co
WHERE co.CarId = #CarId
AND co.TransferDate <= #AsOfDate
AND NOT EXISTS (SELECT * FROM CarOwner co2
WHERE co2.CarId = #CarId
AND co2.TransferDate <= #AsOfDate
AND co2.TransferDate > co.Transferdate)
or a slight variation
SELECT * FROM Car ca
JOIN CarOwner co ON ca.Id = co.CarId
AND co.TransferDate = (SELECT MAX(TransferDate)
FROM CarOwner WHERE CarId = #CarId
AND TransferDate < #AsOfDate)
WHERE co.CarId = #CarId
These solution are functionally equivalent to Javier's suggestion but depending on the database you are using one solution may be faster than the other.
However, depending on your read versus write ratio you may find the performance better if you redundantly update the end date in the associative entity.
Why not have a transaction table? Which would contain the car ID, the FROM owner, the TO owner and the date the transaction occcured.
Then all you do is find the first transaction for a car before the desired date.
To find cars owned by Owner 253 on March 1st:
SELECT * FROM transactions WHERE ownerToId = 253 AND date > '2009-03-01'
cars table can have an id called ownerID, YOu can then simply
1.select car from cars inner join owners on car.ownerid=owner.ownerid where ownerid=y
2.select car from cars where owner=z
Not the exact syntax but simple pseudo code.