Why is my group by not grouping my values as expected? - sql

My project is a clothes factory and I have 3 tables.
Person : A table that contains people's name
Category : Contains each category of clothes in the factory (sockets, shoes, etc.)
Quantity : The quantity of clothes for each person
SELECT Name,
sum(case when category = 'shoes' then quantity else 0 end) as 'Quantity_shoes',
sum(case when category = 'shirts' then quantity else 0 end) as 'Quantity_shirts',
sum(case when category = 'sockets' then quantity else 0 end) as 'Quantity_shirts',
sum(case when category = 'hats' then quantity else 0 end) as 'Quantity_hats'
FROM person p
join inventory i
on i.person_id = p.id
join category c
c.id = i.category_id
WHERE p = 'Paul'
GROUP BY name,
category
And I'm trying to display something like this :
Name
Quantity_shoes
Quantity_shirts
Quantity_sockets
Quantity_hats
Paul
8
25
38
0
But my result isn't that I expected... I got this :
Name
Quantity_shoes
Quantity_shirts
Quantity_sockets
Quantity_hats
Paul
8
0
0
0
Paul
0
0
0
0
Paul
0
25
0
0
Paul
0
0
38
0
It seems that I have one row for each category. So I tried to groupbyquantity but it doesn't sum my quantity and I have more rows.
What I am doing wrong?

As suggested in the above try this (I am guessing the column in person with the name is p.name in which case you may not even need the group by name):
SELECT
Name,
sum(case when category = 'shoes' then quantity else 0 end) as 'Quantity_shoes',
sum(case when category = 'shirts' then quantity else 0 end) as 'Quantity_shirts',
sum(case when category = 'sockets' then quantity else 0 end) as 'Quantity_shirts',
sum(case when category = 'hats' then quantity else 0 end) as 'Quantity_hats'
FROM
person p
join inventory i on i.person_id = p.id
join category c.id = i.category_id
WHERE
p.name = 'Paul'

Related

Sum a column into two different columns based on another column's value

What I need is only a list of the items in "Storage", but the resulting set should include the sum of that item's quantity in both the storage and active locations.
Here's a dataset example:
ID
Item
Location
Qty
1
ItemA
Storage
4
2
ItemA
Active
9
3
ItemB
Storage
3
4
ItemB
Storage
2
5
ItemA
Active
1
6
ItemC
Boxed
3
7
ItemD
Active
1
8
ItemD
Storage
1
The result would look like this:
Item
Storage
Active
ItemA
4
10
ItemB
5
0
ItemD
1
1
Note that ItemC should not be included because it is not in a valid location.
What I have tried so far is:
SELECT
ITEMDESC.A,
SUM(CASE WHEN LOCATION.A='Storage' THEN QTY.A ELSE 0 END),
SUM(CASE WHEN LOCATION.B='Active' THEN QTY.B ELSE 0 END)
FROM
ITEMS A, ITEMS B
INNER JOIN
ITEMDESC.A = ITEMDESC.B
WHERE
GROUP BY
ITEMDESC.A
but this returns ALL items listed. When I add something like "WHERE Location.B = 'Storage'" then it only sums the items in the storage and all the active location items are 0.
Use a WHERE clause to only look at the locations in question:
select
item,
sum(case when location = 'Storage' then qty else 0 end) as storage,
sum(case when location = 'Active' then qty else 0 end) as active
from items
where location in ('Storage', 'Active')
group by item
order by item;
Update
You have changed the desired output in your request and only want items that are in 'Storage'. For this, just add a HAVING clause, e.g.:
select
item,
sum(case when location = 'Storage' then qty else 0 end) as storage,
sum(case when location = 'Active' then qty else 0 end) as active
from items
where location in ('Storage', 'Active')
group by item
having sum(case when location = 'Storage' then qty else 0 end) > 0
order by item;
This should give you the desired results. The other answers are including values not in 'storage'
select
item,
sum(case when location = 'Active' then qty else 0 end) as active_qty,
sum(case when location = 'Storage' then qty else 0 end) as storage_qty
from *table*
where item in (select item from *table* where location = 'Storage')
group by item
order by item;
Select item,
SUM(Case When Location = 'Storage' THEN Qty else 0 END) AS Storage,
SUM(Case When Location = 'Active' THEN Qty else 0 END) AS Active
from table1
where location in ('Storage','Active')
GROUP BY Item
http://sqlfiddle.com/#!9/7339b9a/8
You could use the WHERE clause in a subquery to identify items of interest and then JOIN to filter the rows prior to aggregation
SELECT
A.ITEMDESC,
SUM(CASE WHEN A.LOCATION='Storage' THEN A.QTY ELSE 0 END),
SUM(CASE WHEN A.LOCATION='Active' THEN A.QTY ELSE 0 END)
FROM
ITEMS A
INNER JOIN
(SELECT DISTINCT ITEMDESC FROM ITEMS WHERE LOCATION='Storage') B
ON
A. ITEMDESC = B.ITEMDESC
GROUP BY
A.ITEMDESC
Or you could filter the rows after aggregation with a HAVING clause
SELECT
ITEMDESC,
SUM(CASE WHEN LOCATION='Storage' THEN QTY ELSE 0 END),
SUM(CASE WHEN LOCATION='Active' THEN QTY ELSE 0 END)
FROM
ITEMS
GROUP BY
ITEMDESC
HAVING
MAX(CASE WHEN LOCATION='Storage' THEN 1 ELSE 0 END) > 0

