SQL tables join [closed] - sql

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
I am looking for a way to fetch records in a single sql query for the complex joins. I have three tables lets say users, user_projects and appointments. User can be assigned to multiple projects and user can have multiple appointments on different dates. How do I select all the users which is assigned to project1 and project2 and has appointments on date1 and date2? I am using postgres 9.5
Sample Data:
users table:
id, name
1, Steve
2, Bill
3, Emma
user_projects table:
id, user_id, project_id
1, 1, 1
2, 2, 1
3, 3, 1
4, 1, 2
appointments table:
id, user_id, date
1, 1, 2016-10-07
2, 2, 2016-10-07
3, 3, 2016-10-07
4, 1, 2016-11-15
5, 2, 2016-11-15
For this special case lets say I want to find all the users that belongs to project with id 1 and 2 and has appointment fixed on date 2016-10-07 and 2016-11-15. And the expected output is it should only include user with id 1 ie. Steve in this case

Perhaps I'm missing something in the question. Looks like a simple join to me:
select distinct u.id
from users u
join user_projects up1 on u.id=up1.user_id
join user_projects up2 on u.id=up2.user_id
join appointments a1 on u.id=a1.user_id
join appointments a2 on u.id=a2.user_id
where up1.project_id = 1
and up2.project_id = 2
and a1.date = '2016-10-07'
and a2.date = '2016-11-15'
Here is a fiddle: http://sqlfiddle.com/#!15/415fd/1/0

SELECT ID
FROM USERS U
JOIN (
-- Users in two projects
SELECT USER_ID
FROM USER_PROJECT
WHERE PROJECT_ID = 1
INTERSECTION
SELECT USER_ID
FROM USER_PROJECT
WHERE PROJECT_ID = 2
) UP ON U.ID = UP.USER_ID
JOIN (
-- user ids that have appointments on two dates:
SELECT USER_ID
FROM APPOINTMENT
WHERE DATE = '2016-10-07'
INTERSECTION
SELECT USER_ID
FROM APPOINTMENT
WHERE DATE = '2016-11-15'
) A ON U.ID = A.USER_ID
Another way to do it that should have the same performance (maybe this seems better because there are less lines?):
SELECT ID
FROM USERS U
JOIN (
-- Users in two projects
SELECT USER_ID
FROM USER_PROJECT
WHERE PROJECT_ID IN (1,2)
GROUP BY USER_ID
HAVING COUNT(DISTINCT PROJECT_ID) = 2
) UP ON U.ID = UP.USER_ID
JOIN (
-- user ids that have appointments on two dates:
SELECT USER_ID
FROM APPOINTMENT
WHERE DATE IN ('2016-10-07','2016-11-15')
GROUP BY USER_ID
HAVING COUNT(DISTINCT DATE) = 2
) A ON U.ID = A.USER_ID

Related

SQL joining on 2 separate date columns

I am having trouble wrapping my head around a problem and the more I think about it the further away I get.
I have two tables that log user visits into two separate applications. For simplicities sake, let's say there are only 2 columns, timestamp and userid (key). Each row counts as a visit, so technically 3 columns since there is one derived for visits.
I am trying to create a table that in each row records the userid, the day (to_date format), total visits to app 1 and total visits to app 2 for that user on that day.
The issue is, when I join the tables together on userid and date, I get missing data. For example, if a user logged into application A on day X, but did not log into application B on day X, then joining on userid and day causes this record to be omitted since the date only exists in Application A's table.
How can I set this up where the final table would have a singular date column, userid, visits to app A and visits to app B, regardless if the user visited both applications on said day?
Hope this made sense, happy to elaborate if needed. Below is sort of what my SQL looks like as of now. Any thoughts appreciated!
with app_a_visits as (
select to_date(timestamp) as date, userid, count(*) as visits
from app_a),
app_b_visits as (
select to_date(timestamp) as date, userid, count(*) as visits
from app_b)
select a.date, a.userid, a.visits as app_a_visits, b.visits as app_b_visits
from app_a_visits a
full outer join app_b_visits b on a.userid = b.user_id and a.date = b.date;
Use FULL OUTER JOIN and NVL/COALESCE
with app_a_visits(date, userid,visits) as (
select * from values
('2022-01-01'::date, 1, 100),
('2022-01-03'::date, 1, 100),
('2022-01-05'::date, 1, 100)
), app_b_visits(date, userid,visits) as (
select * from values
('2022-01-02'::date, 1, 200),
('2022-01-03'::date, 1, 200),
('2022-01-04'::date, 1, 200)
)
select
NVL(a.date, b.date) as date,
NVL(a.userid, b.userid) as userid,
a.visits as app_a_visits,
b.visits as app_b_visits
from app_a_visits a
full outer join app_b_visits b
on a.userid = b.userid and a.date = b.date
ORDER BY 1,2;
DATE
USERID
APP_A_VISITS
APP_B_VISITS
2022-01-01
1
100
null
2022-01-02
1
null
200
2022-01-03
1
100
200
2022-01-04
1
null
200
2022-01-05
1
100
null

