I have a data table as shown below
Owner
Month
Year
Target
Achieved
A
April
2021
100
50
B
April
2021
100
80
A
May
2021
100
80
B
May
2021
100
130
A
June
2021
100
50
B
June
2021
100
60
The logic is if there is a shortfall with respect to Achieved then the shortfall amount should be added to next month target.
For Example A's April Target is 100 and Achieved is 50. The Shortfall would be 100-50=50. The 50 should be added to May Target
The output required as
Owner
Month
Year
Target
Achieved
Shortfall(Target-Achieved)
A
April
2021
100
50
50
A
May
2021
150
80
70
A
June
2021
170
50
120
B
April
2021
100
80
20
A
May
2021
120
130
-10
B
June
2021
100
60
40
Is it possible to achieve this automation in SQL?
Thanks
You want a cumulative sum. Assuming that the month column is really ordered, then the final column is:
select t.*,
sum(target - achieved) over (partition by owner, year
order by month
)
from t;
You can use this for the calculation for the new target:
select t.*,
sum(target - achieved) over (partition by owner, year
order by month
)
(achieved +
sum(target - achieved) over (partition by owner, year
order by month
)
) as new_target
from t;
Consider year wise all month as ordering purpose if data available. If previous short fall is negative then current row short fall will be calculated as target - achieved otherwise target + prev.shortfall - achieved.
-- MySQL(v5.8)
SELECT t.owner, t.month, t.year
, t.target + (CASE WHEN t.row_num = 1 THEN 0
ELSE CASE WHEN LAG(short_fall) OVER (PARTITION BY t.owner ORDER BY t.row_num) < 0
THEN 0
ELSE LAG(short_fall) OVER (PARTITION BY t.owner ORDER BY t.row_num)
END
END) target
, t.achieved
, CASE WHEN LAG(short_fall) OVER (PARTITION BY t.owner ORDER BY t.row_num) < 0
THEN t.target - t.achieved
ELSE short_fall
END short_fall
FROM (select owner, month
, year
, target
, achieved
, SUm(target - achieved) OVER
(PARTITION BY owner, year ORDER BY DATE_FORMAT(STR_TO_DATE(CONCAT(month, ' 1, ', year),'%M %d,%Y'), '%c')) short_fall
, ROW_NUMBER() OVER
(PARTITION BY owner, year ORDER BY DATE_FORMAT(STR_TO_DATE(CONCAT(month, ' 1, ', year),'%M %d,%Y'), '%c')) row_num
from test) t;
Please check from url https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=3e114348c2d92015490f76fdbab1c46f
Related
from the below table:
newID
year
ID
newValore
1
2020
111
50
1
2020
111
60
1
2021
111
70
1
2021
112
20
1
2021
112
40
1
2022
113
30
1
2022
113
80
2
2020
222
20
2
2020
223
10
2
2021
223
40
2
2021
224
10
2
2021
224
90
2
2021
224
99
2
2022
225
10
2
2023
225
50
given the example table above i need a single query in mysql which creates a new table which will have in the first column the list of newID values and in the second column it will have the different years present in the table for each newID and in the third column i will have a value which is called diff_cum_year given by this rule:
if the year present in the year column for each newID value is the smallest year then the value of diff_cum_year will be given by the sum of the maximum newValues for each of the different ID values for the same newID value and for the same year value
if for each value present in the year column with the same value of newID I have only one value of ID and this value of ID was already present for the same value of newID with the value of year equal to year -1 then the value of diff_cum_year will be the maximum value of newValue for the same newID and for the same year minus the value of diff_cum_year with the value of year equal to year -1 for the same newID
if, on the other hand, for each year present in the year column with the same newID value I have only one ID value and this is an ID value not present among the IDs having same newID and with year value uagual to year - 1 then the value of diff_cum_year will be the maximum of the newValue field for the year value being predicted for the same newID
if for each year in the year column with the same newID value I have multiple ID values the value will be the sum of the maximum newValue values for each of the different ID values for the same newID minus the value of diff_cum_year with year equal to year -1 for the same newID
the output table should be like this one:
newID
year
diff_cum_year
1
2020
60 [rule 1 max(50,60)]
1
2021
50 [rule 4 max(70)+max(20,40) - 60 (previous value for diff_cum_year)]
1
2022
80 [rule 3 max(30,80)]
2
2020
30 [rule 1 max(20) + max(10)]
2
2021
109 [rule 4 max(40) + max(10,90,99) - 30 (previous value for diff_cum_year)]
2
2022
10
2
2023
40
There's one tricky way of carrying out this problem. These are the steps followed by this solution:
generating the max values for "newValore" with respect to triples <newID, year_, ID>
getting the total sum of max values for each couple <newID, year_>
subtracting the total sums for ids present in consecutive years
getting the least total sums among all the available (since the subtraction is the last operation we did, the smallest sums will be the latest generated values we need)
Each of these operations is done within a separate subquery:
WITH max_vals AS (
SELECT DISTINCT newId,
year_,
ID,
MAX(newValore) OVER(PARTITION BY newID, year_, ID) AS max_value
FROM tab
), sum_max_vals AS (
SELECT *, SUM(max_value) OVER(PARTITION BY newId, year_) AS sum_max_value
FROM max_vals
), sum_max_vals_with_subs AS(
SELECT newID,
year_,
sum_max_value -
CASE WHEN LAG(year_) OVER(PARTITION BY ID ORDER BY year_) = year_-1
THEN LAG(sum_max_value) OVER(PARTITION BY ID ORDER BY year_)
ELSE 0
END AS diff_cum_year
FROM sum_max_vals
)
SELECT newID,
year_,
MIN(diff_cum_year) AS diff_cum_year
FROM sum_max_vals_with_subs
GROUP BY newID, year_
Check the demo here.
I have requirement where i will need to get the number of days a role an employee was on.
Scenario 1
EmployeeId role effectiveFrom
1 A 1-Jan-2021
1 B 15-Jan-2021
No further roles are available for the month of Jan for role A therefore the number of days for role A would be 14.
Scenario 2
EmployeeId role effectiveFrom
1 A 1-Jan-2021
No further roles are available for the month of Jan therefore the number of days for role A would be 31 i.e the entire month of January. For the month of February i would expect to get 28 as the role would be effective for the entire month of february as well.
Scenario 3
EmployeeId role effectiveFrom
1 A 1-Jan-2021
1 B 15-Jan-2021
1 A 25-Jan-2021
To get the number of days for role A the logic would be
1 to 15th is 14 days.
25th to 31st(31st of Jan) would be 6 days.
14 + 6 = 20 days
The query i have come up with so far is this,
SELECT
DATEDIFF(MAX(effectiveFrom),
IF(MIN(effectiveFrom) = MAX(effectiveFrom),
MIN(effectiveFrom),
MIN(effectiveFrom))) + 1 daysWorked
FROM
EmployeeRoles
WHERE grade = 'A'
GROUP BY `employeeId`,effectiveFrom;
which would only give the result as 1 day for Scenario 1. Could someone guide me on the practical way of handling the scenarios. I have looked at loops, window functions but i am at a loss on the best way to proceed.
dbfiddle
When scenario2 has 31 days from 1-jan, until the end of the month, I would suspect that from 25-jan, until the end of the month, is 7 days, and not 6, as you write in scenario3.
The number of days, using above calculation:
SELECT
employeeID,
grade,
effectiveFrom,
DATEDIFF(COALESCE(LEAD(effectiveFrom)
OVER (PARTITION BY employeeID, grade ORDER By effectiveFrom),
DATE_ADD(LAST_DAY(effectiveFrom),INTERVAL 1 DAY)),
effectiveFrom) as '#Days'
FROM EmployeeRole;
This can be grouped, and summed giving:
SELECT
employeeID,
grade,
SUM(`#Days`)
FROM (
SELECT
employeeID,
grade,
effectiveFrom,
DATEDIFF(COALESCE(LEAD(effectiveFrom)
OVER (PARTITION BY employeeID, grade ORDER By effectiveFrom),
DATE_ADD(LAST_DAY(effectiveFrom),INTERVAL 1 DAY)),
effectiveFrom) as '#Days'
FROM EmployeeRole
) x
GROUP BY
employeeID,
grade;
output:
employeeID
grade
SUM(#Days)
1
A
14
1
B
17
2
A
31
3
A
21
3
B
10
see: DBFIDDLE
EDIT: The results were incorrect because the next effectiveFrom date was determined using OVER (PARTITION BY employeeID ORDER By effectiveFrom). this is not correct, because the grade should be taken into account too.
I corrected it to OVER (PARTITION BY employeeID, grade ORDER By effectiveFrom)
P.S. I also corrected this in the piece above the EDIT!
see: DBFIDDLE
So, there is an account number and we have daily information about their payments. Suppose we have information of 1 year leading up to today which is 08/March/2019, I would want to calculate the number of times he/she overpaid in last 1 week. I have used mysql window function but for some reason it does not seem to work
#GMB A sample data would look like this:Suppose for this account we have info from last march 2018. I just want the number of times paid_status = overpaid from the last date that I have on my file which is of today - 08/March/2019 and previous 7 days, 14 days, 1 month or any duration of my choosing. Your query will hardcode it only for 7 days.
ACCOUNT_ID paid_status amt dte
-----------------------
1234 overpaid 100 01/March/2018
.
.
.
1234 overpaid 120 01/March/2019
1234 not paid 0 02/March/2019
1234 overpaid 110 03/March/2019
1234 overpaid 120 04/March/2019
1234 overpaid 130 05/March/2019
1234 overpaid 120 06/March/2019
1234 overpaid 120 07/March/2019
1234 overpaid 121 08/March/2019
Query:
,COUNT(CASE WHEN paid_status = 'OVERPAID' THEN 1 END)
over (PARTITION BY ACCOUNT_ID
ORDER BY DTE ROWS BETWEEN 7 PRECEDING AND UNBOUNDED FOLLOWING
) AS num_times_overpaid_week1
The output should be like this(not including today's info):
account_id num_times_overpaid_week1
1234 6
While I am getting multiple rows for the same account_id and it is not exactly calulating the field correctly
From your sample data it seems like you are looking for a simple aggregated query (no need for window functions):
SELECT account_id, SUM(paid_status = 'OVERPAID') AS num_times_overpaid_week1
FROM mytable
WHERE dte >= CURRENT_DATE - INTERVAL 7 DAY
GROUP BY account_id
Expression SUM(paid_status = 'OVERPAID') uses a nice MySQL feature where conditions return 1 when satisfied and 0 when not.
NB: if, for some reason, you do want to use window functions (maybe to perform other computation), then you would need to use ROW_NUMBER() to rank records by date, and the filter out only the most recent record per account in an outer query. I think that the definition of the window can be largely simplified:
SELECT *
FROM (
SELECT
account_id,
SUM(paid_status = 'OVERPAID') OVER(PARTITION BY account_id) AS num_times_overpaid_week1,
-- possibly other columns
ROW_NUMBER() OVER(PARTITION BY account_id ORDER BY dte DESC) rn
FROM mytable
WHERE dte >= CURRENT_DATE - INTERVAL 7 DAY
) x WHERE rn = 1
I have following data , numbers recruited/applied for a particular office, and would like to find the difference between previous and current year and their percentage increase or decrease.
Mentioned the formulas to use in brackets in expected output.
Office year recruited applied
Pune 2015 10 15
Pune 2016 7 20
Mumbai 2015 10 23
Mumbai 2016 15 18
My expected output should be like:
Office Difference %recruited
Pune -3 (7-10) -30%(7-10/10)
Mumbai 5(15-10) 50%
Please help.
If you were using SQL Server 2012 or higher you could use the LAG (or LEAD) function. Since you aren't you can get creative with a CTE. This approach is taken from http://blog.sqlauthority.com/2013/09/22/sql-server-how-to-access-the-previous-row-and-next-row-value-in-select-statement/.
SELECT 'Pune' AS Office,
'2015' AS year,
10 AS recruited,
15 AS applied
INTO #Temp
UNION
SELECT 'Pune' AS Office,
'2016' AS year,
7 AS recruited,
20 AS applied
UNION
SELECT 'Mumbai' AS Office,
'2015' AS year,
10 AS recruited,
23 AS applied
UNION
SELECT 'Mumbai' AS Office,
'2016' AS year,
15 AS recruited,
18 AS applied;
WITH cte AS (
SELECT rownum = ROW_NUMBER() OVER (PARTITION BY t.office ORDER BY t.year), * FROM #Temp t)
SELECT cte.office, cte.recruited - prv.recruited AS DifferenceRecruited,
((cte.recruited - prv.recruited) / CONVERT(FLOAT, prv.recruited) * 100) AS RecruitedChangePercentage,
cte.applied - prv.applied AS DifferenceApplied,
((cte.applied - prv.applied) / CONVERT(FLOAT, prv.applied) * 100) AS AppliedChangePercentage
FROM cte
LEFT JOIN cte prv ON prv.Office = cte.office AND prv.rownum = cte.rownum - 1
WHERE prv.recruited IS NOT null
ORDER BY cte.Office DESC
Hope this helps.
I have been working with calculating annual taxable amount of a staff over changing salary rates.
salary_assigned_date | salary
-------------------------------
2011-12-06 5000
2012-01-05 10000
2012-02-10 15000
2012-04-08 20000
2012-08-01 28000
Now, my taxable amount for year 2012 in terms of months should be like this:
I have assumed no. of days in a month as 30.
month | taxable_amount
-----------------------------------------------
01 833.33 + 8333.33 /* Since salary has been changed
at 6th of month,
for 5 days,
taxable amount = 5000/30*5
=> 833.33
and for remaining 25 days
= 10000/30*25=> 8333.33
and same case for remaining months.*/
02 3000 + 10500
03 15000
04 4666.67 + 15333.33
05 20000
06 20000
07 20000
08 933.33 + 27066.67
09 28000
10 28000
11 28000
12 28000
I tried to write a stored procedure in order to calculate the taxable amount but I could not accomplish this.
Can someone help on this ?
you need a sql statement that joins a record in the table to the record in the table that has the next salary value... you also need to use a CTE (or whatever **MySQL equivalent* exists ) to generate all the months where no salary change occurs. * [Thanks to #Neville's comment]
Excuse the SQL server syntax, I am not going to look up the MySQL equivalents for you... the intent should be clear. I know MySQL has it's own functions equivalent to SQL servers' date functions getdate(), DateDiff(), DateAdd(), and Day().
With Dates(dt) As
( Select min(salary_assigned_date)
From yourTable
Union All
Select DateAdd(month,1, dt)
from dates
where dt < getdate()) -- replace getdate() with parameter for max date to calculate
-- If MySQL has no equivalent to CTE, you need to generate a temp table with
-- these dates in it and use that instead of the [Dates] construction
Select t.dt, t.salary/30.0 * (day(t.dt)-1) +
+ n.salary/30.0 * (31 - day(t.dt))
From Dates d
join yourTable t On t.salary_assigned_date =
(Select Min(salary_assigned_date)
From test where salary_assigned_date >= d.dt)
join yourTable n On n.salary_assigned_date =
(Select Min(salary_assigned_date)
From test where salary_assigned_date > d.dt)
Select t.salary/30.0 * (day(t.salary_assigned_date)-1) +
+ n.salary/30.0 * (31 - day(t.salary_assigned_date))
From table t
join table n On n.salary_assigned_date =
(Select Min(salary_assigned_date) From table
Where salary_assigned_date > t.salary_assigned_date)