mdx group by measures values range - group-by

I'm looking for the way to create a group by measure value.
I have a cube of bookings with the dimensions, client, supplier.... and more dimensions.
I want to create a query, which must returns the suppliers grouped by Total amount range and count of suppliers that contains each group.
I have created a query which returns for each supplier which his range total amount, that one looks like this:
WITH
MEMBER [Measures].[Range1] as fix([Measures].[Total Amount]/1000)*1000, FORMAT_STRING = "###############0"
MEMBER [Measures].[Range2] as fix([Measures].[Total Amount]/1000 + 1 )*1000 - 1, FORMAT_STRING = "###############0"
MEMBER [Measures].[MyRange] AS
iif([Measures].[Total Amount] >= 0,
Cast([Measures].[Range1] as string) ||" - "|| Cast([Measures].[Range2] as string),
NULL)
SET supli AS
Order(filter([Supplier].[Supplier].Members,[Measures].[Total Amount] >= 0),
[Measures].[Total Amount],BASC)
MEMBER [Measures].[rank] AS
iif([Measures].[Total Amount] >= 0,
rank([Supplier].[Supplier].currentMember,supli),
NULL), FORMAT_STRING = "#####0"
MEMBER [Supplier].[Range] as Aggregate([Measures].[Range1]:[Measures].[Range2])
SELECT
{[Measures].[Total Amount],[Measures].[MyRange], [Measures].[rank], [Measures].[Range1], [Measures].[Range2]} on 0,
NON EMPTY (supli) on 1
FROM [Detail Booking]
WHERE {[Checkin Date.Date].[2015]}
The results looks like this:
Proveedor Importe MyRange rank Range1 Range2
P1 0 0.0 - 999.0 1 0 999
P2 100 0.0 - 999.0 2 0 999
P3 618,27 1000.0 - 1999.0 3 1000 1999
P4 855 1000.0 - 1999.0 4 1000 1999
P5 3819,5 4000.0 - 4999.0 5 4000 4999
P6 11669,23 12000.0 - 12999.0 6 12000 12999
P7 12000 12000.0 - 12999.0 7 12000 12999
P8 14805,49 15000.0 - 15999.0 8 15000 15999
P9 16784,4 17000.0 - 17999.0 9 17000 17999
P10 46967,7 47000.0 - 47999.0 10 47000 47999
There are about 500 suppliers and I don't know how many ranges there are or what ranges exists
I need the query returns results like this:
Proveedor count(supplier)
0.0 - 999.0 2
1000.0 - 1999.0 2
4000.0 - 4999.0 1
12000.0 - 12999.0 2
15000.0 - 15999.0 1
17000.0 - 17999.0 1
47000.0 - 47999.0 1
My problem is I don't know how to create a query with dynamically aggregates
I know I can aggregate using something like this:
WITH
MEMBER [Measures].[0:1000] as
Count(
Filter({[Supplier].[Supplier].CurrentMember},
[Measures].[Total Amount] < 1000),
EXCLUDEEMPTY)
MEMBER [Supplier].[Supplier].[0€-1000€] as Aggregate({[Supplier].[Supplier].Members},[Measures].[0:1000])
MEMBER [Measures].[1000:2000] as
Count(
Filter({[Supplier].[Supplier].CurrentMember},
[Measures].[Total Amount] >= 1000 and [Measures].[Total Amount] < 2000),
EXCLUDEEMPTY)
MEMBER [Supplier].[Supplier].[1000€-2000€] as Aggregate({[Supplier].[Supplier].Members}, [Measures].[1000:2000])
member [Measures].[Total proveedores] as [Measures].[Total Amount], FORMAT_STRING ="####0"
SELECT
NON EMPTY {[Measures].[Total proveedores]} ON 0,
NON EMPTY {[Supplier].[0€-1000€], [Supplier].[1000€-2000€]} ON 1
FROM [Detail Booking]
WHERE {[Checkin Date.Date].[2015]}
My problem is that :there are about 500 suppliers and I don't know how many ranges there are or what ranges exists to use filters
Somebody knows how can I solve this?

You can find detailed explanation here
https://sqlmdx.net/2021/02/02/group-by-an-expression-and-limitations-mdx-vs-sql/
To cut long story short - it's not possible to add unknown number of members to some dimension to do it nice and easy in MDX however there are some alternative approaches without changing the cube design.

