How to group by month and year and also separate the entries? - sql

I have a table [MY_TABLE] with the following datas : a date [DOCUMENT_DATE] and a status [STATUS]. I want to separate and count the 3 differents status : open when status < 8, lost when status = 8 or win when status > 8 while grouping them by month and year.
The final result would be something like that : year, month, count(won), count(lost), count(open), giving effectively the count of each status for each month.
Some months don't have status at all (can be ignored) and some have only some status and not all of them (should write the month and year correctly)
I have a working query right now but it is really huge :
SELECT
CASE WHEN "open".year IS NOT NULL
THEN
"open".year
ELSE
(CASE WHEN "lost".year IS NOT NULL
THEN
"lost".year
ELSE
"won".year
END)
END AS "Année",
CASE WHEN "open".month IS NOT NULL
THEN
"open".month
ELSE
(CASE WHEN "lost".month IS NOT NULL
THEN
"lost".month
ELSE
"won".month
END)
END AS "Mois",
"open".count AS "Ouvertes",
"lost".count AS "Perdues",
"won".count AS "Gagnées"
FROM (SELECT
year([DOCUMENT_DATE]) AS "year",
MONTH([DOCUMENT_DATE]) AS "month",
COUNT(*) AS "count"
FROM [MY_TABLE]
WHERE [STATUS] < 8 AND [DOCUMENT_DATE] >= ?1 AND [DOCUMENT_DATE] <= ?2 AND ([SEGMENT] = ?3 OR ?3 IS NULL)
GROUP BY YEAR([DOCUMENT_DATE]), MONTH([DOCUMENT_DATE])) AS "open"
FULL JOIN (SELECT
year([DOCUMENT_DATE]) AS "year",
MONTH([DOCUMENT_DATE]) AS "month",
COUNT(*) AS "count"
FROM [MY_TABLE]
WHERE [STATUS] = 8 AND [DOCUMENT_DATE] >= ?1 AND [DOCUMENT_DATE] <= ?2 AND ([SEGMENT] = ?3 OR ?3 IS NULL)
GROUP BY YEAR([DOCUMENT_DATE]), MONTH([DOCUMENT_DATE])) AS "lost"
ON "open".month = "lost".month AND "open".year = "lost".year
FULL JOIN (SELECT
year([DOCUMENT_DATE]) AS "year",
MONTH([DOCUMENT_DATE]) AS "month",
COUNT(*) AS "count"
FROM [MY_TABLE]
WHERE [STATUS] > 8 AND [DOCUMENT_DATE] >= ?1 AND [DOCUMENT_DATE] <= ?2 AND ([SEGMENT] = ?3 OR ?3 IS NULL)
GROUP BY YEAR([DOCUMENT_DATE]), MONTH([DOCUMENT_DATE])) AS "won"
ON "open".month = "won".month AND "open".year = "won".year
ORDER BY CASE WHEN "open".year IS NOT NULL
THEN
"open".year
ELSE
(CASE WHEN "lost".year IS NOT NULL
THEN
"lost".year
ELSE
"won".year
END)
END,
CASE WHEN "open".month IS NOT NULL
THEN
"open".month
ELSE
(CASE WHEN "lost".month IS NOT NULL
THEN
"lost".month
ELSE
"won".month
END)
END
I'm fairly sure there is a much simpler and cleaner way to do that but I can't figure it out.

I think this may be what you are looking for, based on the description.
SELECT year([DOCUMENT_DATE]) AS "year",
MONTH([DOCUMENT_DATE]) AS "month",
COUNT(case when [STATUS] > 8 then 1 end) win_count,
COUNT(case when [STATUS] = 8 then 1 end) lost_count,
COUNT(case when [STATUS] < 8 then 1 end) open_count
FROM [MY_TABLE]
GROUP BY year([DOCUMENT_DATE]),MONTH([DOCUMENT_DATE])
ORDER BY 1,2
Add WHERE [DOCUMENT_DATE] >= ?1 AND [DOCUMENT_DATE] <= ?2 AND ([SEGMENT] = ?3 OR ?3 IS NULL) if the condition is common across all the counts.

Related

SQL (BigQuery ANSI) Efficiently get last value that was updated before 1st of each month

