Group by does not show all the rows - sql

I have a table tblPersonaldata and tblStudentsadmitted
tblPersonalData
UID Name Gender
------------------------
E1 xyz M
E2 pqr M
E3 mno M
tblStudentsadmitted
UID Status Stage
----------------------
E1 Y 1
E2 Y 2
E3 Y 1
Now I want the data like this:
Gender Stage1 Stage2
M 2 1
But in this case I dont get the data for female gender. I want the data for female gender even if it is null
I have tried this:
select
case
when gender='M' then 'Male'
when gender='F' then 'Female'
end as Gender,
sum(case when Stage=1 then 1 else 0) end as Stage1,
sum(case when Stage=2 then 1 else 0) end as Stage2
from tblPersonaldata A inner join
tblStudentsadmitted B on A.UID=B.UID
where B.Status='Y'
group by Gender

SELECT CASE WHEN a.Gender = 'M' THEN 'Male' ELSE 'FEMALE' END Gender,
SUM(CASE WHEN Stage = 1 THEN 1 ELSE 0 END) Stage1,
SUM(CASE WHEN Stage = 2 THEN 1 ELSE 0 END) Stage2
FROM personal a
LEFT JOIN studentadmitted b
ON a.UID = b.UID AND b.Status = 'Y'
GROUP BY a.Gender
SQLFiddle Demo
SELECT CASE WHEN c.Gender = 'M' THEN 'Male' ELSE 'Female' END Gender,
SUM(CASE WHEN Stage = 1 THEN 1 ELSE 0 END) Stage1,
SUM(CASE WHEN Stage = 2 THEN 1 ELSE 0 END) Stage2
FROM (SELECT 'F' Gender UNION SELECT 'M' Gender) c
LEFT JOIN personal a
ON a.Gender = c.Gender
LEFT JOIN studentadmitted b
ON a.UID = b.UID AND b.Status = 'Y'
GROUP BY c.Gender
SQLFiddle Demo
OUTPUT
╔════════╦════════╦════════╗
║ GENDER ║ STAGE1 ║ STAGE2 ║
╠════════╬════════╬════════╣
║ Female ║ 0 ║ 0 ║
║ Male ║ 2 ║ 1 ║
╚════════╩════════╩════════╝

In SQL Server, you can use the PIVOT function to generate the result:
select gender,
Stage1,
Stage2
from
(
select
c.gender,
'Stage'+cast(stage as varchar(10)) Stage
from (values ('F'),('M')) c (gender)
left join tblpersonaldata p
on c.gender = p.gender
left join tblStudentsadmitted s
on p.uid = s.uid
and s.Status='Y'
)src
pivot
(
count(stage)
for stage in (Stage1, Stage2)
) piv
See SQL Fiddle with Demo.
Since you are using SQL Server 2008 this query uses the VALUES to generate the list of the genders that you want in the final result set
from (values ('F'),('M')) c (gender)
Then by using a LEFT JOIN on the other tables the final result will return a row for both the M and F values.
This can also be written using a UNION ALL to generate the list of genders:
select gender,
Stage1,
Stage2
from
(
select
c.gender,
'Stage'+cast(stage as varchar(10)) Stage
from
(
select 'F' gender union all
select 'M' gender
) c
left join tblpersonaldata p
on c.gender = p.gender
left join tblStudentsadmitted s
on p.uid = s.uid
and s.Status='Y'
)src
pivot
(
count(stage)
for stage in (Stage1, Stage2)
) piv
See SQL Fiddle with Demo
The result of both is:
| GENDER | STAGE1 | STAGE2 |
----------------------------
| F | 0 | 0 |
| M | 2 | 1 |

This is also working. Using Left joins with a new table (a table with two records for genders M & F).
Fiddle demo
select t.g Gender,
isnull(sum(case when Stage = 1 then 1 end),0) Stage1,
isnull(sum(case when Stage = 2 then 1 end),0) Stage2
from (values ('M'),('F')) t(g)
left join personal a on t.g = a.gender
left join studentadmitted b on a.uid = b.uid and b.Status = 'Y'
group by t.g
order by t.g
| GENDER | STAGE1 | STAGE2 |
----------------------------
| F | 0 | 0 |
| M | 2 | 1 |