Union with Group By

Had a look at other questions, tried different things but still returning more than one row.
Problem with Union on 2 tables, with group by clause. There should only be one row returned, grouped by the serviceID.
SELECT
serviceID,
serviceName,
FullCount,
WaitingCount,
InProgressCount
from (
select
a.serviceID,
serviceName,
count(applicantID) FullCount,
ISNULL(SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END),0) AS WaitingCount,
ISNULL(SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END),0) AS InProgressCount
from Products s
left join Assigns a on a.serviceID = s.productID
WHERE s.clientID = #ClientID
group by serviceID, serviceName
UNION
select
s.serviceID,
p.serviceName,
count(s.ApplicantID) FullCount,
ISNULL(SUM(CASE WHEN s.status = 0 THEN 1 ELSE 0 END),0) AS WaitingCount,
ISNULL(SUM(CASE WHEN s.status = 1 THEN 1 ELSE 0 END),0) AS InProgressCount
from Legacies s
Left Join Products p on s.serviceID = p.productID
WHERE s.client = #CompanyName
group by serviceID, serviceName
) t
GROUP BY serviceID, serviceName
I'm always getting 2 rows returned, one from each of the tables. I need to group them both together so it only returns 1 row, based on the servicedID.
The data I'm trying to return is from the following tables..
Products Table
productID serviceName
-------------------------
1 Gold Service
2 Silver Service
3 Bronze Service
Assigns Table
ApplicantID serviceID status
-------------------------------------
1 1 0
2 1 0
3 1 1
4 2 0
5 1 1
Legacies Table
ApplicantID serviceID status
-------------------------------------
1 1 0
2 1 0
3 1 0
4 2 0
5 1 1
The result I'm trying to get is one row per serviceID, to show how many applicants are on this service in both the Legacies and Assigns table, something like:-
serviceID serviceName FullCount WaitingCount InProgressCount
----------------------------------------------------------------
1 Gold Service 8 5 3
2 Silver Service 2 2 0
3 Bronze Service 0 0 0
FullCount is a total number of applicants on each service, WaitingCount is the number of applicants on the service with a status of '0' and InProgressCount is the number on this service with a status of '1'
Based on additional information, I think you can just union all the Legacies and Assigns tables.
still untested
select serviceID, servicename, count(*) fullcount
,sum(case when status = 0 THEN 1 ELSE 0 END) AS WaitingCount
,SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS InProgressCount
from (
select ApplicantID, serviceID, status
from Assigns
WHERE clientID = #ClientID
union all
select ApplicantID, serviceID, status
from Legacies
WHERE clientID = #ClientID
) combined
left join Products P on P.productID = combined.serviceID
group by serviceID, servicename
below is before edit
It's hard to tell because you do not post enough information (no sample data, no table structures, no expected output). But I think you can probably combine it all into 1 query:
untested which should be obvious with the lack of information.
SELECT isnull(a.serviceID, L.serviceID) serviceID, p.serviceName
,count(*) FullCount, SUM(CASE WHEN isnull(a.status, L.status) = 0 THEN 1 ELSE 0 END) WaitingCount
,sum(CASE WHEN isnull(a.status, L.status) = 1 THEN 1 ELSE 0 END) InProgressCount
from Legacies L
full outer join Assigns a on a.serviceID = L.serviceID
right outer join Products P on P.productID = isnull(a.serviceID, L.serviceID)
where (P.clientID = #ClientID
or L.client = #CompanyName
)
group by isnull(a.serviceID, L.serviceID), p.serviceName

how to get a name from a group by id in sql

