Calculate the rate of change in SSRS - reporting-services

I need to calculate the rate of change in drug usage between two dates using SSRS. I am used to using SQL, therefore I am having a difficult time with SSRS.
I have the following information:
Avg_Alcohol_Use_Month Avg_Drug_Use_Month
First_interview_Date 1.63% 1.34%
1/30/2017
Followup_interview_date 2.58% .80%
6/30/2017
How do I create a report that reflects the rate of change in drug usage between two dates? I need to create the report in SSRS but, I don't know how to write a query in SSRS that will reflect the rate of change.
I cannot create the query in SQL because I only have access to the data through SSRS.

(This example is for SQL Server)
You can do it in SQL if you save the initial results in a table or a temp data structure. If you subquery, you can subtract the line's rates by the previous line's rate. This is by date, so you chose the MAX(date) for the given patient/doctor, whatever that (Primary Key?) is. In this case I have used "PatientID" to identify the patient. See below:
--Assume your values are saved in a table or other temp table
DECLARE #tmp TABLE (PatientID int, Interview_Date date, Avg_Alcohol_Use_Month decimal (4,2), Avg_Drug_Use_Month decimal (4,2))
INSERT INTO #tmp
VALUES
(1, '2017-01-30', 1.63, 1.34)
,(2, '2017-06-30', 2.58, 0.80)
,(1, '2017-03-01', 1.54, 1.23)
,(1, '2017-07-02', 3.21, 0.20)
,(2, '2017-08-23', 2.10, 4.52)
SELECT PatientID
,Interview_Date
,Avg_Alcohol_Use_Month
,Avg_Drug_Use_Month
,Avg_Alcohol_Use_Month
-
(SELECT Avg_Alcohol_Use_Month
FROM #tmp T2
WHERE T2.PatientID = T1.PatientID
AND T2.Interview_Date = (SELECT MAX(Interview_Date)
FROM #tmp T3
WHERE T3.Interview_Date < T1.Interview_Date
AND T3.PatientID = T1.PatientID
-- or whatever PK that makes the row unique for the patient.
)
) AS [Alcohol Use Rate change]
,Avg_Drug_Use_Month
-
(SELECT Avg_Drug_Use_Month
FROM #tmp T2
WHERE T2.PatientID = T1.PatientID
AND T2.Interview_Date = (SELECT MAX(Interview_Date)
FROM #tmp T3
WHERE T3.Interview_Date < T1.Interview_Date
AND T3.PatientID = T1.PatientID
-- or whatever PK makes the row unique for the patient.
)
) AS [Drug Use Rate change]
FROM #tmp T1
ORDER BY PatientID, Interview_Date
Use such a query as the dataset for SSRS.

Related

How to merge and center for certain row in SQL/SSRS report?

