Report Builder 3.0 - grouping rows by time of day - sql-server

I am trying to create a table within a report that appears as follows:
The data set is based on this query:
SELECT
DATENAME(dw, CurrentReadTime) AS 'DAY',
DATEPART(dw, CurrentReadTime) AS 'DOW',
CAST(datename(HH, CurrentReadTime) as int) AS 'HOD',
AVG([Difference]) AS 'AVG'
FROM
Consumption
INNER JOIN Readings ON Readings.[RadioID-Hex] = Consumption.[RadioID-Hex]
WHERE
CONCAT([Building], ' ', [Apt]) = #ServiceLocation
GROUP BY
CurrentReadTime
ORDER BY
DATEPART(DW, CurrentReadTime),
CAST(DATENAME(HH, CurrentReadTime) AS INT)
The data from this table returns as follows:
In report builder, I have added this code to the report properties:
Function GetRangeValueByHour(ByVal Hour As Integer) As String
Select Case Hour
Case 6 To 12
GetRangeValueByHour = "Morning"
Case 12 to 17
GetRangeValueByHour = "Afternoon"
Case 17 to 22
GetRangeValueByHour = "Evening"
Case Else
GetRangeValueByHour = "Overnight"
End Select
Return GetRangeValueByHour
End Function
And this code to the "row group":
=Code.GetRangeValueByHour(Fields!HOD.Value)
When I execute the report, selecting the parameter for the target service location, I get this result:
As you will notice, the "Time of Day" is displaying the first result that meets the CASE expression in the Report Properties code; however, I confirmed that ALL "HOD" (stored as an integer) are being grouped together by doing a SUM on this result.
Furthermore, the actual table values (.05, .08, etc) are only returning the results for the HOD that first meets the requirements of the CASE statement in the VB code.
These are the things I need resolved, but can't figure out:
Why isn't the Report Properties VB code displaying "Morning", "Afternoon", "Evening", and "Overnight" in the Time of Day column?
How do I group together the values in the table? So that the AVG would actually be the sum of each AVG for all hours within the designated range and day of week (6-12, 12-18, etc on Monday, Tuesday etc).
To those still reading, thanks for your assistance! Please let me know if you need additional information.

I'm still not sure if I have a clear picture of your table design, but I'm imagining this as a single row group that's grouped on this expression: =Code.GetRangeValueByHour(Fields!HOD.Value). Based on this design and the dataset above, here's how I would solve your two questions:
Use the grouping expression for the value of the Time of Day cell, like:
Add a SUM with a conditional for the values on each day of the week. Example: the expression for Sunday would be =SUM(IIF(Fields!DOW.Value = 1, Fields!AVG.Value, CDec(0))). This uses CDec(0)instead of 0 because the AVG values are decimals and SSRS will otherwise throw an aggregate of mixed data types error by interpreting 0 as an int.

Related

Average of a player

I have a record that contains stat for a certain cricket player.
It has columns having dates, oppositions, Runs, Balls, Dismissals, Match_Number.
I want to do a query (SQL SERVER) to find out the batting average where every runs (Sum) is to be added; innings having a count of all innings except DNB but dismissal should not have a count of "Not Out", "Retired Hurt", "DNB" grouped by the opposition.
Note : DNB means Did not Bat.
The query doesn't have the required number of innings to calculate the average
So the problem is can't gather information for a single entity (count of no. of innings) having two set of parameters.
Without DNB
Without DNB, Not Out, Retired Hurt.
Please suggest.
You can put a case expression within an aggregate to exclude certain rows from a count/sum/average etc. So you could use something like this:
SELECT a.Opposition,
Matches = COUNT(*),
Innings = COUNT(CASE WHEN a.Dismissal <> 'DNB' THEN 1 END),
Runs = SUM(a.Runs),
Average = SUM(a.Runs) / NULLIF(COUNT(CASE WHEN a.Dismissal NOT IN ('DNB', 'Not Out', 'Retired not out') THEN 1 END), 0)
FROM dbo.SRTundlkarODI AS a
GROUP BY a.Opposition;
N.B. I have wrapped the COUNT for the average in NULLIF(<exp>, 0) so that should the batsmen have never got out you avoid a divide by zero error.

Column in SSRS to show value according to the parameter value in SSRS

