This is difficult to explain in the title, but I have a column that's a join table, and I'd like to count the number of books a character has appeared in based the type of book.
So if cb.type = 2, then I want count(cb.id) + 1 if that makes sense. Otherwise for all other types, just count it normally with count(cb.id).
SELECT
CASE
WHEN cb.type = 2 THEN count(cb.id) + 1
ELSE count(cb.id)
END AS book_count,
c.*
FROM characters c
INNER JOIN character_books cb ON cb.character_id = c.id
GROUP BY c.id, cb.type
ORDER BY book_count DESC
The above query does not work because I have to group by c.id, cb.type, and so I'm not getting the total number of books the character has appeared in.
Now without considering the cb.type, the query would look like this:
SELECT count(cb.id) AS book_count, c.*
FROM characters c
INNER JOIN character_books cb ON cb.character_id = c.id
GROUP BY c.id
ORDER BY book_count DESC
However, if the column cb.type = 2 (which is actually a bitwise column, just using the number 2 here for simplicity), then we should be adding an additional count to book_count.
How would I make this happen?
You want conditional aggregation. I think you want:
SELECT c.id,
SUM(CASE cb.type = 2 THEN 2 ELSE 1 END) as book_count
FROM characters c INNER JOIN
character_books cb
ON cb.character_id = c.id
GROUP BY c.id
ORDER BY book_count DESC;
Related
my query is like that:
SELECT o.id, o.title, oc.category_id,
(SELECT name from categories c where c.id = oc.category_id)
FROM objects o
LEFT JOIN object_categories oc ON oc.object_id = o.id
WHERE type_id = 17
it returns me table like at the image
I want to return non-repeating category name. Can anyone help me?
To do that you can use the DISTINCT ON expression:
https://www.postgresql.org/docs/9.0/sql-select.html#SQL-DISTINCT
In Postgres, you can use distinct on:
SELECT DISTINCT ON(c.id)
o.id,
o.title,
oc.category_id,
c.name,
count(*) over(partition by o.id) cnt
FROM objects o
LEFT JOIN object_categories oc ON oc.object_id = o.id
LEFT JOIN categories c ON c.c.id = oc.category_id
WHERE type_id = 17
ORDER BY c.id, o.id
When a category appears in than one record, this selects only the one that has the smallest object id.
I used the category id rather than the name to identify duplicates - you can use the category name instead, if that matters to you.
Note that I converted the inline subquery on categories to a regular join, since I find it more readable.
I have a query that is running against a SQLite database that uses a couple of subqueries. In order to accommodate some new requirements, I need to translate it to use joins instead. Below is the structure version of the original query:
SELECT c.id AS category_id, b.budget_year,
(
SELECT sum(actual)
FROM lines l1
WHERE status = 'complete'
AND category_id = c.id
AND billing_year = b.budget_year
) AS actual
(
SELECT sum(planned)
FROM lines l2
WHERE status IN ('forecasted', 'in-progress')
AND category_id = c.id
AND billing_year = b.budget_year
) AS rough_proposed
FROM categories AS c
LEFT OUTER JOIN budgets AS b ON (c.id = b.category_id)
GROUP BY c.id, b.budget_year;
The next query is my first attempt to convert it to use LEFT OUTER JOINs:
SELECT c.id AS category_id, b.budget_year, sum(l1.actual) AS actual, sum(l2.planned) AS planned
FROM categories AS c
LEFT OUTER JOIN budgets AS b ON (c.id = b.category_id)
LEFT OUTER JOIN lines AS l1 ON (l1.category_id = c.id
AND l1.billing_year = b.budget_year
AND l1.status = 'complete')
LEFT OUTER JOIN lines AS l2 ON (l2.category_id = c.id
AND l2.billing_year = b.budget_year
AND l2.status IN ('forecasted', 'in-progress'))
GROUP BY c.id, b.budget_year;
However, the actual and rough_proposed columns are much larger than expected. I am no SQL expert, and I am having a hard time understanding what is going on here. Is there a straightforward way to convert the subqueries to joins?
There is a problem with both your queries. However, the first query hides the problem, while the second query makes it visible.
Here is what's going on: you join lines twice - once as l1 and once more as l2. The query before grouping would have the same line multiple times when there are both actual lines and forecast-ed / in-progress lines. When this happens, each line would be counted multiple times, resulting in inflated values.
The first query hides this, because it does not apply aggregation to actual and rough_proposed columns. SQLite picks the first entry for each group, which has the correct value.
You can fix your query by joining to lines only once, and counting the amounts conditionally, like this:
SELECT
c.id AS category_id
, b.budget_year
, SUM(CASE WHEN l.status = 'complete' THEN l.actual END) AS actual
, SUM(CASE WHEN l.status IN ('forecasted', 'in-progress') THEN l.planned END) AS planned
FROM categories AS c
LEFT OUTER JOIN budgets AS b ON (c.id = b.category_id)
LEFT OUTER JOIN lines AS l ON (l.category_id = c.id AND l1.billing_year = b.budget_year)
GROUP BY c.id, b.budget_year
In this new query each row from lines is brought in only once; the decision to count it in one of the actual/planned columns is made inside the conditional expression embedded in the SUM aggregating function.
Im making a join query like:
SELECT * FROM Clothes AS C
JOIN Style AS S on C.StyleId = S.SylelId
WHERE ClothesId = '19'
But i dont want to select everything from Style
I want to select everything from Clothes (20 rows)
And only select 1 row (from 10) from Style
What is the easyest way to do this without having to select every row from Clothes (with 20 things to select) like:
SELECT C.Id, C.Description, C.Name, C.Size, C.Brand, S.Name FROM Clothes AS C
JOIN Style AS S on C.StyleId = ST.SylelId
WHERE ClothesId = '19'
What would be the fastest way? Or is this the only possibillity
Add the table name to the *
SELECT C.*, S.Name as StyleName
FROM Clothes AS C
JOIN Style AS S on C.StyleId = S.SylelId
WHERE ClothesId = '19'
If you have equal names in both tables you have to alias at least one of them to distinguish them.
I have a little wired issue.
I have to select two count from query Likes and Collects but when I add second query instead of 2 likes and 10 collects I get 10 likes and 10 collects.
What am I doing wrong here?
select COUNT(tl.ItemLikeId) as a, COUNT(tib.PacketId) as b
from Items i
left join ItemLikes il
on il.ItemId = i.ItemId
left join ItemsInPackets iip
on iip.ItemId = i.ItemId
where i.ItemId = 14591
Try SELECT COUNT(DISTINCT tl.ItemLikeId) AS a, COUNT(DISTINCT tib.PacketId) as b.
Your join gives you ten rows, so you have ten IDs from each table. However, not all of the IDs are unique. You're looking for unique IDs.
Count returns the number of rows. Not the number of rows with a value, and not the number of distinct rows.
To get number row rows with a value
select SUM(CASE WHEN tl.ItemLikeId IS NOT NULL THEN 1 ELSE 0 END) as a,
SUM(CASE WHEN tib.PacketId IS NOT NULL THEN 1 ELSE 0 END) as b
To get the number of distinct values, do what zimdanen suggested and use COUNT(DISTINCT)
select COUNT(DISTINCT tl.ItemLikeId) as a, COUNT(DISTINCT tib.PacketId) as b
Another approach, if all you are using ItemLikes and ItemsInPackets for are the counts
select
(
SELECT COUNT(ItemLikeId)
FROM ItemLikes
WHERE ItemId = i.ItemId
) as a,
(
SELECT COUNT(PacketId)
FROM ItemsInPackets
WHERE ItemId = i.ItemId
) as b
from Items i
where i.ItemId = 14591
I have three tables: calls, attachments and notes and I want to display everything that's in the calls table, but also display whether a call has attachments and whether the call has notes. - by determining if there is an attachment or note record with a call_id in it. There could be notes and attachments, or there may not be but I would need to know.
Tables structure:
calls:
call_id | title | description
attachments:
attach_id | attach_name | call_id
notes:
note_id | note_text | call_id
If I write:
SELECT c.call_id
, title
, description
, count(attach_id)
FROM calls c
LEFT JOIN attachments a ON c.call_id = a.call_id
GROUP BY c.call_id
, title
, description
to give me a list of all calls and the number of attachments.
How can I also add in a column with the number of notes or a column which indicates that there is notes?
Any ideas?
Thanks.
For the count
SELECT
c.call_id,
title,
description,
count(DISTINCT attach_id) AS attachment_count ,
count(DISTINCT note_id) AS notes_count
FROM calls c
LEFT JOIN attachments a ON c.call_id = a.call_id
LEFT JOIN notes n ON n.call_id = c.call_id
GROUP BY c.call_id,title,description
Or for existence (will be more efficient if this is all you need)
SELECT
c.call_id,
title,
description,
count(attach_id) AS attachment_count ,
case
when exists (select * from notes n WHERE n.call_id = c.call_id) then
cast(1 as bit)
else
cast(0 as bit)
end as notes_exist
FROM calls c
LEFT JOIN attachments a ON c.call_id = a.call_id
GROUP BY c.call_id,title,description
SELECT c.call_id, title, description, a.call_id, n.call_id
FROM calls c
LEFT JOIN attachments a ON c.call_id = a.call_id
LEFT JOIN notes n ON c.call_id = n.call_id
GROUP BY c.call_id,title,description, a.call_id, n.call_id
If call id is present in fiels 4 or 5, you know you have an attachement or a note
If you need to number of attachement or note, look at other answers, look at AtaTheDev's post.
Use distinct in counts
You have to use distinct in counts because your groups have grown by two different entities. So you have to only count distinct values of each. This next query will return both counts as well as bit values whether there are any attachments and notes.
select
c.call_id, c.title, c.description,
count(distinct a.attach_id) as attachments_count,
count(distinct n.note_id) as notes_count,
/* add these two if you need to */
case when count(distinct a.attach_id) > 0 then 1 else 0 end as has_attachments,
case when count(distinct n.note_id) > 0 then 1 else 0 end as has_notes
from calls c
left join attachments a
on (a.call_id = c.call_id)
left join notes n
on (n.call_id = c.call_id)
group by c.call_id, c.title, c.description
I think it should be something like this
SELECT c.call_id, title, description, count(distinct attach_id) , count(distinct note_id)
FROM calls c
LEFT JOIN attachments a ON c.call_id = a.call_id
LEFT JOIN notes n ON n.call_id = a.call_id
GROUP BY c.call_id,title,description
This also works:
SELECT
cl.*,
(SELECT count(1) FROM attachments AS at WHERE at.call_id = cl.id) as num_attachments,
(SELECT count(1) FROM notes AS nt WHERE nt.call_id = cl.id) as num_notes,
FROM calls AS cl
I have used this simple query. This query allows you to use main tables columns easily without group by.
Select StudentName,FatherName,MotherName,DOB,t.count from Student
left JOIN
(
Select StudentAttendance.StudentID, count(IsPresent) as count
from StudentAttendance
group by StudentID, IsPresent
) as t
ON t.StudentID=Student.StudentID