Ideal practice for implementing a 1:1 relationship - database

In a webshop, there are two (relevant to this question) tables: UserSnapshot and Purchase. Upon making a purchase, the user's current information is snapshot so that the purchase records are intact even if the user is later removed or changed. This gives a 1:1 relationship, where each purchase has only one user snapshot, and each user snapshot has only one purchase.
My question is, how should I implement this? Should I have a foreign key pointing to the user snapshot in the purchase table, the other way around, or should I use both (redundant)? Should I combine the two (messy)? Serialise the user snapshot (does not obey 'one value per field')?

I'd suggest looking at the likely queries you want to run, and design your model on that basis.
For instance, I guess you want to know "which orders has this customer placed?". The most natural way of expressing that would be something like:
select *
from customer c
inner join customer_snapshot cs
on c.customer_id = cs.customer_id
inner join orders o
on cs.order_id = o.order_id
where c.customer_id = ?
Or: "What is the current status of the customer who placed this order?".
select *
from order o
inner join customer_snapshot cs
on o.order_id = cs.order_id
inner join customer c
on cs.customer_id = c.customer_id
where o.order_id = ?
This feels natural to me, as it almost uses the customer_snapshot table as a "many to many" joining table.
But that's mostly stylistic - the join could just as easily be on o.customer_snapshot_id = cs.customer_snapshot_id.
How about "how many orders were sent to customers living in city x?"
select *
from order o
inner join customer_snapshot cs
on o.order_id = cs.order_id
inner join customer c
on cs.customer_id = c.customer_id
and cs.city = ?
You don't need "redundant" columns - all queries work without jumping through hoops. You could serialize the snapshot data, but then the "which orders were for customers living in city x" query would be painful.

Related

DB Schema Taxonomy for species : conditional joins or redesign?

I need some advice to design a DB Schema. I am working on a project where I need to classify species.
All species belong to a gender, a family, an order, a class, a branch and finaly a kingdom.
But some of them have a subBranch between class and branch. I first gave the species entity a FK pointing to each single taxonomy.
Then I thought about only giving the species entity the "gender FK" and go all the way up from there to get its full taxonomy. It seemed to work but I realised I could not retreive the subBranch for species concerned by it.
In the class entity I have two FK, one for subBranch and one for branch. Depending on the species, the branchId FK exists in the class entity (and then the subBranch FK is null) leading to Branch and then Kingdom. Or the subBranch FK exists and leads to the SubBranch then from there to Branch and finally kindgom.
In SQL, I have something like this for the species view (I commented in english where I am stuck):
SELECT
S.*,
G.LatinName as 'GenderLatinName',
G.Name as 'GenderName',
F.LatinName as 'FamilyLatinName',
F.Name as 'FamilyName',
O.LatinName as 'OrderLatinName',
O.Name as 'OrderName',
C.LatinName as 'ClassLatinName',
C.Name as 'ClassName',
Sb.LatinName as 'SubBranchLatinName',
Sb.Name as 'SubBranchName',
B.LatinName as 'BranchLatinName',
B.Name as 'BranchName',
K.LatinName as 'KingdomLatinName',
K.Name as 'KingdomName'
from Species S
join Gender G on G.Id = S.GenderId
join Family F on F.Id = G.FamilyId
join [Order] O on O.Id = F.OrderId
join Class C on C.Id = O.ClassId
--if class entity has an existing SubBranchId then join SubBranch to it and then the Branch to the SubBranch
-- if C.SubBranchId is not null
-- then join SubBranch on Sb.BranchId on C.BranchId
-- then join Branch on B.Id on Sb.BranchId
--if class entity has no SubBranchId then straightaway join Branch to it
-- else
-- join Branch on B.Id on C.BranchId
join Branch B on B.Id = C.BranchId
join Kingdom K on K.Id = B.KingdomId
I have seen some questions on conditional joins but I could not get it work. I thought about the UNION ALL but the number of columns vary between the two queries as one has an additional field.
Perhaps the schema design needs to be changed.
How could I do?
Your schema is generally fine, apart from 2 rather minor notes:
In taxonomy, it's usually called "Genus", not "Gender";
I strongly suggest to come up with some other name for the Order table. Trust me, if you will have to write any amount of code worth mentioning against a schema like that, you'll curse the day you chose the table name to be the same as this particular reserved keyword. Orders, Order_, OrderT (from "Taxonomy") - anything will do.
As such, the query should be quite simple:
select s.*,
-- Other columns
isnull(sb.LatinName, '(No sub-branch)') as [SubBranchLatinName],
-- The rest of stuff
from Species S
inner join Genus G on G.Id = S.GenusId
inner join Family F on F.Id = G.FamilyId
inner join OrderT O on O.Id = F.OrderId
inner join Class C on C.Id = O.ClassId
inner join Branch B on B.Id = C.BranchId
left join dbo.SubBranch sb on sb.BranchId = b.Id and sb.Id = c.SubBranchId
inner join Kingdom K on K.Id = B.KingdomId
Left join allows you to bring in the table which might contain no rows that match the condition, without losing these rows in the final output.
I think that having a star schema i.e. SPECIES table contain FKs to each taxonomy table would be faster for selects. This will also remove complications of conditional joins and any other logical "anomalies".
If you want to stick with a chain, then there are two ways:
Conditional join:
Example Below
from Species S
join Gender G on G.Id = S.GenderId
join Family F on F.Id = G.FamilyId
join [Order] O on O.Id = F.OrderId
join Class C on C.Id = O.ClassId
LEFT JOIN SubBranch AS SB ON .....
INNER JOIN Branch AS B ON SB.BranchID = B.Id OR C.BranchID = B.Id
This will be slow.
UNION ALL approach would probably be faster. To work around number of columns differences, you will need to add NULL and/or empty string constants in place of SubBranch columns for the query without SubBranch.
Another way is to add a dummy sub branch records for classes without a sub branch. This way there is always a record in SubBranch table, and you do not need conditional joins. I recommend this solution.