I have one column in SSRS report which should show the value according to the parameter, if the parameter is selected to Million Euro then it should show the value in Million Euro which means the value should be divided by 10^6,if the parameter is selected as K Euro then the value should be as K Euros which means the value should be divided by 10^3.
I tried to create a dataset where I wrote the SQL query and made a parameter but it didn't worked.
Can anyone give the solution like what should be my dataset query and how should I map it with the parameter.
I used
if #CurrencyConv='MEuro' BEGIN
select 1/Power(10,6) as ProductValue
end
else BEGIN select 1/Power(10,3) as ProductValue
END
And made a parameter where I hardcoded the available values as 'MEuro' and 'KEuro'
And in that column's expression I multiplied the column value with Dataset Value(ProductValue)
Column name is Converted Booking and I have to multiply that column according to the parameter value so if the value is MEuro then it should be [Converted Booking]*1/Power(10,6)
In Sql the below query is working
select [Converted Booking]*1/Power(10,6) from T_GCP_REP
How to apply same thing in SSRS
If you want the final calculated value in you dataset then change your dataset query to
SELECT
*,
CAST([Converted Booking] as float) / CASE WHEN #CurrencyConv = 'MEuro' THEN 1000000 ELSE 1000 END as ConvertedBookingFinal
from T_GCP_REP
OR
If you want to do this purely in SSRS (maybe you cannot change your dataset query?)
The Set the parameter values to 1000 and 1000000 respectively.
Then in the report textbox set the expression to
= Fields.Converted_Booking.Value / Parameters!myParameterName.Value
you can leave the parameter labels as "MEuro" etc you only need to change the value.
If you want the field in your SQL, you can add it using a CASE statement.
SELECT *, 1/Power(10, CASE WHEN #CurrencyConv = 'MEuro' THEN 6 ELSE 3 END) as ProductValue
FROM TABLE
or you could use the new IIF in SQL like SSRS has
SELECT *, 1/Power(10, IIF(#CurrencyConv = 'MEuro', 6, 3)) as ProductValue
FROM TABLE
For an SSRS expression, an IIF statement could also be used:
=1/Power(10, IIF(Parameters!CurrencyConv.Value = 'MEuro', 6, 3))
Though it might be better to use MEuro as the parameter label and have 6 as the value (with KEuro, 3 for the other possible value). Then you could shorten it to:
=1/Power(10, Parameters!CurrencyConv.Value)

How can I add values to a chart that do not exist as 0 in google data studio?

I have got 4 tables in BigQuery that keep statistics for messages in a Message Queue. The tables are : receivedMessages, processedMessages, skippedMessages and failedMessages. Each table has among other things a header.processingMetadata.approximateArrivalTimestamp which as you might have guessed it is a timestamp field.
My purpose is to create 4 charts for each one of this tables aggregating in this field as well as a 5th chart that displays the percentage of each message category each day in regards to the receivedMessages as well as the unknown status messages using the following formula :
UNKNOWN_STATUS_MESSAGES = TOTAL_RECEIVED_MESSAGES - (TOTAL_PROCESSED_MESSAGES + TOTAL_SKIPPED_MESSAGES + TOTAL_FAILED_MESSAGES)
However some days do not have skipped or failed messages, therefore there are no records in Big Query in these two tables. This results to these 2 graphics having dates missing and also not displaying correctly the UNKNOWN_STATUS_MESSAGES in the 5th graph.
I also used the following code as a metric in my graphs with no success (changing the variable name appropriately each time).
CASE WHEN TOTAL_FAILED_MESSAGES IS NULL THEN 0 ELSE TOTAL_FAILED_MESSAGES END
Is there a way to make google data studio to fill the dates with no data with 0s so I can display the charts correctly?
As long as you know the date boundaries of your chart, you can fill those holes with zeros. For instance, if you want to generate your report for last 30 days:
with dates as (
select
x as date
from
unnest(generate_date_array(date_sub(current_date(), interval 30 day), current_date())) as x
)
select
date,
received_messages,
processed_messages,
skipped_messages,
failed_messages,
received_messages - (processed_messages + skipped_messages + failed_messages) as unknown_messages from (
select
d.date,
coalesce(count(received.*), 0) as received_messages,
coalesce(count(processed.*), 0) as processed_messages,
coalesce(count(skipped.*), 0) as skipped_messages,
coalesce(count(failed.*), 0) as failed_messages
from dates d
left join dataset.receivedMessages received
on date(received.header.processingMetadata.approximateArrivalTimestamp) = d.date
left join dataset.processedMessages processed
on date(processed.header.processingMetadata.approximateArrivalTimestamp) = d.date
left join dataset.skippedMessages skipped
on date(skipped.header.processingMetadata.approximateArrivalTimestamp) = d.date
left join dataset.failedMessages failed
on date(failed.header.processingMetadata.approximateArrivalTimestamp) = d.date
group by 1
)
order by 1
1) I recommend doing a join in BigQuery with a date master table to return '0' for those date values.
2) Otherwise, in Data Studio, make sure there is a field X that has values for all dates. Then create a calculated field with formula X - X + TOTAL_SKIPPED_MESSAGES and X - X + TOTAL_FAILED_MESSAGES
As I found out it is also possible to do it in non fixed date using date parameters. So the first part of khan's answer can be rewritten as:
WITH dates AS (
select *
from unnest(generate_date_array(PARSE_DATE('%Y%m%d', #DS_START_DATE), PARSE_DATE('%Y%m%d', #DS_END_DATE), interval 1 day)) as day
)

How to get multiple parallel periods set?

I would like to create a MDX query which returns a measure value for 15th day of each month between two dates.
For example, the result for 2010-01-01 and 2016-12-15 should be as below:
2016-12-15: 123
2016-11-15: 789
2016-10-15: 556
(...)
2010-01-15: 456
I know I can calculate the number of months between two dates using DateDiff() function. Also, I can use ParallelPeriod() function to get the value for the previous month.
However I have no idea how I can use these values together and "iterate" from 1 to DateDiff() result to create multiple ParallelPeriod() calls in the "Days" set.
WITH
MEMBER NumberOfMonths AS
DateDiff("m",
[Calendar].[Day].&[20100101].MemberValue,
[Calendar].[Day].&[20160315].MemberValue
)
SET Days AS {
PARALLELPERIOD([Calendar].[Month], 1, [Calendar].[Day].&[20160315]),
PARALLELPERIOD([Calendar].[Month], 2, [Calendar].[Day].&[20160315]),
PARALLELPERIOD([Calendar].[Month], 3, [Calendar].[Day].&[20160315])
-- (...) How to generate this set automatically, using NumberOfMonths?
}
SELECT
{ Days } ON 0,
{ Quantity } ON 1
FROM [MyCube]
Any help would be appreciated.
It's an interesting problem, and though there is a solution, reading the MDX reference just led me down blind alleys.
Here's how you can get a set of the fifteenth day of each month:
WITH SET Months
AS
[Calendar].[Month].Members
SET FifteenthDays AS
GENERATE(
Months,
StrToSet('HEAD(DESCENDANTS(Months.Current,[Calendar].[Day]),1).Item(0).Lead(14)')
)
SELECT {} ON 0,
FifteenthDays ON 1
FROM TheCube
You can adjust this to suit your requirements, by filtering the initial named set "Months" using your date parameters.
Here's what's going on:
The GENERATE/StrToSet combination applies the MDX inside the quotes to each member of the first set ("Months")
The Current function is like CurrentMember, but applied to a set within Generate() brackets.
The DESCENDANTS function gets all the month's "children" at the Day level (I had to use this rather than .Children as in my cube there's an additional generation - Weeks - between Months and Days)
The HEAD function gets the first day in the month. (If your Time dimension leaf level is not sorted in date order, you may need to wrap the set in an ORDER function).
MDX doesn't automatically figure out that a singleton set (HEAD({},1) will trivially always return a singleton or the empty set) is a member, and you can apply member functions on it. So before applying Lead, I have to use the Item() function. I don't know why this function works, because according to the documentation it has a different purpose.
Lead(14) gives you the 15th day of the month, because the argument is 0-based.
The MDX approach №1 (a calculated member with sum aggregation):
WITH
MEMBER [Measures].[Quantity15] AS
SUM(
[Calendar].[Day].[Day].Members,
IIF(
Right([Calendar].[Day].CurrentMember.Properties('Key'),2) = "15",
[Measures].[Quantity],
NULL
)
)
SELECT
NON EMPTY { [Calendar].[Day].&[20100101]:[Calendar].[Day].&[20160315] } ON 0,
{ [Measures].[Quantity15] } ON 1
FROM [MyCube]
The MDX approach №2 (without new members, filtering the set):
SELECT
NONEMPTY(
{[Calendar].[Day].&[20100101]:[Calendar].[Day].&[20160315]},
IIF(
Right([Calendar].[Day].CurrentMember.Properties('Key'),2) = "15",
1,
NULL
)
) ON 0,
{ [Measures].[Quantity] } ON 1
FROM [MyCube]

SQL: Is it possible to SUM() fields of INTERVAL type?

I am trying to sum INTERVAL. E.g.
SELECT SUM(TIMESTAMP1 - TIMESTAMP2) FROM DUAL
Is it possible to write a query that would work both on Oracle and SQL Server? If so, how?
Edit: changed DATE to INTERVAL
I'm afraid you're going to be out of luck with a solution which works in both Oracle and MSSQL. Date arithmetic is something which is very different on the various flavours of DBMS.
Anyway, in Oracle we can use dates in straightforward arithmetic. And we have a function NUMTODSINTERVAL which turns a number into a DAY TO SECOND INTERVAL. So let's put them together.
Simple test data, two rows with pairs of dates rough twelve hours apart:
SQL> alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss'
2 /
Session altered.
SQL> select * from t42
2 /
D1 D2
-------------------- --------------------
27-jul-2010 12:10:26 27-jul-2010 00:00:00
28-jul-2010 12:10:39 28-jul-2010 00:00:00
SQL>
Simple SQL query to find the sum of elapsed time:
SQL> select numtodsinterval(sum(d1-d2), 'DAY')
2 from t42
3 /
NUMTODSINTERVAL(SUM(D1-D2),'DAY')
-----------------------------------------------------
+000000001 00:21:04.999999999
SQL>
Just over a day, which is what we would expect.
"Edit: changed DATE to INTERVAL"
Working with TIMESTAMP columns is a little more labourious, but we can still work the same trick.
In the following sample. T42T is the same as T42 only the columns have TIMESTAMP rather than DATE for their datatype. The query extracts the various components of the DS INTERVAL and converts them into seconds, which are then summed and converted back into an INTERVAL:
SQL> select numtodsinterval(
2 sum(
3 extract (day from (t1-t2)) * 86400
4 + extract (hour from (t1-t2)) * 3600
5 + extract (minute from (t1-t2)) * 600
6 + extract (second from (t1-t2))
7 ), 'SECOND')
8 from t42t
9 /
NUMTODSINTERVAL(SUM(EXTRACT(DAYFROM(T1-T2))*86400+EXTRACT(HOURFROM(T1-T2))*
---------------------------------------------------------------------------
+000000001 03:21:05.000000000
SQL>
At least this result is in round seconds!
Ok, after a bit of hell, with the help of the stackoverflowers' answers I've found the solution that fits my needs.
SELECT
SUM(CAST((DATE1 + 0) - (DATE2 + 0) AS FLOAT) AS SUM_TURNAROUND
FROM MY_BEAUTIFUL_TABLE
GROUP BY YOUR_CHOSEN_COLUMN
This returns a float (which is totally fine for me) that represents days both on Oracle ant SQL Server.
The reason I added zero to both DATEs is because in my case date columns on Oracle DB are of TIMESTAMP type and on SQL Server are of DATETIME type (which is obviously weird). So adding zero to TIMESTAMP on Oracle works just like casting to date and it does not have any effect on SQL Server DATETIME type.
Thank you guys! You were really helpful.
You can't sum two datetimes. It wouldn't make sense - i.e. what does 15:00:00 plus 23:59:00 equal? Some time the next day? etc
But you can add a time increment by using a function like Dateadd() in SQL Server.
In SQL Server as long as your individual timespans are all less than 24 hours you can do something like
WITH TIMES AS
(
SELECT CAST('01:01:00' AS DATETIME) AS TimeSpan
UNION ALL
SELECT '00:02:00'
UNION ALL
SELECT '23:02:00'
UNION ALL
SELECT '17:02:00'
--UNION ALL SELECT '24:02:00' /*This line would fail!*/
),
SummedTimes As
(
SELECT cast(SUM(CAST(TimeSpan AS FLOAT)) as datetime) AS [Summed] FROM TIMES
)
SELECT
FLOOR(CAST(Summed AS FLOAT)) AS D,
DATEPART(HOUR,[Summed]) AS H,
DATEPART(MINUTE,[Summed]) AS M,
DATEPART(SECOND,[Summed]) AS S
FROM SummedTimes
Gives
D H M S
----------- ----------- ----------- -----------
1 17 7 0
If you wanted to handle timespans greater than 24 hours I think you'd need to look at CLR integration and the TimeSpan structure. Definitely not portable!
Edit: SQL Server 2008 has a DateTimeOffset datatype that might help but that doesn't allow either SUMming or being cast to float
I also do not think this is possible. Go with custom solutions that calculates the date value according to your preferences.
You can also use this:
select
EXTRACT (DAY FROM call_end_Date - call_start_Date)*86400 +
EXTRACT (HOUR FROM call_end_Date - call_start_Date)*3600 +
EXTRACT (MINUTE FROM call_end_Date - call_start_Date)*60 +
extract (second FROM call_end_Date - call_start_Date) as interval
from table;
You Can write you own aggregate function :-). Please read carefully http://docs.oracle.com/cd/B19306_01/appdev.102/b14289/dciaggfns.htm
You must create object type and its body by template, and next aggregate function what using this object:
create or replace type Sum_Interval_Obj as object
(
-- Object for creating and support custom aggregate function
duration interval day to second, -- In this property You sum all interval
-- Object Init
static function ODCIAggregateInitialize(
actx IN OUT Sum_Interval_Obj
) return number,
-- Iterate getting values from dataset
member function ODCIAggregateIterate(
self IN OUT Sum_Interval_Obj,
ad_interval IN interval day to second
) return number,
-- Merge parallel summed data
member function ODCIAggregateMerge(
self IN OUT Sum_Interval_Obj,
ctx2 IN Sum_Interval_Obj
) return number,
-- End of query, returning summary result
member function ODCIAggregateTerminate
(
self IN Sum_Interval_Obj,
returnValue OUT interval day to second,
flags IN number
) return number
)
/
create or replace type body Sum_Interval_Obj is
-- Object Init
static function ODCIAggregateInitialize(
actx IN OUT Sum_Interval_Obj
) return number
is
begin
actx := Sum_Interval_Obj(numtodsinterval(0,'SECOND'));
return ODCIConst.Success;
end ODCIAggregateInitialize;
-- Iterate getting values from dataset
member function ODCIAggregateIterate(
self IN OUT Sum_Interval_Obj,
ad_interval IN interval day to second
) return number
is
begin
self.duration := self.duration + ad_interval;
return ODCIConst.Success;
exception
when others then
return ODCIConst.Error;
end ODCIAggregateIterate;
-- Merge parallel calculated intervals
member function ODCIAggregateMerge(
self IN OUT Sum_Interval_Obj,
ctx2 IN Sum_Interval_Obj
) return number
is
begin
self.duration := self.duration + ctx2.duration; -- Add two intervals
-- return = All Ok!
return ODCIConst.Success;
exception
when others then
return ODCIConst.Error;
end ODCIAggregateMerge;
-- End of query, returning summary result
member function ODCIAggregateTerminate(
self IN Sum_Interval_Obj,
returnValue OUT interval day to second,
flags IN number
) return number
is
begin
-- return = All Ok, too!
returnValue := self.duration;
return ODCIConst.Success;
end ODCIAggregateTerminate;
end;
/
-- You own new aggregate function:
CREATE OR REPLACE FUNCTION Sum_Interval(
a_Interval interval day to second
) RETURN interval day to second
PARALLEL_ENABLE AGGREGATE USING Sum_Interval_Obj;
/
Last, check your function:
select sum_interval(duration)
from (select numtodsinterval(1,'SECOND') as duration from dual union all
select numtodsinterval(1,'MINUTE') as duration from dual union all
select numtodsinterval(1,'HOUR') as duration from dual union all
select numtodsinterval(1,'DAY') as duration from dual);
Finally You can create SUM function, if you want.

Resources