Check if a chat between 2 people exists

I have a chat system that handles group chats. A conversation can have many participants and the only distinction between group and non group chats is a non group chat will only have 2 participants.
I want a query to check if a non group chat exists between 2 users.
Conversations table
| id |
Participants table
| id | conversation (FK) | user (FK to a users table) |
To check if a single conversation exists I have come up with this query
select participants.conversation, CAST ( count(participants.user) AS INTEGER )
as members
from participants
where participants.user = 3 or participants.user = 18
group by participants.conversation
having CAST ( count(participants.user) AS INTEGER ) = 2
I Have created a single chat between users 3 and 18, and also a group chat between 3, 18 and 17. But when I run this query, both conversations are retuning 2 members, when the group chat has 3 participants.
What am I doing wrong?
You can get all such non-groups with an aggregation query:
select conversation
from participants p
group by conversation
having count(*) = 2 and
count(*) filter (where p.user = 3) = 1 and
count(*) filter (where p.user = 18) = 1;
Or a simpler having clause:
having array_agg(p.user order by p.user) = array[3,18]
the main problem is that query filters all users (except 2) first, before grouping, this is why all conversations looks like 2-user groups, I suggest to rewrite query like this:
with chats as (
select distinct conversation
from participants
where "user" in (3, 18)
),
nongroup as (
select conversation
from participants
where conversation in (select conversation from chats)
group by conversation
having count(*) = 2
)
select * from nongroup
first CTE will pick all conversations for given 2 users, second CTE will pick only 2 user rooms
fiddle: http://sqlfiddle.com/#!15/b5e25/3

In SQL, how can i segment users by number of items they have? (redshift)

I'm not a SQL expert so apologies if this is actually really simple.
I have a table that lists users and the different questionnaires they have taken. Users can take questionnaires in any order and take as many as they like. There are a total of 7 available and I want to get a view of how many have taken 1 out of 7, 2 of 7, 3 of 7 etc etc
So a really rough example is the table might look like this:
And I want a query that will show me:
count Users with 1 Q: 1
count Users with 2 Q: 2
count Users with 3 Q: 0
count Users with 4 Q: 0
count Users with 5 Q: 1
count Users with 6 Q: 0
count Users with 7 Q: 0
You can do this with two levels of aggregation:
select cnt_questionnaires, count(*) cnt_users
from (
select count(*) cnt_questionnaires from mytable group by userID
) t
IF OBJECT_ID('tempdb..#t') IS NOT NULL DROP TABLE #t ;
create table #t (userid INT, q nvarchar(32));
insert into #t
values
(1,'Q1'),
(1,'Q3'),
(2,'Q2'),
(3,'Q1'),
(3,'Q2'),
(3,'Q3'),
(3,'Q4'),
(3,'Q5'),
(4,'Q2'),
(4,'Q3')
-- select * from #t
SELECT
v.qCount,
Count(c.userid) uCount
FROM
(VALUES (1),(2),(3),(4),(5),(6),(7)) v(qCount)
LEFT JOIN (
select
userid, count(q) qCount
from
#t
group by userid
) c ON c.qCount = v.qCount
GROUP BY
v.qCount
Assuming you have user_id on each row, the challenge is getting the zero values. Redshift is not very flexible when it comes to creating tables. Assuming your source data has enough rows, you can use:
select n.n, coalesce(u.cnt, 0)
from (select row_number() over () as n
from t
limit 7
) n left join
(select user_id, count(*) as cnt
from t
group by user_id
) u
on n.n = u.cnt;

WHERE NOT EXISTS check and equals to "=" check fail together but work seperatly

