Subquery - case when - subselect - group by - count - sql

Given 2 tables, I need a grouped summary of the awards for a shopping. More items means more awards.
tab_basket
id
name
shopping_no
type
001
Mike
00001
A
002
Mike
00001
A
003
Mike
00001
A
004
Tom
00002
B
005
Tom
00002
B
006
Tony
00003
A
007
Heinz
00004
A
tab_award
items
award
type_award
1
0.05
A
2
0.50
A
3
0.90
A
4
1.00
A
1
0.15
B
2
0.70
B
3
1.10
B
4
1.30
B
I need following table as result.
award
items
shopping_no
type _award
0.90
3
00001
A
0.70
2
00002
B
0.05
1
00003
A
0.05
1
00004
A
My solution attempt. I every time only get null as result for award (the else result) or the error "sql0811 result of select more than one row".
I am happy for any help, thanks!
SELECT award,
items,
shopping_no,
type_award
FROM tab_basket t
LEFT OUTER JOIN tab_award u ON t.TYPE = u.type_award
AND CASE WHEN (SELECT COUNT(shopping_no)
FROM tab_basket m
WHERE t.id = m.id
AND t.name = m.name
AND t.shopping_no = m.shopping_no
AND t.TYPE = u.TYPE
GROUP BY shopping_no ) = '1' THEN '1'
case WHEN (SELECT COUNT(shopping_no)
FROM tab_basket m
WHERE t.id = m.id
AND t.name = m.name
AND t.shopping_no = m.shopping_no
AND t.TYPE = u.TYPE
GROUP BY shopping_no ) = '2' THEN '2'
CASE WHEN (SELECT COUNT(shopping_no)
FROM tab_basket m
WHERE t.id = m.id
AND t.name = m.name
AND t.shopping_no = m.shopping_no
AND t.TYPE = u.TYPE
GROUP BY shopping_no ) = '3' THEN '3'
CASE WHEN (SELECT COUNT(shopping_no)
FROM tab_basket m
WHERE t.id = m.id
AND t.name = m.name
AND t.shopping_no = m.shopping_no
AND t.TYPE = u.TYPE
GROUP BY shopping_no ) = '4' THEN '4'
ELSE 'no_price'
END = u.award

You can first aggregate your tab_basket table and then can join that with tab_award table -
SELECT TB.award, TB.items, TA.shopping_no, TB.type_award
FROM (SELECT name, shopping_no, type, COUNT(*) CNT
FROM tab_basket
GROUP BY name, shopping_no, type) TB
JOIN tab_award TA ON TB.type = TA.type_award
AND TB.CNT = TA.items

Related

Select Containers having subitems in given range