So my database has a changes history table which I am looking up to know my user's status on 1st of each month. Since changing dates are arbitrary, I am trying to get date of last update before 1st of that month (considering the fact that users stay in same status unless recorded by the same table again) then checking what status user had on that timestamp and regarding that as the status of user on the first of the month. So doing something like this:
WITH converted_before_time_changed as (
SELECT dch.user_id,
max(CASE WHEN dch.time_changed <= '2022-01-01' THEN dch.time_changed ELSE NULL END) as time_changed_before_jan_1,
max(CASE WHEN dch.time_changed <= '2022-02-01' THEN dch.time_changed ELSE NULL END) as time_changed_before_feb_1,
max(CASE WHEN dch.time_changed <= '2022-03-01' THEN dch.time_changed ELSE NULL END) as time_changed_before_mar_1,
max(CASE WHEN dch.time_changed <= '2022-04-01' THEN dch.time_changed ELSE NULL END) as time_changed_before_apr_1,
max(CASE WHEN dch.time_changed <= '2022-05-01' THEN dch.time_changed ELSE NULL END) as time_changed_before_may_1,
max(CASE WHEN dch.time_changed <= '2022-06-01' THEN dch.time_changed ELSE NULL END) as time_changed_before_jun_1,
max(CASE WHEN dch.time_changed <= '2022-07-01' THEN dch.time_changed ELSE NULL END) as time_changed_before_jul_1,
FROM my_database.defacto_users_changes_history dch
WHERE dch.table = 'all_users' AND dch.column='status'
GROUP BY user_id
),
c2_before_flags as (SELECT
c2b.user_id,
jan_dch.new_value as status_on_jan_1,
feb_dch.new_value as status_on_feb_1,
mar_dch.new_value as status_on_mar_1,
apr_dch.new_value as status_on_apr_1,
may_dch.new_value as status_on_may_1,
jun_dch.new_value as status_on_jun_1,
jul_dch.new_value as status_on_jul_1
FROM
converted_before_time_changed c2b
LEFT JOIN my_database.defacto_users_changes_history jan_dch on jan_dch.time_changed = time_changed_before_jan_1 AND c2b.user_id = jan_dch.user_id
LEFT JOIN my_database.defacto_users_changes_history feb_dch on feb_dch.time_changed = time_changed_before_feb_1 AND c2b.user_id = feb_dch.user_id
LEFT JOIN my_database.defacto_users_changes_history mar_dch on mar_dch.time_changed = time_changed_before_mar_1 AND c2b.user_id = mar_dch.user_id
LEFT JOIN my_database.defacto_users_changes_history apr_dch on apr_dch.time_changed = time_changed_before_apr_1 AND c2b.user_id = apr_dch.user_id
LEFT JOIN my_database.defacto_users_changes_history may_dch on may_dch.time_changed = time_changed_before_may_1 AND c2b.user_id = may_dch.user_id
LEFT JOIN my_database.defacto_users_changes_history jun_dch on jun_dch.time_changed = time_changed_before_jun_1 AND c2b.user_id = jun_dch.user_id
LEFT JOIN my_database.defacto_users_changes_history jul_dch on jul_dch.time_changed = time_changed_before_jul_1 AND c2b.user_id = jul_dch.user_id
)
SELECT * FROM c2_before_flags
This already takes a lot of time which increases exponentially with each month added, plus its not scalable as I have to edit the query to add each month. What would be the ideal way of achieving the same, dynamically and efficiently?

A SQL query for the retrieval of result based on input month

Table 1
Table 2
My requirement is to input the Redemption month and list the tickets that has been scanned double or more.
For example Ticket No. T1 has been scanned 2 times under pickup,only once under PickupOutforDelivery and 2 times under Delivery.
Result needed like this:
How can I write a query to get the result like this?
Tried:
SELECT
Ticket,
COUNT(Scantype = 0) AS Pickup,
COUNT(Scantype = 1) AS PickupOutforDelivery,
COUNT(Scantype = 2) AS Delivery
FROM
Scans
GROUP BY
Ticket, ScanType
HAVING
(Pickup > 1 OR PickupOutforDelivery > 1 OR Delivery > 1)
OR (Pickup >= 1 AND PickupOutforDelivery >= 1)
ORDER BY
Ticket
Result
Assuming that RedemptionMonth has a datatype of DATE (which is clearly required); the following query will give you the result you want, except the "cosmetic" part (breaking by year month for the report part) that you have to do on your application:
SELECT YEAR(RedemptionMonth) AS [YEAR], MONTH(RedemptionMonth) AS [MONTH], TicketNo,
COALESCE(SUM(CASE WHEN ScanName = 'Pickup' THEN 1 ELSE 0 END), 0) AS Pickup,
COALESCE(SUM(CASE WHEN ScanName = 'PickupOutForDelivery' THEN 1 ELSE 0 END), 0) AS PickupOutForDelivery ,
COALESCE(SUM(CASE WHEN ScanName = 'Delivery' THEN 1 ELSE 0 END), 0) AS Delivery
FROM [Table 1] AS T1
JOIN [Table 2] AS T2
ON T1.ScanType = T2.ScanType
GROUP BY YEAR(RedemptionMonth) AS [YEAR], MONTH(RedemptionMonth) AS [MONTH], TicketNo
Because you are using the numeric value of scanType, no JOIN is needed. So, the only fix is needed for conditional aggregation:
SELECT Ticket,
SUM(CASE WHEN Scantype = 0 THEN 1 ELSE 0 END) as Pickup,
SUM(CASE WHEN Scantype = 1 THEN 1 ELSE 0 END) as PickupOutforDelivery,
SUM(CASE WHEN Scantype = 2 THEN 1 ELSE 0 END) as Delivery
FROM Scans
WHERE redemptionMonth = 'Jan-21'
GROUP BY Ticket
HAVING Pickup > 1 OR
PickupOutforDelivery > 1 OR
Delivery > 1 OR
(Pickup >= 1 AND PickupOutforDelivery >= 1)
ORDER BY Ticket;
Note that you can add redemptionMonth to the GROUP BY (and SELECT) to get the results for each month.
If redemptionMonth is really a date and not a string, then define the time period using a range of dates:
WHERE redemptionMonth >= '2021-01-01' AND
redemptionMonth < '2021-02-01'

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

