Case when statement with summed values in SQL - sql

I have a dataset with two columns. I want to categorise one of the columns into bins, and then sum the values in the other column that are within each bin.
I have tried the following code
select DISTINCT (
CASE WHEN H=1 THEN '1'
WHEN H BETWEEN 2 AND 3 THEN '2-3'
WHEN H BETWEEN 4 AND 6 THEN '4-6'
ELSE '' END
) AS H , sum(V) [V]
from
TABLE1 inner join TABLE 2 on TABLE1.X=TABLE2.X
where
TABLE.X=1 and Y='id'
GROUP BY H
ORDER BY H ASC
The table below gives a sample of my data (where H and V are headers)
H V
1 100
1 1000
1 1500
2 300
3 500
4 9000
5 800
6 1100
My desired output is
H V
1 2600
2 TO 3 800
4 TO 6 10900
However, I am getting (ie. duplicated bins as column V is not being summed across all values in each bin)
H V
1 100
1 1000
1 1500
2-3 300
2-3 500
4-6 9000
4-6 800
4-6 1100

You seem to want aggregation on a computed column:
select (CASE WHEN H = 1 THEN '1'
WHEN H BETWEEN 2 AND 3 THEN '2-3'
WHEN H BETWEEN 4 AND 6 THEN '4-6'
ELSE ''
END) AS H , sum(V) as V
from TABLE1 inner join
TABLE2
on TABLE1.X = TABLE2.X
where TABLE.X = 1 and Y = 'id'
GROUP BY (CASE WHEN H = 1 THEN '1'
WHEN H BETWEEN 2 AND 3 THEN '2-3'
WHEN H BETWEEN 4 AND 6 THEN '4-6'
ELSE ''
END)
ORDER BY MIN(H) ASC;
You should qualify all column references in the query.
SELECT DISTINCT is almost never appropriate with GROUP BY.

Related

How to implement sequence in the SQL query

I have a query in SQL Server which gives me the count of policies according to AGE and SEX as mentioned below.
SELECT
PLYMMRAGE as AGE,
MMRSEX as SEX,
COUNT(PLYNO) AS POLICYCOUNT
FROM
ADMGMPLYMSR
GROUP BY
PLYMMRAGE, MMRSEX
ORDER BY
MMRSEX DESC, PLYMMRAGE
Output of this query is:
AGE SEX POLICYCOUNT
------------------------
2 M 10
4 M 9
5 M 6
8 M 0
1 F 4
2 F 6
4 F 0
But I want that even if the age is not present the row should display for all ages with policy count as 0 till age 10.
AGE SEX POLICYCOUNT
-------------------------
1 M 0
2 M 10
3 M 0
4 M 9
5 M 6
6 M 0
7 M 0
8 M 0
9 M 0
10 M 0
1 F 4
2 F 6
4 F 0
and so on.
How can I insert the data with zero count if the age for that row is not present? Even if using procedure is fine
You can use a LEFT JOIN and coalesce together
with ADMGMPLYMSR( PLYMMRAGE, MMRSEX, PLYNO ) as
(
select 2,'M',10 union all
select 4,'M', 9 union all
select 5,'M', 6 union all
select 8,'M', 0 union all
select 1,'M', 4 union all
select 2,'M', 6 union all
select 4,'M', 0
), t AS (
SELECT 1 AS n
UNION ALL
SELECT n+1 FROM t WHERE n+1<=10
)
SELECT COALESCE(PLYMMRAGE,n) as AGE, COALESCE(MMRSEX,'M') as SEX,
COUNT(PLYNO) AS POLICYCOUNT
FROM t
LEFT JOIN ADMGMPLYMSR ON PLYMMRAGE = n
GROUP BY coalesce(PLYMMRAGE,n), MMRSEX
ORDER BY coalesce(MMRSEX,'M') DESC, coalesce(PLYMMRAGE,n);
Demo

Sum Amount, display full resultset where Groups of Column Values Match in Same Table Oracle SQL

I need to get the sum of all TOTAL fields where the ID, RECNO and DRCR fields have the same value, while also displaying these fields in the result set.
eg
ID RECNO SECRECNO DRCR TOTAL
1 9 5 D 25
1 9 12 D 22
1 9 6 C 33
1 9 5 D 50
1 8 2 D 12
1 8 2 C 23
2 9 5 D 100
So the results of the query should be
ID RECNO SECRECNO DRCR SUM(TOTAL)
1 9 5 D 75
1 9 12 D 22
1 9 6 C 33
1 8 2 D 12
1 8 2 C 23
2 9 5 D 100
This query will give the results set, without the TOTAL:
select distinct t1.recno, t1.secrecno
from table t1, table t2
where t1.recno = t2.recno and t.id = '1' and t1.drcr = 'D'
But I can't see how to SUM the TOTAL of these results.
How to do this?
select t1.id,
t1.recno,
t1.secrecno,
t1.drcr,
SUM( TOTAL )
from table t1
INNER JOIN
table t2
ON ( t1.recno = t2.recno )
WHERE t1.id = '1'
AND t1.drcr = 'D'
GROUP BY
t1.id,
t1.recno,
t1.secrecno,
t1.drcr

