ForeignKey Referencing Same Table - mysql

There was an interview test in which below was the table and structure
Table Person = id, name, dob, dod, mother_id, father_id
Primary Key (id)
Foreign Key mother_id references Person
Foreign Key father_id references Person
and it was asked
"select all who are mothers"
"select those child who are children of 'John Smith' and 'Jane'
and I was puzzled because I was assuming that foreign key would be linked with some other table as usual. But at that point I failed. do some one know the actual answer and reason?

This kind of data structure is called a "Self Referencing Table"
SELECT DISTINCT mothers.*
FROM person
inner join person mothers on person.mother_id = mothers.id
and
SELECT person.*
FROM person
inner join person fathers on person.father_id = fathers.id
inner join person mothers on person.mother_id = mothers.id
WHERE
fathers.name='john smith'
and
mothers.name='jane'

You can always have foreign key that link to the same table.. There is not problem in that..
See, suppose you are storing a person record in the table, now that person would be having mother and father. And mother and father both are themselves a person.. so, they are itself a record of the same table..
Suppose you have following two records in Person table: -
id name age mother_id
1 xyz 24 5
5 abc 57 6
So, from the above table you see that, person with id = 5 is actually mother of person with id = 1.. So, it is a foreign key referencing the same table..
So, here rather than performing a join operation with a different table, you have to perform join with the same table..
SELECT p2.id FROM
Person p1 join Person p2
WHERE p1.mother_id = p2.id
This query will select the record of mother of your current record..

I was assuming that foreign key would be linked with some other table
as usual.
It still is - it references other records in the same Person table.
-- All Mothers
SELECT mom.name
FROM Person mom
WHERE EXISTS (SELECT 1 FROM Person WHERE mother_id = mom.id);
-- Children of John Smith and Jane
SELECT kid.name
FROM Person kid
INNER JOIN Person mom on kid.mother_id = mom.id
INNER JOIN Person dad on kid.father_id = dad.id
WHERE mom.name = 'Jane' AND
dad.name = 'John Smith';
Have a look at this SQL fiddle here

SELECT *
FROM Person
WHERE father_id = (SELECT id FROM Person WHERE name = 'John Smith')
AND mother_id = (SELECT id FROM Person WHERE name = 'Jane')

The answer was already given by podiluska, just explaining how it works since it looks like you're new to MySql.
By giving an Alias to a table(like mother or father to the table person) you do something like a pseudo-table, that the MySql interprets as another table, so the join happens normally, just imagine there are 3 tables now. Person, Father and Mother, and they are all linked together by the join.

Related

Filtering Dates in mySQL

Just started learning SQL and need help on the Feb 1, 2020 part.
The database has three tables for tracking horse-riding lessons:
Horse with columns:
ID - primary key
RegisteredName
Breed
Height
BirthDate
Student with columns:
ID - primary key
FirstName
LastName
Street
City
State
Zip
Phone
EmailAddress
Lesson Schedule with columns:
HorseID - partial primary key, foreign key references Horse(ID)
StudentID - foreign key references Student(ID)
LessonDateTime - partial primary key
Write a SELECT statement to create a lesson schedule for Feb 1, 2020 with the lesson date/time, student's first and last names, and the horse's registered name. Order the results in ascending order by lesson date/time, then by the horse's registered name. Make sure unassigned lesson times (student ID is NULL) appear in the results.
Hint: Perform a join on the LessonSchedule, Student, and Horse tables, matching the student IDs and horse IDs.
This is what I have; not sure of the correct way to go about it. I've tried using WHERE and AND but get an error or only all NULL first/last names in the table.
SELECT LessonDateTime, FirstName, LastName, RegisteredName
FROM LessonSchedule
LEFT JOIN Student ON LessonSchedule.StudentID = Student.ID
INNER JOIN Horse ON LessonSchedule.HorseID = Horse.ID
ORDER BY LessonDateTime, RegisteredName;
Please try this.
I think it will work.
SELECT LessonDateTime, FirstName, LastName, RegisteredName
FROM LessonSchedule
LEFT JOIN Student ON LessonSchedule.StudentID = Student.ID
INNER JOIN Horse ON LessonSchedule.HorseID = Horse.ID
WHERE LessonDateTime = '2020-02-01'
ORDER BY LessonDateTime, RegisteredName;