I have a table as below:
I'd like to merge and center S/No 1.0 and 2.0 only, and display to SSRS report, the rest of the row remained unchanged, is there a way to do it?
The result will be like the 2nd image below.
Method 1: No real changes to dataset.
Add a parent row group with a header to your current tablix. Set the grouping to an expression such as (assuming S/No is from a field called SerialNumber
=CINT(Fields.SerialNumber.Value)
So here we just convert S/No to an integer so 1.0 and 1.1 both return 1
Now you will have a header row for each group of rows.
You can merge the cells in the header row and set the expression to
=FIRST(Fields!Description.Value)
You may have to force the order of the data in your dataset to ensure that 1.0 is always before 1.1 etc.
Method 2: Add the group names to your dataset
Note: This is written for SQL Server not MySQL but should be easy enough to translate if required
If this does not work then you could add the group headers into a new column in your dataset query, then just group on that. The dataset query would look something like this...
(I've replicated your data to show it working)
DECLARE #myTable TABLE (SerialNumber decimal (5,1), Description varchar(50), UOM varchar(50), rate decimal (10,2))
INSERT INTO #myTable VALUES
(1.0, 'Warehouse Charges', NULL, NULL),
(1.1, 'Storage in pallet', 'perpallet per month or part thereof', 15.84),
(2.0, 'Handling', NULL, NULL),
(2.1, 'Unstuffing - Palletised', 'per pallet', 5.00),
(2.2, 'De-palletised', 'per palett', 5.00)
SELECT * FROM #myTable
SELECT
b.Description as GroupName, a.*
FROM #myTable a
JOIN (SELECT SerialNumber, Description FROM #myTable WHERE CAST(SerialNumber AS INT) = SerialNumber) b -- headers only
ON CAST(a.SerialNumber AS INT) = b.SerialNumber
WHERE a.SerialNumber != b.SerialNumber
This produces the following output
So now you can just group on the groupname field and then merge as described in the earlier method.

MySQL query index & performance improvements

I have created an application to track progress in League of Legends for me and my friends. For this purpose, I collect information about the current rank several times a day into my MySQL database. To fetch the results and show the to them in the graph, I use the following query / queries:
SELECT
lol_summoner.name as name, grid.series + ? as timestamp,
AVG(NULLIF(lol.points, 0)) as points
FROM
series_tmp grid
JOIN
lol ON lol.timestamp >= grid.series AND lol.timestamp < grid.series + ?
JOIN
lol_summoner ON lol.summoner = lol_summoner.id
GROUP BY
lol_summoner.name, grid.series
ORDER BY
name, timestamp ASC
SELECT
lol_summoner.name as name, grid.series + ? as timestamp,
AVG(NULLIF(lol.points, 0)) as points
FROM
series_tmp grid
JOIN
lol ON lol.timestamp >= grid.series AND lol.timestamp < grid.series + ?
JOIN
lol_summoner ON lol.summoner = lol_summoner.id
WHERE
lol_summoner.name IN (". str_repeat('?, ', count($names) - 1) ."?)
GROUP BY
lol_summoner.name, grid.series
ORDER BY
name, timestamp ASC
The first query is used in case I want to retrieve all players which are saved in the database. The grid table is a temporary table which generated timestamps in a specific interval to retrive information in chunks of this interval. The two variable in this query are the interval. The second query is used if I want to retrieve information for specific players only.
The grid table is produces by the following stored procedure which is called with three parameters (n_first - first timestamp, n_last - last timestamp, n_increments - increments between two timestamps):
BEGIN
-- Create tmp table
DROP TEMPORARY TABLE IF EXISTS series_tmp;
CREATE TEMPORARY TABLE series_tmp (
series bigint
) engine = memory;
WHILE n_first <= n_last DO
-- Insert in tmp table
INSERT INTO series_tmp (series) VALUES (n_first);
-- Increment value by one
SET n_first = n_first + n_increment;
END WHILE;
END
The query works and finishes in reasonable time (~10 seconds) but I am thankful for any help to improve the query by either rewriting it or adding additional indexes to the database.
/Edit:
After review of #Rick James answer, I modified the queries as follows:
SELECT lol_summoner.name as name, (lol.timestamp div :range) * :range + :half_range as timestamp, AVG(NULLIF(lol.points, 0)) as points
FROM lol
JOIN lol_summoner ON lol.summoner = lol_summoner.id
GROUP by lol_summoner.name, lol.timestamp div :range
ORDER by name, timestamp ASC
SELECT lol_summoner.name as name, (lol.timestamp div :range) * :range + :half_range as timestamp, AVG(NULLIF(lol.points, 0)) as points
FROM lol
JOIN lol_summoner ON lol.summoner = lol_summoner.id
WHERE lol_summoner.name IN (<NAMES>)
GROUP by lol_summoner.name, lol.timestamp div " . $steps . "
ORDER by name, timestamp ASC
This improves the query execution time by a really good margin (finished way under 1s).
Problem 1 and Solution
You need a series of integers between two values? And they differ by 1? Or by some larger value?
First, create a permanent table of the numbers from 0 to some large enough value:
CREATE TABLE Num10 ( n INT );
INSERT INTO Num10 VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
CREATE TABLE Nums ( n INT, PRIMARY KEY(n))
SELECT a.n*1000 + b.n*100 + c.n*10 + d.n
FROM Num10 AS a
JOIN Num10 AS b -- note "cross join"
JOIN Num10 AS c
JOIN Num10 AS d;
Now Nums has 0..9999. (Make it bigger if you might need more.)
To get a sequence of consecutive numbers from 123 through 234:
SELECT 123 + n FROM Nums WHERE n < 234-123+1;
To get a sequence of consecutive numbers from 12345 through 23456, in steps of 15:
SELECT 12345 + 15*n FROM Nums WHERE n < (23456-12345+1)/15;
JOIN to a SELECT like one of those instead of to series_tmp.
Barring other issue, that should significantly speed things up.
Problem 2
You are GROUPing BY series, but ORDERing by timestamp. They are related, so you might get the 'right' answer. But think about it.
Problem 3
You seem to be building "buckets" (called "series"?) from "timestamps". Is this correct? If so, let's work backwards -- Turn a "timestamp" into a "bucket" number:
bucket_number = (timestamp - start) / bucket_size
By doing that throughout, you can avoid 'Problem 1' and eliminate my solution to it. That is, reformulate the entire queries in terms of buckets.

How to divide two values in same column

I am trying divide "counts". The requirements for division are the values should have the same batch ID and Carry acronym. The divisor should be the count value for "Dental -NEBD" and the dividend should be "Added from batch".
How can I do that?
Here's a data sample:
Batch Carr_Acronym DATE Count Datatype
45056 ARM 12/31/2014 20 Added from batch
45056 ARM 12/31/2014 0 Deleted from batch
45056 ARM 12/31/2014 5 Dental - NEDB
45055 CUU 12/31/2014 0 Dental - NEDB
Something like this should work. You can create a temporary table to join on and select exactly what you need. The temporary table will be dropped after the transaction. This will take your current table (in the question) and duplicate it to a temp table. It will then join the two tables (current one in the question plus the newly created temp table) based on the conditions (matching Batch and matching Carr_Acronym) and then divide the counts when the Datatypes are of the appropriate value.
CREATE TEMPORARY TABLE IF NOT EXISTS tempTable AS (SELECT * FROM MyTable);
SELECT (`a`.`Count` / `b`.`Count`) as `result`
FROM MyTable `a`
INNER JOIN tempTable `b` ON (`a`.`Batch` = `b`.`Batch`) AND (`a`.`Carr_Acronym` = `b`.`Carr_Acronym`)
WHERE a.Datatype LIKE 'added%' AND b.Datatype LIKE 'dental%';
One approach is to user conditional aggregation:
select batch, Carr_Acronym,
(sum(case when datatype = 'Added from batch' then count else 0 end) /
sum(case when datatype = 'Dental - NEDB' then count end)
) as ratio
from table t
group by batch, Carr_Acronym;

How to add parameter to aggregate function in report dataset

Thank you for coming to look at my question.
I have an SQL group by function which I'd like to add parameters to. (If that's possible)
I've tried to splice the parameters, two columns from the table into the function but I don't seem to get it right.
This function creates a table that counts records, I would like to be able to filter with parameters by 'Team' and 'Location'.
How would I go about adding this information to the dataset to allow me to filter?
I would normally add them using:
select
i.Team
,i.Location
From
incident i
Where i.Team in (#Team)
and i.Location in (#Location)
The table is called incident and all the information is from the same table.
I would very much appreciate an idea to do this. Thank you.
Oh, and I'm using Report Builder 3, with SQL 2008 R2
declare #st_date datetime;
declare #en_date datetime;
declare #days int;
declare #offset int;
set #en_date = (#en_datein);
set #offset = (#BrowserTimezoneOffset);
set #days = -6;
set #st_date = DATEADD(dd, #days, #en_date);
with daterange(dt) as
(select
#st_date dt
union all
select
DATEADD(dd, 1, dt) dt
from daterange
where dt <= DATEADD(dd, -1, #en_date)
)
select
left(DATENAME(dw, dt), 3) as weekday
,ISNULL(sum(inc.createdc), 0) as createdcount
,ISNULL(sum(inr.resolvedclosedc), 0) as resolvedclosedcount
from daterange left outer join
(select
left(DATENAME(dw,DATEADD(mi,#offset,CreatedDateTime)), 3) as createddatetime
,count(recid) as createdc
from Incident
where DATEADD(mi,#offset,CreatedDateTime) >= #st_date
and DATEADD(mi,#offset,CreatedDateTime) <= #en_date
group by left(DATENAME(dw, DATEADD(mi,#offset,CreatedDateTime)), 3)
) as inc
on inc.CreatedDateTime = left(DATENAME(dw, dt), 3)
left outer join
(select
left(DATENAME(dw, DATEADD(mi,#offset,ResolvedDateTime)), 3) as ResolvedDateTime
,count(case when status in ('Resolved', 'Closed') then 1 end) as resolvedclosedc
from Incident
where DATEADD(mi,#offset,ResolvedDateTime) between #st_date and #en_date
group by left(DATENAME(dw, DATEADD(mi,#offset,ResolvedDateTime)), 3)
) as inr
on inr.ResolvedDateTime = left(DATENAME(dw, dt), 3)
group by dt
order by dt
When using parameters that will be using one or many values you may tie them to a dataset as well.
Say if I have orders and people in a pretend sequence but I want to find orders of only certain people. I would follow a few steps:
I would create a dataset only for a parameter and call it 'People' for this example lets use a table variable that self executes and place this 'Query' box for a dataset.
declare #People Table ( personID int identity, person varchar(8));
insert into #People values ('Brett'),('Sean'),('Chad'),('Michael')
,('Ray'),('Erik'),('Queyn');
select * From #People
I would want to start with the dependency first which is a variable #Person I set up as an Integer and check 'Allow multiple values'. I then choose 'Available Values' on the left pane of the variable. I choose 'Get values from a query' choose my 'people' dataset from 1, choose PersonID as the Value field, and person as the label.
Now my parameter is bound and I can move on to my orders set. Again create a Dataset but call this one 'OrdersMain' and use a self extracting table variable but I am adding a predicate now referencing my variable from above as well.
declare #Orders table ( OrderID int identity, PersonID int, Desciption varchar(32), Amount int);
insert into #Orders values (1, 'Shirt', 20),(1, 'Shoes', 50),(2, 'Shirt', 22),
(2, 'Shoes', 52),(3, 'Shirt', 20),(3, 'Shoes', 50),(3, 'Hat', 20),
(4, 'Shirt', 20),(5, 'Shirt', 20),(5, 'Pants', 30), (6, 'Shirt', 20),
(6, 'RunningShoes', 70),(7, 'Shirt', 22),(7, 'Shoes', 40),(7, 'Coat', 80)
Select * from #Orders where PersonID in (#Person)
Now if populate my report with a tablix item and put the values from 'OrdersMain' in a tablix a user is prompted with a label for Brett, Sean, etc.. but the id is used for the orders to limit the scope of the dataset.
Optional
You can repeat step 1 for a SUBSET of people in another dataset and call it 'Defaults'. Then with an expanse of step 2 leave everything as is, but add this new dataset to 'Default Values' get from a query. This way I could create a temp table to get some of my people I most often use and then set them to be defaults instead. This would make the report auto execute when called.
Filtering can mean other things in SSRS as well. You can on any dataset see on the left pane a 'filter' and you may apply this. Keep in mind this will evaluate the whole expression first and then filter it. This use IMHO is best with shared datasets that are rather small and fast. Or you can use the filter clause in tablix elements as well which often is good when you want three objects from the same set but different predicates evaluated after runtime but to limit scope with reuse of one dataset for many objects.

Generic procedure to perform SCD in sql

I have 2 tables in mssql server.I can perform scd through custom insert/update/delete and also through Merge statement.
Awesome Merge
I want to know that is there any generic procedure that could server the purpose. we just pass it 2 tables and it should porform the SCD. any option in SQL server 2008?
Thanks
No, there isn't and there can't be a generic one suitable for no matter what tables you pass to it. For several reasons:
How do you know which SCD type? (Okay, could be another parameter, but...)
How do you know which column should be historicized and which should be overwritten?
How do you determine which column is the business key, the surrogate key, the expiration column and so on?
To specify the columns in an update statement you must write dynamic sql, which is possible, but the above point comes into play
Not a reason why it's not possible but also consider: For a proper UPSERT one usually works with temporary tables, the MERGE statement sucks for SCDs except in special cases. That is because you can't use a MERGE statement together with an INSERT/UPDATE and you would have to disable foreign keys for that, since an UPDATE is implemented as DELETE THEN INSERT (or something like that, don't remember clearly, but I had those problems when I tried).
I prefer doing it this way (SCD type 2 and SQL Server that is):
Step 1:
IF EXISTS (
SELECT * FROM sys.objects
WHERE name = 'tmpDimSource')
DROP TABLE tmpDimSource;
SELECT
*
INTO tmpDimSource
FROM
(
SELECT whatever
FROM yourTable
);
Step 2:
IF EXISTS (
SELECT * FROM sys.objects
WHERE name = 'tmpDimYourDimensionName')
DROP TABLE tmpDimYourDimensionName;
SELECT * INTO tmpDimYourDimensionName FROM D_yourDimensionName WHERE 1 = 0;
INSERT INTO tmpDimYourDimensionName
(
sid, /*a surrogate id column*/
theColumnsYouNeedInYourDimension,
validFrom
)
SELECT
ISNULL(d.sid, 0),
ds.theColumnsYouNeedInYourDimension,
DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()), 0) /*the current date*/
FROM
tmpDimSource ds
LEFT JOIN D_yourDimensionName d ON ds.whateverId = c.whateverId
;
The ISNULL(d.sid, 0) in step 2 is important. It returns the surrogate id of your dimension, if an entry already exists, otherwise 0.
Step 3:
UPDATE D_yourDimensionName SET
validTo = DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()) - 1, 0) /*yesterday*/
FROM
D_yourDimensionName d
INNER JOIN tmpDimYourDimensionName t ON d.sid = t.sid
WHERE t.sid <> 0 AND
(
d.theColumnWhichHasChangedAndIsImportant <> t.theColumnWhichHasChangedAndIsImportant OR
d.anotherColumn <> t.anotherColumn
)
;
In Step 3 you mark the existing entry as not valid anymore and keep a history of it. The valid entry you get with WHERE validTo IS NULL.
You can also add another UPDATE to overwrite any other column with the new value if needed.
Step 4:
INSERT INTO D_yourDimensionName
SELECT * FROM tmpDimYourDimensionName
WHERE sid = 0;
And that's it.