SSRS - calculate percent change with variable years and number of years - reporting-services

Hopefully, I can explain this clearly! Our report allows user to select a variable number of academic years. They can be consecutive or not. In this case, say the user selects 2017, 2015, 2013. The report presents like this:
AcYear TotCredits %Change
2013 251 0.00
2015 255 0.00
2017 1102 0.00
Because the SQL that provides the datasource is assuming the previous year is one year prior. So 2015 is being compared against 2014, even though we're not selecting that info, which is why it's 0. 2017 is being compared against 2016, even though the user selected 2015. So, either in the initial SQL or in the report table expression, how would I go about getting the credits for the most immediately prior SELECTED academic year, no matter how many were selected, and then calculating the percent change based on that??
(If the user selected consecutive years, say 2017 and 2016, the data looks like this:)
AcYear TotCredits %Change
2016 458 0.00%
2017 19 -95.85%
This is the self join to get the "previous" year:
from
cte
left join cte as prev1
on cte.academic_year - 1 = prev1.academic_year
and cte.subject = prev1.subject
Thanks for any ideas!

You could try to use previous function to see whether it works or not. You need to make sure Year has been sorted. Below is design
select * from (select 2012 as year, 12 as amount
union all
select 2013 as year, 5 as amount
union all
select 2014 as year, 6 as amount
union all
select 2015 as year, 4 as amount
union all
select 2016 as year, 24 as amount)tt
where year in (#t)
Expression:
=iif(Fields!year.Value-previous(Fields!year.Value)=1,(Fields!amount.Value-previous(Fields!amount.Value))/iif(IsNothing(previous(Fields!amount.Value) ), 0,previous(Fields!amount.Value)),0)
Then you will get the result like below
Zoe

Surprisingly easy/elegant fix, just took me a long time to find it. The original import bit:
SELECT
cte.academic_year
, cte.subject
, cte.subject_desc
, cte.credits as cur_credits
, prev.credits as prev_credits
FROM
cte
LEFT JOIN cte as prev
on cte.academic_year - 1 = prev.academic_year
and cte.subject = prev.subject
The new improved code:
SELECT
t.academic_year
, t.subject
, t.subject_desc
, t.credits as cur_credits
, prev_credits = prev.credits
FROM
cte t
OUTER APPLY
(SELECT top 1 credits from
cte
WHERE t.academic_year > academic_year
and t.subject = subject
ORDER BY academic_year desc) prev
It would have been very nice to use LAG, but alas, we are on SQL2008.

Related

SQL code for counting consecutive years - syntax error msg in code

I'm a heavy MS Access user, but still a novice with SQL. I need to count consecutive years in a table and am attempting to use SQL code provided on a previous post in Stack in order to count consecutive years by ID. My table name is "TEMP" and two field names are "ID" (ten-digit field stored as short text) and a 4-digit field named "YEAR" (stored as a number). I'm attempting to use a solution provided at Sql query to Count Total Consecutive Years from latest year but I'm getting a syntax error in the code on "over"
A sample of the data:
ID YEAR
0363267020 1982
0374650010 1982
0395373010 1982
0397175020 1982
0427746010 1982
0435187010 1985
0435187010 1984
0435187010 1982
0445237010 2017
0445237010 2016
0445237010 2015
0445237010 2014
0445237010 2013
0445237010 2012
0445237010 2010
0445237010 2009
0445237010 2008
0445237010 2005
The overall solution is to enable a count of consecutive of giving. So for above, the first 8 IDs wouldn't yet any counts, but the 9th ID would yield a result of 9. Is anyone able to assist?
In the end, I could export to Excel, but then it becomes a much more manual process and I'm hoping to build something that is more automatic (the table has more than 20,000 records).
I've also tried the following module, but am having trouble building a query to pull the data out.
Option Compare Database
With CurrentDb.OpenRecordset("TEMP")
Do Until ID.EOF
If !YEAR = strPrevious Then
Debug.Print strPrevious, lngCount
lngCount = 0
Else
strPrevious = !YEAR
lngCount = lngCount + 1
End If
Loop
End With
CODE from Sql query to Count Total Consecutive Years from latest year
select ID,
first_value(year) over (partition by ID order by year desc) - year +1 as x,
row_number() over (partition by ID order by year desc) as rn
from Temp
with cte as
(
select ID, year,
first_value(year) over (partition by ID order by year desc) - year + 1 as x,
row_number() over (partition by ID order by year desc) as rn
from Temp
)
select ID,
sum(case when year <> 0 then 1 else 0 end)
from cte
where x = rn
group by ID
I get the syntax error in the SQL query code. When I have attempted a query to work with the module, I continue to receive various error messages.

adding row value to total if condition (don't know how to ask this)

I'm running a points system for companies where every employee that works for that company is worth some points.
Every month the points for the companies are calculated.
This works so far, however In the 9th month of this year I would like to give double points for each acquired employee in that month.
I don't know how to do that.
I have this query now:
SELECT company, (employees *2) as "Points"
FROM data
WHERE month = '10'
GROUP BY company
But as you can see I give 2 points for each employee that works for that company in that month.
But for month 9 I want to give double points and add them to current points in current month(10)
I have this SQLfiddle as example: http://sqlfiddle.com/#!9/2cb812/7
Expected result:
company Points
__________________
company 1 26 + (extra points from month 9)
company 2 32 + (extra points from month 9)
company 3 44 + (extra points from month 9)
So it's all about the August/September delta 2018. If you run the query for any month before September 2018 (June 2018, May 2012, whatever), you just want to get the current month's points. If you run the query for any month after August 2018 (December 2018, March 2022, ...) you want the 2018 bonus points added.
Group by company and use conditional aggregation (an aggregation function on a condition) in order to calculate this.
We must look at the requested month (e.g. 10/2018) and August 2018 and September 2018.
SET #yearmonth = '201810';
SELECT
company,
SUM(
CASE WHEN yearmonth = #yearmonth THEN employees * 2 ELSE 0 END +
CASE WHEN #yearmonth >= '201809' AND yearmonth = '201809' THEN employees * 4 ELSE 0 END -
CASE WHEN #yearmonth >= '201809' AND yearmonth = '201808' THEN employees * 4 ELSE 0 END
) AS points
FROM data
WHERE yearmonth in ('201808', '201809', #yearmonth)
GROUP BY company
ORDER BY company;
The WHERE clause is superfluous, as the months are checked inside the SUM function, but it may speed up the query.
Rextester demo: https://rextester.com/ELOWTL44361

Getting the Max In Different Years

I am pulling together a table of training data for employees who took various training courses between 2010 and 2013. I am getting the counts by course. Some training an employee can accidentally register for multiple times in one year, I just need the individual employee counts. Some training however is required to be taken each year or every other year, I am trying to get the max date for each training completed for each year, so in case someone did take a course one year and then again a year later, both of them are counted. The code I have so far is,
SELECT [Training_GEMS].JOB_CLS_CD_DSC_TE, [Training_GEMS].TNG_NA, [Training_GEMS].TNG_SYS_NR, [Training_GEMS].JOB_CLS_CD, sum(iif(Max_Date BETWEEN #1/1/2010# AND #12/31/2010#,1,0)) AS 2010, sum(iif(Max_Date BETWEEN #1/1/2011# AND #12/31/2011#,1,0)) AS 2011, sum(iif(Max_Date BETWEEN #1/1/2012# AND #12/31/2012#,1,0)) AS 2012, sum(iif(Max_Date BETWEEN #1/1/2013# AND #12/31/2013#,1,0)) AS 2013
FROM Training_GEMS
GROUP BY [Training_GEMS].JOB_CLS_CD_DSC_TE, [Training_GEMS].TNG_NA, [Training_GEMS].TNG_SYS_NR, [Training_GEMS].JOB_CLS_CD;
I have the SUM IIFs setup, I am just trying to get the max for each year.
Add the following four lines (after adding comma to last SUM ... 2013 field)
Max(Iif(max_date BETWEEN #1 / 1 / 2010 #
AND #12 / 31 / 2010 # , max_date, null)) AS 2010max,
Max(Iif(max_date BETWEEN #1 / 1 / 2011 #
AND #12 / 31 / 2011 # , max_date, null)) AS 2011max,
Max(Iif(max_date BETWEEN #1 / 1 / 2012 #
AND #12 / 31 / 2012 # , max_date, null)) AS 2012max,
Max(Iif(max_date BETWEEN #1 / 1 / 2013 #
AND #12 / 31 / 2013 # , max_date, null)) AS 2013max
Something like this may require two steps. The first would be as you describe in eliminating any people that took a course more than once in a year.
Select UserID, Year(CourseDate) as CourseYear, Course, Max(CourseDate) as CourseDate
From Training_Gems
Group by Year(CourseDate), UserID, Course;
You can get the year from a date using the Year() function without having to create a new column for each year with an IIF.
Once you have your first results you can use them in a second sub-query like so.
select Count(Course), Course From
(Select UserID, Year(CourseDate) as CourseYear, Course, Max(CourseDate) as CourseDate
From Training_Gems
Group by Year(CourseDate), UserID, Course) as t1
Group By Course;

Range query from month and year

I have a table like this:
ID month year content
1 4 2013 xxxxx
2 5 2013 yyyyy
3 6 2013 zzzzz
4 8 2014 fffff
I want to query it based on a year and month range.
I have query like this:
SELECT * FROM UPP
WHERE ( month = '4' AND year = '2013' )
AND ( month = '6' AND year = '2013' )
That query runs but returns no result. Can anyone help me for fix this query?
NB: The month and year columns are integers.
Why not use the correct data type?
Failing that:
SELECT * FROM UPP WHERE (year=2013) AND (month BETWEEN 4 AND 6);
Would be the easiest path to this particular answer.
EDIT
SQL Fiddle for reference.
There will never be any rows where both month=4 and month=6 which is what your query is asking for. Adding brackets like that will not alter the AND behaviour as you seem to want them to so you are asking for WHERE year=2013 AND month=4 AND month=6.
There are a number of ways you could ask for what you seem to be wanting, for instance:
WHERE (year=2013 AND month=4) OR (year=2013 AND month=6)
or
WHERE year=2013 AND (month=4 OR month=6)
or
WHERE year=2013 AND month IN (4,6)
If you want the full range (the full quarter, months 4, 5, and 6 not just months 4 and 6 then swasheck's suggestion is probably the clearest way to go, though this will fall down if the date range straddles a boundary between years. If you need to do fully flexible ranged queries ("the six months to February 2013" and so forth) then you might want to rethink the table structure to more easily/efficiently support that.

Transformation of records 1 column 3 row -> 1 row 3 column

First look at below query
SELECT COUNT(id) AS total_record, id, modeller,
MONTHNAME(completed_date) AS current_month,
QUARTER(completed_date) AS current_quarter,
Difficulty,
YEAR(completed_date) AS current_year
FROM model
WHERE modeller != ''
AND completed_date BETWEEN '2010-04-01'
AND '2010-05-31' AND Difficulty != ''
GROUP BY Difficulty,
MONTH(completed_date) ORDER BY
MONTH(completed_date) ASC
Results I am getting is
Modeller Month Year Difficulty
XYZ Jan 2010 23
XYZ Jan 2010 14
XYZ Jan 2010 15
ABC Feb 2010 5
ABC Feb 2010 14
ABC Feb 2010 6
I want result like
Modeller Month Year Difficulty
XYZ Jan 2010 23, 14, 15
ABC Feb 2010 5, 14, 6
My database is MySQL for application I am developing so any help would be greatly appericated.
Take a look on GROUP_CONCAT
I think you want GROUP_CONCAT(). I've simplified your fields so you'll need to add the calculations back in, but something like this should do:
SELECT modeller, month, year, GROUP_CONCAT(DISTINCT Difficulty)
FROM Model
WHERE $conditions
GROUP BY modeller, month, year
Nice, the GROUP_CONCAT function is powerfull stuff, and probably is a perfect solution in this case.
But watch out, in big systems this kind of manipulation can belong in the application layer and not in the database queries, maximizing easy of maintenance, re-utilization and reducing dependency in the specific RDBMS.
An alternative (more engineered) wait it to use the DAO pattern. Like documented in http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html