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

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

Related

Case when statement with summed values in 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.

Concetating results from Oracle table with several criterias

This is a tough one. I've read about concatating values from multible rows in a table, but can't find anything on how to go about the task set before me.
I'm not an oracle-man, and untill now have only made simple select queries, so I'm at a loss here.
In a huge oracle database table (severel hundred millions of rows) containing laboratory results, I need to select information on specific requisitions, that meet a specific criteria.
Criteria: For the same ReqNo, Analysis A B and C must be present with an answer, if they are, any instance of the answer to analysis X, Y or Z should be selected
Table contents:
ReqNo Ana Answer
1 A 7
1 B 14
1 C 18
1 X 250
2 A 8
2 X 35
2 Y 125
3 A 8
3 B 16
3 C 20
3 Z 100
4 X 115
4 Y 355
5 A 6
5 B 15
5 C 22
5 X 300
5 Y 108
5 C 88
Desired result:
ReqNo A B C X Y Z
1 7 14 18 250
3 8 16 20 100
5 6 15 22 300 108 88
leaving out ReqNo 2 and 4, since they don't meet the A/B/C criteria.
Is that even possible?
You may first filter the records that have all 3 (A,B and C) and then use PIVOT to convert them to columns for those which satisfy the criteria.
with req
AS
(
select reqno from t where ana IN ('A','B','C')
GROUP BY reqno HAVING
count(DISTINCT ana) = 3
)
select * FROM
(
select * from t where
exists ( select 1 from req r where t.reqno = r.reqno )
)
PIVOT(
min(answer) for ana in ('A' as A, 'B' as B, 'C' as C,
'X' as X, 'Y' as Y, 'Z' as Z)
) ORDER BY reqno;
Demo
I would just use conditional aggregation:
select reqno,
max(case when Ana = 'A' then Answer end) as a,
max(case when Ana = 'B' then Answer end) as b,
max(case when Ana = 'C' then Answer end) as c,
max(case when Ana = 'X' then Answer end) as x,
max(case when Ana = 'Y' then Answer end) as y,
max(case when Ana = 'Z' then Answer end) as z
from t
group by reqno
having sum(case when Ana = 'A' then 1 else 0 end) > 0 and
sum(case when Ana = 'B' then 1 else 0 end) > 0 and
sum(case when Ana = 'C' then 1 else 0 end) > 0 ;
Given that you don't seem to have duplicates, you can simplify the having to:
having sum(case when Ana in ('A', 'B', 'C') then 1 else 0 end) = 3

Sql Query Output Join with another table

I have a query which gives me the following output :
select
PD.ProductId, TotalCalls = COUNT(DISTINCT PD.LogId),
TrueCalls = COUNT(DISTINCT case when PD.ExceptionCode = ' ' then PD.LogId END),
ErrorCalls =COUNT(DISTINCT case when PD.ExceptionCode != ' ' then PD.LogId END),
PassPercentage = CONVERT(DECIMAL(10,1),100 - (CAST(COUNT(DISTINCT case when PD.ExceptionCode != ' ' then PD.LogId END) as float)/CAST(COUNT(PD.LogId) as float)*100))
from
Log P
INNER JOIN LogProduct PD ON P.LogId = PD.LogId
WHERE
(ResponseTime < '2013-09-28' and RequestTime > '2013-09-01')
Group By
PD.ProductId
It gives me the following output :
ProductId TotalCalls TrueCalls ErrorCalls PassPercentage
1 6 6 0 100.0
2 1 0 1 85.7
3 33 15 18 92.2
Now I have another Table :
Levels :
LevelId Min Max Bool ProductId
1 100 100 0 2
2 80 99 0 2
3 60 79 0 2
4 40 59 0 2
5 1 39 1 2
6 0 0 0 2
7 -1 -1 0 2
1 100 100 0 1
2 80 99 0 1
3 60 79 1 1
4 40 59 0 1
5 1 39 0 1
6 0 0 0 1
7 -1 -1 0 1
What I would like to do is compare the output of the first query and add a new LevelId column :
example :
I am looking for an output like this :
ProductId TotalCalls TrueCalls ErrorCalls PassPercentage LevelId
1 6 6 0 100.0 1
2 1 0 1 85.7 2
The logic here is that : I would like to compare the PassPercentage for each row for that particular product and find out which level it falls in .
In the example above : PassPercentage is 85.7 for product 2 . If you check the Levels table above for ProductId 2 ,
Level 2 should be chosen as 80 < 87.5 < 99
I cannot figure out How I can do this..
Please let me know how I go forward from here ... or give me ideas of what I ought to do ??
The query would look like
with stats as (
select
PD.ProductId, TotalCalls = COUNT(DISTINCT PD.LogId),
TrueCalls = COUNT(DISTINCT case when PD.ExceptionCode = ' ' then PD.LogId END),
ErrorCalls =COUNT(DISTINCT case when PD.ExceptionCode != ' ' then PD.LogId END),
PassPercentage = CONVERT(DECIMAL(10,1),100 - (CAST(COUNT(DISTINCT case when PD.ExceptionCode != ' ' then PD.LogId END) as float)/CAST(COUNT(PD.LogId) as float)*100))
from
Log P
INNER JOIN LogProduct PD ON P.LogId = PD.LogId
WHERE
(ResponseTime < '2013-09-28' and RequestTime > '2013-09-01')
Group By
PD.ProductId
)
select s.*, l.LevelId
from stats s
join levels l on l.ProductId = s.ProductId and s.PassPercentage between l.Min and l.Max

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.