SELECT GENDER, 0 AS 'STAGE 0', 1 AS 'STAGE 1', 2 AS 'STAGE 2'
FROM
(
SELECT P.ID, GENDER,CASE WHEN STAGE IS NULL THEN 0 ELSE STAGE END STAGE
FROM tblPersonaldata P
LEFT JOIN tblStudentsadmitted S ON P.UID = S.UID
) AS A
PIVOT
(
COUNT (ID) FOR STAGE IN ([0],[1],[2])
)P

Related

SQL - SUM within subquery

I have the following code that looks at the SalesVol of different products and groups it by transaction_week
SELECT a.transaction_week,
SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
WHERE series in (62,236,501,52)
GROUP BY a.transaction_week
ORDER BY a.transaction_week
| tw | SalesVol |
| 1 | 4768 |
| 2 | 4567 |
| 3 | 4354 |
| 4 | 4678 |
I want to be able to have multiple subqueries where I change the series numbers for example.
SELECT a.transaction_week,
(SELECT SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
WHERE series in (62,236,501,52)) as personal care
(SELECT SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
WHERE series in (37,202,203,456)) as white goods
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
GROUP BY a.transaction_week
ORDER BY a.transaction_week
I can't get the subqueries at work as it is giving me the overall sum value and not grouping it by transaction_week
Instead of using subqueries, add series to the condition of the CASE statements:
SELECT a.transaction_week,
sum(CASE WHEN series IN (62,236,501,52) AND record_type IN (6,37,13)
THEN quantity ELSE 0 END) as personal_care,
sum(CASE WHEN series IN (37,202,203,456) AND record_type IN (6,37,13)
THEN quantity ELSE 0 END) as white_goods
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
GROUP BY a.transaction_week
ORDER BY a.transaction_week;
You just miss the a.transaction_week in you subquery. The JOIN in outer query is unneccessary.
SELECT a.transaction_week,
(
SELECT SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a2
LEFT JOIN table 2 b ON b.Date = a2.transaction_date
LEFT JOIN table 3 c ON c.sku = a2.product
WHERE series in (62,236,501,52) AND a2.transaction_week = a.transaction_week
) as personal care,
(
SELECT SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a 2
LEFT JOIN table 2 b ON b.Date = a2.transaction_date
LEFT JOIN table 3 c ON c.sku = a2.product
WHERE series in (37,202,203,456) AND a2.transaction_week = a.transaction_week
) as white goods
FROM table 1 a
GROUP BY a.transaction_week
ORDER BY a.transaction_week
Try this it would work fast as well as up to your requirement:
SELECT a.transaction_week ,
whitegoods.SalesVol AS 'White Goods' ,
personalcare.SalesVol1 AS 'Personal Care'
FROM table1 a
LEFT JOIN table2 b ON b.[Date] = a.transaction_date
LEFT JOIN table3 c ON c.sku = a.product
CROSS APPLY ( SELECT SUM(CASE WHEN record_type IN ( 6, 37, 13 )
THEN quantity
ELSE 0
END) AS SalesVol
FROM table1 a2
WHERE b.[Date] = a2.transaction_date
AND c.sku = a2.product
AND series IN ( 37, 202, 203, 456 )
AND a2.transaction_week = a.transaction_week
) whitegoods
CROSS APPLY ( SELECT SUM(CASE WHEN record_type IN ( 6, 37, 13 )
THEN quantity
ELSE 0
END) AS SalesVol1
FROM table1 a2
WHERE b.[Date] = a2.transaction_date
AND c.sku = a2.product
AND series IN ( 62, 236, 501, 52 )
AND a2.transaction_week = a.transaction_week
) personalcare
GROUP BY a.transaction_week
ORDER BY a.transaction_week
You should use the UNION operator. Please refer to the query below:
select a.transaction_week, SalesVol from
(SELECT a.transaction_week as transaction_week,
SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
WHERE series in (62,236,501,52)
UNION
SELECT a.transaction_week as transaction_week,
SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
WHERE series in (37,202,203,456)
) AS tbl1
GROUP BY tbl1.transaction_week
ORDER BY tbl1.transaction_week

