SQL -- SELECT 3 TABLES WITH 2 IDs - sql

I'm trying to join 3 tables where the two IDs from two tables are related but the third table is not related but the values are. It's very convoluted. I tried running a subquery without luck.
Here's the first table:
+---------------------------------+
| ListID | ListValue | ListLabel |
+--------------------------------+
| 1 | 1 | Male |
+--------------------------------+
Second table:
+--------------------+
| ListID | ListName |
+-------------------+
| 1 | Gender |
+-------------------+
Third table:
+---------------------------------+
| ClientID | Name | Gender |
+--------------------------------+
| 23422 | John West | 1 |
+--------------------------------+
So basically I'm looking for the Gender column from the third table to reference the first table where the ListValue column equals the Gender column. Yes, the Gender column value is stored as INT.
+---------------------------------+
| ClientID | Name | Gender |
+--------------------------------+
| 23422 | John West | Male |
+--------------------------------+

From a complete guess, the problem is your design, fix that, fix the problem. You should have 2 tables here, not 3. A Client table and a gender table, and then you can do a trivial JOIN:
CREATE TABLE dbo.Client (ClientID int NOT NULL,
[Name] nvarchar(50) NOT NULL,
Gender tinyint NULL); --As Gender is Sensitive data, and probably not mandatory as some don't wish to share
ALTER TABLE dbo.Client ADD CONSTRAINT PK_Client PRIMARY KEY CLUSTERED (ClientID);
GO
CREATE TABLE dbo.Gender (GenderID tinyint IDENTITY(1,1) NOT NULL,
GenderDescription varchar(15) NOT NULL);
ALTER TABLE dbo.Gender ADD CONSTRAINT PK_Gender PRIMARY KEY CLUSTERED (GenderID);
GO
ALTER TABLE dbo.Client ADD CONSTRAINT FK_ClientGender FOREIGN KEY (Gender) REFERENCES dbo.Gender (GenderID);
GO
--Assume existance of data
SELECT C.ClientID,
C.[Name],
G.GenderDescription AS Gender
FROM dbo.Client C
LEFT JOIN dbo.Gender G On C.Gender = G.GenderID; --LEFT JOIN as it's NULLable
GO
If you must stick with the design you have, you'll probably want to write it as subqueries; and it'll get messy pretty fast:
SELECT T3.ClientID,
T3.[Name],
(SELECT T1.ListLabel
FROM dbo.Table2 T2
JOIN dbo.Table1 T1 ON T2.ListID = T1.ListID
WHERE T2.ListName = N'Gender'
AND T1.ListValue = T3.Gender) AS Gender,
(SELECT T1.ListLabel
FROM dbo.Table2 T2
JOIN dbo.Table1 T1 ON T2.ListID = T1.ListID
WHERE T2.ListName = N'PrimaryLanguage'
AND T1.ListValue = T3.PrimaryLanguage) AS PrimaryLanguage
FROM dbo.Table3 T3;
You can put these (sub)queries in the FROM is you prefer using APPLY. I've used an OUTER rather than CROSS APPLY, as I assume that the value could be NULL and thus acts like a LEFT JOIN, rather than an INNER JOIN:
SELECT T3.ClientID,
T3.[Name],
G.Gender,
PL.PrimaryLanguage
FROM dbo.Table3 T3
OUTER APPLY (SELECT T1.ListLabel AS Gender
FROM dbo.Table2 T2
JOIN dbo.Table1 T1 ON T2.ListID = T1.ListID
WHERE T2.ListName = N'Gender'
AND T1.ListValue = T3.Gender) G
OUTER APPLY (SELECT T1.ListLabel AS PrimaryLanguage
FROM dbo.Table2 T2
JOIN dbo.Table1 T1 ON T2.ListID = T1.ListID
WHERE T2.ListName = N'PrimaryLanguage'
AND T1.ListValue = T3.Gender) PL;
To make it shorter, you could turn the "look up" part of the above into an inline table-value function, but I feel that entertaining that is validating the design, and I specifically don't want to do that.
This'll be a real pain to keep rewriting, but forced by the design choice. Though you could do this with Dynamic SQL, I doubt you'll be able to understand it or maintain it (as if you could understand dynamic SQL you'd have likely at least got the solution I give above), and I doubt you want to write all of your queries as a dynamic statement.
Fix the design, fix the problem (as I mentioned at the start).

This is a very bad data model. Change it if you can.
If there is a column gender in the client table, why muddle through with some generic list? Just add a table gender and link to its rows with client.gender_id:
table gender (gender_id, description)
table client (client_id, name, gender_id)
If you really must make this generic and are ready to live with the consequences (slower access, no guaranteed consistency, ...), then remove the gender column and add a table client_attribut instead, consisting of client_id, list_id and list_value.
Anyway, with your current design the query would be
select c.clientid, c.name, la.listlabel
from client c
join list_attribute la
on la.listvalue = c.gender
and la.listid = (select listid from list where listname = 'Gender')
order by c.clientid;

A simple join is of course possible, it's just unecessarily convoluted!
select t3.ClientId, t3.Name, t1.ListLabel as Gender
from t3 join t2 on t2.ListName='Gender'
join t1 on t1.ListId=t2.ListId and t2.ListValue=t3.Gender

Related

Postgres join and count multiple relational tables

I want to join the 2 tables to the first table and group by a vendor name. I have three tables listed below.
Vendors Table
| id | name
|:-----------|------------:|
| test-id | Vendor Name |
VendorOrders Table
| id | VendorId | Details | isActive(Boolean)| price |
|:-----------|------------:|:------------:| -----------------| --------
| random-id | test-id | Sample test | TRUE | 5000
OrdersIssues Table
| id | VendorOrderId| Details. |
|:-----------|--------------:-----------:|
| order-id | random-id | Sample test|
The expected output is to count how many orders belong to a vendor and how many issues belongs to a vendor order.
I have the below code but it's not giving the right output.
SELECT "vendors"."name" as "vendorName",
COUNT("vendorOrders".id) as allOrders,
COUNT("orderIssues".id) as allIssues
FROM "vendors"
LEFT OUTER JOIN "vendorOrders" ON "vendors".id = "vendorOrders"."vendorId"
LEFT OUTER JOIN "orderIssues" ON "orderIssues"."vendorOrderId" = "vendorOrders"."id"
GROUP BY "vendors".id;```
You need the keyword DISTINCT, at least for allOrders:
SELECT v.name vendorName,
COUNT(DISTINCT vo.id) allOrders,
COUNT(DISTINCT oi.id) allIssues
FROM vendors v
LEFT OUTER JOIN vendorOrders vo ON v.id = vo.vendorId
LEFT OUTER JOIN orderIssues oi ON oi.vendorOrderId = vo.id
GROUP BY v.id, v.name;
Consider using aliases instead of full table names to make the code shorter and more readable.
You are joining along two related dimensions. The overall number of rows is the number of issues. But to get the number of orders, you need a distinct count:
SELECT v.*, count(distinct vo.id) as num_orders,
COUNT(oi.vendororderid) as num_issues
FROM vendors v LEFT JOIN
vendorOrders vo
ON v.id = vo.vendorId LEFT JOIN
orderIssues oi
ON oi.vendorOrderId = vo.id
GROUP BY v.id;
Notes:
Table aliases make the query easier to write and to read.
Quoting column and table names makes the query harder to write and read. Don't quote identifiers (you may need to recreate the tables).
Postgres support SELECT v.* . . . GROUP BY v.id assuming that the id is the primary key (actually, it only needs to be unique). This seems like a reasonable assumption.

Replacing select values with values from other tables

I am trying to query upon a table, where I want to replace it's foreign key with a value the related table has a fk constraint to. I don't know how to explain it better in words - I have made a minimal example to demonstrate my difficulty.
create table customers (
cpr VARCHAR(10) PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
CREATE TABLE accounts (
id SERIAL PRIMARY KEY,
balance INT DEFAULT 0,
customerCpr VARCHAR(10) references customers (cpr) NOT NULL,
);
CREATE TABLE transactions(
id SERIAL PRIMARY KEY,
retriever INT references accounts (id) NOT NULL,
giver INT references accounts (id) NOT NULL,
timestamp timestamp default current_timestamp,
amount INT NOT NULL
);
So I want to query something like
SELECT retrieverCustomer.name, giverCustomer.name, tran.timestamp tran.amount
FROM transactions tran
WHERE tran.retriever = 20 OR tran.giver = 20...
I want it to return this format:
id | retName | gName | timestamp | amount
----+-----------+-------+----------------------------+--------
1 | Elisabeth | Jonas | 2020-05-09 11:07:50.614155 | 1500
2 | Veronica | Peter | 2020-05-09 11:07:50.614155 | 2200
3 |Kristoffer | Jens | 2020-05-09 11:07:50.614155 | 1700
I am not sure how to achieve this, I have tried with some joining and some conditionals but I fail to see the problem through. If someone know how to achieve this or can explain why it is not ideal to do so it would really help me out.
You want to join the acounts and customers table twices, one time as the giver, one time as the retriever:
SELECT cr.name as retriever_customer, cg.name as giver_customer, t.timestamp, t.amount
FROM transactions t
JOIN accounts ar on ar.id = t.retriever
JOIN accounts ag on ag.id = t.giver
JOIN customers cr on cr.cpr = ar.customercpr
JOIN customers cg on cg.cpr = ag.customercpr
WHERE t.retriever = 20 OR t.giver = 20
ORDER BY t.timestamp;
This is a place where a CTE can prove clearer:
with ac as ( -- accounts with the customer name added on
select a.*, c.name
from accounts a join
customers c
on a.customercpr = c.cpr
)
select t.id, acg.name as giver_name, acr.name as retriever_name, t.timestamp, t.amount
from transactions t join
ac acg
on acg.id = t.giver join
ac acr
on acr.id = t.retriever
where 20 in (t.giver, t.retriever);

Query on table joined with itself

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;

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 !!!

SQL: Filtering data using a join is bad?

For example, I have the following tables:
animal
-----------------------
animal_id | animal_name
-----------------------
owners
-----------------------
owner_id | owner_name
-----------------------
owners_animals
--------------------
owner_id | animal_id
--------------------
I want to find the animals with no owners so I do the query:
select animal_name
from (select * from animals) as a
left join (select * from owners_animals) as o on (a.animal_id = o.animal_id)
where owner_id is NULL
Is this way of filtering data using a join acceptable and safe? With the same schema, is there a better alternative to get the same result?
Use a Not Exists clause:
Select animal_name
From animals as a
Where Not Exists(Select 1
From owners_animals oa
Where oa.animal_id = a.animal_id)
Also, put an index of owners_animals.animal_id to make this filter as fast as possible
Assuming there's nothing postgres specific going on (I'm not familiar with postgres) then the following is easier to follow.
Select *
From animals a
left outer join owners_animals oa On a.animal_id = oa.animal_id
Where oa.owner_id is NULL
Don't ever do, FROM (SELECT * FROM table), just do FROM table, same goes with the LEFT JOIN. What you wrote is just an overly verbose
SELECT animal_name
FROM animals
LEFT JOIN owners_animals
USING ( animal_id )
WHERE owner_id IS NULL;
With that said, I often like the NOT EXISTS() option, because it keeps the owner_id IS NULL fragment out.
USING (foo) is the same as foo = foo, on the joined tables, except only one of them will be in the result set.