How to find out results for not matching particular condition in SQL from multiple tables?

I have 3 tables :
Person table stores basic person wise details with ID as primary Key
This person can have relationships (father / mother etc), which are saved in Relationship table, however the users for them are created in Person table (e.g. ID = 2,3 in person table), This way we know that 2,3 are related to user 1 (carry).
We also have 3rd table - address, which store user ID wise addresses.(for both a user and his related persons, who are also users)
I want to find out if an address exists for either a user or for his related users in SQL. How to achieve this ?
You can combine two rules and search on the combined table as below
SELECT * FROM
(
SELECT username,id,Address.Address
FROM Person
INNER JOIN Address ON Person.id = Address.Userid
UNION ALL
SELECT username,id,Address.Address
FROM Person
INNER JOIN Relationship ON Relationship.Relatedid = Person.id
INNER JOIN Address ON Relationship.Userid = Address.Userid
) as RES
WHERE Address = 'xyz road'
Also you can find DBFiddle link to workout
Query:
select p.id,p.username,(case when a.userid is null then 'No' else 'Yes'end) IsAddressAvailable
from Person p
left join Address a on p.id=a.Userid
Output:
id
username
IsAddressAvailable
1
Carry
Yes
2
Carry-Father
No
3
Carry-Mother
Yes
db<fiddle here

Designing a mysql database of relationships between contacts?

I'm trying to design a database of contacts but I also want to keep track of their relationships with other contacts whether it be family, friends, cowokers etc. I created a table for contacts and created an affiliates table that labels the type of relationship but I'm not sure if I did the design correct and I'm unsure if this would just be a normal many to many relationship or some sort of recursive relationship. I would also like to know how I would query to be able to get all the relationships to one contact and all of the relationships to all contacts.
Contacts Table
CID CFirstName CLastName
1 Roy Saldana
2 Linda Rodriguez
3 Hector Rodriguez
Both CID & C_ID are the same I just thought I couldn't name both columns the same so I gave one a _.
Affiliates Table
CID AfiliateType C_ID ex: CID is the mother of C_ID 1
2 mother 1
1 son 2
3 husband 2
3 step-father 1
3 wife 3
SELECT Contacts.FirstName, Contacts.LastName, Afiliates.AfiliateType
FROM Contacts
INNER JOIN Afiliates
ON Contacts.CID = Afiliates.C_ID
I know this isn't the correct way but I can't seem to get passed this part, I'm thinking I need to query the names twice or maybe it's a programming issue not the design, I'm totally lost. Any help would be appreciated. Thanks in advance
-- Contact CID is named FIRST_NAME LAST_NAME.
--
contacts {CID, FIRST_NAME, LAST_NAME}
PK {CID}
AK {FIRST_NAME, LAST_NAME}
-- Contact CID_1 is AFF_TYPE of contact CID_2.
--
affiliates {CID_1, CID_2, AFF_TYPE}
PK {CID_1, CID_2}
FK1 {CID_1} REFERENCES contacts {CID}
FK2 {CID_2} REFERENCES contacts {CID}
CHECK (CID_1 <> CID_2)
Contact FROM_FIRST FROM_LAST is AFF_TYPE of TO_FIRST TO_LAST.
SELECT b.FIRST_NAME AS FROM_FIRST
, b.LAST_NAME AS FROM_LAST
, a.AFF_TYPE
, c.FIRST_NAME AS TO_FIRST
, c.LAST_NAME AS TO_LAST
FROM affiliates AS a
JOIN contacts AS b ON b.cid = a.cid_1
JOIN contacts AS c ON c.cid = a.cid_2
WHERE a.cid_1 = the_contact_id
OR a.cid_2 = the_contact_id ;
One thing to consider is what to do with symmetrical relations, for example
CID_1 is mother of CID_2, is not symmetrical, but CID_1 is sibling of CID_2 is.
For symmetrical relations it is the usual way to insert rows only for CID_1 < CID_2.
Note:
All attributes (columns) NOT NULL
PK = Primary Key
AK = Alternate Key (Unique)
FK = Foreign Key
Yes, you need to query the names twice. Once for the Contact and a second time for their affiliate:
SELECT c.FirstName, c.LastName, a.AfiliateType, ac.FirstName AS AffiliateFirstName, ac.LastName AS AffiliateLastName
FROM Contacts c LEFT JOIN Afiliates a ON c.CID = a.C_ID
LEFT JOIN Contact ac ON a.CID = ac.CID
The key is to set up an alias for the Contacts table to use it a second time as the Affiliate Contact.
Please note, I didn't test this, so I might have made a slight error, but I hope it gives you the idea.

