How to get multiple parallel periods set? - sql-server

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]

Related

unable to understand the dateadd function in SQL

I have a SQL query like
SET THIS_YEAR_END = '2022-11-01';
SET THIS_YEAR_START = DATEADD(DAY, -4*7+1, $THIS_YEAR_END);
SET LAST_YEAR_END = '2021-11-02';
SET LAST_YEAR_START = DATEADD(DAY, -4*7+1, $LAST_YEAR_END);
select end_date from (
select * from data
where DATE>= DATEADD(DAY, -27 * 7, $LAST_YEAR_START))
AND END_DATE BETWEEN CUST.END_DATE - 26 * 7 AND CUST.END_DATE - 7
I'm confused with this dateadd function in SQL. Can anyone please explain what exactly it's doing?
Here is the docs: https://docs.snowflake.com/en/sql-reference/functions/dateadd.html
Basically DATEADD is adding a specified value to a certain date. The first parameter is indicating the units of time that you want to add (e.g. DAY or MONTH), the second parameter specifies the number of units (e.g. number of days or number of months) and the third paramter is the date, to which you want to add something.
In your example in line 2 you are reducing THIS_YEAR_END by -4*7+1 (=-27) days.

Checking existence of dimension in MDX

How I can check if one dimension exist on axis in MDX statetment?
I need to check how many time units (days, weeks, months...) exist on axis1 and use it to calculate measure. Here is example, what should happen, I take some dimensions:
days -> [Measures].[A] = [Measures].[B] / number of members in axis 1, from only date dimension (365)
months -> [Measures].[A] = [Measures].[B] / number of members in axis 1, from only date dimension (12)
months, product group -> [Measures].[A] = [Measures].[B] / number of members in axis 1, from only date dimension (12)
So dimension different than date dimension should't affect calcutation. I only need to get count on members from [Date] dimension.
A simple example is counting of days:
With
Member [Measures].[Members on rows] AS
Axis(1).Count
Select
Non Empty [Measures].[Members on rows] on columns,
Non Empty [Date].[Day].[Day].Members on rows
From [Sales]
Where [Date].[Month].[Month].&[201701]
But you'll get only row count, you can't predict what's going on with an axis. Also you may check whether the whole attribute count = the report attribute count:
Count(existing [Date].[Day].[Day].Members) = Count([Date].[Day].[Day].Members)
If it returns true, most likely that means you don't use filter the [Date].[Day] hierarchy within your report.

Counting Columns with conditions, assigning values based on count

I have a table with call logs. I need to assign time slots for next call based on which time slot the phone number was reachable in.
The relevant columns of the table are:
Phone Number | CallTimeStamp
CallTimeStamp is a datetime object.
I need to calculate the following:
Time Slot: From the TimeStamp, I need to calculate the count for each time slot (eg. 0800-1000, 1001-1200, etc.) for each phone number. Now, if the count is greater than 'n' for a particular time slot, then I need to assign that time slot to that number. Otherwise, I select a default time slot.
Weekday Slot: Same as above, but with weekdays.
Priority: Basically a count of how many times a number was reached
Here's I have gone about solving these issues:
Priority
To calculate the number of times a phone number is called is straight forward. If a number exists in the call log, I know that it was called. In that case, the following query will give me the call count for each number.
SELECT DISTINCT(PhoneNumber), COUNT(PhoneNumber) FROM tblCallLog
GROUP BY PhoneNumber
However, my problem is that I need to change the values in the field Count(PhoneNumber) based on the value in that column itself. How do I go about achieving this? (eg. If Count(PhoneNumber) gives me a value > 20, I need to change it to 5).
Time Slot / Weekday
This is where I'm completely stumped and am looking for the "database" way of doing things.
Unfortunately, I can't get out of my iterative process of thinking. For example, if I was aggregating for a certain phone number (say '123456') and in a certain time slot (say between 0800-1000 hrs), I can write a query like this:
DECLARE #T1Start time = '08:00:00.0000'
DECLARE #T2End time = '10:00:00.0000'
SELECT COUNT(CallTimeStamp) FROM tblCallLog
WHERE PhoneNumber = '123456' AND FORMAT(CallTimeStamp, 'hh:mm:ss') >= #T1Start AND FORMAT(CallTimeStamp, 'hh:mm:ss') < #T2End
Now, I could go through each and every Distinct Phone Number in the table, count the values for each time slot and then assign a slot value for the phone number. However, there has to be a way that does not involve me iterating through a database.
So, I am looking for suggestions on how to solve this.
Thanks
You can use DATEPART Function to get week day slot.
To calculate time slot you can try dividing number of minutes from beginning of day and dividing it by size of the time slot. It would return you slot number. You can use either CASE statement to translate it to proper string or look table where you can store slot descriptions.
SELECT
PhoneNumber
, DATEPART(WEEKDAY, l.CallTimeStamp) AS DayOfWeekSlot
, DATEDIFF(MINUTE, CONVERT(DATE, l.CallTimeStamp), l.CallTimeStamp) / 120 AS TwoHourSlot /*You can change number of minutes to get different slot size*/
, COUNT(*) AS Count
FROM tblCallLog l
GROUP BY PhoneNumber
, DATEPART(WEEKDAY, l.CallTimeStamp)
, DATEDIFF(MINUTE, CONVERT(DATE, l.CallTimeStamp), l.CallTimeStamp) / 120
You could try this to return the phone number, the day of the week and a 2 hour slot. If the volume of calls is greater than 20 the value is set to 5 (not sure why to 5?). The code for the 2 hour section is adapted from this question How to Round a Time in T-SQL where the value 2 in (24/2) is the number of hours in your time period.
SELECT
PhoneNumber
, DATENAME(weekday,CallTimeStamp) as [day]
, CONVERT(smalldatetime,ROUND(CAST(CallTimeStamp as float) * (24/2),0)/(24/2)) AS RoundedTime
, CASE WHEN COUNT(*) > 20 THEN 5 ELSE COUNT(*) END
FROM
tblCallLog
GROUP BY
PhoneNumber
, DATENAME(weekday,dateadd(s,start_ts,'01/01/1970'))