Showing rows with greatest count of a specific value of a column

I have this query:
select
d.sdealer_number
,c.icontract_term
,case when (c.icontract_term / 12) = 0 THEN cast(c.icontract_term as varchar) + ' M' ELSE cast((c.icontract_term / 12) as varchar) + ' Y' END as Term
,count(c.icontract_term) as [Count]
from dealers d
inner join contracts c on c.sdealer_number = d.sdealer_number
where d.sdealer_number not like '%demo%'
group by c.icontract_term, d.sdealer_number
order by d.sdealer_number
Which returns this result set:
sdealer_number icontract_term Term Count
DL00001 84 7 Y 3
DL00001 12 1 Y 12
DL00001 48 4 Y 15
DL00001 60 5 Y 2
DL00001 24 2 Y 2
DL00001 3 3 M 1
DL00001 6 6 M 5
DL00001 36 3 Y 1
DL00002 84 7 Y 4
DL00002 48 4 Y 2
DL00002 6 6 M 35
DL00002 3 3 M 8
DL00002 12 1 Y 8
DL00002 36 3 Y 2
DL00007 36 3 Y 1
DL00007 12 1 Y 1
DL00007 60 5 Y 4
DL00007 24 2 Y 2
DL00007 48 4 Y 9
DL00007 84 7 Y 1
I need to filter the result set and only show rows where 4 Y (48 month term) and 5 Y (60 month term) are the majority of contracts sold.
So, in the above example DL00001 should not show up nor should DL00002, but DL00007 should show up because they have more 4-5 Y terms contracts sold than any other contract type they sell.
EDIT:
Here is solution used with credit going to #MWillemse:
; with t as (select d.sdealer_number, sum(case when c.icontract_term in (48,60) then 1 else 0 end) as '4-5 Yeam Term', sum(case when c.icontract_term not in (48,60) then 1 else 0 end) as 'Non 4-5 Yeam Term'
from dealers d
inner join contracts c on c.sdealer_number = d.sdealer_number
where d.sdealer_number not like '%demo%'
group by d.sdealer_number)
select * from t
where t.[4-5 Yeam Term] > t.[Non 4-5 Yeam Term]
order by sdealer_number
Group by you dealer_number and conditionally sum the counts using a construct like this: SUM(CASE WHEN Term IN ( '4 Y', '5 Y' ) THEN [Count] ELSE 0 END) and use a having clause to filter which groups to keep.
EDIT: After rereading your query I realize you need to filter the results, not group them altogether. The query below will probably better suit your needs.
WITH
YourOriginalQuery
AS (SELECT d.sdealer_number
, c.icontract_term
, CASE WHEN (c.icontract_term / 12) = 0 THEN CAST(c.icontract_term AS VARCHAR) + ' M'
ELSE CAST((c.icontract_term / 12) AS VARCHAR) + ' Y'
END AS Term
, COUNT(c.icontract_term) AS [Count]
FROM dealers d
INNER JOIN contracts c ON c.sdealer_number = d.sdealer_number
WHERE d.sdealer_number NOT LIKE '%demo%'
GROUP BY c.icontract_term
, d.sdealer_number
) ,
Totals
AS (SELECT YOQ.*
, Y45Total = SUM(CASE WHEN Term IN ('4 Y', '5 Y') THEN 1
ELSE 0
END) OVER (PARTITION BY dealer_number)
, NY45Total = SUM(CASE WHEN Term NOT IN ('4 Y', '5 Y') THEN 1
ELSE 0
END) OVER (PARTITION BY dealer_number)
FROM YourOriginalQuery AS YOQ
)
SELECT *
FROM Totals
WHERE Totals.Y45Total > Totals.NY45Total
ORDER BY d.sdealer_number

Week based count