How do I query three related tables?

I'm new to databases and I'm having a hard time figuring this out. Any assistance would be greatly appreciated
Deliveries Table
-- ID (PK)
-- DriverID (FK to Drivers Table)
Drivers Table
-- ID (PK)
-- LocationID (FK to Locations Table)
Locations Table
-- ID (PK)
-- RestaurantID (FK to Restaurants Table)
Restaurant Table
--ID (PK)
A Restaurant can have multiple locations (1 to many). A location can have multiple drivers (1 to many). a Driver can have multiple deliveries (1 to many). This design is supposed to break things out in 3rd normal form. So if I want to go to the deliveries table and get all of the deliveries associated with a particular restaurant, how would I query or do a join for that? Would I have to add a second foreign key to Deliveries that directly references the Restaurant table? I think after I see the query I can figure out what is going on. Thx
You can use left or right outer join to make a combined table and then you can easily query it, or else you can use a query with multiple sub-queries inside it to attain the required result without using join. Here is an example on how to use sub-query for your use-case.
SELECT ID FROM Deliveries De
WHERE De."DriverID" IN (SELECT ID FROM Drivers Dr
WHERE Dr. "LocationID" IN (SELECT ID FROM Locations L
WHERE L. "RestaurantID" IN (SELECT ID FROM Restaurant)))
I hope this solves your issue without using join statement.
You can use inner join or union depending on what you want to achieve. Example:
SELECT a."articleId" AS id, a.title, a."articleImage" AS "articleImage/url", c.category AS "category/public_id", a."createdOn", concat("firstName", ' ', "lastName") AS author
FROM articles a
INNER JOIN users u ON a."userId" = u."userId"
INNER JOIN categories c ON a."categoryId" = c."categoryId"
UNION
SELECT g."gifId" AS id, g.title, g."imageUrl" AS "articleImage/url", g.public_id AS "category/public_id", g."createdOn", concat("firstName", ' ', "lastName") AS author
FROM gifs g
INNER JOIN users u ON g."userId" = u."userId"
ORDER BY "createdOn" DESC
You can say how you want to get the results for more detailed query.
If I understand what you want to do then it maybe like this,
1st you have to join all those table to get corresponding result you want,
the join condition will be
select <your desire column name>
from Restaurant A,Locations B,Drivers C,Deliveries D
where A.ID = B.RestaurantID
and B.ID = C.LocationID
and C.ID = D.DriverID
Hope this is helpful, fell free to say anything.

