Query on table joined with itself - sql

Today I have a final Exam. I approved, happily :D but one of the problems is really blowing my mind.
I need help, so I can rest in peace.
THE PROBLEM
We have a table "People"
(PK)id | name | fatherID
---------------------
1 | gon | 2
2 | cesar| 6
3 | luz | 2
4 | maria| 5
5 | diego| 6
6 | john | -
this is only an example of data.
This table has a relation with itself, on table fatherId(FK) with table id(PK)
I need to do a query that show me 2 columns, in one the name of a person, and in the another one, his/her cousin.
Pretty simple until here, right?
The problem is that I have some restrictions
ONLY ANSI allowed. NO T-sql, or another one. Also, ANSI 99 standard, not 2003 or higher
subquerys are not allowed. And the worst:
NO relations repeated.
For example, considering in this example, gon and maria are cousins.
If I show, gon | maria in the results, I can't show maria | gon.
SO, how I can do this?
Is really burning my head.
What I tried?
Well, the big problem was in the last requisite, the repetition of data. Ignoring that, I put this on my exam (knowing is wrong..)
select p3.name as OnePerson, p4.name as Cousin
from
people p1
inner join people p2 on p1.fatherid = p2.fatherid and p1.id != p2.id
inner join people p3 on p1.id = p3.fatherid
inner join people p4 on p1.id = p4.fatherid
of course, this is not solving the last requeriment, and I have a 4 in the test(we pass with 4) but anyway, my head is burning. So please, help me!
Another options explored
one of my friends, that also had the same exam said me
"Well, considering every relation is duplicated, I can use top
count(*) and an order by and get the half correct"
but.. Top is not ANSI!

You can add to your query WHERE p3.id < p4.id. This will eliminate duplicate results like gon | maria and maria | gon.

SELECT T1.id , T2.id FROM
(
SELECT A.id,A.fid FROM family A
WHERE a.fid IN
(
SELECT id FROM family
WHERE fid IN (SELECT id FROM family WHERE fid IS NULL)
)
)T1
JOIN
(
SELECT A.id,A.fid FROM family A
WHERE a.fid IN
(
SELECT id FROM family
WHERE fid IN (SELECT id FROM family WHERE fid IS NULL)
)
)T2
ON t1.fid<>t2.fid
AND t1.id<t2.id

This will give you the results in format you want.
SELECT TAB1.ID,TAB2.ID
FROM
(
SELECT * FROM people T1
WHERE fatherID IN ( SEL T1.ID FROM people T1 INNER JOIN people T2
ON( T1.id=T2.fatherID) WHERE T1.fatherID IS NOT NULL GROUP BY 1) ) TAB1
INNER JOIN
(
SELECT * FROM people T1
WHERE fatherID IN ( SEL T1.ID FROM people T1 INNER JOIN people T2
ON( T1.id=T2.fatherID)WHERE T1.fatherID IS NOT NULL GROUP BY 1) ) TAB2
ON( TAB1.fatherID<>TAB2.fatherID)
GROUP BY 1,2
WHERE TAB1.ID <TAB2.ID;

Related

Select records that do not have a product associated on a second table

I have two tables:
Table PL:
plid
plname
1
Alice
2
John
3
Danielle
And table PLproducts
plid
productIdentifier
1
membership
1
life
1
dental
2
membership
3
membership
3
life
3
auto
I need to find those plid where productIdentifier does not contain "dental"
Expected results:
plid
plname
2
John
3
Danielle
If I Outer Join for PLproducts <> 'dental', I get all the records that do not contain 'dental' but that is not what Im looking for.
I've never found this scenario before. I understand it may be a simple question.
Thank you all.
You're looking for where something does not exist
select *
from pl
where not exists (
select * from plProducts p
where p.plid = pl.plid and p.productidentifier = 'dental'
);
There are multiple ways to approach this problem. You might be interested in looking at cross apply. It could be a useful approach in more complicated scenarios.
select pl.*
from pl cross apply (
select count(*) as hasdental from plproducts pp
where pp.plid = pl.plid and p2.productidentifier = 'dental'
) as oa
where hasdental = 0;
One of the methods is using string_agg and then use having to remove the phrases that includes "dental".
select * from PL p1
where p1.plid in
(select p2.plid from PLproducts p2
group by p2.plid
having STRING_AGG(productIdentifier,';') NOT LIKE N'%dental%')

postgres STRING_AGG() returns duplicates?