DateAdd( on parameter

Hello I'm currently writing a report based on weekly sales,
I've attained my current figure correctly and that works with my #FirstdayofWeek
and #LastDayOfWeek parameters
and im now trying to replicate it for my Previous week, as you know previous week is -7 days behind
when I run it with this in my where clause
and FirstDayOfWeek = dateadd(day,-7,'2014/06/02')
and LastDayOfWeek = dateadd(day,-7,'2014/06/08')
it works and i get this figure for pre quantity and its correct
BUT when I do this for my parameter
AND dateadd(day,-7,w.FirstDayOfWeek) in (
SELECT Item
FROM DataWarehouse.dbo.ufnSplit(#FirstDayOfWeek, ',')
)
AND dateadd(day,-7,w.LastDayOfWeek) in (
SELECT Item
FROM DataWarehouse.dbo.ufnSplit(#LastDayOfWeek, ',')
)
I get the column headers nothing anywhere.
any ideas?
Here is the code I am using to execute the stored proc:
exec WeeklySalesAndUSW #BD=N'798664',
#CGNo=N'47',
#SCGNo=N'01,02,03,04,05,06,07,08',
#ProductClass=N'1',
#‌​ProductCode=N'1108',
#Region=N'772',
#FirstDayOfWeek = '2014/06/02',
#LastDayOfWeek = '2014/06/08'
Why isnt my parameter passing this through? why does it work if I hard code the date in but when i make it dynamic it gets nothing?
It's probably not working because you reversed the equation.
In the code that works, you are doing this:
and FirstDayOfWeek = dateadd(day,-7,'2014/06/02')
and LastDayOfWeek = dateadd(day,-7,'2014/06/08')
Notice that you are subtracting 7 days from the hard-coded "parameters", and not from the columns in the table.
Now that you are trying to use dynamic parameter values, you are doing the opposite:
AND dateadd(day,-7,w.FirstDayOfWeek) in (
SELECT Item
FROM DataWarehouse.dbo.ufnSplit(#FirstDayOfWeek, ',')
)
AND dateadd(day,-7,w.LastDayOfWeek) in (
SELECT Item
FROM DataWarehouse.dbo.ufnSplit(#LastDayOfWeek, ',')
)
You are subtracting 7 days from the table column, and not the parameter values.
You can correct this by simply removing the minus (-) sign before both of the 7's. This is because adding 7 to the left side of the equation is mathematically the same as subtracting 7 from the right side.

Report Builder 3.0 - grouping rows by time of day

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.

Resources