sql query different column based on input

I am using MS-SQL 2008. I have a table with different columns based on locations in it that will have a 'Y' or Null value. The table also has other data other than location from survey results. I have set up a temptable #TempLocation to hold the location based on the one or all. I need to select rows from the table based on 'Y' from one or more location rows within a date range.
TableID Northwest Northeast Southwest Southeast Batchno first_choice date_completed
1 Y Y Y 1 A 2012-11-10
2 Y Y 1 SA 2012-19-10
3 Y Y 1 N 2012-07-10
4 Y Y Y 2 A 2012-10-10
5 Y 2 A 2012-16-10
6 Y Y 2 D 2012-21-10
7 Y NULL A 2012-19-10
8 Y Y Y Y 3 SA 2012-11-10
9 Y 3 A 2012-10-10
10 Y Y 3 A 2012-07-10
I have created a Dynamic SQL statement to pull one location successfully but is it possible to pull all of them?
select ''' + (SELECT * FROM #TempLocation) + ''',
count(batchno),
count(case when first_choice is not null then batchno end),
count(case when t.First_choice =''SD'' then 1 end) ,
count(case when t.First_choice=''D'' then 1 end) ,
count(case when t.First_choice=''N'' then 1 end) ,
count(case when t.First_choice=''A'' then 1 end) ,
count(case when t.First_choice=''SA'' then 1 end)
from customer_satisfaction_survey t
where t.date_completed>= ''' + CAST(#beg_date AS VARCHAR) + '''
and t.date_completed < ''' + CAST(dateadd(day,1,#end_date) AS Varchar) + '''
and t.' + (SELECT * FROM #TempLocation) + ' = ''Y'''
An All result would look like this.
Number Location Total Total2 SA A N D SD
1 Northwest 6 6 1 3 1 1 0
2 Northeast 5 4 2 2 1 0 0
3 Southwest 4 4 1 3 0 0 0
4 Southeast 6 6 2 3 0 1 0
I have to think that you are approaching this in the wrong way, because your data is not normalized. The first thing you should do is to normalize the data using UNPIVOT. I'm assuming that you are using SQL Server, since your syntax suggests that. It is a good idea to tag all questions with the database, though.
You can unpivot your data with a statement such as:
select BatchNo, FirstChoice, DateCompleted, Location
from d
unpivot (val for location in (Northwest, Northeast, Southwest, Southeast)) as unpvt
Next, set up your temporary table to have a separate row for each location. Then, you can do the join with no dynamic SQL. Something like:
with dnorm as (
THE NORMALIZATION QUERY HERE
)
select dnorm.location, count(*) as total,
sum(case when dnorm.first_choice is not null then 1 else 0 end) as total2,
sum(case when dnorm.first_choice = 'SA' then 1 else 0 end) as SA,
. . .
from dnorm join
#TempLocation tl
on dnorm.location = tl.location
where ALL YOUR WHERE CONDITIONS HERE
The final query looks something like:
with dnorm as (
select BatchNo, FirstChoice, DateCompleted, Location
from d
unpivot (val for location in (Northwest, Northeast, Southwest, Southeast)) as unpvt
)
select dnorm.location, count(*) as total,
sum(case when dnorm.first_choice is not null then 1 else 0 end) as total2,
sum(case when dnorm.first_choice = 'SA' then 1 else 0 end) as SA,
. . .
from dnorm join
#TempLocation tl
on dnorm.location = tl.location
where ALL YOUR WHERE CONDITIONS HERE
The dynamic SQL approach is quite clever, but I don't think it is the simplest way to approach this.