Replacing select values with values from other tables - sql

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);

Related

SQL -- SELECT 3 TABLES WITH 2 IDs

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

How to join two tables with same primary key name but different values

I learnt today about "exclusive entities". I have following ER diagram:
With 2 exclusive entities. When I do a physical data model via power designers creation tool it resolves to
Now I want to join both in one table and display the id and room_name
The structure I want to get is:
room_id | room_name
The room_id's in room and bedroom are different. bedroom has for example the ids 1-10 and kitchen the ids from 11-20.
I have the feeling that I might have a bad design, because the joins I tried don't get me the desired result.
My best guess is to use a natural join like*
SELECT room_id, room_name
FROM bedroom
NATURAL JOIN kitchen;
This returns the correct format but the results are empty.
Furthermore I'm looking to get a table in the format:
room_id | roon_name | bedCount | chairCount
You can do exactly what you requested with a full outer join:
select room_id, room_name, b.bedcount, k.chaircount
from bedroom b full outer join kitchen k using (room_id, room_name)
;
This is almost equivalent to the query you attempted - but you need a natural FULL OUTER join rather than the (inner) natural join you tried. Note, however, that many (most?) practitioners view the natural join syntax with suspicion, for various reasons; the using clause as I demonstrated above seems to be accepted more easily. (Of course, even with a natural join, you might be best served still naming specifically the columns you want in the output.)
Although the more common approach for cases like this is a straightforward union all:
select room_id, room_name, bedcount, cast (null as number) as chaircount
from bedroom
UNION ALL
select room_id, room_name, null , chaircount
from kitchen
;
You can union the two tables together such as:
select room_id, room_name
from bedroom
union
select room_id,room_name
from kitchen
You cannot join them as you have nothing in common between the tables upon which to join the tables. What you need to do is give the tables a common column that they can join upon; such as a room will be in a building so create a buildings table and then each of the rooms should contain a foreign key to the containing building.
I.e.
CREATE TABLE buildings (
id INT
GENERATED ALWAYS AS IDENTITY
CONSTRAINT buildings__id__pk PRIMARY KEY,
name VARCHAR2(20)
NOT NULL
);
CREATE TABLE rooms (
id INT
GENERATED ALWAYS AS IDENTITY
CONSTRAINT rooms__id__pk PRIMARY KEY,
building_id INT
NOT NULL
CONSTRAINT rooms__building_id__fk REFERENCES buildings (id),
room_name VARCHAR2(20)
NOT NULL,
CONSTRAINT rooms__id__rn__u UNIQUE ( id, room_name )
);
CREATE TABLE kitchens (
id INT
CONSTRAINT kitchens__id__pk PRIMARY KEY,
room_name VARCHAR2(20)
GENERATED ALWAYS AS ( 'kitchen' )
NOT NULL,
chairCount INT,
CONSTRAINT kitchens__id__rn__fk
FOREIGN KEY ( id, room_name ) REFERENCES rooms ( id, room_name )
);
CREATE TABLE bedrooms (
id INT
CONSTRAINT bedrooms__id__pk PRIMARY KEY,
room_name VARCHAR2(20)
GENERATED ALWAYS AS ( 'bedroom' )
NOT NULL,
bedCount INT,
CONSTRAINT bedrooms__id__rn__fk
FOREIGN KEY ( id, room_name ) REFERENCES rooms ( id, room_name )
);
Then, if you:
INSERT INTO buildings ( id, name ) VALUES ( DEFAULT, 'Building1' );
INSERT INTO rooms ( id, building_id, room_name ) VALUES ( DEFAULT, 1, 'kitchen' );
INSERT INTO rooms ( id, building_id, room_name ) VALUES ( DEFAULT, 1, 'bedroom' );
INSERT INTO kitchens ( id, chairCount ) VALUES ( 1, 42 );
INSERT INTO bedrooms ( id, bedCount ) VALUES ( 2, 13 );
Then:
SELECT b.id AS building_id,
b.name AS building_name,
rk.id AS kitchen_id,
k.chairCount,
rb.id AS bedroom_id,
br.bedCount
FROM buildings b
LEFT OUTER JOIN rooms rk
ON ( b.id = rk.building_id AND rk.room_name = 'kitchen' )
LEFT OUTER JOIN kitchens k
ON ( rk.id = k.id AND rk.room_name = k.room_name )
LEFT OUTER JOIN rooms rb
ON ( b.id = rb.building_id AND rb.room_name = 'bedroom' )
LEFT OUTER JOIN bedrooms br
ON ( rb.id = br.id AND rb.room_name = br.room_name )
Outputs:
BUILDING_ID | BUILDING_NAME | KITCHEN_ID | CHAIRCOUNT | BEDROOM_ID | BEDCOUNT
----------: | :------------ | ---------: | ---------: | ---------: | -------:
1 | Building1 | 1 | 42 | 2 | 13
db<>fiddle here

selecting records from parent / child tables