Aggregative sum of objects belonging to objects residing inside hierarchy structure

My problem is similar in a way to this one, yet different enough in my understanding.
I have three tables:
Units ([UnitID] int, [UnitParentID] int)
Students ([StudentID] int, [UnitID] int)
Events ([EventID] int, [EventTypeID] int, [StudentID] int)
Students belong to units, units are stacked in a hierarchy (tree form - one parent per child), and each student can have events of different types.
I need to sum up the number of events of each type per user, then aggregate for all users in a unit, then aggregate through hierarchy until I reach the mother of all units.
The result should be something like this:
My tools are SQL Server 2008 and Report Builder 3.
I put up a SQL fiddle with sample data for fun.
Use this query:
;WITH CTE(Id, ParentId, cLevel, Title, ord) AS (
SELECT
u.UnitID, u.UnitParentID, 1,
CAST('Unit ' + CAST(ROW_NUMBER() OVER (ORDER BY u.UnitID) AS varchar(3)) AS varchar(max)),
CAST(RIGHT('000' + CAST(ROW_NUMBER() OVER (ORDER BY u.UnitID) AS varchar(3)), 3) AS varchar(max))
FROM
dbo.Units u
WHERE
u.UnitParentID IS NULL
UNION ALL
SELECT
u.UnitID, u.UnitParentID, c.cLevel + 1,
c.Title + '.' + CAST(ROW_NUMBER() OVER (PARTITION BY c.cLevel ORDER BY c.Id) AS varchar(3)),
c.ord + RIGHT('000' + CAST(ROW_NUMBER() OVER (ORDER BY u.UnitID) AS varchar(3)), 3)
FROM
dbo.Units u
JOIN
CTE c ON c.Id = u.UnitParentID
WHERE
u.UnitParentID IS NOT NULL
), Units AS (
SELECT
u.Id, u.ParentId, u.cLevel, u.Title, u.ord,
SUM(CASE WHEN e.EventTypeId = 1 THEN 1 ELSE 0 END) AS EventA,
SUM(CASE WHEN e.EventTypeId = 2 THEN 1 ELSE 0 END) AS EventB,
SUM(CASE WHEN e.EventTypeId = 3 THEN 1 ELSE 0 END) AS EventC,
SUM(CASE WHEN e.EventTypeId = 4 THEN 1 ELSE 0 END) AS EventD
FROM
CTE u
LEFT JOIN
dbo.Students s ON u.Id = s.UnitId
LEFT JOIN
dbo.[Events] e ON s.StudentId = e.StudentId
GROUP BY
u.Id, u.ParentId, u.cLevel, u.Title, u.ord
), addStudents AS (
SELECT *
FROM Units
UNION ALL
SELECT
s.StudentId, u.Id, u.cLevel + 1,
'Student ' + CAST(s.StudentId AS varchar(3)),
u.ord + RIGHT('000' + CAST(s.StudentId AS varchar(3)), 0),
SUM(CASE WHEN e.EventTypeId = 1 THEN 1 ELSE 0 END),
SUM(CASE WHEN e.EventTypeId = 2 THEN 1 ELSE 0 END),
SUM(CASE WHEN e.EventTypeId = 3 THEN 1 ELSE 0 END),
SUM(CASE WHEN e.EventTypeId = 4 THEN 1 ELSE 0 END)
FROM Units u
JOIN
dbo.Students s ON u.Id = s.UnitId
LEFT JOIN
dbo.[Events] e ON s.StudentId = e.StudentId
GROUP BY
s.StudentID, u.ID, u.cLevel, u.ord
)
SELECT --TOP(10)
REPLICATE(' ', cLevel) + Title As Title,
EventA, EventB, EventC, EventD
FROM
addStudents
ORDER BY
ord
For this:
Title | EventA | EventB | EventC | EventD
-----------------+--------+---------+--------+--------
Unit 1 | 0 | 1 | 0 | 0
Student 6 | 0 | 1 | 0 | 0
Unit 1.1 | 0 | 0 | 0 | 1
Student 21 | 0 | 0 | 0 | 1
Student 33 | 0 | 0 | 0 | 0
Unit 1.1.1 | 0 | 0 | 0 | 0
Student 23 | 0 | 0 | 0 | 0
Unit 1.1.1.1 | 3 | 2 | 3 | 0
Student 10 | 0 | 0 | 0 | 0
Student 17 | 1 | 0 | 0 | 0
...
SQL Fiddle Demo
Do you need also the hierarchy to be sorted / visualized? At least this will calculate the sums, but the order of the data is pretty random :)
;with CTE as (
select S.StudentId as UnitID, S.UnitId as UnitParentID,
S.StudentID, 'Student' as Type
from Students S
union all
select U.UnitId, U.UnitParentId,
CTE.StudentId as StudentID, 'Unit ' as Type
from
Units U
join CTE
on U.UnitId = CTE.UnitParentId
)
select C.Type + ' ' + convert(varchar, C.UnitId),
sum(case when EventTypeId = 1 then 1 else 0 end) as E1,
sum(case when EventTypeId = 2 then 1 else 0 end) as E2,
sum(case when EventTypeId = 3 then 1 else 0 end) as E3,
sum(case when EventTypeId = 4 then 1 else 0 end) as E4
from
CTE C
left outer join events E on C.StudentId = E.StudentId
group by
C.Type, C.UnitId
SQL Fiddle
If you need also the hierarchy to be in order, you'll probably have add few extra CTEs to get the numbering from top down with something like #shA.t did. This gathers the hierarchy separately for each student, so it's not really possible to add the level numbers in a simple way.

