Joining on a table but only match if it has at least one row based on a condition - sql

I'm trying to find all users who have at least 1 transaction that has the StoreLocationID=123.
The basic query to get the count of users is:
SELECT COUNT(*)
FROM Users u
The transaction table looks like:
Transactions
- ID
- UserID
- Amount
- Date
- StoreLocationID
How can I find ALL users who have at least 1 transaction where StoreLocationID=123.
I can join on the table, but I just need to know if there is at least 1 row with StoreLocationID=123.

You can use a correlated subquery with an exists condition:
select *
from users u
where exists (
select 1
from transactions t
where t.userID = u.userID
and t.StoreLocationID = 123
)
This will give you all users that have at least one transaction on in store 123.
If you just want to count of such users, then:
select count(*)
from users u
where exists (
select 1
from transactions t
where t.userID = u.userID
and t.StoreLocationID = 123
)
Or:
select count(distinct userID) from transactions where StoreLocationID = 123

Related

select all row values as a list

I have a table tasks that looks like this:
userId caption status id
1 Paul done 1
2 Ali notDone 18
3 Kevin notDone 12
3 Elisa notDone 13
I join it with another table users to find the number of taskswhere status = notDone. I do it like this:
SELECT u.id,
t.number_of_tasks,
FROM users u
INNER JOIN (
SELECT userId, COUNT(*) number_of_tasks
FROM tasks
WHERE status = "notDone"
GROUP BY userId
) t ON u.id = t.userId
"""
Now, I want create another column captions that somehow includes a list of all captions that were included in the countand fulfil the join + where conditions.
For example, I would expect this as one of the rows. How can I achieve this?
userId number_of_tasks captions
3 2 ["Kevin", "Elisa"]
You can use json_group_array() aggregate function inside the subquery to create the list of captions for each user:
SELECT u.id, t.number_of_tasks, t.captions
FROM users u
INNER JOIN (
SELECT userId,
COUNT(*) number_of_tasks,
json_group_array(caption) captions
FROM tasks
WHERE status = 'notDone'
GROUP BY userId
) t ON u.id = t.userId;

SELECT 100 last entries with maximum 3 entries per unique user id

I'm having the following request to get all artworks inner join with their user info:
SELECT a.*, row_to_json(u.*) as users
FROM artworks a INNER JOIN users u USING(address)
WHERE (a.flag != "ILLEGAL" OR a.flag IS NULL)
ORDER BY a.date DESC
LIMIT 100
How could i have the same query but including no more than 3 entries per user?
Each user have a unique id called "address"
I think DISTINCT ON only work for 1 per user, maybe ROW_NUMBER?
Thank you in advance, i'm pretty new to DB queries.
You need an extra column in which you specify the nth time that the user is in the table. This will look something like this:
USER | N
user1 | 1
user1 | 2
user1 | 3
user2 | 1
user2 | 2
Getting the extra column in a new table can be done by using the following code
--Create new Table as T
WITH T AS (
SELECT TOP 100
a.*,
row_to_json(u.*) as users,
ROW_NUMBER() OVER(PARTITION BY u.user ORDER BY a.date DESC) AS N
FROM artworks a INNER JOIN users u USING(address)
WHERE (a.flag != "ILLEGAL" OR a.flag IS NULL) )
--Select columns from your new table
SELECT columns from T
WHERE (T.N =1 OR T.N =2 OR T.N =3)
Just an addition to your original query will do. Count the resulting records for each user and then filter by the counter value.
I am using users.address as the user id.
SELECT * from
(
SELECT a.*, row_to_json(u.*) as userinfo,
row_number() over (partition by u.address order by a.date desc) as ucount
FROM artworks a INNER JOIN users u ON a.address = u.address
WHERE a.flag != "ILLEGAL" OR a.flag IS NULL
) t
WHERE ucount <= 3
ORDER BY date DESC
LIMIT 100;
A remark - you have users as a column alias and as a table name which may cause confusion. I have changed the alias to userinfo.

H2 making one select from 2

I got 3 tables, Users, courses and course realation tables. I want to get users who aren't on specific course. So I figure I need somehow merge 2 selects with right join. How could I make one select from 2 selects?
SELECT ID, NAME, LASTNAME, ROLE FROM COURSERELATION JOIN USERS ON
ID_USER = ID WHERE ID_COURSE = ?
RIGTH JOIN
SELECT ID, NAME, LASTNAME, ROLE from COURSERELATION JOIN USERS ON
ID_USER = ID WHERE ID_COURSE != ?
You need to extract users for which it doesn't exist a record of that user for the specific course. You can filter the rows using a NOT EXISTS clause over a subquery.
Please try below query:
SELECT u.ID,
u.NAME,
u.LASTNAME,
u.ROLE
FROM USERS u
WHERE NOT EXISTS (SELECT 1
FROM COURSERELATION s
WHERE s.id_user = u.id
AND s.id_course = 'YOUR_COURSE_ID_HERE' )

SQL Server 2008 - Best way to handle public and private records on the same table

I have a table of records on my database which has about a million records. Most of the records are public - meaning all the users on the system are able to view them. However on the same exact table, I have private records as well, usually couple of hundreds for each user. I have about 1K users on the system.
Each record has 3 main columns:
ID - Enum of the record ID. Unique primary key.
UserID - Identifies the record owner. Null = General record available to everyone. ID = Private record available only for this specific user ID.
RecID - Public record ID. Unique for all public records. If a public record is changed by a user, the system duplicates this record with a new ID, but the same RecID.
Example
ID RecID UserID Comments
----------------------------------------------------------------------------
1 1000 NULL General record
2 1000 1 Modification of record ID=1, available only for userID=1
3 1001 NULL General Record
4 1002 NULL General Record
5 1001 2 Modification of record ID=3, available only for userID=2
If User 1 logs into the system, he should get the list of records 2,3,4
If User 2 logs into the system, he should get the list of records 1,4,5
If user 3 logs into the system, he should get the list of records 1,3,4
The query I'm using is as follow:
SELECT *
FROM TB_Records
WHERE UserID = #UserID
OR (RecID IS NULL AND NOT RecID IN (SELECT RecID
FROM TB_Records
WHERE UserID = #UserID)
The problem I'm having is performance. Adding on top of this query sorting filtering and paging results with a performance of 5-10 seconds for each select. When removing the 3rd line of the query - selecting all the records, the performance is much better, 1-2 seconds.
I would like to know if there is a better way to handle such a requirement.
Thanks
This query doesn't make sense. The AND NOT part is unnecessary, because a NULL value of RecID would not do what you expect. I think you mean:
SELECT r.*
FROM TB_Records r
WHERE r.UserID = #UserID OR
(r.UserId IS NULL AND NOT r.RecID IN (SELECT r2.RecID
FROM TB_Records r2
WHERE r2.UserID = #UserID)
First, create indexes on TB_Records(UserId, RecId). That might help. Next, I would try changing this to an explicit left outer join:
select r.*
from TB_Records r left outer join
TB_Records r2
on r2.UserId = #UserId and
r2.RecId = r.RecId
where r.UserId = #UserId or r2.RecId is NULL;
EDIT:
One more attempt, with a different approach. This uses a window function to see if the user is present for a given record:
select r.*
from (select r.*,
max(case when r.UserId = #UserId then 1 else 0 end) over (partition by RecId) as HasUser
from TB_Records r
) t
where r.UserId = #UserId or HasUser = 0;
Otherwise, you should put the execution plans in the question. Sometimes, it a query with union all will optimize better than one with or:
select r.*
from TB_Records r
where r.UserId = #UserId
union all
select r.*
from TB_Records r left outer join
TB_Records r2
on r2.UserId = #UserId and
r2.RecId = r.RecId
where r2.RecId is NULL;

How to exclude some rows from a SELECT Statement when 2 keys match?

I have 3 tables in my system: Courses, Scores and Users. Scores is a table which has the test results for each course and each user. So I have the ScoreID, The CourseID the UserID and the Score itself.
I want to show in some page the list of courses that the user didn't finished yet. So I want it to show all the courses excluding those the user has records in the Scores table (meaning he already has finished it).
How do I exclude the rows from a SELECT statement when certain CourseID and UserID match at the same time?
Assuming that this is for just one user, Mark Bannister's answer can be simplified a little...
SELECT
*
FROM
Courses
WHERE
NOT EXISTS (SELECT * FROM Scores WHERE CourseID = Courses.CourseID AND UserID = #userID)
Try:
select *
from Courses c
cross join Users u
where not exists
(select null from Scores s where s.CourseID = c.CourseID and s.UserID = u.UserID)
select *
from Courses
where not exists
(
select null from Scores where Scores.CourseID = Courses.CourseID
and Scores.UserID = Courses.UserID
)
Assuming you are using SQL Server you can
CROSS APPLY the courses and users, creating every possible combinations of courses and users
use NOT EXISTS to filter out those records where a UserID exists.
SQL Statement
SELECT *
FROM Courses c
CROSS APPLY Users u
WHERE NOT EXISTS (
SELECT *
FROM Scores
WHERE UserID = u.UserID
AND ScoreID = c.ScoreID
)
In case you are using any other DBMS, following should work on most DBMS's
SELECT *
FROM Courses AS c
, Users AS u
WHERE NOT EXISTS (
SELECT *
FROM Scores
WHERE UserID = u.UserID
AND ScoreID = c.ScoreID
)