I have two tables:
Project:
Id
Name
1
ABC
2
DEF
3
GHI
4
JKL
5
MNO
6
PQR
Attachment:
Id
Status
ProjectId
1
a1
1
2
a1
1
3
a2
2
4
a2
2
5
a1
3
6
a2
3
7
a1
4
8
a2
4
9
a3
4
10
a1
5
11
a2
5
12
a4
5
I'd like to get projectnames which has assignments only with statuses a1 and a2 (must have both of them) and doesn't havve assignments in statuses a3 and a4.
So the result should be:
GHI
What I've tried wass:
select distinct
p.Name
from
Attachment a
inner join Project p on p.Id = a.ProjectId
group by
p.Name, a.status
having (a.Status = 'a1' or a.Status = 'a2'
You can use exist|not exists keywords to achieve this.
select [Name] from Project t1
where
exists (select 1 from Attachment where [Status] = 'a1' and ProjectId = t1.Id)
and
exists (select 1 from Attachment where [Status] = 'a2' and ProjectId = t1.Id)
and
not exists (select 1 from Attachment where [Status] = 'a3' and ProjectId = t1.Id)
and
not exists (select 1 from Attachment where [Status] = 'a4' and ProjectId = t1.Id)
use String_agg,Subquery and join to get your desired result
SELECT name
FROM (SELECT P.name,
String_agg(status, ',')
within GROUP (ORDER BY status) Status
FROM project P
join attachment A
ON A.projectid = P.id
GROUP BY P.name) t
WHERE status = 'a1,a2'
dbfiddle
You can use count distinct in combination with NOT IN:
select P.name
from Project P
left join Attachment A on P.id = A.ProjectId
where p.id not in (select A2.ProjectId
from Attachment A2
where A2.status in ('a3', 'a4'))
and A.status in ('a1', 'a2')
group by P.name
having count(distinct A.status) = 2
Here is a demo

SQL Server Join Query returns redundant rows

I have two table
tblMaster tblTrans
ID Desc ID IDMaster Qty Garage
== ====== == ========= ===== =====
1 Type1 1 1 1 1
2 Type2 2 2 2 1
3 1 3 2
4 2 2 2
5 1 2 3
6 2 4 3
And i want this output when i join them :
ID Desc Garage1Qty Garagae2Qty Garage3Qty Garage4Qty
== ==== =========== =========== =========== ==========
1 Type1 1 3 2 null
2 Type2 2 2 4 null
Note that the "Garage" value is something that could be added in the future. So how do i achieve that? Tried this one:
SELECT M.*, Garage1Qty.*, Garage2Qty.* FROM tblMaster M
LEFT JOIN ( SELECT a.Id, b.Qty FROM tblMaster a JOIN tblTrans b on a.Id =b.Id WHERE Garage = 1 ) as Garage1Qty on Garage1Qty.Id = M.Id )
LEFT JOIN ( SELECT a.Id, b.Qty FROM tblMaster a JOIN tblTrans b on a.Id =b.Id WHERE Garage = 2 ) as Garage2Qty on Garage2Qty.Id = M.Id )
but it always returns something like:
ID Desc Garage1Qty Garage2Qty Garage3Qty Garage4Qty
== ==== =========== =========== =========== ==========
1 Type1 1 null null null
1 Type1 null 3 null null
1 Type1 null null 2 null
2 Type2 2 null null null
2 Type2 null 2 null null
2 Type2 null null 4 null
Well, you could use subquery
select ID, Desc,
(select sum(Qty) from tblTrans where IDMaster = m.ID and Garage = 1) as Garage1Qty,
(select sum(Qty) from tblTrans where IDMaster = m.ID and Garage = 2) as Garage2Qty,
(select sum(Qty) from tblTrans where IDMaster = m.ID and Garage = 3) as Garage3Qty,
(select sum(Qty) from tblTrans where IDMaster = m.ID and Garage = 4) as Garage4Qty
from tblMaster m;
In other way, you could also do that via conditional aggregation
select m.ID, m.Desc,
sum(case when t.Garage = 1 then t.Qty else 0 end) as Garage1Qty,
...
sum(case when t.Garage = 4 then t.Qty else 0 end) as Garage4Qty
from tblMaster m left join tblTrans t
on t.IDMaster = m.ID
group by m.ID, m.Desc;

Optimizing SQL query having DISTINCT keyword and functions