How to count for each ID, how many of each Type of outcomes(1,2,3) has that ID diagnosised?

I have tried many times but i have no idea how to group the answer for each technician for each diagnosis outcome (Only three types 1,2,3) that they have? (For example, how many diagnosis outcome 1 did technicianID 54 have .)
I try to put in the count function but it is not works.Could someone give me some idea in SQL?
It is what i found in the database.
TechnicianID FirstName
------------ -------------
54 Wayne
TechnicianID DiagnosisOutcomeID
------------ ------------------
54 1
54 2
54 1
54 3
The Result should be
TechnicianID FirstName DiagnosisOutcome=1 DiagnosisOutcome=2 DiagnosisOutcome=3
------------ -------------
54 Wayne
ThankYou
Since you have not mentioned any RDBMS, the query below will work on many RDBMS,
SELECT a.TechnicianID,
a.FirstName,
SUM(CASE WHEN b.DiagnosisOutcomeID = 1 THEN 1 ELSE 0 END) DiagnosisOutcomeID1,
SUM(CASE WHEN b.DiagnosisOutcomeID = 2 THEN 1 ELSE 0 END) DiagnosisOutcomeID2,
SUM(CASE WHEN b.DiagnosisOutcomeID = 3 THEN 1 ELSE 0 END) DiagnosisOutcomeID3
FROM Table1 a
INNER JOIN Table2 b
ON a.TechnicianID = b.TechnicianID
GROUP BY a.TechnicianID, a.FirstName
SQLFiddle Demo
If you are using RDBMS that supports PIVOT function,
SELECT TechnicianID,
FirstName,
DiagnosisOutcomeID1,
DiagnosisOutcomeID2,
DiagnosisOutcomeID3
FROM
(
SELECT a.TechnicianID,
a.FirstName,
'DiagnosisOutcomeID' + CAST(b.DiagnosisOutcomeID AS VARCHAR(5)) AS DiagnosisOutcomeID
FROM Table1 a
INNER JOIN Table2 b
ON a.TechnicianID = b.TechnicianID
) data
PIVOT
(
COUNT(DiagnosisOutcomeID)
FOR DiagnosisOutcomeID IN (DiagnosisOutcomeID1,DiagnosisOutcomeID2,DiagnosisOutcomeID3)
) pvt
ORDER BY TechnicianID
SQLFiddle Demo
The CASE statement is what you need.
Have a look at this SQL Fiddle
SELECT T.FirstName
,SUM(CASE WHEN DiagnosisOutcomeID = 1 THEN 1 ELSE 0 END) AS OUTCOME1
,SUM(CASE WHEN DiagnosisOutcomeID = 2 THEN 1 ELSE 0 END) AS OUTCOME2
,SUM(CASE WHEN DiagnosisOutcomeID = 3 THEN 1 ELSE 0 END) AS OUTCOME3
FROM Tech T
INNER JOIN
Diagnosis D
ON T.TechnicianID = D.TechnicianID
GROUP BY
T.FirstName

