How to Group Counted Data - mysql

I have 3 categories (Below SLA, Near SLA, Over SLA) that has different conditions, I try to count the data but the result is not summarized by their category
This is my query:
SELECT
B.province AS 'PROVINCE',
CASE
WHEN TIMEDIFF(A.deli_time, A.create_time) < '20:00:00' THEN COUNT(TIMEDIFF(A.deli_time, A.create_time))
END AS 'Below SLA',
CASE
WHEN (TIMEDIFF(A.deli_time, A.create_time) > '20:00:00') AND (TIMEDIFF(A.deli_time, A.create_time) < '24:00:00') THEN COUNT(TIMEDIFF(A.deli_time, A.create_time))
END AS 'NEAR SLA',
CASE
WHEN TIMEDIFF(A.deli_time, A.create_time) > '24:00:00' THEN COUNT(TIMEDIFF(A.deli_time, A.create_time))
END AS 'OVER SLA'
FROM
deli_order A
INNER JOIN
deli_order_delivery B on A.id = B.order_id
WHERE
(DATE(A.plat_create_time) BETWEEN '2019-03-30' AND'2019-04-07') AND (TIMEDIFF(A.deli_time, A.create_time) IS NOT NULL)
GROUP BY B.province;
and this is the result that i got:
Province | Below SLA | Near SLA | Over SLA
------------------------------------------------
Bali 30 Null Null
30 is the total of all the records of 'Bali', but its actually divided into 19 Below SLAs, 5 Near SLAs, and 6 Over SLAs.
What should i change in my query?