I have this query that generates about 40,000 records and the execution time of this query is about 1 minute 30 seconds.
SELECT DISTINCT
a.ID,
a.NAME,
a.DIV,
a.UID,
(select NAME from EMPLOYEE where UID= a.UID and UID<>'') as boss_id,
(select DATE(MAX(create_time)) from XYZ where XYZ_ID= 1 and id = a.ID) as TERM1,
(select DATE(MAX(create_time)) from XYZ where XYZ_ID= 2 and id = a.ID) as TERM2,
(select DATE(MAX(create_time)) from XYZ where XYZ_ID= 3 and id = a.ID) as TERM3,
(select DATE(MAX(create_time)) from XYZ where XYZ_ID= 4 and id = a.ID) as TERM4,
(select DATE(MAX(create_time)) from XYZ where XYZ_ID= 5 and id = a.ID) as TERM5,
(select DATE(MAX(create_time)) from XYZ where XYZ_ID= 6 and id = a.ID) as TERM6,
(select DATE(MAX(create_time)) from XYZ where XYZ_ID= 7 and id = a.ID) as TERM7,
(select DATE(MAX(create_time)) from XYZ where XYZ_ID= 8 and id = a.ID) as TERM8
FROM EMPLOYEE a
WHERE ID LIKE 'D%'
I tried using group by, different kinds of join to improve the execution time but couldn't succeed.Both the tables ABC and XYZ are indexed.
Also, I think that the root cause of this problem is either the DISTINCT keyword or the MAX function.
How can I optimize the above query to bring down the execution time to at least less than a minute?
Any help is appreciated.
Query is not tested, this is just an idea on how you could get this done in two different ways.
(SQL Server solutions here)
Using LEFT JOIN for each ID should look something like this:
SELECT a.ID,
a.NAME,
a.DIV,
a.UID,
b.Name as boss_id,
MAX(xyz1.create_time) as TERM1,
MAX(xyz2.create_time) as TERM2,
MAX(xyz3.create_time) as TERM3,
MAX(xyz4.create_time) as TERM4,
MAX(xyz5.create_time) as TERM5,
MAX(xyz6.create_time) as TERM6,
MAX(xyz7.create_time) as TERM7,
MAX(xyz8.create_time) as TERM8
FROM EMPLOYEE a
JOIN EMPLOYEE b on a.UID = b.UID and b.UID <> ''
LEFT JOIN XYZ xyz1 on a.ID = xyz1.ID and xyz1.XYZ_ID = 1
LEFT JOIN XYZ xyz2 on a.ID = xyz2.ID and xyz1.XYZ_ID = 2
LEFT JOIN XYZ xyz3 on a.ID = xyz3.ID and xyz1.XYZ_ID = 3
LEFT JOIN XYZ xyz4 on a.ID = xyz4.ID and xyz1.XYZ_ID = 4
LEFT JOIN XYZ xyz5 on a.ID = xyz5.ID and xyz1.XYZ_ID = 5
LEFT JOIN XYZ xyz6 on a.ID = xyz6.ID and xyz1.XYZ_ID = 6
LEFT JOIN XYZ xyz7 on a.ID = xyz7.ID and xyz1.XYZ_ID = 7
LEFT JOIN XYZ xyz8 on a.ID = xyz8.ID and xyz1.XYZ_ID = 8
WHERE a.ID LIKE 'D%'
GROUP BY a.ID, a.NAME, a.DIV, a.UID, b.Name
Using PIVOT would look something like this:
select * from (
SELECT DISTINCT
a.ID,
a.NAME,
a.DIV,
a.UID,
b.NAME as boss_id,
xyz.xyz_id,
xyz.create_time
FROM EMPLOYEE a
JOIN EMPLOYEE b on a.UID = b.UID and b.UID <> ''
LEFT JOIN (SELECT DATE(MAX(create_time)) create_time, XYZ_ID, ID
from XYZ
where XYZ_ID between 1 and 8
group by XYZ_ID, ID) xyz on a.ID = xyz1.ID
WHERE a.ID LIKE 'D%') src
PIVOT (
max(create_time) for xyz_id IN (['1'], ['2'], ['3'], ['4'],
['5'], ['6'], ['7'], ['8'])
) PIV
Give it a shot
I would recommend group by and conditional aggregation:
SELECT e.ID, e.NAME, e.DIV, e.UID,
DATE(MAX(CASE WHEN XYZ_ID = 1 THEN create_time END)) as term1,
DATE(MAX(CASE WHEN XYZ_ID = 2 THEN create_time END)) as term2,
DATE(MAX(CASE WHEN XYZ_ID = 3 THEN create_time END)) as term3,
DATE(MAX(CASE WHEN XYZ_ID = 4 THEN create_time END)) as term4,
DATE(MAX(CASE WHEN XYZ_ID = 5 THEN create_time END)) as term5,
DATE(MAX(CASE WHEN XYZ_ID = 6 THEN create_time END)) as term6,
DATE(MAX(CASE WHEN XYZ_ID = 7 THEN create_time END)) as term7,
DATE(MAX(CASE WHEN XYZ_ID = 8 THEN create_time END)) as term8
FROM EMPLOYEE e LEFT JOIN
XYZ
ON xyz.ID = e.id
WHERE e.ID LIKE 'D%'
GROUP BY e.ID, e.NAME, e.DIV, e.UID;
I don't understand the logic for boss_id, so I left that out. This should improve the performance significantly.