select data that has at least P and R

I have a table named Table1 as shown below:
ID AccountNo Trn_cd
1 123456 P
2 123456 R
3 123456 P
4 12345 P
5 111 R
6 111 R
7 5625 P
I would like to display those records that accountNo appears more than one time (duplicate) and trn_cd has at least both P and R.
In this case the output should be at this way:
ID AccountNo Trn_cd
1 123456 P
2 123456 R
3 123456 P
I have done this sql but not the result i want:
select * from Table1
where AccountNo IN
(select accountno from table1
where trn_cd = 'P' or trn_cd = 'R'
group by AccountNo having count(*) > 1)
Result as below which AccountNo 111 shouldn't appear because there is no trn_cd P for 111:
ID AccountNo Trn_cd
1 123456 P
2 123456 R
3 123456 P
5 111 R
6 111 R
Any idea?
Use aggregation for this. To get the account numbers:
select accountNo
from table1
having count(*) > 1 and
sum(case when trn_cd = 'P' then 1 else 0 end) > 0 and
sum(case when trn_cd = 'N' then 1 else 0 end) > 0
To get the account information, use a join or in statement:
select t.*
from table1 t
where t.accountno in (select accountNo
from table1
having count(*) > 1 and
sum(case when trn_cd = 'P' then 1 else 0 end) > 0 and
sum(case when trn_cd = 'N' then 1 else 0 end) > 0
)
This problem is called Relational Division.
This can be solved by filtering the records which contains P and R and counting the records for every AccountNo returned, and filtering it again using COUNT(DISTINCT Trn_CD) = 2.
SELECT a.*
FROM tableName a
INNER JOIN
(
SELECT AccountNo
FROM TableName
WHERE Trn_CD IN ('P','R')
GROUP BY AccountNo
HAVING COUNT(DISTINCT Trn_CD) = 2
) b ON a.AccountNO = b.AccountNo
SQLFiddle Demo
SQL of Relational Division
OUTPUT
╔════╦═══════════╦════════╗
║ ID ║ ACCOUNTNO ║ TRN_CD ║
╠════╬═══════════╬════════╣
║ 1 ║ 123456 ║ P ║
║ 2 ║ 123456 ║ R ║
║ 3 ║ 123456 ║ P ║
╚════╩═══════════╩════════╝
For faster performance, add an INDEX on column AccountNo.

SQL condition sum from two joined tables