I have a table of customers groups and I would like to select the group's names (and another value called Notes) but group by doesn't allow me to do that even though the entire group has the same value of names and notes.
select cg.idCustomerGroup,
sum(case when c.type = 'child' then 1 else 0 end) as Children,
sum(case when c.type = 'adult' then 1 else 0 end) as Adults,
sum(case when c.type = 'senior' then 1 else 0 end) as Seniors
from CustomersGroups cg
inner Join CustomersInGroup cig
on cg.idCustomerGroup = cig.idCustomerGroup
inner Join Customers c
on c.idCustomer = cig.idCustomer
Group by cg.idCustomerGroup
You have to include the group's name and notes in the GROUP BY:
select cg.idCustomerGroup, cg.Name, cg.Notes,
sum(case when c.type = 'child' then 1 else 0 end) as Children,
sum(case when c.type = 'adult' then 1 else 0 end) as Adults,
sum(case when c.type = 'senior' then 1 else 0 end) as Seniors
from CustomersGroups cg
inner Join CustomersInGroup cig
on cg.idCustomerGroup = cig.idCustomerGroup
inner Join Customers c
on c.idCustomer = cig.idCustomer
Group by cg.idCustomerGroup, cg.Name, cg.Notes

Getting sub-counts along with Average and Count

I have two tables...
MODULES ENROLMENTS
GroupNo StudentNo
Title GroupNo
Tutor CourseworkMark
DayNo ExamMark
Time
Room
Semester
I wish to create a view that displays the average mark achieved in coursework and exam for each module and also a count of the number of students who achieved >70, 60-69, 50-59, 40-49 and <40. Is this possible?
I have the average marks worked out with...
SELECT Title,
AVG(CourseworkMark) AS AverageCoursework,
AVG(ExamMark) AS AverageExam
FROM tblModules INNER JOIN tblEnrolments
ON tblModules.GroupNo = tblEnrolments.GroupNo
GROUP BY Title;
You can use a SUMmed CASE expression to do this;
SELECT Title,
AVG(CourseworkMark) AS AverageCoursework,
AVG(ExamMark) AS AverageExam,
SUM(CASE WHEN CourseworkMark > 70 THEN 1 ELSE 0 END) AS CourseworkMarkOver70,
SUM(CASE WHEN CourseworkMark BETWEEN 60 AND 70 THEN 1 ELSE 0 END) AS CourseworkMarkOver60To69,
SUM(CASE WHEN CourseworkMark BETWEEN 50 and 59 THEN 1 ELSE 0 END) AS CourseworkMarkOver50To59,
SUM(CASE WHEN CourseworkMark BETWEEN 40 and 49 THEN 1 ELSE 0 END) AS CourseworkMarkOver40To49,
SUM(CASE WHEN CourseworkMark < 40 THEN 1 ELSE 0 END) AS CourseworkMarkUnder40
FROM tblModules INNER JOIN tblEnrolments
ON tblModules.GroupNo = tblEnrolments.GroupNo
GROUP BY Title;

sql subquery that collects from 3 rows

I have a huge database with over 4 million rows that look like that:
Customer ID Shop
1 Asda
1 Sainsbury
1 Tesco
2 TEsco
2 Tesco
I need to count customers that within last 4 weeks had shopped in all 3 shops Tesco Sainsbury and Asda. Can you please advice if its possible to do it with subqueries?
This is an example of a "set-within-sets" subquery. You can solve it with aggregation:
select customer_id
from Yourtable t
where <shopping date within last four weeks>
group by customer_id
having sum(case when shop = 'Asda' then 1 else 0 end) > 0 and
sum(case when shop = 'Sainsbury' then 1 else 0 end) > 0 and
sum(case when shop = 'Tesco' then 1 else 0 end) > 0;
This structure is quite flexible. So if you wanted Asda and Tesco but not Sainsbury, then you would do:
select customer_id
from Yourtable t
where <shopping date within last four weeks>
group by customer_id
having sum(case when shop = 'Asda' then 1 else 0 end) > 0 and
sum(case when shop = 'Sainsbury' then 1 else 0 end) = 0 and
sum(case when shop = 'Tesco' then 1 else 0 end) > 0;
EDIT:
If you want a count, then use this as a subquery and count the results:
select count(*)
from (select customer_id
from Yourtable t
where <shopping date within last four weeks>
group by customer_id
having sum(case when shop = 'Asda' then 1 else 0 end) > 0 and
sum(case when shop = 'Sainsbury' then 1 else 0 end) > 0 and
sum(case when shop = 'Tesco' then 1 else 0 end) > 0
) t