I have a requirement to retrieve the data in the below fashion
Weeks delay_count
0 6
1 0
2 3
3 4
4 0
5 1
6 0
7 0
8 0
9 0
10 2
11 0
12 0
13 0
14 0
15 3
Here weeks is the hard coded column from 0 to 15 and delay_count is the derived column. I have a column delay_weeks. Based on the values in this column I need to populate the values in the delay_count column (derived column)
delay_weeks column values are below.
blank
blank
blank
2
10
5
blank
3
2
10
2
3
3
3
0
0
15
22
29
Conditions:
When delay_weeks is blank or 0 then count in the delay_count column should be 1
When delay_weeks is 3 then in the delay_count column the count should be 1 under week 3
When delay_weeks is 10 then in the delay_count column the count should be 1 under week 10
When delay_weeks is greater than or equal to 15 then in the delay_count column the count should be 1 under week 15.
I wrote code like below
SELECT "Weeks", a."delay_count"
FROM (SELECT LEVEL AS "Weeks"
FROM DUAL
CONNECT BY LEVEL <= 15) m,
(SELECT VALUE, COUNT (VALUE) AS "delay_numbers"
FROM (SELECT CASE
WHEN attr11.VALUE >= 15
THEN '15'
ELSE attr11.VALUE
END
VALUE
FROM docs,
(SELECT object_id, VALUE, attribute_type_id
FROM ATTRIBUTES
WHERE attribute_type_id =
(SELECT attribute_type_id
FROM attribute_types
WHERE name_display_code =
'ATTRIBUTE_TYPE.DELAY IN WEEKS')) attr11
WHERE docs.obj_id = attr11.object_id(+)
GROUP BY VALUE) a
WHERE m."Weeks" = a.VALUE(+)
select
weeks,
nvl(cnt, 0) as delay_count
from
(select level-1 as weeks from dual connect by level < 17)
left join (
select
nvl(least(attr11.value, 15), 0) as weeks,
count(0) as cnt
from
DOCS
left join (
ATTRIBUTES attr11
join ATTRIBUTE_TYPES atr_tp using(attribute_type_id)
)
on atr_tp.name_display_code = 'ATTRIBUTE_TYPE.DELAY IN WEEKS'
and docs.obj_id = attr11.object_id
group by nvl(least(attr11.value, 15), 0)
) using(weeks)
order by 1
Reverse-engineering the relevant parts of the table definitions, I think this gives you what you want:
select t.weeks, count(delay) as delay_count
from (select level - 1 as weeks from dual connect by level <= 16) t
left join (
select case when a.value is null then 0
when to_number(a.value) > 15 then 15
else to_number(a.value) end as delay
from docs d
left join (
select a.object_id, a.value
from attributes a
join attribute_types at on at.attribute_type_id = a.attribute_type_id
where at.name_display_code = 'ATTRIBUTE_TYPE.DELAY IN WEEKS'
) a on a.object_id = d.obj_id
) delays on delays.delay = t.weeks
group by t.weeks
order by t.weeks;
With what I think is matching data I get:
WEEKS DELAY_COUNT
---------- -----------
0 6
1 0
2 3
3 4
4 0
5 1
6 0
7 0
8 0
9 0
10 2
11 0
12 0
13 0
14 0
15 3
But obviously since you haven't given the real table structures I'm guessing a bit on the relationships. Obligatory SQL Fiddle.

How do I return the sum for this query?

I have the following tables I need to find out the sum.
Table A
ID Name
1 Jason
2 Peter
3 Ravi
Table B
ID ID_SEC
1 11
1 12
1 13
2 21
2 22
2 23
3 31
3 32
3 33
Table C
ID_SEC Value Include_Ind
11 100 Y
12 200 Y
13 300 N
21 10 Y
22 20 N
23 30 N
31 1000 N
32 2000 N
33 3000 N
Output
ID Name Total Include_Ind_count [only count when Y]
1 Jason 600 2
2 Peter 60 1
3 Ravi 6000 0
Use:
SELECT a.id,
a.name,
SUM(c.value) AS total
FROM TABLE_A a
JOIN TABLE_B b ON b.id = a.id
JOIN TABLE_C c ON c.id_sec = b.id_sec
GROUP BY a.id, a.name
The trick to counting INCLUDE_IND only when the flag is set to 'Y' is to use CASE() to test its value:
SQL> select a.id
2 , a.name
3 , sum ( c.val) as total
4 , count( case when c.include_ind = 'Y' then 1
5 else null end ) as inc_ind_cnt
6 from a
7 join b on ( b.id = a.id )
8 join c on ( c.id_sec = b.id_sec )
9 group by a.name, a.id
10 order by a.id
11 /
ID NAME TOTAL INC_IND_CNT
---------- ---------- ---------- -----------
1 Jason 600 2
2 Peter 60 1
3 Ravi 6000 0
SQL>
The ORDER BY is necessary to guarantee sort order since Oracle changed the algorithm it uses for GROUP BY operations in 10g.
You can use inner Joins and SUM for getting the result -
Assuming you tableC.Value is int field. Else you need to cast it.
SELECT tabA.id, tabA.name, SUM(tabC.value)
FROM TABLE_A tabA
INNER JOIN TABLE_B tabB ON tabB.id = tabA.id
INNER JOIN TABLE_C tabc ON tabC.id_sec = tabB.id_sec
GROUP BY tabA.id, tabA.name