Select query not printing all of the items - sql

When running
SELECT
s.id space_id
,s.name space_name
,i.name item_name
,GROUP_CONCAT(DISTINCT a.category) attribute_names
FROM spaces s
INNER JOIN spaceItemAssociations sia ON sia.space_id = s.id
INNER JOIN items i ON i.id = sia.item_id
INNER JOIN itemAssociations ia ON ia.items_id = i.id
INNER JOIN itemAttributes a ON ia.itemAttributes_id = a.id
WHERE s.id = 1
on this sql fiddle I get only one row instead of four.
Expected:
4 rows with the objects belonging to this space and their attributes
Actual:
1 row
Is it my select that is wrong?

Because an aggregate function (GROUP_CONCAT) is present, this is an aggregate query - but because no GROUP BY is present, it aggregates over the entire result set, leaving you with a single result row.
If you want distinct categories for each item (each group of rows corresponding to the same item), rather than distinct categories across all items, add a GROUP BY i.id or similar.
Reference

When you tried to GROUP_CONCAT rows by categories, you don't see that you only have attribute2 for these 4 rows. Normal behavior is to show only 1 row because of GROUP_CONCAT clause.
Check now on my SQL FIDDLE when I commented out GROUP_CONCAT that query return all 4 rows.
SELECT
s.id space_id
,s.name space_name
,i.name item_name
, a.category
--,GROUP_CONCAT(DISTINCT a.category) attribute_names
FROM spaces s
INNER JOIN spaceItemAssociations sia ON sia.space_id = s.id
INNER JOIN items i ON i.id = sia.item_id
INNER JOIN itemAssociations ia ON ia.items_id = i.id
INNER JOIN itemAttributes a ON ia.itemAttributes_id = a.id
WHERE s.id = 1

Related

How do I filter out SELECT results from tables?

I have three tables that have user name/id and how many tasks they have submitted. I'm trying to SELECT user.name and the max amount of submissions they have for a single task.
SELECT DISTINCT O.nimi, COUNT(T.id)
FROM Opiskelijat O
LEFT JOIN Lahetykset L ON O.id = L.opiskelija_id
LEFT JOIN Tehtavat T ON T.id = L.tehtava_id
GROUP BY O.id, L.tehtava_id
The first picture shows the tables in question. In the second picture the above is what I'm trying to get, and the bottom is what my code does at the moment. I'm trying to get it to only show Maija - 3 instead of both.
Maybe somehing like this:
SELECT O.nimi, COUNT(*)
FROM Opiskelijat O
LEFT JOIN Lahetykset L ON O.id = L.opiskelija_id
LEFT JOIN Tehtavat T ON T.id = L.tehtava_id
GROUP BY O.id, O.nimi
As was pointed out, you need to have all the selected output fields in the GROUP BY. And also the DISTINCT should not be needed. I think the problem was the group by on L.tehtava_id.
If you want one row per nimi that should be the only column in the GROUP BY. I think you want:
SELECT O.nimi, COUNT(T.id)
FROM Opiskelijat O LEFT JOIN
Lahetykset L
ON O.id = L.opiskelija_id LEFT JOIN
Tehtavat T
ON T.id = L.tehtava_id
GROUP BY O.nimi;
I suspect that you don't actually need the join to Tehtavat:
SELECT O.nimi, COUNT(L.tehtava_id)
FROM Opiskelijat O LEFT JOIN
Lahetykset L
ON O.id = L.opiskelija_id
GROUP BY O.nimi;

How to add condition on the left table to include “zero” / “0” results in COUNT aggregate?

I have an SQL-select:
SELECT
p.id,
COUNT(a.id)
FROM Person p
LEFT JOIN Account a
ON a.person_id = p.id
WHERE p.id = 1
GROUP BY p.id;
and it works fine. But if I add a condition on left table this query will return no rows instead of zero count:
SELECT
p.id,
COUNT(a.id)
FROM Person p
LEFT JOIN Account a
ON a.person_id = p.id
WHERE p.id = 1 AND a.state = '0'
GROUP BY p.id;
How can add the condition on the left table that returns 0 count in case there are no results?
In a LEFT JOIN, conditions on the second table need to be in the ON clause:
SELECT p.id, COUNT(a.id)
FROM Person p LEFT JOIN
Account a
ON a.person_id = p.id AND a.state = '0'
WHERE p.id = 1
GROUP BY p.id;
The rule is pretty simple to follow. A LEFT JOIN keeps all rows in the first table, even when there is no match in the second table. The values in the second table become NULL. The NULL value will fail the condition a.state = '0'.

SQL LEFT JOIN for joining three tables but one with to exclude content