I have parent / child tables that look like this:
CREATE TABLE users(
id integer primary key AUTOINCREMENT,
pnum varchar(10),
dloc varchar(100),
cc varchar(10),
name varchar(255),
group active bit(1)
);
CREATE TABLE group_members(
id integer primary key AUTOINCREMENT,
group_id integer,
member_id integer,
FOREIGN KEY (group_id) REFERENCES users(id),
FOREIGN KEY (member_id) REFERENCES users(id)
);
Users Data looks like:
ID PNUM DLOC CC NAME GRP
86|23101|dloc 89| | |0
87|23101|dloc 90| | |0
88|23102|dloc 91| | |0
590|12345|Group | |Test Group|1
591|90000|dloc 1 | | |0
group_members data looks like:
ID GROUP_ID
1 |590 | 87
2 |590 | 88
Based on the PNUM, I would like to be able to get the dloc values for all users, whether its a group or not.
So for example, if someone requests pnum 23101, I would like to get back
"dloc 89" and
"dloc 90"
But if they request 12345, I would like to get back
"dloc 90", and
"dloc 91"
So far, I have come up with this query:
SELECT users.dloc
FROM users
WHERE users.id IN
(SELECT group_members.member_id
FROM group_members
INNER JOIN users on users.id = group_members.group_id
WHERE users.pnum='12345');
That works for groups, but it won't return any results if i run the same query with pnum 23101.
What I've tried so far
I tried to see if I could use OR like so:
SELECT users.dloc
FROM users
WHERE users.id in
(SELECT group_members.member_id
FROM group_members
INNER JOIN users on users.id = group_members.group_id
WHERE users.pnum='12345');
OR
(SELECT users.dloc
FROM users
WHERE pnum='12345')
Any suggestions would be appreciated.
You may use a UNION to execute both queries, one of which matches directly on PNUM for values where GROUP <> 1 and the other which matches on PNUM and joins through group_members for values where GROUP = 1.
In the second part, you need to join twice to users to get the members of the group matching the original PNUM.
Since the GROUP condition is opposite in each, only one part of the UNION will ever return results.
/* First part of UNION directly matches PNUM for GROUP = 0 */
SELECT dloc
FROM users
WHERE
PNUM = 23101
AND `group` <> 1
UNION
/**
Second part of UNION matches GROUP = 1
and joins through group_members back to users
to get member dloc (from the second users join)
*/
SELECT uu.dloc
FROM users u
INNER JOIN group_members m ON u.`GROUP` = 1 AND u.id = m.group_id
INNER JOIN users uu ON m.member_id = uu.id
WHERE
u.PNUM = 23101
This does unfortunately require placing the PNUM value twice in the query, once per UNION part, but that isn't so bad.
Here it is in action (using MySQL rather than SQLite, but that doesn't really matter)
Using your original method with OR and an IN() subquery, it can also be done, but I've added WHERE conditions for GROUP = 0 and GROUP = 1.
SELECT users.dloc
FROM users
WHERE users.id in (
SELECT group_members.member_id
FROM group_members
INNER JOIN users on users.id = group_members.group_id
WHERE users.pnum='23101' AND `GROUP` = 1
)
OR users.id IN (
SELECT users.ID
FROM users
WHERE pnum='23101' AND `GROUP` = 0
);
And here's the alternative method in action...

Select from multiple tables with group by clause

I have these table in my database :
Ticket
-------------------------------
|ID int PK |
|Paid varchar(50) |
-------------------------------
TicketRow
----------------------------------
|ID int PK |
|TicketID_FK int |
|SHtimeID_FK int |
----------------------------------
I want to fetch the duplicated rows, that have same SHTiemID_FK and have Paid='ok' state in Ticket table, from TicketRow table.
I try this :
select SHtimeID_FK,count(*) as cnt from dbo.TicketRow
group by SHtimeID_FK
having count(*)>1
But i don't know how should i add Ticket table in my result set.
UPDATE :
I also need Ticket.ID in my resultset
If I understand your scenario correctly you can simply join these two tables by a inner join as I suppose TicketRow.TicketID_FK is a foreign key to Ticket table.
select SHtimeID_FK,count(*) as cnt
from dbo.TicketRow as tr inner join dbo.Ticket as t on tr.TicketID_FK=t.ID
where t.Paid='ok'
group by SHtimeID_FK
having count(*)>1

MySQL SQL Subquery?

Given the following schema / data / output how would I format a SQL query to give the resulting output?
CREATE TABLE report (
id BIGINT AUTO_INCREMENT,
name VARCHAR(255) NOT NULL UNIQUE,
source VARCHAR(255) NOT NULL UNIQUE,
PRIMARY KEY(id)
) ENGINE = INNODB;
CREATE TABLE field (
id BIGINT AUTO_INCREMENT,
name VARCHAR(255) NOT NULL UNIQUE,
report_id BIGINT,
PRIMARY KEY(id)
) ENGINE = INNODB;
ALTER TABLE filed ADD FOREIGN KEY (report_id) REFERENCES report(id) ON DELETE CASCADE;
reports:
id, name, source
1 report1 source1
2 report2 source2
3 report3 source3
4 report4 source4
field:
id, name, report_id
1 firstname 3
2 lastname 3
3 age 3
4 state 4
5 age 4
6 rank 4
Expected output for search term "age rank"
report_id, report_name, num_fields_matched
3 report3 1
4 report4 2
Thanks in advance!
This query will return all the reports with words you need.
SELECT *
FROM report r
INNER JOIN field f ON r.id = f.report_id
WHERE name IN ('age','rank')
You have to nest it. So the final query is:
SELECT a.id, a.name, COUNT(*)
FROM
(
SELECT r.id, r.name
FROM report r
INNER JOIN field f ON r.id = f.report_id
WHERE f.name
IN ('age', 'rank')
)a
GROUP BY a.id, a.name