SELECT
B.province AS 'PROVINCE',
SUM(CASE
WHEN TIMEDIFF(A.deli_time, A.create_time) < '20:00:00' THEN 1
END) AS 'Below SLA',
Put an aggregate function for each case,OUTSIDE of it.I did it for just one case,it`s all the same.

Related

MYSQL - SUM values of last 10 days

I have this table in mysql:
| player1 | player2 | date | fs_1 | fs_2 |
Jack Tom 2015-03-02 10 2
Mark Riddley 2015-05-02 3 1
...
I need to know how many aces (fs_1) player 1 have done BEFORE the match reported in date_g (10 days before for example).
This is what i tried without success:
OPTION 1
SELECT
players_atp.name_p AS 'PLAYER 1',
P.name_p AS 'PLAYER 2',
DATE(date_g) AS 'DATE',
result_g AS 'RESULT',
FS_1,
FS_2,
SUM(IF(date_sub(date_g, interval 10 day)< date_g, FS_1, 0)) AS 'last 10 days'
FROM
stat_atp stat_atp
JOIN
backup3.players_atp ON ID1 = id_P
JOIN
backup3.players_atp P ON P.id_p = id2
JOIN
backup3.games_atp ON id1_g = id1 AND id2_g = id2
AND id_t_g = id_t
AND id_r_g = id_r
WHERE
date_g > '2015-01-01'
GROUP BY ID1;
OPTION 2
SELECT
players_atp.name_p AS 'PLAYER 1',
P.name_p AS 'PLAYER 2',
DATE(date_g) AS 'DATE',
result_g AS 'RESULT',
FS_1,
FS_2,
SUM(CASE WHEN date_g between date_g and date_sub(date_g, interval 10 day) then fs_1 else 0 end) AS 'last 10 days'
FROM
stat_atp stat_atp
JOIN
backup3.players_atp ON ID1 = id_P
JOIN
backup3.players_atp P ON P.id_p = id2
JOIN
backup3.games_atp ON id1_g = id1 AND id2_g = id2
AND id_t_g = id_t
AND id_r_g = id_r
WHERE
date_g > '2015-01-01'
GROUP BY ID1;
I have edited the code, now is more easy to read and understand.
SELECT
id1 AS 'PLAYER 1',
id2 AS 'PLAYER 2',
DATE(date_g) AS 'DATE',
result_g AS 'RESULT',
FS_1,
FS_2,
SUM(CASE
WHEN date_g BETWEEN date_g AND DATE_SUB(date_g, INTERVAL 10 DAY) THEN fs_1
END) AS 'last 20 days' FROM
stat_atp stat_atp
JOIN
backup3.games_atp ON id1_g = id1 AND id2_g = id2
AND id_t_g = id_t
AND id_r_g = id_r GROUP BY ID1;
Thanx in advance.
Maybe this could help you:
SELECT
id1,
SUM(fs_1)
FROM
stat_atp
WHERE
date_g <= DATE_SUB('2015-03-02', INTERVAL 1 DAY) AND date_g >= DATE_SUB('2015-03-02', INTERVAL 10 DAY)
AND
id1='Jack'
GROUP BY id1;
Remember that RDBMS are used to construct rigorous data sets that are linked between each others by clear ids (keys when talking about SQL). It's easier to respect the three first normal forms. That's why you should use keys to identify your match itself. By this way you could use subqueries (subsets) to achieve your goal.
Then, keep in mind that SQL is STRUCTURED. It's its force and weakness cause you won't be able to use it as a Turing complete programming langage with loops and conditions. But in any situation you will be able to find the same structure for a query. However you can interact with a SQL query result with another langage and use loops and condition on the result set itself. That's up to you.
Anyway, you may want to read about the MySQL GROUP BY clause which is different from the ISO SQL form : https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html

How to avoid null value in a select query and replace that value as 0 in a select query not in the table? [duplicate]

This question already has answers here:
Replace nulls values in sql using select statement in mysql?
(4 answers)
Closed 6 years ago.
select t1.d as Date,t1.S AS StoreNo ,t1.Cash,N3.price,(N3.price-t1.Cash)as Totalsale,ISNUll(t2.gift,0)as Card,ISNULL(t3.coupon,0) as couup
from
(Select Date as d,StoreNo as S,SUM(DayTotalAmt)Cash
from POS_FinTtl where (SerialNo like '23')
group by StoreNo,SerialNo,Date) t1
Join (Select Date as d,StoreNo as S, SUM(DayTotalAmt)gift
from POS_FinTtl where (SerialNo like '31')
group by StoreNo,SerialNo,Date) t2 On t1.d =t2.d and t1.S = t2.S
join (Select Date as d,StoreNo as S, SUM(DayTotalAmt)coupon
from POS_FinTtl where (SerialNo like '30')
group by StoreNo,SerialNo,Date)t3 On t2.d =t3.d and t2.S = t3
Left join (Select BuyDate as d,StoreNo as S ,Sum(SalePrice)as price
from POS_TtlTran where StoreNo = 467
GROUP BY StoreNo,BuyDate) As N3
On N3.d = t1.d and N3.S = t1.S
Where (N3.S = 467) AND (N3.d >= '6/01/2015') AND (t1.d <= '6/30/2015')
Result
Date StoreNo Cash price Totalsale Card couup
2015-06-01 467 14860 88145 73285 78334 2000
2015-06-05 467 23032 76380 53348 40456 19000
2015-06-07 467 44225 133737 89512 95205 5000
2015-06-14 467 78134 110940 32806 30677 11000
2015-06-21 467 76234 166070 89836 97114 6000
2015-06-23 467 19844 46221 26377 29072 1000
2015-06-28 467 91165 127911 36746 40974 6000
But date 2015-06-02 couup value 0 but its not extracting from table but other columns as values.
You should be using conditional aggregation. The basic query is:
Select Date as d, StoreNo as S,
SUM(case when SerialNo like '23' then DayTotalAmt else 0 end) as cash,
SUM(case when SerialNo like '31' then DayTotalAmt else 0 end) as gift,
SUM(case when SerialNo like '30' then DayTotalAmt else 0 end) as coupon,
SUM(case when StoreNo = 467 then SalePrice else 0 end) as price
from POS_FinTtl
group by StoreNo, Date;
I don't quite follow how the date arithmetic fits in -- either in a where clause to apply to all values or the in individual case statements. Wherever it goes, you do not need such a complicated query.
There are a number of things to use, they are all in the manual. For conditional replacement in an expression IFNULL(var,0) will return var if it has a value otherwise 0.

How to split SQL query results into columns based on two WHERE conditions and two calculated COUNT fields?

I have the following (simplified) database schema:
Persons:
[Id] [Name]
-------------------
1 'Peter'
2 'John'
3 'Anna'
Items:
[Id] [ItemName] [ItemStatus]
-------------------
10 'Cake' 1
20 'Dog' 2
ItemDocuments:
[Id] [ItemId] [DocumentName] [Date]
-------------------
101 10 'CakeDocument1' '2016-01-01 00:00:00'
201 20 'DogDocument1' '2016-02-02 00:00:00'
301 10 'CakeDocument2' '2016-03-03 00:00:00'
401 20 'DogDocument2' '2016-04-04 00:00:00'
DocumentProcessors:
[PersonId] [DocumentId]
-------------------
1 101
1 201
2 301
I have also set up an SQL fiddle to play with: http://www.sqlfiddle.com/#!3/e6082
The relation logic is the following: every Person can work on zero or infinite number of ItemDocuments (many-to-many); each ItemDocument belongs to exactly one Item (one-to-many). Item has status 1 - Active, 2 - Closed
What I need is a report that fulfills the following requirements:
for each person in Persons table, display count of Items that have ItemDocuments related to this person
the counts should be split in two columns by ItemStatus
the query should be filterable by two optional date periods (using two BETWEEN conditions on ItemDocuments.Date field) and the Item counts should also be split into two periods
if a Person does not have any ItemDocuments assigned, it still should be shown in the results with all count values set to 0
if a Person has more than one ItemDocument for an Item, the Item still should be counted only once
Essentially, here is how the results should look like if I use both periods to NULL (to read all the data):
[PersonName] [Active Items for period 1] [Closed Items for period 1] [Active Items for period 2] [Closed Items for period 2]
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
'Peter' 1 1 1 1
'John' 1 0 1 0
'Anna' 0 0 0 0
While I can create an SQL query for each requirement separately, I have a problem to understand how to combine all of them together into one.
For example, I can split ItemStatus counts in two columns using
COUNT(CASE WHEN t.ItemStatus = 1 THEN 1 ELSE NULL END) AS Active,
COUNT(CASE WHEN t.ItemStatus = 2 THEN 1 ELSE NULL END) AS Closed
and I can filter by two periods (with max/min date constants from MS SQL server specification to avoid NULLs for optional period dates) using
between coalesce(#start1, '1753-01-01') and coalesce(#end1, '9999-12-31')
between coalesce(#start2, '1753-01-01') and coalesce(#end2, '9999-12-31')
but how to combine all of this together, considering also JOINs between tables?
Is there any technique, join or MS SQL Server specific approach to do this in efficient way?
My first attempt seems to work as required but it looks like ugly subquery duplications multiple times:
DECLARE #start1 DATETIME, #start2 DATETIME, #end1 DATETIME, #end2 DATETIME
-- SET #start2 = '2017-01-01'
SELECT
p.Name,
(SELECT COUNT(1)
FROM Items i
WHERE i.ItemStatus = 1 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(#start1, '1753-01-01') AND COALESCE(#end1, '9999-12-31')
)
) AS Active1,
(SELECT COUNT(*)
FROM Items i
WHERE i.ItemStatus = 2 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(#start1, '1753-01-01') AND COALESCE(#end1, '9999-12-31')
)
) AS Closed1,
(SELECT COUNT(1)
FROM Items i
WHERE i.ItemStatus = 1 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(#start2, '1753-01-01') AND COALESCE(#end2, '9999-12-31')
)
) AS Active2,
(SELECT COUNT(*)
FROM Items i
WHERE i.ItemStatus = 2 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(#start2, '1753-01-01') AND COALESCE(#end2, '9999-12-31')
)
) AS Closed2
FROM Persons p
I'm not absolutely sure if I really got what you want, but you might try this
WITH AllData AS
(
SELECT p.Id AS PersonId
,p.Name AS Person
,id.Date AS DocDate
,id.DocumentName AS DocName
,i.ItemName AS ItemName
,i.ItemStatus AS ItemStatus
,CASE WHEN id.Date BETWEEN COALESCE(#start1, '1753-01-01') AND COALESCE(#end1, '9999-12-31') THEN 1 ELSE 0 END AS InPeriod1
,CASE WHEN id.Date BETWEEN COALESCE(#start2, '1753-01-01') AND COALESCE(#end2, '9999-12-31') THEN 1 ELSE 0 END AS InPeriod2
FROM Persons AS p
LEFT JOIN DocumentProcessors AS dp ON p.Id=dp.PersonId
LEFT JOIN ItemDocuments AS id ON dp.DocumentId=id.Id
LEFT JOIN Items AS i ON id.ItemId=i.Id
)
SELECT PersonID
,Person
,COUNT(CASE WHEN ItemStatus = 1 AND InPeriod1 = 1 THEN 1 ELSE NULL END) AS ActiveIn1
,COUNT(CASE WHEN ItemStatus = 2 AND InPeriod1 = 1 THEN 1 ELSE NULL END) AS ClosedIn1
,COUNT(CASE WHEN ItemStatus = 1 AND InPeriod2 = 1 THEN 1 ELSE NULL END) AS ActiveIn2
,COUNT(CASE WHEN ItemStatus = 2 AND InPeriod2 = 1 THEN 1 ELSE NULL END) AS ClosedIn2
FROM AllData
GROUP BY PersonID,Person

How to optimize query when using sub-queries in left join

Tables:
Please take a look here to see tables. How to query counting specific wins of team and find the winner of the series
Questions:
How to make query more optimize?
How to reduce query redundancy?
How to make this query more faster?
Summary
As you can see in the example query this part is use many times.
WHERE leagueid = 2096
AND start_time >= 1415938900
AND ((matches.radiant_team_id= 1848158 AND matches.dire_team_id= 15)
OR (matches.radiant_team_id= 15 AND matches.dire_team_id= 1848158))
SELECT matches.radiant_team_id,
matches.dire_team_id,
matches.radiant_name,
matches.dire_name,
TA.Count AS teamA,
TB.Count AS teamB,
TA.Count + TB.Count AS total_matches,
SUM(TA.wins),
SUM(TB.wins),
(CASE
WHEN series_type = 0 THEN 1
WHEN series_type = 1 THEN 2
WHEN series_type = 2 THEN 3
END) AS wins_goal
FROM matches
LEFT JOIN
(SELECT radiant_team_id,
COUNT(id) AS COUNT,
CASE
WHEN matches.radiant_team_id = radiant_team_id && radiant_win = 1 THEN 1
END AS wins
FROM matches
WHERE leagueid = 2096
AND start_time >= 1415938900
AND ((matches.radiant_team_id= 1848158
AND matches.dire_team_id= 15)
OR (matches.radiant_team_id= 15
AND matches.dire_team_id= 1848158))
GROUP BY radiant_team_id) AS TA ON TA.radiant_team_id = matches.radiant_team_id
LEFT JOIN
(SELECT dire_team_id,
COUNT(id) AS COUNT,
CASE
WHEN matches.dire_team_id = dire_team_id && radiant_win = 0 THEN 1
END AS wins
FROM matches
WHERE leagueid = 2096
AND start_time >= 1415938900
AND ((matches.radiant_team_id= 1848158
AND matches.dire_team_id= 15)
OR (matches.radiant_team_id= 15
AND matches.dire_team_id= 1848158))
GROUP BY dire_team_id) AS TB ON TB.dire_team_id = matches.dire_team_id
WHERE leagueid = 2096
AND start_time >= 1415938900
AND ((matches.radiant_team_id= 1848158
AND matches.dire_team_id= 15)
OR (matches.radiant_team_id= 15
AND matches.dire_team_id= 1848158))
GROUP BY series_id
Scheduled Matches
ID| leagueid| team_a_id| team_b_id| starttime
1| 2096| 1848158| 15| 1415938900
I believe it can be done without subqueries.
I made the following match table
And used the following query to group results, one line per series
SELECT
matches.leagueid,
matches.series_id,
matches.series_type,
COUNT(id) as matches,
IF(radiant_team_id=1848158,radiant_name, dire_name) AS teamA,
IF(radiant_team_id=1848158,dire_name, radiant_name) AS teamB,
SUM(CASE
WHEN radiant_team_id=1848158 AND radiant_win=1 THEN 1
WHEN dire_team_id=1848158 AND radiant_win=0 THEN 1
ELSE 0 END) AS teamAwin,
SUM(CASE
WHEN radiant_team_id=15 AND radiant_win=1 THEN 1
WHEN dire_team_id=15 AND radiant_win=0 THEN 1
ELSE 0 END) AS teamBwin
FROM `matches`
WHERE leagueid = 2096
AND start_time >= 1415938900
AND dire_team_id IN (15, 1848158)
AND radiant_team_id IN (15, 1848158)
group by leagueid,series_id,series_type,teamA,teamB
which yields the following result
Please note that, when grouping the results of one series, there isn't such thing as radiant team or dire team. The radiant and dire roles might be switched several times during the same series, so I only addressed the teams as teamA and teamB.
Now, looking at your prior question, I see that you need to determine the series winner based on the series type and each team victories. This would need to wrap the former query and use it as a subquery such as
SELECT matchresults.*,
CASE series_type
WHEN 0 then IF(teamAwin>=1, teamA,teamB)
WHEN 1 then IF(teamAwin>=2, teamA,teamB)
ELSE IF(teamAwin>=3, teamA,teamB)
END as winner
from ( THE_MAIN_QUERY) as matchresults
There may be more efficient ways to get the results you want. But, to make this query more efficient, you can add indexes. This is the repeated where clause:
WHERE leagueid = 2096 AND
start_time >= 1415938900 AND
((matches.radiant_team_id= 1848158 AND matches.dire_team_id= 15) OR
(matches.radiant_team_id= 15 AND matches.dire_team_id= 1848158))
Conditions with or are hard for the optimizer. The following index will be helpful: matches(leagueid, start_time). A covering index (for the where conditions at least) is matches(leagueid, start_time, radiant_team_id, dire_team_id). I would start with this latter index and see if that improves performance sufficiently for your purposes.

Count with Aggreate function in SQL

Hi my actual code is below:
Select M.TicketID,M.CreatedMoment, Max(L.StatusChangeMoment)AS StatusChangeTime,
Elapsed_time_in_Hours_Minutes = CONVERT(NUMERIC(18,2),DATEDIFF(minute, M.createdmoment, MAX(L.statuschangemoment))/60+(DATEDIFF(minute, M.createdmoment, MAX(L.statuschangemoment)) % 60/100.0))
From XX_MASTER_TICKETS AS M Left Join XX_DETAIL_TICKET_STATUS_LOG AS L
On M.RowID = L.TicketRowID
Where M.CreatedMoment between '08-23-2014' And '08-26-2014'
Group by M.TicketID,M.CreatedMoment
Order by M.TicketID asc
And the Partial Results is this:
TicketID CreatedMoment StatusChangeTime Elapsed_time_in_Hours_Minutes
201408231 8/23/14 8:05 AM 8/25/14 11:47 AM 51.42
2014082310 8/23/14 8:19 AM 8/23/14 12:43 PM 4.24
20140823100 8/23/14 8:38 AM 8/24/14 11:15 AM 26.37
20140823101 8/23/14 8:38 AM 8/23/14 11:58 AM 3.2
20140823102 8/23/14 8:38 AM 8/24/14 10:33 AM 25.55
Basically the statuschangetime came from aggregate function, and the last column is the difference of the 2nd and 3rd column.
I want to modify the query so the results will look like this:
Date below24Hrs above24hours
2014-8-23 2 3
My problem is i'm getting error when running this code:
Select
[below24hrs] = COUNT (Case WHEN (CONVERT(NUMERIC(18,2),DATEDIFF(minute, TM.createdmoment, MAX(LG.statuschangemoment))/60+(DATEDIFF(minute, TM.createdmoment, MAX(LG.statuschangemoment)) % 60/100.0))) < 24 THEN 1 END)
From XX_MASTER_TICKETS AS M Left Join XX_DETAIL_TICKET_STATUS_LOG AS L
On M.RowID = L.TicketRowID
Where M.CreatedMoment between '08-23-2014' And '08-26-2014'
Group by M.TicketID,M.CreatedMoment
Order by M.TicketID asc
It says cannot count with the MAX aggregrate function inside the query.
You need to use subquery and sum them.
select cast(sqry.CreatedMoment as date) as CreatedMoment
, sum(case when Elapsed_time_in_Hours_Minutes < 24 then 1 else 0 end) as below24Hrs
, sum(case when Elapsed_time_in_Hours_Minutes > 24 then 1 else 0 end) as above24Hrs
, sum(case when Elapsed_time_in_Hours_Minutes = 24 then 1 else 0 end) as At24Hrs
from
(
Select M.TicketID,M.CreatedMoment, Max(L.StatusChangeMoment)AS StatusChangeTime,
Elapsed_time_in_Hours_Minutes = CONVERT(NUMERIC(18,2),DATEDIFF(minute, M.createdmoment, MAX(L.statuschangemoment))/60+(DATEDIFF(minute, M.createdmoment, MAX(L.statuschangemoment)) % 60/100.0))
From XX_MASTER_TICKETS AS M Left Join XX_DETAIL_TICKET_STATUS_LOG AS L
On M.RowID = L.TicketRowID
Where M.CreatedMoment between '08-23-2014' And '08-26-2014'
Group by M.TicketID,M.CreatedMoment
Order by M.TicketID asc
) sqry
group by cast(CreatedMoment as date)