how to do math function (divide) for results of a group by function - sql

I have a table of 6 digit event numbers;
i am trying to count one event number (let's say 600189) that has occurred during a specific time period, and divide it by a count of a second event number (lets say 600122) during the same time period.
the below query gets my two counts but I want to modify the query to do the calculation (count of 600189 / count of 600122) * 100 :
select count (messageno)
from event
where timestamp > '2019-03-14' and timestamp < '2019-03-15' and
messageno in ('600122','600189')
group by messageno

I would simply use conditional aggregation:
select sum(case when messageno = '600122' then 1 else 0 end) as cnt_1,
sum(case when messageno = '600189' then 1 end) as cnt_2,
( sum(case when messageno = '600122' then 1 else 0 end) /
sum(case when messageno = '600189' then 1 end)
) as ratio
from event
where timestamp > '2019-03-14' and
timestamp < '2019-03-15' and
messageno in ('600122', '600189');
Note that the else in the denominator is removed. This is intentional. If there are no messages with that number, then this returns NULL instead of a divide-by-zero error.

There are a couple of query patterns.
One approach is conditional aggregation. We can return a value of 1 if a condition is true, otherwise return 0, and add up the 1s and 0s with a SUM aggregate to get a count. Consider:
For MySQL, we can do something like this:
SELECT SUM( IF(e.messageno='600122',1,0) ) AS cnt_600122
, SUM( IF(e.messageno='600189',1,0) ) AS cnt_600189
, ( 100.0
* SUM( IF(e.messageno='600122',1,0) )
/ SUM( IF(e.messageno='600189',1,0) )
) AS pct
FROM event e
WHERE e.timestamp > '2019-03-14'
AND e.timestamp < '2019-03-15'
AND e.messageno IN ('600122','600189')
More portable ANSI standards compliant equivalent will work for Microsoft SQL Server et al.:
SELECT SUM( CASE e.messageno WHEN '600122' THEN 1 ELSE 0 END ) AS cnt_600122
, SUM( CASE e.messageno WHEN '600189' THEN 1 ELSE 0 END ) AS cnt_600189
, ( 100.0
* SUM( CASE e.messageno WHEN '600122' THEN 1 ELSE 0 END )
/ SUM( CASE e.messageno WHEN '600122' THEN 1 ELSE 0 END )
) AS pct
FROM event e
WHERE e.timestamp > '2019-03-14'
AND e.timestamp < '2019-03-15'
AND e.messageno IN ('600122','600189')
Other approaches would be to do aggregation in an inline view(s), or use subqueries in the SELECT list:
SELECT 100.0
* ( SELECT SUM(1)
FROM event e
WHERE e.timestamp > '2019-03-14'
AND e.timestamp < '2019-03-15'
AND e.messageno IN ('600122')
)
/ ( SELECT SUM(1)
FROM event e
WHERE e.timestamp > '2019-03-14'
AND e.timestamp < '2019-03-15'
AND e.messageno IN ('600189')
)
AS pct

Related

SQL CASE WHEN THEN logics of calculating the types of a column

Have a tableA like this:
I wanna receive a tableŠ˜ like this (group by startTime and endTime, count of Severity in cnt column and count of every type of Severity in a distinct column):
The simple count (cnt column) works fine. But with the other I tired CASE WHEN THEN logics and it seems not working (line 10 for example). Can you please assist me with SQL query in this case.
You need conditional aggregation :
select starttime, endtime, count(*),
sum(case when severity = 'low' then 1 else 0 end),
sum(case when severity = 'med' then 1 else 0 end),
sum(case when severity = 'high' then 1 else 0 end)
from table t
group by starttime, endtime;
Try below query: with case when
select starttime, endtime, count(severity) as cnt, count(case when severity='LOW' then 1 end) cnt_low,count(case when severity='MED' then 1 end) cnt_med,count(case when severity='HIGH' then 1 end) as cnt_high
from tablename
group by starttime, endtime
use case when and aggregate function sum
select startTime , endTime,count(*) as Cnt,
sum( case when Severity='MED' then 1 else 0 end) as cntMed,
sum( case when Severity='LOW' then 1 else 0 end) as cntLow,
sum( case when Severity='HIGH' then 1 else 0 end) as cntHIGH from yourtable
group by startTime , endTime

How to aggregate and make a ratio between two fields from a CTE

I have a query which return a flag wheter a client who made a contract with my company this year is new or returning:
WITH Resultset AS(
SELECT
Cnt = COUNT(*)
,KliRC --personal identification number
FROM dbo.Smlouvy
WHERE VyplacenaCastka > 0
GROUP BY KliRC
)
SELECT
s.KliRC
,CASE WHEN Cnt > 1 THEN 1 ELSE 0 END AS Novy --new client
,CASE WHEN Cnt = 1 THEN 1 ELSE 0 END AS Stavajici --existing client
FROM Resultset JOIN dbo.Smlouvy s ON s.KliRC = resultset.KliRC
WHERE (YEAR(DatumZadosti) = YEAR(GETDATE())) AND (s.KliRC NOT LIKE '%x')
Now, I need to aggregate all the new and existing clients and make a ratio between them.
Any ideas? Thanks in advance.
I think this does what you want:
WITH Resultset AS (
SELECT COUNT(*) as cnt,
KliRC --personal identification number,
(CASE WHEN COUNT(*) > 1 THEN 1 ELSE 0 END) AS Novy --new client
(CASE WHEN COUNT(*) = 1 THEN 1 ELSE 0 END) AS Stavajici
FROM dbo.Smlouvy
WHERE VyplacenaCastka > 0
GROUP BY KliRC
)
SELECT SUM(Novy) / SUM(Stavajici)
FROM Resultset r JOIN
dbo.Smlouvy s
ON s.KliRC = r.KliRC
WHERE YEAR(DatumZadosti) = YEAR(GETDATE()) AND
s.KliRC NOT LIKE '%x';
Your query can be simplified to
SELECT SUM(Novy)*1.0/SUM(Stavajici)
FROM (
SELECT KliRC
,CASE WHEN COUNT(*) OVER(PARTITION BY KliRC) > 1 THEN 1 ELSE 0 END AS Novy --new client
,CASE WHEN COUNT(*) OVER(PARTITION BY KliRC) = 1 THEN 1 ELSE 0 END AS Stavajici --existing client
FROM dbo.Smlouvy
WHERE YEAR(DatumZadosti) = YEAR(GETDATE()) AND KliRC NOT LIKE '%x'
) T

SSRS: how to get top 3 in order Z to A

I try to get in my diagram the top 3 of the worst value in SSRS:
my Code:
SELECT *
FROM (
Select top 3
intervaldate as Datum
,Name
,teamname as Team
,SUM(case when CounterName = 'Blown away' then calculationUnits else 0 end) as Blown
,Sum(case when CounterName = 'Thrown away' then calculationUnits else 0 end) as Thrown
,Sum(case when CounterName = 'total' then calculationUnits else 0 end) as Total
from Counting
where IntervalDate >= dateadd(day,datediff(day,1,GETDATE()),0)
AND IntervalDate < dateadd(day,datediff(day,0,GETDATE()),0)
and Name in (Select SystemID from tSystemView where SystemViewID = 2)
group by intervaldate, teamName, Name
) c
Expression of the diagram:
=Sum(Fields!Blown.Value + Fields!Thrown.Value) / Sum(Fields!Total.Value) * 100
And I sorted it from highest to lowest
But it does not show me the right order.
If I choose every "Name" then it shows me other value then the top 3:
all Names with value:
top 3:
It's because your top 3 statement is in the SQL while your sort is in the report. Without an order by SQL picks the top 3 random records. Also, unless there is more SQL you are not showing, the outer select is unnecessary. Add an order by <column> desc below your group by.
with Calcs as
(
select intervaldate as Datum,
Name,
TeamName,
SUM(case when CounterName = 'Blown away' then calculationUnits else 0 end) as Blown,
Sum(case when CounterName = 'Thrown away' then calculationUnits else 0 end) as Thrown,
Sum(case when CounterName = 'total' then calculationUnits else 0 end) as Total
from Counting
where IntervalDate >= dateadd(day,datediff(day,1,GETDATE()),0)
AND IntervalDate < dateadd(day,datediff(day,0,GETDATE()),0)
and Name in (Select SystemID from tSystemView where SystemViewID = 2)
group by intervaldate, teamName, Name
)
select b.*
from
(
select a.*, row_number() over (order by (Blown + Thrown)/Total desc) as R_Ord -- Change between ASC/DESC depending on needs
from Calcs a
) b
where R_Ord <=3

Count, Having and Case statement

I'm looking to produce a count of projects based on 3 different conditions (DB2 database). I need to count all projects <= .10, >= .5, and >= 1.00 (percentage_used) but can only group by dim_building_id and building_name. Of course this query will not run because it requires percentage_used to be added to the group by. How do I handle those 3 conditions with percentage_used?
SELECT
SUM(CAST(FTS.GROUP_A AS BIGINT)) AS GROUP_A,
SUM(CAST(FTS.GROUP_B AS BIGINT)) AS GROUP_B,
SUM(CAST(FTS.GROUP_C AS BIGINT)) AS GROUP_C,
CASE WHEN FAT.PERCENTAGE_USED <= '0.10'
THEN COUNT(*)
END AS PROJECTS_L10,
CASE WHEN FAT.PERCENTAGE_USED >= '0.50'
THEN COUNT(*)
END AS PROJECTS_G50,
CASE WHEN FAT.PERCENTAGE_USED >= '1.00'
THEN COUNT(*)
END AS PROJECTS_G100,
DAYS(DATE('2014-07-01')) - DAYS(CURRENT DATE) AS DAYS_LEFT,
(DAYS(DATE('2014-07-01')) - DAYS(CURRENT DATE))/7 AS WEEKS_LEFT,
DAYS(DATE('2013-12-31')) - DAYS(CURRENT DATE) AS DAYS_LEFT_YEAR
FROM FACT_TABLE AS FAT
INNER JOIN GROUPS AS FTS ON FAT.DIM_PROJECT_ID = FTS.DIM_PROJECT_ID
GROUP BY FAT.DIM_BUILDING_ID, FAT.BUILDING_NAME;
I'd do something like this:
select bn.building_name ,
t.*
from ( select fat.building_id ,
sum( case when fat.percentage_used <= 0.10 then 1 else 0 end ) as group_a ,
sum( case when fat.percentage_used > 0.10 and fat.percentage_used < 0.50 then 1 else 0 end ) as group_b ,
sum( case when fat.percentage_used >= 0.50 and fat.percentage_used < 1.00 then 1 else 0 end ) as group_c ,
sum( case when fat.percentage_used >= 1.00 then 1 else 0 end ) as group_d ,
sum( case when fat.percentage_used is null then 1 else 0 end ) as group_e
from fact_table fat
join groups fts on tfs.dim_project_id = fat.dim_project_id
group by fat.building_id
) t
join fact_table bn on bn.building_id = t.building_id
Instead of putting the aggregate as the THEN, wrap the CASE statement in an aggregate:
SELECT
SUM(CAST(FTS.GROUP_A AS BIGINT)) AS GROUP_A,
SUM(CAST(FTS.GROUP_B AS BIGINT)) AS GROUP_B,
SUM(CAST(FTS.GROUP_C AS BIGINT)) AS GROUP_C,
SUM(CASE WHEN FAT.PERCENTAGE_USED <= '0.10' THEN 1 ELSE 0 END) AS PROJECTS_L10,
SUM(CASE WHEN FAT.PERCENTAGE_USED >= '0.50' THEN 1 ELSE 0 END) AS PROJECTS_G50,
SUM(CASE WHEN FAT.PERCENTAGE_USED >= '1.00' THEN 1 ELSE 0 END) AS PROJECTS_G100,
DAYS(DATE('2014-07-01')) - DAYS(CURRENT DATE) AS DAYS_LEFT,
(DAYS(DATE('2014-07-01')) - DAYS(CURRENT DATE))/7 AS WEEKS_LEFT,
DAYS(DATE('2013-12-31')) - DAYS(CURRENT DATE) AS DAYS_LEFT_YEAR
FROM FACT_TABLE AS FAT
INNER JOIN GROUPS AS FTS ON FAT.DIM_PROJECT_ID = FTS.DIM_PROJECT_ID
GROUP BY FAT.DIM_BUILDING_ID, FAT.BUILDING_NAME;
The ELSE 0 isn't needed, but some like to see it in the query.
Alternatively this should work as well:
COUNT(CASE WHEN FAT.PERCENTAGE_USED >= '0.50' THEN 1 END) AS PROJECTS_G50,

counting events over flexible ranges

I am trying to count events (which are rows in the event_table) in the year before and the year after a particular target date for each person. For example, say I have a person 100 and target date is 10/01/2012. I would like to count events in 9/30/2011-9/30/2012 and in 10/02/2012-9/30/2013.
My query looks like:
select *
from (
select id, target_date
from subsample_table
) as i
left join (
select id, event_date, count(*) as N
, case when event_date between target_date-365 and target_date-1 then 0
when event_date between target_date+1 and target_date+365 then 1
else 2 end as after
from event_table
group by id, target_date, period
) as h
on i.id = h.id
and i.target_date = h.event_date
The output should look something like:
id target_date after N
100 10/01/2012 0 1000
100 10/01/2012 1 0
It's possible that some people do not have any events in the before or after periods (or both), and it would be nice to have zeros in that case. I don't care about the events outside the 730 days.
Any suggestions would be greatly appreciated.
I think the following may approach what you are trying to accomplish.
select id
, target_date
, event_date
, count(*) as N
, SUM(case when event_date between target_date-365 and target_date-1
then 1
else 0
end) AS Prior_
, SUM(case when event_date between target_date+1 and target_date+365
then 1
else 0
end) as After_
from subsample_table i
left join
event_table h
on i.id = h.id
and i.target_date = h.event_date
group by id, target_date, period
This is a generic answer. I don't know what date functions teradata has, so I will use sql server syntax.
select id, target_date, sum(before) before, sum(after) after, sum(righton) righton
from yourtable t
join (
select id, target_date td
, case when yourdate >= dateadd(year, -1, target_date)
and yourdate < target_date then 1 else 0 end before
, case when yourdate <= dateadd(year, 1, target_date)
and yourdate > target_date then 1 else 0 end after
, case when yourdate = target_date then 1 else 0 end righton
from yourtable
where whatever
group by id, target_date) sq on t.id = sq.id and target_date = dt
where whatever
group by id, target_date
This answer assumes that an id can have more than one target date.