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.
Related
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
I have the following table:
person_index score year
3 76 2003
3 86 2004
3 86 2005
3 87 2006
4 55 2005
4 91 2006
I want to group by person_index, getting the average score difference between consecutive years, such that I end up with one row per person, indicating the average increase/decrease:
person_index avg(score_diff)
3 3.67
4 36
So for person with index 3 - there were changes over 3 years, one was 10pt, one was 0, and one was 1pt. Therefore, their average score_diff is 3.67.
EDIT: to clarify, scores can also decrease. And years aren't necessarily consecutive (one person might not get a score at a certain year, so could be 2013 followed by 2015).
Simplest way is to use LAG(MySQL 8.0+):
WITH cte AS (
SELECT *, score - LAG(score) OVER(PARTITION BY person_index ORDER BY year) AS diff
FROM tab
)
SELECT person_index, AVG(diff) AS avg_diff
FROM cte
GROUP BY person_index;
db<>fiddle demo
Output:
+---------------+----------+
| person_index | avg_diff |
+---------------+----------+
| 3 | 3.6667 |
| 4 | 36.0000 |
+---------------+----------+
If the scores only increase -- as in your example -- you can simply do:
select person_id,
( max(score) - min(score) ) / nullif(max(year) - min(year) - 1, 0)
from t
group by person_id;
If they do not only increase, it is a bit trickier because you have to calculate the first and last scores:
select t.person_id,
(tmax.score - tmin.score) / nullif(tmax.year - tmin.year - 1, 0)
from (select t.person_id, min(year) as miny, max(year) as maxy
from t
group by person_id
) p join
t tmin
on tmin.person_id = p.person_id and tmin.year = p.miny join
t tmax
on tmax.person_id = p.person_id and tmax.year = p.maxy join
I am working with a parts / motorcycle fitment Mysql database where all parts are linked to all motorcycles they can be installed on. It looks like this:
part_number motorcycle year
1000 HONDA_CBR1000 2008
1000 HONDA_CBR1000 2009
1000 HONDA_CBR1000 2010
1000 HONDA_CBR1000 2011
1000 HONDA_CBR1000 2012
1000 HONDA_CBR1000 2013
1001 HONDA_CBR600 2008
1001 HONDA_CBR600 2009
1001 HONDA_CBR1000 2008
1001 HONDA_CBR1000 2009
1001 HONDA_CBR1000 2013
So it means that:
part #1000 can be installed on the Honda CBR1000 from 2008 to 2013
part #1001 can be installed on the Honda CBR600 from 2008 to 2009 AND on the Honda CBR1000 from 2008 to 2013.
Unfortunately, the table (which has ~650,000 rows) was not always filled correctly. In this example, you will notice the following lines are missing:
part_number motorcycle year
1001 HONDA_CBR1000 2010
1001 HONDA_CBR1000 2011
1001 HONDA_CBR1000 2012
because the part #1001 which can be installed on the HONDA_CBR1000 from 2008, 2009 and 2013 can also be installed in the "forgotten" years in between (2010, 2011 and 2012).
So the simple query:
SELECT * FROM mytable WHERE motorcycle = 'HONDA_CBR1000' AND year = '2011'
would only retrieve the row for part #1000 (while in reality, part #1001 is also installable on this bike).
in plain English, I guess a query like
SELECT * FROM mytable WHERE motorcycle = 'HONDA_CBR1000'
AND ("minimum year of part_number applicable to HONDA_CBR1000" <= '2011')
AND ("maximum year of part_number applicable to HONDA_CBR1000" >= '2011')
would retrieve all results (1000 and 1001).
But how can I ask that in SQL? Do you think it would too slow?
Thanks for any help!
SELECT part_number, max(year), Min(year)
FROM mytable
WHERE motorcycle = 'HONDA_CBR1000'
Group By part_number
Having Min(year) <= 2011
And max(year) >= 2011
*********************Edit****************
To improve performance, Lets try this,
1)
SELECT part_number
FROM mytable t,
(Select part_number, Min(year) Minyear, max(year) Maxyear
FROM mytable
Group BY part_number) t1
WHERE t.motorcycle = 'HONDA_CBR1000'
AND t.year Between MinYear and Maxyear
AND t.year = '2011'
*********************EDIT 2**********************************
So This is the query that will list out the years that are missed out. You can put the entire query in to a insert statement
SELECT partsnumber , yrs.allyears
FROM (Select max(year) maxyear, min(year) minyear, partsnumber
FROM yourtable
group by partsnumber) q1
(Select 1950+1+b+a*10 as allyears
from (select 0 as a union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) a,
(select 0 as b union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) b) y
Where yrs.allyears between maxyear and minyear
MINUS
SELECT partsnumber , yrs.allyears
From yourtable
yrs --> Subquery that generates years from 1950 to 2050 (If you have more years ( beyond 2050 or before 1950 ) then this has to be changed)
Am selecting the years between the min and max years for each productnumber. then with yrs table as reference am finding the years between min and max years.
The result from above query will give all years between min and max. The minus will give the years that are missed
Here is my approach for getting all combinations of parts and motorcycles and the years they have no data.
Generate all the rows for all the years, then filter out the ones you have. The first part uses cross join. The second left join:
select pm.part_number, pm.motorcycle, y.year
from (select part_number, motorcycle, min(year) as miny, max(year) as maxy
from mytable
group by part_number, motorcycle
) pm cross join
(select distinct year
from mytable
) y
on y.year between pm.miny and pm.maxy left join
mytable t
on t.part_number = pm.part_number and t.motorcycle = pm.motorcycle and
t.year = y.year
where y.year is null;
This assumes that all years are in your table, somewhere. The y table is just a list of years, so you can get it from another table or by creating a derived table. The subquery is just a convenient way to get it.
i have this table called RELEASE:
*Country, Album, Date_year, Date_month, Date_day*
Italy Z 1940 2 27
Italy Y 1992 11 22
Italy X 1940 1 20
Italy R 1998 null null
France W 1944 9 18
UK L 1989 8 21
UK P 1970 10 1
Germany E 2002 null null
I need to specify a SQL query that take the name of album, the name of country and the date (year, month, day) of the oldest album.
(it's ok also if the values of month and day are null)
I can't use LIMIT, OFFSET, ROWNUM... i can use only standard SQL constructs.
I try to make this query but it isn't correct:
SELECT country, album, min(date_year), min(date_month), min(date_day)
FROM release
The result it would be:
*Country, Album, Date_year, Date_month, Date_day*
Italy X 1940 1 20
How i can solve? Thanks
This should work, ultimately all you need to do is build the date in a sortable format, then sort by it.
select release.country, Album, Date_Year, Date_Month, Date_Day from RELEASE
left join
(
select country,
min(date_year*10000+date_month*100+date_day) minDay
from RELEASE
group by country) albumDay
on albumDay.country = RELEASE.country
where
date_year*10000+date_month*100+date_day = minDay
With the proviso that if you have multiple 'oldest' albums, it will show all of the joint oldest. The problem statement didn't specify how to handle this.
You need to add NULL handling (replace every reference to a date field with coalesce(date_foo,0); or coalesce(date_foo,99); depending on how you want to treat them.
Not tested this, I'd be amazed if it works. Instead of right you should probably use mid(8,len()) h (as the left is always 8 characters)
SELECT
release.country,
right(
min(
cast(Date_year as nvarchar)+ cast(Date_month as nvarchar) + cast(Date_Day as varchar) +album
),1) Album,
min(cast(Date_year as nvarchar)+ cast(Date_month as nvarchar) + cast(Date_Day as varchar) +album) minDate
from release
group by country
i want to show these records column wise for particular month and year, like below table format
Source Total
Organic 1252
Paid 121
Email Campaign 121
Total 1494
select Organic,Paid ,EmailCampaign ,Total from tbl_leads where Month='Aug' and Year='2015'
below is sample date
Organic Paid EmailCampaign Total ProjectName Month Year
4444 5555 2222 1111 demo project Feb 2015
1252 121 121 1494 debug test Aug 2015
In Sql Server you can use Cross Apply with Tabled Valued Constructor to unpivot the data
SELECT cs.Source,
cs.Total
FROM tbl_leads
CROSS apply (VALUES ('Organic',Organic),
('Paid',Paid),
('EmailCampaign',EmailCampaign),
('Total',Total)) cs(Source, Total)
WHERE Month = 'Aug'
AND Year = '2015'
Or Generic Sql solution
SELECT 'Organic' AS Source,
Organic AS Total
FROM tbl_leads
UNION ALL
SELECT 'Paid',
Paid
FROM tbl_leads
UNION ALL
SELECT 'EmailCampaign',
EmailCampaign
FROM tbl_leads
UNION ALL
SELECT 'Total',
Total
FROM tbl_leads