I have seen some similar posts, requesting advice for getting distinct results from the query. This can be solved with a subquery, but the column I am aggregating image_name is unique image_name VARCHAR(40) NOT NULL UNIQUE. I don't believe that should be necersarry.
This is the data in the spot_images table
spotdk=# select * from spot_images;
id | user_id | spot_id | image_name
----+---------+---------+--------------------------------------
1 | 1 | 1 | 81198013-e8f8-4baa-aece-6fbda15a0498
2 | 1 | 1 | 21b78e4e-f2e4-4d66-961f-83e5c28d69c5
3 | 1 | 1 | 59834585-8c49-4cdf-95e4-38c437acb3c1
4 | 1 | 1 | 0a42c962-2445-4b3b-97a6-325d344fda4a
(4 rows)
SELECT Round(Avg(ratings.rating), 2) AS rating,
spots.*,
String_agg(spot_images.image_name, ',') AS imageNames
FROM spots
FULL OUTER JOIN ratings
ON ratings.spot_id = spots.id
INNER JOIN spot_images
ON spot_images.spot_id = spots.id
WHERE spots.id = 1
GROUP BY spots.id;
This is the result of the images row:
81198013-e8f8-4baa-aece-6fbda15a0498,
21b78e4e-f2e4-4d66-961f-83e5c28d69c5,
59834585-8c49-4cdf-95e4-38c437acb3c1,
0a42c962-2445-4b3b-97a6-325d344fda4a,
81198013-e8f8-4baa-aece-6fbda15a0498,
21b78e4e-f2e4-4d66-961f-83e5c28d69c5,
59834585-8c49-4cdf-95e4-38c437acb3c1,
0a42c962-2445-4b3b-97a6-325d344fda4a,
81198013-e8f8-4baa-aece-6fbda15a0498,
21b78e4e-f2e4-4d66-961f-83e5c28d69c5,
59834585-8c49-4cdf-95e4-38c437acb3c1,
0a42c962-2445-4b3b-97a6-325d344fda4a
Not with linebreaks, I added them for visibility.
What should I do to retrieve the image_name's one time each?
If you don't want duplicates, use DISTINCT:
String_agg(distinct spot_images.image_name, ',') AS imageNames
Likely, there are several rows in ratings that match the given spot, and several rows in spot_images that match the given sport as well. As a results, rows are getting duplicated.
One option to avoid that is to aggregate in subqueries:
SELECT r.avg_raging
s.*,
si.image_names
FROM spots s
FULL OUTER JOIN (
SELECT spot_id, Round(Avg(ratings.rating), 2) avg_rating
FROM ratings
GROUP BY spot_id
) r ON r.spot_id = s.id
INNER JOIN (
SELECT spot_id, string_agg(spot_images.image_name, ',') image_names
FROM spot_images
GROUP BY spot_id
) si ON si.spot_id = s.id
WHERE s.id = 1
This actually could be more efficient that outer aggregation.
Note: it is hard to tell without seeing your data, but I am unsure that you really need a FULL JOIN here. A LEFT JOIN might actually be what you want.

Is it possible to replace content from multiple foreign keys when doing a query?

I have the following tables:
TABLE PLAYER
id | name
1 | A
2 | B
3 | C
4 | D
TABLE PAIRINGS
id | player_a | player_b
1 | 3 |1
2 | 2 |4
Both columns in table Pairings are foreign keys to column id of table player.
My problem is, I would like to avoid making a query from code every time I want to know which is my player's name (like, Select name From Player Where Id = pairings.player_a). I have thought about adding Name as an extra columnd to Pairings table, but that would denormalize it.
Instead, it would be nice if I could get the names in just one query, like:
RESULT
player_a | player_b
C | A
B | D
Is it this possible? Thanks so much in advance.
You may join the PAIRINGS table to the PLAYER table, twice:
SELECT
p1.name AS player_a,
p2.name AS player_b
FROM PAIRINGS pr
INNER JOIN PLAYER p1
ON pr.player_a = p1.id
INNER JOIN PLAYER p2
ON pr.player_b = p2.id;
Demo
Don't do it! One of the points of using a relational database is that data is stored in only one place. That is a big convenience. Of course, there are exceptions, but these exceptions should have firm reasons.
In this case, just define a view:
CREATE VIEW vw_pairings AS
SELECT p.*, pa.name AS player_a_name,
pb.name AS player_b_name
FROM PAIRINGS p JOIN
PLAYER pa
ON p.player_a = pa.id JOIN
PLAYER pb
ON p.player_b = pb.id;
When you query from the view, you will see the names, along with all the other information in the PAIRINGS table.
Hope can help you
Select * Into #PLAYER From (
Select 1 [ID], 'A' [Name] Union All
Select 2 [ID], 'B' [Name] Union All
Select 3 [ID], 'C' [Name] Union All
Select 4 [ID], 'D' [Name]
) A
Select * Into #PAIRINGS From (
Select 1 [ID], 3 [PLAYER_A], 1 [PLAYER_B] Union All
Select 2 [ID], 2 [PLAYER_A], 4 [PLAYER_B]
) A
Select
P.ID, A.NAME, B.NAME
From #PAIRINGS P
Left Join #PLAYER A On A.ID = P.PLAYER_A
Left Join #PLAYER B On B.ID = P.PLAYER_B
You can create view, for avoid making query
Example
Create View vwPAIRINGS As
Select
P.ID, A.NAME, B.NAME
From #PAIRINGS P
Left Join #PLAYER A On A.ID = P.PLAYER_A
Left Join #PLAYER B On B.ID = P.PLAYER_B
After that, just select usual
Select * from vwPAIRINGS

SQL select two rows from same table (same column, in fact), based on join with other table