How to select entries WITHOUT matching dependents in same table

What I have is a table containing people. Some people are primary entries and others are secondary (partners, children etc)
The primary entries have a 'cls' of say 3 (where cls is a column name). The secondary entries have different cls, say 4. The secondary entries also have a 'primary' field linking them back to the unique ID of their primary person. (primary field is empty on primary people)
What I want to do is select all primary entries that do not have anyone linking back to them.
Here's where I got to, but it is obviously not right. I figure there is some other form of JOIN that I need? (pp1 is referring to secondaries and pp2 is referring to primaries)
SELECT pp2.per_ID
FROM person pp1 LEFT OUTER JOIN person pp2 ON pp1.primary = pp2.per_ID
WHERE pp1.cls = 4 AND pp2.cls =3
AND pp2.primary IS NULL;
.
TABLE person
COLUMN per_ID, cls, primary
SELECT p.* FROM person as p
LEFT JOIN person AS s
ON p.`primary` = s.per_ID
WHERE s.per_ID IS NULL AND p.cls =3

How do I delete duplicate values with group by

Example database :
ID StudentName StudentClass
1 John A
2 John B
3 Peter A
4 John A
5 John B
I want the result should be
ID StudentName StudentClass
1 John A
2 John B
3 Peter A
Statment
DELETE FROM Student
WHERE ID NOT IN (SELECT *
FROM (SELECT MIN(n.ID)
FROM Student n
GROUP BY n.StudentName) x)
How do I keep John name on class A & B?
DELETE a FROM Student a
LEFT JOIN
(
SELECT MIN(ID) AS minid
FROM Student
GROUP BY StudentName, StudentClass
) b ON a.id = b.minid
WHERE
b.minid IS NULL
A better method to disallow even insertion of such duplicates would be multi-column unique index(it will optimize your searches too). Here is how:
ALTER TABLE `Student`
ADD UNIQUE INDEX `idx` (`StudentName`, `StudentClass`)
You should be able to join Students against itself, with a JOIN predicate that ensures the JOIN matches duplicate students, and delete the join'd row:
DELETE
duplicate_students.*
FROM Students JOIN Students as duplicate_students
ON Students.StudentName = duplicate_students.StudentName
AND Students.StudentClass = duplicate_students.StudentClass
AND duplicate_students.ID > Students.ID
NOTE: Please back up your data first; I take no responsibility for lost data :-) This is a conceptual idea and has not been tested.
This should work:
DELETE S FROM Student S
INNER JOIN(
SELECT MIN(ID) AS ID,StudentName,StudentClass FROM Student
GROUP BY StudentName,StudentClass
) S2 ON S.ID != S2.ID AND S.StudentName = S2.StudentName AND S.StudentClass = S2.StudentClass
Its basically selecting the minimum ID out of all the duplicate records in sub query. Then we simply delete everything that matches that Class and Name, But we don't match the Minimum Id, so at end of day, we are keeping (presumably) 1st record out of duplicates and eradicating rest.