How to merge rows in select query result set

My result of is like this
date acc cr date acc dr
---------------------------------------------------
null null 0 13/3/12 A 1300
null null 0 13/3/12 c 1200
null null 0 13/3/12 D 1100
13/3/12 A 1000 null null 0
18/3/12 E 2000 null null 0
19/3/12 F 3000 null null 0
31/3/12 G 3000 null null 0
this result i got from following query bu joining 2 tables to get cash book
select(case when mli.voucher_type = 1 THEN tav.voucher_date end)pdate,
(case when mli.voucher_type = 1 THEN mli.description end) acc,
(case when tav.voucher_type_id = 1 then sum(tvl.amount) else 0 end) cr,
(case when mli.voucher_type = 2 THEN tav.voucher_dateend) rdate,
(case when mli.voucher_type = 2 THEN mli.description end) acc,
(case when tav.voucher_type_id = 2 then sum(tvl.amount) else 0 end) dr
from t_acc_voucher tav
join t_voucher_ledger tvl on tvl.voucher_id = tav.voucher_id
join m_ledger_index mli on mli.ledger_index_id = tvl.ledger_index_id
group by mli.description, mli.voucher_type, tav.voucher_type_id,tav.voucher_date
I want result like this
date acc cr date acc dr
---------------------------------------------------
13/3/12 A 1000 13/3/12 A 1300
18/3/12 E 2000 13/3/12 c 1200
19/3/12 F 3000 13/3/12 D 1100
31/3/12 G 3000 null null 0
can any body help me.or give some suggestion is it write way to get it or i can try with 2 diffrent query.
thanks in advance
Break your query into two separate queries, one for credit and another for debit and do a full outer join based on descending date, you can order by any column in fact. From the original query, I assumed that VOUCHER_TYPE = 1 is credits and VOUCHER_TYPE = 2 is debit.
Try this (not tested)
with CREDITS as (select TAV.VOUCHER_DATE PDATE,
MLI.DESCRIPTION ACC,
(case when TAV.VOUCHER_TYPE_ID = 1 then SUM(TVL.AMOUNT) else 0 end) CR
from t_acc_voucher tav
join t_voucher_ledger tvl
on tvl.voucher_id = tav.voucher_id
join M_LEDGER_INDEX MLI
on MLI.LEDGER_INDEX_ID = TVL.LEDGER_INDEX_ID
where MLI.VOUCHER_TYPE = 1
group by MLI.DESCRIPTION,
MLI.VOUCHER_TYPE,
TAV.VOUCHER_TYPE_ID,
TAV.VOUCHER_DATE),
debits as (select TAV.VOUCHER_DATE RDATE,
MLI.DESCRIPTION ACC,
(case when TAV.VOUCHER_TYPE_ID = 2 then SUM(TVL.AMOUNT) else 0 end) DR
from T_ACC_VOUCHER TAV
where mli.voucher_type = 2
join t_voucher_ledger tvl
on tvl.voucher_id = tav.voucher_id
join M_LEDGER_INDEX MLI
on MLI.LEDGER_INDEX_ID = TVL.LEDGER_INDEX_ID
group by MLI.DESCRIPTION,
MLI.VOUCHER_TYPE,
TAV.VOUCHER_TYPE_ID,
TAV.VOUCHER_DATE )
select T1.PDATE, T1.ACC, T1.CR, T2.RDATE, T2.ACC, T2.DR
from (select a.*, row_number() over (order by a.PDATE) RN
from credits a) T1
full outer join
select b.*, row_number() over (order by b.RDATE) RN
from debits b) T2
on (t1.rn = t2.rn);
select t1.date1, t1.acc1, t1.cr, t2.date2, t2.acc2, t2.dr
from (the table or query) t1
join (the table or query) t2 on t1.acc1=t2.acc2
where t1.acc1 is not null
we join the same query (or table) twice joinint to itself by acc.

Group by does not show all the rows

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