I have 3 tables
STUDENTS
FEES_PAID
SUSPENDED
I want to get the details of the students who have paid the fees but not from SUSPENDED.
SELECT
ID
FROM
STUDENTS s
LEFT JOIN
SUSPENDED p ON s.ID = p.ID
INNER JOIN
FEES_PAID f ON f.ID = s.ID
WHERE
s.ID IS NULL
Unfortunately this does not work. Can any one suggest an efficient query?
Thanks in advance
You need to check if the second table is missing from the LEFT JOIN. So, you need to look at a column in that table. Change the WHERE to:
WHERE p.ID IS NULL
Alternatively, use NOT EXISTS:
SELECT s.ID
FROM STUDENTS s INNER JOIN
FEES_PAID f
ON f.ID = s.ID
WHERE NOT EXISTS (SELECT 1 FROM SUSPENDED p WHERE s.ID = p.ID);
Note that for both these queries, you will need to qualify the ID in the SELECT to specify the table where it comes from.
This should work:
SELECT
s.ID
FROM
STUDENTS s
LEFT JOIN
SUSPENDED p
ON s.ID=p.ID
INNER JOIN
FEES_PAID f
ON f.ID= s.ID
WHERE
p.ID IS NULL

Joining three tables with aggregation

I have the following items table:
items:
id pr1 pr2 pr3
-------------------
1 11 22 tt
...
and two tables associated with the items:
comments:
item_id text
-------------
1 "cool"
1 "very good"
...
tags:
item_id tag
-------------
1 "life"
1 "drug"
...
Now I want to get a table with columns item_id, pr1, pr2, count(comments), count(tags) with a condition WHERE pr3 = zz. What is the best way to get it? I can do this by creating additional tables, but I was wondering if there is a way achieve this by using only a single SQL statement. I'm using Postgres 9.3.
The easiest way is certainly to get the counts in the select clause:
select
id,
pr1,
pr2,
(select count(*) from comments where item_id = items.id) as comment_count,
(select count(*) from tags where item_id = items.id) as tag_count
from items;
You can just join, but you need to be careful that you don't get double count. E.g. you can use a subqueries to get what you want.
SELECT i.id,i.pr1,i.pr2, commentcount,tagcount FROM
items i
INNER JOIN
(SELECT item_id,count(*) as commentcount from comments GROUP BY item_id) c
ON i.id = c.item_id
INNER JOIN
(SELECT item_id,count(*) as tagcount from tags GROUP BY item_id) t
ON i.id = t.item_id
[EDIT] based on the comment, here's the left join version...
SELECT i.id,i.pr1,i.pr2, coalesce(commentcount,0) as commentcount,
coalesce(tagcount,0) as tagcount FROM
items i
LEFT JOIN
(SELECT item_id,count(*) as commentcount from comments GROUP BY item_id) c
ON i.id = c.item_id
LEFT JOIN
(SELECT item_id,count(*) as tagcount from tags GROUP BY item_id) t
ON i.id = t.item_id
Try this:
SELECT i.id, i.pr1, i.pr2, A.commentCount, B.tagCount
FROM items i
LEFT OUTER JOIN (SELECT item_id, COUNT(1) AS commentCount
FROM comments
GROUP BY item_id
) AS A ON i.id = A.item_id
LEFT OUTER JOIN (SELECT item_id, count(1) as tagCount
FROM tags
GROUP BY item_id
) AS B ON i.id = B.item_id;
select
i.id
, i.pr1
, i.pr2
, count(c.item_id) as count_comments
, count(t.item_id) as count_tags
from items i
left outer join comments c on i.id = c.item_id
left outer join tags t on i.id = t.item_id
group by i.id, i.pr1, i.pr2
I've used a LEFT OUTER JOIN to also return counts of zero.

SQL Inner join division

I have issue with my inner join division below. From my oracle, it keep prompt me missing right parenthesis when I have already close it. I'll need to get the names of the patient who have collected all items.
Select P.name
From ((((Select Patientid From Patient) As P
Inner Join (Select Accountno, Patientid From Account) As A1
on P.PatientID = A1.PatientID)
Inner Join (Select Accountno, Itemno From AccountType) As Al
On A1.Accountno = Al.Accountno)
Inner Join (Select Itemno From Item) As I
On Al.Itemno = I.Itemno)
Group By Al.Itemno
Having Count(*) >= (Select Count(*) FROM AccountType);
Here's a simpler approach that I believe is essentially equivalent:
select a.name
from Patient a
inner join Account b on a.PatientID = b.PatientID
inner join AccountType c on b.Accountno = c.Accountno
inner join Item d on c.Itemno = d.Itemno
group by c.Accountno, a.name
having Count(*) >= (Select Count(*) FROM AccountType);
This approach is a bit simpler. It has the added benefit of being much more likely to use indexes on the tables -- if you do joins between what are essentially 'join tables' in memory, you don't get the benefit of the indexes that exist for the physical tables in memory.
I also usually alias table names using sequential letters -- 'a', 'b', 'c', 'd' as you can see. I find that when I'm writing complicated queries it makes it easier for me to follow. 'a' is the first table in the join, 'b' is the second, etc.
It sounds like you just want
SELECT p.name
FROM patient p
INNER JOIN account a ON (a.patientID = p.patientID)
INNER JOIN accountType accTyp ON (accTyp.accountNo = a.accountNo)
INNER JOIN item i ON (i.itemNo = accTyp.itemNo)
GROUP BY accTyp.itemNo
HAVING COUNT(*) = (SELECT COUNT(*)
FROM accountType);
Note that having an alias of A1 and an alias of Al is quite confusing. You want to pick more meaningful and more distinguishing aliases.