Related

How to calculate Fuctuation % select statement with a number of variables?

I need to write a query for a fluctuation report month to month.
For monthly consumption I have start and end readings that I use to calculate consumption. From this calculated value I need to to find the fluctuation of month to month.
Code used
select distinct
c.CNAME as MeterPointName,
m.SNUMBER as SerialNumber,
bill.RDATE as BillingPeriod,
IF(bill.E1 > bill1.E1 and bill.BRID = bill1.BRID, (bill.E1 - bill1.E1), 0) as Consumption,
IF(bill.E1 > bill1.E1 and DATE(bill.RDATE) > DATE(bill1.RDATE), ((bill.E1 - bill1.E1)/(bill.E1-bill1.E1)), 0) as Fluctuation
from table1.CUSTOMER c
RIGHT JOIN table2.METER m on c.CNUMBER = m.MNAME
LEFT JOIN table3.BILLMTOTAL bill on m.MNAME = bill.CNUMBER
LEFT JOIN table3.BILLMTOTAL bill1 on bill.SNUMBER = bill1.SNUMBER
RIGHT JOIN table4.CUSTOMERTREE cust on c.ID = cust.Customer_id
where bill.RP = 'E'
and bill1.RP = 'S'
and bill.BRID between 179 and 190
and bill.BRID = bill1.BRID
and date(bill.RDATE) > date(bill1.RDATE)
and cust.lft between 2185 and 2633
GROUP BY m.SNUMBER, bill.BRID, bill.E1
ORDER BY bill.RDATE
bill1.E1 and bill2.E1 are the start and end readings.
Example :
bill.E1 - 5
bill.E2 - 10
Everything works as it should just the fluctuation figure is not right.

Show Only 2 Decimal Places in SQL Server