Joining multiple tables yields duplicates

In order to retrieve all Projects for a UserId, or all in case the user is admin, I want to join multiple tables. I'm using the statment in a TableAdapter query for MSSQL.
SELECT P.ID, P.CountryID, P.ProjectYear, P.Name, P.Objective, P.StartDate, P.EndDate, P.BaseCampaign, P.ManagerID, P.IsClosed, P.OrganisationUnitID, P.QualitativeZiele, P.QuantitativeZiele,
P.Herausforderungen, P.Learnings, P.ObjectiveQuantitativ, P.Remarks, P.ProjectOverallID, C.Name AS CountryName, O.Name AS OEName, R.RoleName
FROM wc_Projects AS P
INNER JOIN wc_OrganisationUnit AS O ON P.OrganisationUnitID = O.ID
INNER JOIN wc_Countries AS C ON P.CountryID = C.ID
INNER JOIN aspnet_Roles AS R ON C.ID = R.CountryID
INNER JOIN aspnet_UsersInRoles AS UR ON R.RoleId = UR.RoleId
WHERE (#ViewAll = 1) OR (UR.UserId = #UserId)
ORDER BY P.CountryID, P.OrganisationUnitID, P.ProjectYear DESC
In order to apply to the rather static approach for the table adapter, I start with the project.
Get all projects, resolve CountryName and OEName via FK's. Now look if you can find the role that is assoicated to the country. Then find the user that is attached to the role.
I know that this is a terrible query, but it's the only one somewhat applicable to the WebForms TableAdapter way to deal with it.
When I have a UserId that has one or multiple roles associated with countries it works. When a admin user, that has no roles with countries associated but ViewAll = 1 it breaks. I get constraint exceptions and the amount of results nearly tripple.
I tried rewriting the query, adding paranthesis and different joins. But none of it worked. How can I solve this?

Relational Algebra union, join and intersect

I'm studying computer science and am brushing up on database systems. I'm having difficulties grasping certain parts.
Say I have the following relations:
Lecturers(LecturerID, Name, DeptID)
Course(DeptID, CrsCode, CrsName, Description)
I note that they both share a common attribute, DeptID, therefore they are union-compatible.
How would I go about listing all courses that are taught by lecturers belonging to computer science dept (CS) or electronic engineering dept (eEng)?
My answer would be using intersection with selection. Would the following be correct or near the mark?
πDeptID,CrsName(Course) intersection πDeptID,Name(σDeptID = CS or DeptID = eEng(Lecturers))
I'm sure join could be used here, but I'm unsure how to use the predicate with it.
Thanks for your help. Once I understand what to use in a few situations I'm sure the rest will be easier.
Thanks for any help.
I would use a simple INNER JOIN for this.
SELECT DEPTID, CRSNAME
FROM COURSE A
INNER JOIN LECTURERS B on A.DEPTID=B.DEPTID
WHERE B.DEPTID='eENG' or B.DEPTID='CS'
There must be also a table for Departments, as you have a refference to the DeptID field, which should be an INT. I assume it is DEPARTMENTS with DeptID and Code as fields. In this case:
SELECT
*
FROM
Course C
INNER JOIN
LECTURERS L on C.DeptId = L.DeptID
INNER JOIN
Departments D on C.DeptID = D.DeptID
WHERE
D.code = 'eENG' or D.code = 'CS'

Database Schema question

I am having a difficult time deciding how to handle a business requirement in my database schema. I have a lot of tables in the database, but there are only three I need to deal with for this problem: Courses, PersonnelCourses, and Personnel.
Courses is a list of Courses
Personel
is a list of Personnel
PersonnelCourses is a list of Courses
that Personnel have taken.
In courses there is a column called Universal. If a course is universal, that means all Personnel must take that course.
I need to generate a list of all the universal courses that Personnel must take, but the only way I am able to generate this list is with a cross join / cartesian join:
select P.LastName, C.Name
from Courses C, Personnel P
where Universal = 1
From that I want to do a left join onto PersonnelCourses so that I can have a list of all the Personnel and the Courses they must take as well as the courses they have taken. I'm thinking this would all be easier if there was a many to many table between Personnel and Courses. But if all Personnel are going to be in this middle table anyway, isn't that a bit redundant?
Is there a better way to handle this?
Much appreciated,
-Matt
There is a list of courses that everybody has to take. Why not just take this list and work with it, instead of repeating the same list for every personnel row? I don't understand why you are trying to multiply your result set.
Isn't your PersonnelCourses establishing a many to many relationship between a Persons and Courses? If it isn't then I am not sure, If it is then...
select *
from Personnel_Courses
inner join Person on... /*get the Person details*/
inner join Courses on... /*get the Course details*/
where Course.Universal = 1 and Person.Id = #Id
would tell you what universal courses they have taken...
and then
select *
from Courses
where Courses.Universal = 1 and Course.Id not in (
select Course.Id from Personnel_Courses
inner join Person on... /*get the Person details*/
inner join Courses on... /*get the Course details*/
where Course.Universal = 1 and Person.Id = #Id
)
Would give you the universal courses that they haven't taken...
To me it might be easier to do the 2nd in your code (Get the first query results, Do a select from the Course table to get all the universal and then do a comparison...)
This is a topic Database Normalization that books have been written on
and part of why you want to do this is DRY or don't repeat yourself.
So to answer your question about a better way - I would answer no.
How about something like this (using the existing structure)?
SELECT P.LastName, C.Name, 1 as Taken
FROM Courses C
INNER JOIN PersonnelCourses PC ON (C.CourseID=PC.CourseID)
INNER JOIN Personnel P ON (P.PersonID=PC.PersonID)
WHERE(C.Universal = 1)
UNION
SELECTP.LastName, C.name, 0 as Taken
FROM Courses C, Personnel P
WHERE (Universal = 1) and
NOT EXISTS(SELECT * FROM Courses C2
INNER JOIN PersonnelCourses PC2 ON (C.CourseID=PC.CourseID)
INNER JOIN Personnel P2 ON (P.PersonID=PC.PersonID)
WHERE (Universal = 1) and
(PC2.CourseID=C.CourseID) and
(P2.PersonID=PC2.PersonID)
)
Universal is a two-value (boolean) attribute of Course, right? In that case, consider normalizing further. Redesign so that UniversalCourse is a table, not a column on Course. That table would have a single column referencing the course. To find all universal courses, simply select everything from this table. Now you can shorten your cartesian join considerably since you have to multiply Personnel only by the UniversalCourse table, having eliminated the where Universal = 1 clause.
All personnel will not be in the personnelcourses table, only personnel who have taken courses.
I think you design is fine. You just need to tweak your query to get what your after.
In a subquery pull the courses the personnel have taken. Then in an outer query select all the courses that the personnel must take and do a left outer join with the subquery.
Select a.CourseName, b.PersonName from Courses a,
(select P.LastName, C.Name from Courses C, Personnel P, PersonnelCourses pc
c.courseid = pc.courseid and
p.personnelid = pc.personnelid and
c.Universal = 1) b
where
a.courseid += b.courseid order by courseid
It would probably be best to filter by personnel, if this is for a report. That way you would see all of the courses required including the ones taken per person.

Resources