I have two tables as below:
Invoice
InvId | Amount | Name
-----------------------
1 | 50 | John
2 | 30 | Mike
3 | 20 | John
Detail
MetalType| Weight | InvId
-------------------------
Gold | 2 | 2
Silver | 4 | 3
Silver | 3 | 3
Gold | 5 | 1
I would like to have the following output, but my query will only provide the total for silver and gold for John. How can I build a query that will also include the total invoice amount for John.
Total Invoice Amount For John = 70
Total Silver Weight = 7
total Gold Weith = 5
SELECT
SUM(IFF(D.MetalType=”Gold”, D.Weight, 0)) AS TotGold,
SUM((IFF(D.MetalType=”Silver”, D.Weight, 0)) AS TotSilver
FROM Invoice I INNER JOIN Detail D ON I.InvId = D.InvId WHERE I.Name = “John”
Try this:
For Sql-Server:
SELECT
SUM(TotalAmount) AS TotalAmount,
SUM(TotGold) AS TotGold,
SUM(TotSilver) AS TotSilver
FROM(
SELECT
SUM (I.Amount) OVER (Partition by D.Invid) AS TotalAmount,
SUM(CASE WHEN D.MetalType='Gold' THEN D.Weight ELSE 0 END) AS TotGold,
SUM(CASE WHEN D.MetalType='Silver' THEN D.Weight ELSE 0 END) AS TotSilver
FROM Invoice I INNER JOIN Detail D ON I.InvId = D.InvId
WHERE I.Name = 'John'
GROUP BY D.InvId, I.Amount) n
Here is an SQL Fiddle - now it kills the duplicate detail and counts it only once.
EDITED for Access:
SELECT
n.Name,
MAX(TotalAmount),
SUM(TotGold) AS TotGold,
SUM(TotSilver) AS TotSilver
FROM(
SELECT
I.Name,
SUM(CASE WHEN D.MetalType='Gold' THEN D.Weight ELSE 0 END) AS TotGold,
SUM(CASE WHEN D.MetalType='Silver' THEN D.Weight ELSE 0 END) AS TotSilver
FROM Invoice I
INNER JOIN Detail D ON I.InvId = D.InvId
GROUP BY I.Name, D.InvId, I.Amount) n
INNER JOIN (
SELECT
I.Name, SUM (I.Amount) AS TotalAmount
FROM Invoice I
GROUP BY I.Name) m ON m.Name = n.Name
GROUP BY n.Name
Try with this:
With tbl3 (Amt,Gold,Silver)
as
(
SELECT
SUM (I.Amount) OVER (Partition by D.Invid) AS TotalAmount,
SUM(CASE WHEN D.MetalType='Gold' THEN D.Weight ELSE 0 END) AS TotGold,
SUM(CASE WHEN D.MetalType='Silver' THEN D.Weight ELSE 0 END) AS TotSilver
FROM Invoice I Right JOIN Detail D ON I.InvId = D.InvId
WHERE I.Name = 'John' Group by D.InvId, I.Amount
)
Select SUM(Amt) as Total_Invoice_Amount_For_John,
SUM(Gold) as Total_Silver_Weight,
SUM(Silver) as Total_Gold_Width from tbl3
SQL Fiddle
I havent tried out the other queries already posted and they might already be suitable for what you want but here's my take on it :-
SELECT X.NAME, X.METALTYPE, X.WEIGHT, Y.TOTAL
FROM
(SELECT NAME, METALTYPE, SUM(Weight) AS WEIGHT
FROM INVOICE i
INNER JOIN DETAIL d ON i.InvId = d.InvId
GROUP BY NAME, METALTYPE) X
INNER JOIN
(SELECT SUM(AMOUNT) AS Total, NAME
FROM INVOICE
GROUP BY NAME)Y
ON X.NAME = Y.NAME
ORDER BY NAME, TOTAL, METALTYPE
select name, sum(Amount) as 'total invoice',sum(Gold) as 'Gold',sum(Silver) as Silver from(
select aa.Name,aa.Amount,
sum(case when bb.MetalType='Gold' then bb.Weight else 0 end) as 'Gold',
sum(case when bb.MetalType='Silver' then bb.Weight else 0 end) as 'Silver'
from a aa left outer join b bb on aa.InvID=bb.InvID group by aa.InvID) as c group by c.name