Limit SQL query to days

I use this SQL query to make status report by day:
CREATE TABLE TICKET(
ID INTEGER NOT NULL,
TITLE TEXT,
STATUS INTEGER,
LAST_UPDATED DATE,
CREATED DATE
)
;
Query:
SELECT t.created,
COUNT(CASE WHEN t.status = '1' THEN 1 END) as cnt_status1,
COUNT(CASE WHEN t.status = '2' THEN 1 END) as cnt_status2,
COUNT(CASE WHEN t.status = '3' THEN 1 END) as cnt_status3,
COUNT(CASE WHEN t.status = '4' THEN 1 END) as cnt_status4
FROM ticket t
GROUP BY t.created
How I can limit this query to last 7 days?
Also I would like to get the results split by day. Fow example I would like to group the first dates for 24 hours, second for next 24 hours and etc.
Expected result:
This might help:
SELECT TO_CHAR(t.created, 'YYYY-MM-DD') AS created_date,
COUNT(CASE WHEN t.status = '1' THEN 1 END) as cnt_status1,
COUNT(CASE WHEN t.status = '2' THEN 1 END) as cnt_status2,
COUNT(CASE WHEN t.status = '3' THEN 1 END) as cnt_status3,
COUNT(CASE WHEN t.status = '4' THEN 1 END) as cnt_status4
FROM ticket t
WHERE t.created >= SYSDATE-7
GROUP BY TO_CHAR(t.created, 'YYYY-MM-DD')
ORDER BY created_date;
I used the oracle function for date conversion. I'm sure you'll find the corresponding one for postgresql.

One date check for entire query

I have the following query:
select
fp.id,
fr.id,
sum(case
when to_date(fp.offered_date) BETWEEN TO_DATE( :ad_startdate, 'YYYY-MM-DD')
AND TO_DATE(:ad_enddate, 'YYYY-MM-DD') and fp.result <> 'E'
then 1
else 0
end) total,
sum(case when fp.result = 'G'
and to_date(fp.offered_date) >= :ad_startdate
and to_date(fp.offered_date) <= :ad_enddate then 1 else 0 end) colorgreen,
sum(case when fp.resultat = 'R'
and to_date(fp.offered_date) >= :ad_startdate
and to_date(fp.offered_date) <= :ad_enddate then 1 else 0 end) colorred
FROM
fruit_properties fp, fruit fr
WHERE
fp.id = fr.id
GROUP BY
fp.id, fr.id
I'm checking dates 1 time for each sum column and have a feeling this can be made once somehow? Right now if I check only once at the total column, then colorgreen + colorred might be larger than the total since it counts no matter what date they have.
Can my query be enhanced somehow?
you can simplify like this. but PLEASE check your SQL. you're mixing TO_DATE and CHAR datatypes. this will only end in disaster.
eg you have:
when to_date(fp.offered_date) BETWEEN TO_DATE( :ad_startdate, 'YYYY-MM-DD')
AND TO_DATE(:ad_enddate, 'YYYY-MM-DD')
vs
sum(case when fp.result = 'G'
and to_date(fp.offered_date) >= :ad_startdate
in one case you are TO_DATE'ing ad_startdate but not another (so is it a date already or not?). you are also TO_DATEing the column but crucially WITHOUT a format mask. is the column really a VARCHAR datatype? if so you really should not store dates as anything but DATEs.
anyway assuming the column is a DATE datatype and the binds are of type DATE..
select fruit_prop_Id,fruit_id,
sum(case when result != 'E' then within_offer else 0 end) total,
sum(case when result = 'R' then within_offer else 0 end) colorred,
sum(case when result = 'G' then within_offer else 0 end) colorgreen
from (select fp.id fruit_id,
fr.id fruit_prop_Id,
fp.result,
case
when fp.offered_date >= :ad_startdate
and fp.offered_date <= :ad_enddate then 1 else 0 end within_offer
from fruit_properties fp, fruit fr
where fp.id = fr.id)
group by fruit_id, fruit_prop_Id
You can put the date check in the where clause:
select
fp.id,
fr.id,
sum(case when and fp.result <> 'E' then 1 else 0 end) total,
sum(case when fp.result = 'G' then 1 else 0 end) colorgreen,
sum(case when fp.resultat = 'R' then 1 else 0 end) colorred
FROM
fruit_properties fp, fruit fr
WHERE
fp.id = fr.id
AND to_date(fp.offered_date) >= :ad_startdate
AND to_date(fp.offered_date) <= :ad_enddate
GROUP BY
fp.id, fr.id
Edit: as pointed out in the comments, this query will filter out ids which doesn't have any offer dates in the given interval.