How to add parameter to aggregate function in report dataset - mysql

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.

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.

Calculate the rate of change in SSRS

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.

MySQL - Select row only if previous row field was 0?

I have a table as below:
CREATE TABLE IF NOT EXISTS `status`
(`code` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY
,`IMEI` varchar(15) NOT NULL
,`ACC` tinyint(1) NOT NULL
,`datetime` datetime NOT NULL
);
INSERT INTO status VALUES
(1, 123456789012345, 0, '2014-07-09 10:00:00'),
(2, 453253453334445, 0, '2014-07-09 10:05:00'),
(3, 912841851252151, 0, '2014-07-09 10:08:00'),
(4, 123456789012345, 1, '2014-07-09 10:10:00'),
(5, 123456789012345, 1, '2014-07-09 10:15:00');
I need to get all rows for a given IMEI (e.g 123456789012345) where ACC=1 AND the previous row for same IMEI has ACC=0. The rows may be one after the other or very apart.
Given the exampl above, I'd want to get the 4th row (code 4) but not 5th (code 5).
Any ideas? Thanks.
Assuming that you mean previous row by datetime
SELECT *
FROM status s
WHERE s.imei='123456789012345'
AND s.acc=1
AND (
SELECT acc
FROM status
WHERE imei=s.imei
AND datetime<s.datetime
ORDER BY datetime DESC
LIMIT 1
) = 0
The way I would approach this problem is much different from the approaches given in other answers.
The approach I would use would be to
1) order the rows, first by imei, and then by datetime within each imei. (I'm assuming that datetime is how you are going to determine if a row is "previous" to another row.
2) sequentially process the rows, first comparing imei from the current row to the imei from the previous row, and then checking if the ACC from the current row is 1 and the ACC from the previous row is 0. Then I would know that the current row was a row to be returned.
3) for each processed row, in the resultset, include a column that indicates whether the row should be returned or not
4) return only the rows that have the indicator column set
A query something like this:
SELECT t.code
, t.imei
, t.acc
, t.datetime
FROM ( SELECT IF(s.imei=#prev_imei AND s.acc=1 AND #prev_acc=0,1,0) AS ret
, s.code AS code
, #prev_imei := s.imei AS imei
, #prev_acc := s.acc AS acc
, s.datetime AS datetime
FROM (SELECT #prev_imei := NULL, #prev_acc := NULL) i
CROSS
JOIN `status` s
WHERE s.imei = '123456789012345'
ORDER BY s.imei, s.datetime, s.code
) t
WHERE t.ret = 1
(I can unpack that a bit, to explain how it works.)
But the big drawback of this approach is that it requires MySQL to materialize the inline view as a derived table (temporary MyISAM table). If there was no predicate (WHERE clause) on the status table, the inline view would essentially be a copy of the entire status table. And with MySQL 5.5 and earlier, that derived table won't be indexed. So, this could present a performance issue for large sets.
Including predicates (e.g. WHERE s.imei = '123456789' to limit rows from the status table in the inline view query may sufficiently limit the size of the temporary MyISAM table.
The other gotcha with this approach is that the behavior of user-defined variables in the statement is not guaranteed. But we do observe a consistent behavior, which we can make use of; it does work, but the MySQL documentation warns that the behavior is not guaranteed.
Here's a rough overview of how MySQL processes this query.
First, MySQL runs the query for the inline view aliased as i. We don't really care what this query returns, except that we need it to return exactly one row, because of the JOIN operation. What we care about is the initialization of the two MySQL user-defined variables, #prev_imei and #prev_acc. Later, we are going to use these user-defined variables to "preserve" the values from the previously processed row, so we can compare those values to the current row.
The rows from the status table are processed in sequence, according to the ORDER BY clause. (This may change in some future release, but we can observe that it works like this in MySQL 5.1 and 5.5.)
For each row, we compare the values of imei and acc from the current row to the values preserved from the previous row. If the boolean in the IF expression evaluates to TRUE, we return a 1, to indicate that this row should be returned. Otherwise, we return a 0, to indicate that we don't want to return this row. (For the first row processed, we previously initialized the user-defined variables to NULL, so the IF expression will evaluate to 0.)
The #prev_imei := s.imei and #prev_acc := s.acc assigns the values from the current row to the user-defined values, so they will be available for the next row processed.
Note that it's important that the tests of the user-defined variables (the first expression in the SELECT list) before we overwrite the previous values with the values from the current row.
We can run just the query from the inline view t, to observe the behavior.
The outer query returns rows from the inline view that have the derived ret column set to a 1, rows that we wanted to return.
select * from status s1
WHERE
ACC = 1
AND code = (SELECT MIN(CODE) FROM status WHERE acc = 1 and IMEI = s1.IMEI)
AND EXISTS (SELECT * FROM status WHERE IMEI = s1.IMEI AND ACC = 0)
AND IMEI = 123456789012345
SELECT b.code,b.imei,b.acc,b.datetime
FROM
( SELECT x.*
, COUNT(*) rank
FROM status x
JOIN status y
ON y.imei = x.imei
AND y.datetime <= x.datetime
GROUP
BY x.code
) a
JOIN
( SELECT x.*
, COUNT(*) rank
FROM status x
JOIN status y
ON y.imei = x.imei
AND y.datetime <= x.datetime
GROUP
BY x.code
) b
ON b.imei = a.imei
AND b.rank = a.rank + 1
WHERE b.acc = 1
AND a.acc = 0;
you can do a regular IN() and then group any duplicates (you could also use a limit but that would only work for one IMEI)
SETUP:
INSERT INTO `status`
VALUES
(1, 123456789012345, 0, '2014-07-09 10:00:00'),
(2, 453253453334445, 0, '2014-07-09 10:05:00'),
(3, 912841851252151, 0, '2014-07-09 10:08:00'),
(4, 123456789012345, 1, '2014-07-09 10:10:00'),
(5, 123456789012345, 1, '2014-07-09 10:15:00'),
(6, 123456789012345, 1, '2014-07-09 10:15:00'),
(7, 453253453334445, 1, '2014-07-09 10:15:00');
QUERY:
SELECT * FROM status
WHERE ACC = 1 AND IMEI IN(
SELECT DISTINCT IMEI FROM status
WHERE ACC = 0)
GROUP BY imei;
RESULTS:
works with multiple IMEI that have a 0 then a 1... IMAGE
EDIT:
if you would like to go by the date entered as well then you can just order it first by date and then group.
SELECT * FROM(
SELECT * FROM status
WHERE ACC = 1 AND IMEI IN(
SELECT DISTINCT IMEI FROM status
WHERE ACC = 0)
ORDER BY datetime
) AS t
GROUP BY imei;

Multiple Join on same table with different columns

I have problems making a SQL request.
Here is my tables:
CREATE TABLE dates(
id INT PRIMARY KEY,
obj_id INT,
dispo_date text
);
CREATE TABLE option(
id INT PRIMARY KEY,
obj_id INT,
random_option INT
);
CREATE TABLE obj(
id INT PRIMARY KEY,
);
and a random date that the user gives me and some options.
I'd like to select everything on both tables which correspond to an obj having his date equal to the user's date.
let's say that DATE = "22/01/2013" and OPTIONS = 3.
SELECT * FROM obj
INNER JOIN dates
ON dates.obj_id=obj.id
INNER JOIN option
ON option.obj_id=obj.id
WHERE dates.dispo_date="22/01/2013"
AND option.random_option=3;
That just gives me everything from my obj table with, for each one, the same dates and options without filtering anything.
Can someone give me some pointers about what I'm doing wrong ?
SOLUTION:
Since everybody seemed to get what I was looking for I restarted my SQL server and since, everything works ...
Thanks for your help and sorry for the time-loss :-(
As far as I can see, there is nothing wrong with the query.
When I try it, it returns only the obj rows where there is a corresponding date and a corresponding option.
insert into dates values
(1, 1, '22/01/2013'),
(2, 1, '23/01/2013'),
(3, 2, '22/01/2013'),
(4, 2, '23/01/2013'),
(5, 3, '23/01/2013'),
(6, 3, '24/01/2013');
insert into `option` values
(1, 1, 4),
(2, 1, 5),
(3, 2, 3),
(4, 2, 4),
(5, 3, 3),
(6, 3, 4);
insert into obj values
(1),
(2),
(3)
With this data it should filter out obj 1 because there is no option 3 for it, and filter out obj 3 because there is no date 22 for it.
Result:
ID OBJ_ID DISPO_DATE RANDOM_OPTION
-------------------------------------
2 2 22/01/2013 3
Demo: http://sqlfiddle.com/#!2/a398f/1
Change your line
WHERE dates.dispo_date="22/01/2013"
for
WHERE DATE(dates.dispo_date)="22/01/2013"
Handling dates in text fields is a little tricky (also bad practice). Make sure both dates are in the same format.
First, I'm a little confused on which ID's map to which tables. I might respectfully suggest that the id field in DATES be renamed to date_id, the id in OPTION be renamed to option_id, and the id in obj to obj_id. Makes those relationships MUCH clearer for folks looking in through the keyhole. I'm going in a bit of a circle making sure I understand your relationships properly. On that basis, I may be understanding your problem incorrectly.
I think you have obj.id->dates.obj_id, and option.obj_id->dates.obj_id, so on that basis, I think your query has to be a bit more complicated:
This gives you object dates:
Select *
from obj obj
join dates d
on obj.id=d.obj_id
This gives you user dates:
select *
from option o
join dates d
on o.obj_id=d.obj_id
To get the result of objects and users having the same dates, you'd need to hook these two together:
select *
from (Select *
from obj obj
join dates d
on obj.id=d.obj_id) a
join (select *
from option o
join dates d
on o.obj_id=d.obj_id) b
on a.dispo_date=b.dispo_date
where b.random=3
I hope this is useful. Good luck.

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.