I have two tables: votes and submissions. votes stores all the received votes (each person can give a first and second vote), submissions holds the items people can vote for. Each item has its own ID. The votes table stores the ID of each vote. I want to retrieve the names of the items people votes on. Here's an example of what the tables look like:
votes:
**voter** | **vote1_ID** | **vote2_ID**
Foo | 1 | 2
Bar | 3 | 2
Mark | 2 | 3
submissions:
**ID** | **name**
1 | John
2 | Jane
3 | Mary
I already stated I want to retrieve both the name associated with the first vote and the name associated with the second vote within one query (in fact, I don't really care how many queries it takes, but a single query is always nicer and cleaner of course). How would I go on doing this? I already tried figured I need to use a join, but I can't figure out how to retrieve the value from a same column twice.
EDIT: I figured giving an example of what query I'm trying to perform might be useful:
For example, if I want to see what Bar has voted for, the result of the query should be submissions.name twice. In the result of Mark, this is Jane and Mary.
You can do two inner joins to select the separate values.
SELECT s1.name, s2.name
FROM votes v
INNER JOIN submissions s1 ON v.vote1_ID = s1.ID
INNER JOIN submissions s2 ON v.vote2_ID = s2.ID
You have to join the submissions table twice to get the expected result.
select v.voter, s1.name, s2.name
from votes v
join submissions s1 on v.vote1_id = s1.id
join submissions s2 on v.vote2_id = s2.id
And if you want rows instead of columns, you could do two joins and union them together:
select v.voter, s1.name
from votes v
join submissions s1 on v.vote1_id = s1.id
UNION ALL
select v.voter, s2.name
from votes v
join submissions s2 on v.vote2_id = s2.id

Which table exactly is the "left" table and "right" table in a JOIN statement (SQL)?

What makes a given table the left table?
Is it that the table is indicated in the "From" part of the query?
Or, is it the left table because it is on the left hand side of the = operator?
Are the following equivalent
SELECT *
FROM left_table
LEFT JOIN right_table ON left_table.right_id = right_table.id
and
SELECT *
FROM left_table
LEFT JOIN right_table on right_table.left_id = left_table.id
???
Thanks
The Left table is the first table in the select. Yes, your two examples are equivalent.
The right table is always the table that you are joining on. So yes, both of your statements are equivalent.
JOIN [Table] ON ...
[Table] is always the right table.
Roughly "left" is the result of everything that appears first in the whole FROM clause when reading from left to right - including the result of other JOINs, sub-queries, VIEWs and STORED PROCEDURES.
Both SQL statements are equivalent because the = operator at the ON part of the JOIN clause is symmetric (if a = b then b = a) so the result is the same no matter the order.
The regular join shows only the lines where the ON clause of the JOIN is true, while the LEFT JOIN shows also the records from "left" if the condition is false (showing NULL for any column from "right" present in the SELECT).
For example:
-- People: -- Car
id | name owner_id | model
---+------------ ---------+------------
1 | Paul 1 | Ferrari
2 | Nancy 2 | Porsche
3 | Arthur NULL | Lamborghini
4 | Alfred 10 | Maserati
> select people.name, car.model from people join car on car.owner_id=people.id;
name | model
---------+--------------
Paul | Ferrari
Nancy | Porsche
2 record(s) found
> select people.name, car.model from people left join car on
car.owner_id=people.id;
name | model
---------+--------------
Paul | Ferrari
Nancy | Porsche
Arthur | NULL
Alfred | NULL
4 record(s) found
> select people.name, car.model from people left join car on
people.id = car.owner_id;
name | model
---------+--------------
Paul | Ferrari
Nancy | Porsche
Arthur | NULL
Alfred | NULL
4 record(s) found
See this for a pretty good walkthrough on joins: http://en.wikipedia.org/wiki/Join_(SQL)
And yes, both statements are equivalent :-)
Yes, it's determined by the side of the JOIN operator the table appears on. Your two examples are indeed equivalent.
CREATE TABLE ORDERS (
ORDERID INT,
CUSTOMERID INT,
ORDERDATE DATE
);
INSERT INTO ORDERS VALUES (10123,10,DATE '16-08-20');
INSERT INTO ORDERS VALUES (10122,11,DATE '14-09-20');
INSERT INTO ORDERS VALUES (10121,12,DATE '10-10-20');
CREATE TABLE CUSTOMERS (
CUSTOMERID INT,
CUSTOMERNAME VARCHAR(20),
COUNTRY VARCHAR(20)
);
INSERT INTO CUSTOMERS VALUES (11 , 'BUDDHA','INDIA');
INSERT INTO CUSTOMERS VALUES (12 , 'JOHNWIK','UNITED STATES');
INSERT INTO CUSTOMERS VALUES (100, 'SERENA','UNITED KINGDOM');
discussing LEFT JOIN query:
select orders.orderid, customers.customername, orders.orderdate from orders
inner join customers on orders.customerid = customers.customerid;
If you want to know exact left and right tables. From left to right the table attached with from is [left] and table attached with join is [right].
Happy Hacking !!!