How to do left join to get all the rows with param? - sql

I have articles and users tables. I also have another table articles_users with FK columns: userId articleId.
I also have userId=1
How to get get all the rows also with extra column that tell me if the userid is linked to this row?
I have try to do this with left join but the problem is articles_users have duplicate entries like:
articleId: 1, userId: 1
articleId: 2, userId: 1
articleId: 3, userId: 2
And it get duplicate articles rows or none.
SELECT * FROM articles LEFT JOIN articles_users ON articles_users.articleid = articles.id
WHERE articles_users.userid = 1

I would recommend case and exists:
SELECT a.*,
(CASE WHEN EXISTS (SELECT 1
FROM articles_users au
WHERE au.articleid = a.id AND
au.userid = 1
)
THEN 1 ELSE 0
END) as flag_1
FROM articles a;
Although you can use JOIN, I wouldn't recommend it. If you followed the same pattern for multiple users, you might end up with multiple rows.
Many databases support boolean types explicitly. In those databases, you can eliminate the CASE:
SELECT a.*,
(SELECT 1
FROM articles_users au
WHERE au.articleid = a.id AND
au.userid = 1
) as flag_1
FROM articles a;

Related

SQL Query: Count "id" occurrences in two tables

I have these 3 tables and I am trying to count, how many "hints" and "quizzes" are there for specific town id.
db_town
id
town
1
New York
db_hint
id
town_id
hint
1
1
test
db_quiz
id
town_id
quiz
1
1
quiz 1
2
1
quiz 2
I am using this statement, but it does not work :(
SELECT count(q.id),count(h.id) FROM `db_town` t LEFT JOIN `db_quiz` q ON t.id = q.town_id LEFT JOIN `db_hint` h ON t.id = h.town_id WHERE t.id = 1 GROUP BY t.id
and it produces this result:
count(q.id)
count(h.id)
2
2
Do I need to use two statements? Or is it possible to query it in a single SQL statement? I am using MariaDB.
You can use union all and aggregation:
select town_id, sum(is_hint), sum(is_quiz)
from ((select town_id, 1 as is_hint, 0 as is_quiz
from hints
) union all
(select town_id, 0, 1
from quizzes
)
) t
group by town_id;
Alternatively, you can use correlated subqueries:
select t.*,
(select count(*) from hints h where h.town_id = t.id),
(select count(*) from quizzes q where q.town_id = t.id)
from towns t;
Two things to look out for:
JOINs are likely to multiply rows and throw off the counts.
Getting 0 values if a town has no hints or quizzes.
You can use COUNT (DISTINCT) if both the hint id and the quiz id are unique.
SELECT
count(distinct q.id),count(distinct h.id)
FROM `db_town` t
LEFT JOIN `db_quiz` q ON t.id = q.town_id
LEFT JOIN `db_hint` h ON t.id = h.town_id
WHERE t.id = 1 GROUP BY t.id

SQL Query check if ID is part of another table

I have 2 tables:
tblBook:
BookID, Title
tblFavorite:
FavoriteID, UserID, BookID
I would like to develop a SQL query to Select BookID, Title, IsFavorite (true|false) while given the UserID as parameter.
Foreach BookID there should be checked if there is a row with this BookID and the given UserID in tblFavorite. -> true/false
You can use a correlated subquery:
select b.*,
case when exists (select 1 from tblfavorite f where f.bookid = b.bookid and f.userid = ?)
then 1
else 0
end as isfavorite
from tblbook b
The question mark represents the id of the user for who you are generating the report. In databases that support evaluating conditions as booleans or integers (such as MySQL or Postgres for example), you can dispense the case expression:
select b.*,
exists (select 1 from tblfavorite f where f.bookid = b.bookid and f.userid = ?) as isfavorite
from tblbook b
Use a LEFT JOIN and a CASE expression so that if there is no record in tblFavorite (NULL), it shows False, otherwise it's True.
SELECT B.BookID, B.Title, CASE WHEN FavoriteID IS NULL THEN 'False' ELSE 'True' END AS IsFavorite
FROM tblBook B
LEFT JOIN tblFavorite F on B.BookID = F.BookID

Checking 2 tables for identical IDs and returning 1/0 in column if ID in table 1 exists in table 2

I have 2 tables, 1 containing all reservation ids, and 1 containing reservation ids for livestream reservations. I am trying to write a query that checks to see if a reservation id exists in the livestream table, and returns '1' if true & '0' if false. I figure the best way to do this is with a case statement that returns my result if the reservation id exists in the livestream table, but I am running into issues. Is there a better way to do this?
with table_name as(
select
reservation_id
from all_reservations
)
select t.*,
case when exists(l.reservation_id)
then '1'
else '0' end as is_livestream
from livestream_reservations l
left join table name t
on l.reservation_id = t.reservation_id
So long as reservation_id shows up with at most one record in livestream_reservations, this will work for you:
select r.*,
case
when l.reservation_id is null then 0
else 1
end as is_livestream
from reservations r
left join livestream_reservations l
on l.reservation_id = r.reservation_id;
The case relies on the fact that a failure to join to livestream_reservations returns null in all columns from that table.
In case there may be more than one row with the same reservation_id in the livestream_reservations table, then you could do this:
with ls_count as (
select reservation_id, count(*) as count_livestream
from livestream_reservations
group by reservation_id
)
select r.*, coalesce(lc.count_livestream, 0) as count_livestream
from reservations r
left join ls_count lc on lc.reservation_id = r.reservation_id;
I would recommend exists and using booleans:
select r.*,
(exists (select 1 from livestream_reservations lr where lr.reservation_id = r. reservation_id)
) as is_livestream
from reservations r;
There is a good chance that this is faster than other solutions. More importantly, it avoids problems with duplicates in livestream_reservations.

Count for all values of enum in PostgreSQL

I have a table called users, which has the following columns:
id: INT NOT NULL
face: face_type
face_type is an ENUM type that has the following values: 'square', 'round' and 'triangle'.
And I have another table called houses, which has the following columns:
id: INT NOT NULL
user_id: INT NOT NULL
Now, I want to get all the houses grouped by the different type of face types. So, what I have so far is this:
SELECT users.face_type, COUNT(*)
FROM users
LEFT JOIN houses ON houses.user_id = users.id
GROUP BY users.face_type
The problem is that I also want to get rows for face_type which none of the users have, as well as a result for NULL face_type. So, for example, if I have the following data:
users (id, face_type)
1, 'round'
2, 'triangle'
houses (id, user_id)
1, 1
2, 1
3, 2
I would expect the result to be:
face_type, count
'round' 2
'triangle' 1
'square' 0
null 0
I know how to get all the potential values of the face_type ENUM, by doing :
SELECT unnest(enum_range(NULL::face_type)) AS face_types;
But I don't know how to use that to count all potential face types in the aggregate, as well as also calculating for NULL face types.
You can use LEFT JOIN:
SELECT ft.face_type, COUNT(h.user_id)
FROM (SELECT unnest(enum_range(NULL::face_type)) AS face_types
) ft LEFT JOIN
users u
ON u.face_type = ft.face_type LEFT JOIN
houses h
ON h.user_id = u.id
GROUP BY ft.face_type;
To get NULL, just use UNION ALL:
SELECT ft.face_type, COUNT(h.user_id)
FROM (SELECT unnest(enum_range(NULL::face_type)) AS face_types
UNION ALL
SELECT NULL
) ft LEFT JOIN
users u
ON u.face_type = ft.face_type LEFT JOIN
houses h
ON h.user_id = u.id
GROUP BY ft.face_type;
Of course, the = will not every match. If that is possible, then you want to change the JOIN condition to u.face_type is not distinct from ft.face_type.
to COUNT(houses.*)
SELECT face_type.type, COUNT(houses.*)
FROM (SELECT unnest(enum_range(NULL::face_type))) AS face_type(type)
FULL JOIN users ON users.face_type=face_type.type
LEFT JOIN houses ON houses.user_id = users.id
GROUP BY face_type.type
A LEFT JOIN starting from the ENUM and going to users and houses will allow you to recover totals for each enumerated value. To also display the NULL face types, you can use a UNION query.
SELECT
ft.face_type,
COUNT(ho.user_id) as cnt
FROM
(SELECT unnest(enum_range(NULL::face_type)) AS face_types) ft
LEFT JOIN users us ON us.face_type = ft.fact_type
LEFT JOIN houses ho ON ho.user_id = us.id
GROUP BY ft.face_type
UNION
SELECT
null,
COUNT(ho.user_id)
FROM houses ho
INNER JOIN users us ON ho.user_id = us.id AND us.face_type IS NULL
ORDER BY cnt desc

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;