I've got some columns that are stored as an INT value that I am doing some addition and division on and I would like to display the results by limiting the digits after the decimal to 2. I've tried different combinations of DECIMAL / NUMERIC / ROUND but I can't get the solution.
Could anyone offer any advice on how to get the desired solution?
Query:
SELECT cc.code AS [CountyID], RTRIM(LTRIM(cc.[description])) AS [CountyName],
SUM(ISNULL(ps.push_count,0)) AS [CountyPushCounts],
SUM(ISNULL(ps.push_unique_count,0)) AS [UniquePushCount],
SUM(ISNULL(ps.error_count,0)) AS [PushErrorCount],
SUM(ISNULL(ps.warning_count,0)) AS [PushWarningCount],
(CAST(SUM(ISNULL(ps.push_unique_count,0)) AS DECIMAL(15,2)) / CAST(SUM(ISNULL(ps.push_count,0)) AS DECIMAL(15,2)) * 100.0) AS [Unique Push Per Day] ,
((CAST(SUM(ISNULL(ps.error_count,0)) AS DECIMAL(15,2)) + CAST(SUM(ISNULL(ps.warning_count,0)) AS DECIMAL(15,2))) / CAST(SUM(ISNULL(ps.push_count,0)) AS DECIMAL(15,2)) * 100.0) AS [Data Error Rate]
FROM dbo.push_stats AS [ps]
INNER JOIN CCIS.dbo.county_codes AS [cc] ON ps.county_code = cc.code
WHERE DATEPART(YEAR,ps.ldstat_date) = 2017
AND DATEPART(MONTH,ps.ldstat_date) = 3
GROUP BY cc.code, cc.[description]
ORDER BY cc.[description];
And my data set looks as follows:
CountyID CountyName PushCounts UniquePushCount PushErrorCount PushWarningCount [Unique Push Per Day] [Data Error Rate]
1 ALACHUA 210422 77046 0 39 36.61499273 0.018534184
2 BAKER 8099 5306 0 71 65.51426102 0.876651438
3 BAY 3178214 434893 117 2793 13.68356568 0.091560857
4 BRADFORD 17654 12119 0 131 68.64733205 0.742041464
I think this will do it:
SELECT cc.code AS [CountyID], RTRIM(LTRIM(cc.[description])) AS [CountyName],
SUM(ISNULL(ps.push_count,0)) AS [CountyPushCounts],
SUM(ISNULL(ps.push_unique_count,0)) AS [UniquePushCount],
SUM(ISNULL(ps.error_count,0)) AS [PushErrorCount],
SUM(ISNULL(ps.warning_count,0)) AS [PushWarningCount],
CAST((SUM(ISNULL(ps.push_unique_count,0)) * 100.00) / SUM(ISNULL(ps.push_count,0)) AS DECIMAL(15,2)) AS [Unique Push Per Day] ,
CAST((SUM(ISNULL(ps.error_count,0)) + SUM(ISNULL(ps.warning_count,0)) * 100.00) / SUM(ISNULL(ps.push_count,0)) AS DECIMAL(15,2)) AS [Data Error Rate]
FROM dbo.push_stats AS [ps]
INNER JOIN CCIS.dbo.county_codes AS [cc] ON ps.county_code = cc.code
WHERE DATEPART(YEAR,ps.ldstat_date) = 2017
AND DATEPART(MONTH,ps.ldstat_date) = 3
GROUP BY cc.code, cc.[description]
ORDER BY cc.[description];
There are two main points here:
With the division operation, get at least one term to a floating point type of some kind before the division happens, so that it doesn't do integer division and truncate the decimal portion of the result. You're okay as long as either term is a floating point type of some kind, and you can accomplish this simply by moving the * 100.00 earlier.
You want to allow much greater than 2 decimal places for the internal calculations, and only set your output format at the very end. Rounding or casting to limited types too soon in an expression can change intermediate values in small ways that are exaggerated in the final results. This means you only want one big CAST() operation around the whole set.
You have to cast the final result, not just the individual numerator and denominator
DECLARE #Numerator INTEGER
DECLARE #Denominator INTEGER
SET #Numerator = 10
SET #Denominator = 3;
-- This will produce 3.33333333
SELECT CAST(#Numerator AS DECIMAL(5,2))/CAST(#Denominator AS DECIMAL(5,2))
-- This will give you 3.33
SELECT CAST(
CAST(#Numerator AS DECIMAL(5,2))
/CAST(#Denominator AS DECIMAL(5,2))
AS DECIMAL(5,2))

Pulling correct results from my PitchValues Table

I am getting a tad frustrated and was wondering if you can help:
I have a Pitch Values Table with the following Columns PitchValues_Skey, PitchType_Skey (this is a foreign key), Start Date, End Date and finally value:
For Example:
1 7 01/01/2010 31/12/2010 £15
2 7 01/01/2011 31/12/2011 £20
And all I want to do is update my Bookings table with how much each booking is going to be, so I put together the code below which worked fine when I only had 2010 data, but I know have 2011 and 2012 and want to update it but it will only update with the 2010 prices.
SELECT Bookings.Booking_Skey, DATEDIFF(day, Bookings.ArrivalDate, Bookings.DepartureDate) * PitchValues.Value AS BookingValue,
PitchValues.PitchType_Skey
FROM Bookings INNER JOIN
PitchValues ON Bookings.PitchType_Skey = PitchValues.PitchType_Skey
WHERE (Bookings.Booking_Skey = 1)
So when I run the query above I would expect to see one line of data but instead I see 4 (See Below)
I would expect this:
Booking_Skey BookingValue PitchType_Skey
1 420 4
But I get this
Booking_Skey BookingValue PitchType_Skey
1 420 4
1 453.6 4
1 476.7 4
1 476.7 4
All sorted now, thanks for your help.
SELECT Bookings.Booking_Skey, DATEDIFF(DAY, Bookings.ArrivalDate, Bookings.DepartureDate) * PitchValues.Value AS BookingValue, PitchValues.PitchType_Skey
FROM Bookings
INNER JOIN PitchValues ON Bookings.PitchType_Skey = PitchValues.PitchType_Skey
AND Bookings.ArrivalDate BETWEEN PitchValues.StartDate AND PitchValues.EndDate
WHERE (Bookings.Booking_Skey = 1)

TSQL GROUP BY part of string

I have a simple query. Something like this:
SELECT l.list_name, COUNT(order_id)
FROM orders o JOIN lists l ON l.order_id=o.order_id
WHERE l.list_name LIKE 'orders_1%' or l.list_name LIKE 'orders_2%'
GROUP BY l.list_name
The situation looks like this: overight a stored procedure is updating the lists table, but it chops lists in parts if there are more than 1000 orders.
If I have 1200 orders with criteria or list 'orders_1', then my procedure will create two lists: 'orders_1_1' 'and orders_1_2', the first having 1,000 and second 200 orders.
So when I run my query to count those orders I will get results like so:
list_name count
orders_1 100
orders_1_more_than_100_1 1000
orders_1_more_than_100_2 200
orders_2 400
orders_3_1 1000
orders_3_2 1000
orders_3_3 420
orders_3_more_than_100_1 1000
orders_3_more_than_100_2 900
orders_3_more_than_200_1 1000
orders_3_more_than_200_2 1000
orders_3_more_than_200_3 100
orders_4 200
orders_4_more_than_300 200
The result I would like to get should look like this:
list_name count
orders_1 100
orders_1_more_than_100 1200
orders_2 400
orders_3 2420
orders_3_more_than_100 1900
orders_3_more_than_200 2100
orders_4 200
orders_4_more_than_300 200
So that it will sum all lists that start the same.
Any ideas on that? :)
These are the exact values that I have in my list_names column:
WYS_AUT_PISMO_NR_6
WYS_AUT_PISMO_NR_5_POWYZEJ_240
WYS_AUT_PISMO_NR_5_DO_240
WYS_AUT_PISMO_NR_4_POWYZEJ_240_5
WYS_AUT_PISMO_NR_4_POWYZEJ_240_4
WYS_AUT_PISMO_NR_4_POWYZEJ_240_3
WYS_AUT_PISMO_NR_4_POWYZEJ_240_2
WYS_AUT_PISMO_NR_4_POWYZEJ_240_1
WYS_AUT_PISMO_NR_4_DO_240
WYS_AUT_PISMO_NR_3_POWYZEJ_240
WYS_AUT_PISMO_NR_3_DO_240
WYS_AUT_PISMO_NR_2_POWYZEJ_240
WYS_AUT_PISMO_NR_2_DO_240
WYS_AUT_PISMO_NR_1
What I want is to group them like so:
WYS_AUT_PISMO_NR_6
WYS_AUT_PISMO_NR_5_POWYZEJ_240
WYS_AUT_PISMO_NR_5_DO_240
WYS_AUT_PISMO_NR_4_POWYZEJ_240 /*here I must group those 5 lists*/
WYS_AUT_PISMO_NR_4_DO_240
WYS_AUT_PISMO_NR_3_POWYZEJ_240
WYS_AUT_PISMO_NR_3_DO_240
WYS_AUT_PISMO_NR_2_POWYZEJ_240
WYS_AUT_PISMO_NR_2_DO_240
WYS_AUT_PISMO_NR_1
This monstruous expression will isolate string starting from beginning of parameter to last _ if there are more than one underscores:
select case when len (l.list_name) - len (replace (l.list_name, '_', '')) > 1
then left(l.list_name,
len (l.list_name) - charindex('_', reverse(l.list_name)))
else l.list_name
end
Alternatively you might strip 'orders_' from string, replace underscore with dot and convert it to float, then to int to remove decimals, and then back to string using this monstrosity:
select 'orders_' + cast (cast (cast (
replace (substring (#str, 8, 100), '_', '.')
as float) as int) as varchar(100))
To avoid repeating this blobs, use derived table instead of lists:
SELECT l.TrimmedListName, COUNT(order_id)
FROM orders o
JOIN
(
select lists.*,
-- Remove optional list continuation number
case when len (list_name) - len (replace (list_name, '_', '')) > 1
then left(list_name,
len (list_name) - charindex('_', reverse(list_name)))
else list_name
end AS TrimmedListName
from lists
) l ON l.order_id=o.order_id
WHERE (l.list_name LIKE 'orders_1%' or l.list_name LIKE 'orders_2%')
GROUP BY l.TrimmedListName
try something like
select substring(l.list_name, 0, 8), count(order_Id)
FROM orders o JOIN lists l ON l.order_id=o.order_id
WHERE l.list_name LIKE 'orders_1%' or l.list_name LIKE 'orders_2%'
group by substring(l.list_name, 0, 8)
Updated answer for the updated question:
select newColName, COUNT(order_id)
from
(
select case when GetSubstringCount(l.list_name, '_', '') > 1 then
SUBSTRING(l.list_name, 0, len(l.list_name) - 2)
else l.list_name end as NewColName
, order_Id
FROM orders o JOIN lists l ON l.order_id=o.order_id
WHERE l.list_name LIKE 'orders_1%' or l.list_name LIKE 'orders_2%'
) mySubTable
group by newColName
You'll need something like This to create the GetSubstringCount method

tsql how to validate a number's scale

I need to validate the number of digits to the right of the decimal (the scale)
0, is a valid number in any of the places (tenths, hundredths, thousandths, etc.).
Any tips or tricks?... w/o an extensive regex library, and no built in function, I would prefer a function that accepts the number, the number of places the scale should equal, and then return a bit.
Following up with Maess's suggestion I came up with this:
CREATE FUNCTION [dbo].[GetScale]
(
#tsValue varchar(250)
, #tiScale int
)
RETURNS int
AS
BEGIN
DECLARE
#tiResult int
, #tiValueScale int
SET #tiResult = 0
SELECT #tiValueScale = LEN( SUBSTRING ( #tsValue, PATINDEX('%.%', #tsValue) + 1, LEN(#tsValue) ) )
IF (#tiValueScale = #tiScale)
SET #tiResult = 1
RETURN #tiResult
END
GO
Seems to work as desired. Thanks for the help.
Just as a followup... i ran into an issue where a number didnt have a decimal (which returns the patindex to 0) and the number was the same size as the scale, it would return a false positive... so i add an additional select from the patindex to determine if it does exist or not... it now looks like this:
- =============================================
ALTER FUNCTION [dbo].[GetScale]
(
#tsValue varchar(250)
, #tiScale int
)
RETURNS int
AS
BEGIN
DECLARE
#tiResult int
, #tiValueScale int
, #tiDecimalExists int
SET #tiResult = 0
SET #tiDecimalExists = 0
SELECT #tiDecimalExists = PATINDEX('%.%', #tsValue)
IF (#tiDecimalExists != 0)
BEGIN
SELECT #tiValueScale = LEN( SUBSTRING ( #tsValue, #tiDecimalExists + 1, LEN(#tsValue) ) )
IF (#tiValueScale = #tiScale)
SET #tiResult = 1
END
RETURN #tiResult
END
I tried Anthony's solution with some success, but there is some undesirable side effects when the first whole number is 9.
For example...
select 0.11 as fraction, Math.NumberOfDecimalPlaces(0.11) dp union
select 9.1, Math.NumberOfDecimalPlaces(9.1) union
select 9.01, Math.NumberOfDecimalPlaces(9.01) union
select 9.0, Math.NumberOfDecimalPlaces(9.0) union
select 99.0, Math.NumberOfDecimalPlaces(99.0) union
select 10999.0, Math.NumberOfDecimalPlaces(10999.0) union
select 8.0, Math.NumberOfDecimalPlaces(8.0) union
select 0, Math.NumberOfDecimalPlaces(0)
Produces...
0.00 0
0.11 2
8.00 0
9.00 -1
9.01 2
9.10 1
99.00 -2
10999.00 -3
Which shows some incorrect calculations when 9 is the first whole number.
I've made a small improvement to Anthony's original function.
CREATE FUNCTION [Math].[NumberOfDecimalPlaces]
(
#fraction decimal(38,19)
)
RETURNS INT
AS
BEGIN
RETURN FLOOR(LOG10(REVERSE(ABS(#fraction % 1) +1))) +1
END
This simply strips of the whole number part of the fraction. Which when implemented produces...
0.00 0
0.11 2
8.00 0
9.00 0
9.01 2
9.10 1
99.00 0
10999.00 0
The correct result
CREATE FUNCTION dbo.DecimalPlaces(#n decimal(38,19))
RETURNS int
AS
BEGIN
RETURN FLOOR(LOG10(REVERSE(ABS(#n % 1) + 1))) + 1
END
Edit (Feb 5 '15):
Thanks sqlconsumer. I have included your fix for nines before the decimal point.