I have an INSERT SQL query which needs to check if the entry will cause double booking, and that also the 2 users (doctor and patient) are affiliated with the same clinic. (Found it easier to explain in regard to the scenario), the SQL I have is:
WITH ins (MedStaffID, PatientID, TimeSlot, AppDate)
AS (SELECT 1, 6, 10, DATEADD(day, 1, CAST(GETDATE() AS DATE)))
INSERT INTO Appointments (MedStaffID, PatientID, TimeSlot, AppDate)
SELECT ins.MedStaffID, ins.PatientID, ins.TimeSlot, ins.AppDate
FROM ins
WHERE
NOT EXISTS
(
SELECT *
FROM Appointments
WHERE (MedStaffID = ins.MedStaffID OR PatientID = ins.PatientID)
AND TimeSlot = TimeSlot
AND AppDate = ins.AppDate
)
AND
(
SELECT Clinic
FROM Users UD
WHERE UD.ID = ins.MedStaffID
)
=
(
SELECT Clinic
FROM Users UD
WHERE UD.ID = ins.PatientID
);
The 2 WHERE checks function correctly without the other, but when together always results in the INSERT not proceeding even if it should pass both checks.
Example Users Table:
ID Clinic Role Forename n'otherstuff
1 1 Doctor Bob Potato
2 1 Patient Jim Cake
3 1 Patient Laura Tart
4 2 Doctor Sally Muffin
5 2 Patient Khaaan Lolly
Example Appointments Table:
ID MedStaffID PatientID TimeSlot Date
1 1 2 1 *today
2 1 2 2 *today
3 1 3 4 *today
4 4 5 2 *today
So the insert needs to prevent another appointment for a doctor if he already has an appointment for that time and date, likewise for a patient.
It also needs to ensure the doctor and patient are with the same clinic.
Individually the checks do their jobs, but I need them to work together, and also another check will be needed to ensure the ID placed in the "MedStaffID" field is tied to a User with "Role" "Doctor", but right now I'm stumped at these 2 checks.
Maybe you just want to join directly?
WITH ins (MedStaffID, PatientID, TimeSlot, AppDate)
AS (SELECT 1, 6, 10, DATEADD(day, 1, CAST(GETDATE() AS DATE)))
INSERT INTO Appointments (MedStaffID, PatientID, TimeSlot, AppDate)
SELECT ins.MedStaffID, ins.PatientID, ins.TimeSlot, ins.AppDate
FROM ins
JOIN Users Staff ON (Staff.ID = ins.MedStaffID)
JOIN Users Patient ON (Patient.ID = ins.PatientID)
WHERE Staff.Clinic = Patient.Clinic
AND NOT EXISTS ( SELECT *
FROM Appointments
WHERE (MedStaffID = ins.MedStaffID OR PatientID = ins.PatientID)
AND TimeSlot = TimeSlot
AND AppDate = ins.AppDate
)
;
I haven't found the problem for sure. However...
In the not-exists code, shouldn't:
AND TimeSlot = TimeSlot
be
AND TimeSlot = ins.TimeSlot
As an optimization step, the ClinicID checks should come first. Where-not-exists is typically a full-table-scan (or full index scan). If the table is always small, that's not an issue.
As an optimization step, the where-not-exists subclause should select id, not *.
It should not be the case, but it might be the case, since I can't find anything else, that operator-precedence is biting you. Add parentheses around your ClinicID check (the whole expression).
Explain Query is your friend.

How to query two same values in SQL Server 2008?

I have table in SQL Server 2008 with following fields
RoomUserId->Primary key
RoomId->Foreign Key of Table Rooms
UserId->Foreign Key of Table Users
Now I have the values as following where the RoomId is common for both users
RoomUserId RoomId UserId
1 11 1
2 11 2
3 12 1
4 12 3
5 13 1
6 13 4
Now I need a SQL query to find the roomid of two users which is distinct. i.e, roomid of user 1 and user 2, roomid of user 1 and user 3.
Please anyone help me with is since I am new to SQL Server.
Try this:
SELECT r1.roomId FROM room r1 JOIN room r2 ON r1.roomId = r2.roomId WHERE r1.userId = 1 AND r2.userId = 3
If you mean find a roomid that is common to users:
select distinct f.roomid
from rooms f
inner join rooms s on f.roomid = s.roomid and f.userid <> s.userid
Or you can use grouping:
select roomid
from rooms
group by roomid
having count(distinct userid) > 1
If you only ever need rooms where there is more than one user then this will work:
SELECT DISTINCT RoomID
FROM RoomUser r1
INNER JOIN RoomUser r2
ON r1.RoomID = r2.RoomID
AND r1.RoomUserID != r2.RoomUserID
If you need the room ID of rooms with x users then use the Having Clause, this is more extensible than self joining e.g. if you need to find room IDs with 3 or more User IDs then you would end up with:
SELECT RoomID
FROM RoomUser
GROUP BY RoomID
HAVING COUNT(DISTINCT UserID) > 3
Whereas using self joins, while probably more efficient will end up with some quite messy SQL. Check execution plans and run some tests to see which is more efficient for your needs.
If you actually need the user IDs of the Users in Rooms with more than one User ID then you could use a CTE to build a comma separated string of users in each room:
;WITH RoomUserCTE AS
( SELECT RoomID,
MIN(UserID) [UserID],
CONVERT(VARCHAR(20), MIN(UserID)) [Users],
0 [Recursion]
FROM RoomUser
GROUP BY RoomID
UNION ALL
SELECT a.RoomID,
b.UserID [UserID],
CONVERT(VARCHAR(20), Users + ', ' + CONVERT(VARCHAR, b.UserID)),
Recursion + 1
FROM RoomUserCTE a
INNER JOIN RoomUser b
ON a.RoomID = b.RoomID
AND b.UserID > a.UserID
)
SELECT RoomID, Users
FROM ( SELECT *, MAX(Recursion) OVER(PARTITION BY RoomID) [MaxRecursion]
FROM RoomUserCTE
) cte
WHERE MaxRecursion = Recursion
For the data in your question this will yield
| RoomID | Users |
|---------+---------|
| 11 | 1, 2 |
| 12 | 1, 3 |
| 13 | 1, 4 |
This would work no matter how many user IDs were associated with the same Room ID, so again is more forward compatible.