CTE Pivot with Group by results - tsql

I have a table with Datetime column, I want to check if there's a record for each hour of the day, then to display the missing hour of the day, so I write a first script with Pivot to find if ther's a record of each hour but the result don't group the rows by the same Date,
with test as (SELECT [numero] ,[client] ,[creele] ,[montantPaye] ,[AnneeProduction] ,cast(creele as Date) as Datee ,DATEPART(HOUR, creele) as 'Heure' FROM [CLMDatabase].[dbo].[Fiches] group by creele, numero, client, montantPaye,AnneeProduction ) select Datee,
isnull([1],0) as "01h",
isnull([2],0) as "02h",
isnull([3],0) as "03h",
isnull([4],0) as "04h",
isnull([5],0) as "05h",
isnull([6],0) as "06h",
isnull([7],0) as "07h",
isnull([8],0) as "08h",
isnull([9],0) as "09h",
isnull([10],0) as "10h",
isnull([11],0) as "11h",
isnull([12],0) as "12h",
isnull([13],0) as "13h",
isnull([14],0) as "14h",
isnull([15],0) as "15h",
isnull([16],0) as "16h",
isnull([17],0) as "17h",
isnull([18],0) as "18h",
isnull([19],0) as "19h",
isnull([20],0) as "20h",
isnull([21],0) as "21h",
isnull([22],0) as "22h",
isnull([23],0) as "23h",
isnull([00],0) as "00h" from test pivot ( count (client)for Heure in ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15] ,[16],[17] ,[18] ,[19] ,[20],[21],[22],[23],[00])) as pvt order by datee ;
The result is like that :
Date ¦ 01h ¦ 02h ¦03h ¦ 04h ...
03-10.2006 ¦ 1 ¦ 0 ¦ 0 ¦ 0 ...
03-10.2006 ¦ 0 ¦ 0 ¦ 1 ¦ 0 ...
13-11.2006 ¦ 0 ¦ 0 ¦ 0 ¦ 1 ...
rather than :
Date ¦ 01h ¦ 02h ¦03h ¦ 04h ...
03-10.2006 ¦ 1 ¦ 0 ¦ 1 ¦ 0 ...
13-11.2006 ¦ 0 ¦ 0 ¦ 0 ¦ 1 ...

you can agregate in subquery or in cte required data and then transpose it thro pivot
SELECT Datee ,
ISNULL([1], 0) AS [01h] ,
ISNULL([2], 0) AS [02h] ,
ISNULL([3], 0) AS [03h] ,
ISNULL([4], 0) AS [04h] ,
ISNULL([5], 0) AS [05h] ,
ISNULL([6], 0) AS [06h] ,
ISNULL([7], 0) AS [07h] ,
ISNULL([8], 0) AS [08h] ,
ISNULL([9], 0) AS [09h] ,
ISNULL([10], 0) AS [10h] ,
ISNULL([11], 0) AS [11h] ,
ISNULL([12], 0) AS [12h] ,
ISNULL([13], 0) AS [13h] ,
ISNULL([14], 0) AS [14h] ,
ISNULL([15], 0) AS [15h] ,
ISNULL([16], 0) AS [16h] ,
ISNULL([17], 0) AS [17h] ,
ISNULL([18], 0) AS [18h] ,
ISNULL([19], 0) AS [19h] ,
ISNULL([20], 0) AS [20h] ,
ISNULL([21], 0) AS [21h] ,
ISNULL([22], 0) AS [22h] ,
ISNULL([23], 0) AS [23h] ,
ISNULL([00], 0) AS [00h]
FROM ( SELECT COUNT([client]) AS Client ,
CONVERT(DATE, creele) AS Datee ,
DATEPART(HOUR, creele) AS [Heure]
FROM #t
GROUP BY CONVERT(DATE, creele) ,
DATEPART(HOUR, creele)
) AS newtest PIVOT ( SUM(Client) FOR Heure IN ( [1], [2], [3], [4],
[5], [6], [7], [8],
[9], [10], [11],
[12], [13], [14],
[15], [16], [17],
[18], [19], [20],
[21], [22], [23],
[00] ) ) AS pvt
ORDER BY Datee;

Related

Breakdown SUM of INT by Each Month

Query I use to find the total spend per station for a year. How do I break down the spend of each product by month i.e. each month as a column?
e.DT is a datetime, but only gets populated once a month.
select s.StationID, sum(e.Spend)
from Station s with(nolock)
join Expenditure e with(nolock)
on e.ProductID = s.ProductID
where e.DT between '1 JAN 18' and '1 DEC 18'
group by s.StationID
order by sum(e.Spend) desc
It would be much easier to report your month/year data points as separate rows, rather than columns. I suggest the following query:
SELECT
s.StationID,
LEFT(CONVERT(varchar, e.DT, 120), 7) AS ym,
SUM(e.Spend) AS total_spend
FROM Station s WITH(nolock)
INNER JOIN Expenditure e WITH(nolock)
ON e.ProductID = s.ProductID
WHERE e.DT BETWEEN '2018-01-01' AND '2018-12-01'
GROUP BY
s.StationID,
LEFT(CONVERT(varchar, e.DT, 120), 7)
ORDER BY
s.StationID,
SUM(e.Spend) DESC;
Try with PIVOT this way:
select ProductID, [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12]
from (
select Spend, month(DT) m, ProductID
from Expenditure
where DT between '1 JAN 18' and '1 DEC 18'
) r
PIVOT
( sum(Spend)
for m in ([1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12])
)
AS PivotTable;

Improve performance on CTE with sub-queries

I have a table with this structure:
WorkerID Value GroupID Sequence Validity
1 '20%' 1 1 2018-01-01
1 '10%' 1 1 2017-06-01
1 'Yes' 1 2 2017-06-01
1 '2018-01-01' 2 1 2017-06-01
1 '17.2' 2 2 2017-06-01
2 '10%' 1 1 2017-06-01
2 'No' 1 2 2017-06-01
2 '2016-03-01' 2 1 2017-06-01
2 '15.9' 2 2 2017-06-01
This structure was created so that the client can create customized data for a worker. For example Group 1 can be something like "Salary" and Sequence is one value that belongs to that Group like "Overtime Compensation". The column Value is a VARCHAR(150) field and the correct validation and conversation is done in another part of the application.
The Validity column exist mainly for historical reasons.
Now I would like to show, for the different workers, the information in a grid where each row should be one worker (displaying the one with the most recent Validity):
Worker 1_1 1_2 2_1 2_2
1 20% Yes 2018-01-01 17.2
2 10% No 2016-03-01 15.9
To accomplish this I created a CTE that looks like this:
WITH CTE_worker_grid
AS
(
SELECT
worker,
/* 1 */
(
SELECT top 1 w.Value
FROM worker_values AS w
WHERE w.GroupID = 1
AND w.Sequence = 1
ORDER BY w.Validity DESC
) AS 1_1,
(
SELECT top 1 w.Value
FROM worker_values AS w
WHERE w.GroupID = 1
AND w.Sequence = 2
ORDER BY w.Validity DESC
) AS 1_2,
/* 2 */
(
SELECT top 1 w.Value
FROM worker_values AS w
WHERE w.GroupID = 2
AND w.Sequence = 1
ORDER BY w.Validity DESC
) AS 2_1,
(
SELECT top 1 w.Value
FROM worker_values AS w
WHERE w.GroupID = 2
AND w.Sequence = 2
ORDER BY w.Validity DESC
) AS 2_2
)
GO
This produces the correct result but it's very slow as it creates this grid for over 18'000 worker with almost 30 Groups and up to 20 Sequences in each Group.
How could one speed up the process of a CTE of this magnitude? Should CTE even be used? Can the sub-queries be changed or re-factored out to speed up the execution?
Use a PIVOT!
+----------+---------+---------+------------+---------+
| WorkerId | 001_001 | 001_002 | 002_001 | 002_002 |
+----------+---------+---------+------------+---------+
| 1 | 20% | Yes | 2018-01-01 | 17.2 |
| 2 | 10% | No | 2016-03-01 | 15.9 |
+----------+---------+---------+------------+---------+
SQL Fiddle: http://sqlfiddle.com/#!18/6e768/1
CREATE TABLE WorkerAttributes
(
WorkerID INT NOT NULL
, [Value] VARCHAR(50) NOT NULL
, GroupID INT NOT NULL
, [Sequence] INT NOT NULL
, Validity DATE NOT NULL
)
INSERT INTO WorkerAttributes
(WorkerID, Value, GroupID, Sequence, Validity)
VALUES
(1, '20%', 1, 1, '2018-01-01')
, (1, '10%', 1, 1, '2017-06-01')
, (1, 'Yes', 1, 2, '2017-06-01')
, (1, '2018-01-01', 2, 1, '2017-06-01')
, (1, '17.2', 2, 2, '2017-06-01')
, (2, '10%', 1, 1, '2017-06-01')
, (2, 'No', 1, 2, '2017-06-01')
, (2, '2016-03-01', 2, 1, '2017-06-01')
, (2, '15.9', 2, 2, '2017-06-01')
;WITH CTE_WA_RANK
AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY WorkerID, GroupID, [Sequence] ORDER BY Validity DESC) AS VersionNumber
, WA.WorkerID
, WA.GroupID
, WA.[Sequence]
, WA.[Value]
FROM
WorkerAttributes AS WA
),
CTE_WA
AS
(
SELECT
WA_RANK.WorkerID
, RIGHT('000' + CAST(WA_RANK.GroupID AS VARCHAR(3)), 3)
+ '_'
+ RIGHT('000' + CAST(WA_RANK.[Sequence] AS VARCHAR(3)), 3) AS SMART_KEY
, WA_RANK.[Value]
FROM
CTE_WA_RANK AS WA_RANK
WHERE
WA_RANK.VersionNumber = 1
)
SELECT
WorkerId
, [001_001] AS [001_001]
, [001_002] AS [001_002]
, [002_001] AS [002_001]
, [002_002] AS [002_002]
FROM
(
SELECT
CTE_WA.WorkerId
, CTE_WA.SMART_KEY
, CTE_WA.[Value]
FROM
CTE_WA
) AS WA
PIVOT
(
MAX([Value])
FOR
SMART_KEY IN
(
[001_001]
, [001_002]
, [002_001]
, [002_002]
)
) AS PVT

Ignore null values when using SQL Server 2012's Last_Value() function

I am using SQL Server 2012 and have a table of values that look like this. It is populated with event data.
FldType Date Price Size
--------------------------------------------
2 2012-08-22 00:02:01 9140 1048
0 2012-08-22 00:02:02 9140 77
1 2012-08-22 00:02:03 9150 281
2 2012-08-22 00:02:04 9140 1090
0 2012-08-22 00:02:05 9150 1
1 2012-08-22 00:02:06 9150 324
2 2012-08-22 00:02:07 9140 1063
I would like to track the lastest value for each of the 3 field types (0,1,2) so that the final output looks like this.
Date Price0 Size0 Price1 Size1 Price2 Size2
-----------------------------------------------------------------
2012-08-22 00:02:01 NULL NULL NULL NULL 9140 1048
2012-08-22 00:02:02 9140 77 NULL NULL 9140 1048
2012-08-22 00:02:03 9140 77 9150 281 9140 1048
2012-08-22 00:02:04 9140 77 9150 281 9140 1090
2012-08-22 00:02:05 9150 1 9150 281 9140 1090
2012-08-22 00:02:06 9150 1 9150 324 9140 1090
2012-08-22 00:02:07 9150 1 9150 324 9140 1063
Unfortunately, it is not ignoring subsequent null values so I get this instead.
Date Price0 Size0 Price1 Size1 Price2 Size2
-----------------------------------------------------------------
2012-08-22 00:02:01 NULL NULL NULL NULL 9140 1048
2012-08-22 00:02:02 9140 77 NULL NULL NULL NULL
2012-08-22 00:02:03 NULL NULL 9150 281 NULL NULL
2012-08-22 00:02:04 NULL NULL NULL NULL 9140 1090
2012-08-22 00:02:05 9150 1 NULL NULL NULL NULL
2012-08-22 00:02:06 NULL NULL 9150 324 NULL NULL
2012-08-22 00:02:07 NULL NULL NULL NULL 9140 1063
My current query looks like this
SELECT [Date],
LAST_VALUE(Price0) OVER (PARTITION BY FldType ORDER BY [Date] ) AS Price0,
LAST_VALUE(Size0) OVER (PARTITION BY FldType ORDER BY [Date]) AS Size0,
LAST_VALUE(Price1) OVER (PARTITION BY FldType ORDER BY [Date] ) AS Price1,
LAST_VALUE(Size1) OVER (PARTITION BY FldType ORDER BY [Date]) AS Size1,
LAST_VALUE(Price2) OVER (PARTITION BY FldType ORDER BY [Date] ) AS Price2,
LAST_VALUE(Size2) OVER (PARTITION BY FldType ORDER BY [Date]) AS Size2
FROM (
SELECT FldType, [Date], Price, Size,
CASE WHEN FldType = 0 THEN Price END as Price0,
CASE WHEN FldType = 0 THEN Size END as Size0,
CASE WHEN FldType = 1 THEN Price END as Price1,
CASE WHEN FldType = 1 THEN Size END as Size1,
CASE WHEN FldType = 2 THEN Price END as Price2,
CASE WHEN FldType = 2 THEN Size END as Size2
FROM [RawData].[dbo].[Events]
) as T1
ORDER BY [Date]
Is there some way to have SQL Server 2012 ignore null values when determining the lastest value? Or is there a better approach not using Last_Value() function?
To summarize I am trying to achieve two thing.
Split the Price and Size columns into 6 columns (2 columns x 3 field types)
Keep track of the latest value in each of these columns.
Any suggestions would be apprciated.
I'm not sure you can do it with LAST_VALUE, unless you add a PIVOT maybe.
Also, you need to treat Size and Price separately because they come from different rows. So, this achieves what you want be breaking it down.
DECLARE #source TABLE (FldType int, DateCol DateTime, Price int, Size int);
INSERT #source VALUES
(2, '2012-08-22 00:02:01', 9140, 1048),(0, '2012-08-22 00:02:02', 9140, 77),
(1, '2012-08-22 00:02:03', 9150, 281),(2, '2012-08-22 00:02:04', 9140, 1090),
(0, '2012-08-22 00:02:05', 9150, 1),(1, '2012-08-22 00:02:06', 9150, 324),
(2, '2012-08-22 00:02:07', 9140, 1063);
SELECT
S.DateCol, Xp0.Price0, Xs0.Size0, Xp1.Price1, Xs1.Size1, Xp2.Price2, Xs2.Size2
FROM
#source S
OUTER APPLY
(SELECT TOP 1 S0.Price AS Price0 FROM #source S0 WHERE S0.FldType = 0 AND S0.DateCol <= S.DateCol ORDER BY S0.DateCol DESC) Xp0
OUTER APPLY
(SELECT TOP 1 S1.Price AS Price1 FROM #source S1 WHERE S1.FldType = 1 AND S1.DateCol <= S.DateCol ORDER BY S1.DateCol DESC) Xp1
OUTER APPLY
(SELECT TOP 1 S2.Price AS Price2 FROM #source S2 WHERE S2.FldType = 2 AND S2.DateCol <= S.DateCol ORDER BY S2.DateCol DESC) Xp2
OUTER APPLY
(SELECT TOP 1 S0.Size AS Size0 FROM #source S0 WHERE S0.FldType = 0 AND S0.DateCol <= S.DateCol ORDER BY S0.DateCol DESC) Xs0
OUTER APPLY
(SELECT TOP 1 S1.Size AS Size1 FROM #source S1 WHERE S1.FldType = 1 AND S1.DateCol <= S.DateCol ORDER BY S1.DateCol DESC) Xs1
OUTER APPLY
(SELECT TOP 1 S2.Size AS Size2 FROM #source S2 WHERE S2.FldType = 2 AND S2.DateCol <= S.DateCol ORDER BY S2.DateCol DESC) Xs2
ORDER BY
DateCol;
The other way is to maintain a separate table via triggers or some ETL that does it the summary for you.

Create Top 10 but with everything except Top 9 in Other

What i would like to do is have a Top 10, but the 10th entry is called "Other" with the sum of everything bar the top 9 within it and has a total. So basically it looks like this:
ReportingDate FundCode Currency Duration Contribution Percentage
31/10/2012 1111 Malaysian Ringgit 0.5 14.6
31/10/2012 1111 Turkish Lira 0.3 13.5
31/10/2012 1111 Russian Rouble 0.5 11.9
31/10/2012 1111 Indunesian Rupiah 0.6 11.7
31/10/2012 1111 Mexican Peso 0.6 11.7
31/10/2012 1111 Polish Zloty 0.3 10.2
31/10/2012 1111 Mexican Peso 0.4 10.1
31/10/2012 1111 Polish Zloty 0.3 9.9
31/10/2012 1111 South African Rand 0.2 5.8
31/10/2012 1111 Brazilian Real 0.3 2.0
31/10/2012 1111 Other 0.6 -1.4
31/10/2012 1111 Total 4.6 100.0
My code currently looks like this:
;;WITH CTE AS
(
SELECT
ReportingDate
, PortfolioID
, DV.dmv_nme AS Currency
, RANK() OVER (PARTITION BY PortfolioID ORDER BY SUM(Percentage) DESC) AS [Rank]
, ISNULL(CAST(SUM(DurationContribution)/100.0 AS DECIMAL(22,1)),0) AS [Duration Contribution]
, CAST(SUM(Percentage) AS DECIMAL(22,1)) AS [Weight]
FROM #Worktable as WT
INNER JOIN dw_domain_value AS DV
ON DV.dmv_value = WT.Currency
AND DV.data_cls_num = 2
GROUP BY WT.ReportingDate
, WT.PortfolioID
, DV.dmv_nme
)
SELECT
ReportingDate
, PortfolioID
, Currency
, [Rank]
, [Duration Contribution]
, [Weight]
FROM CTE
WHERE [Rank] <= 10
ORDER BY ReportingDate, PortfolioID, [Rank], [Weight] DESC
So this gives me the top 10 fine. So how could i get it so that the final 10th line is "Other" with everything bar the top 9 summed within it, and also include a total at the end?
Here is one solution. But you may be able to move it into the CTE for better performance.
;;WITH CTE AS
(
SELECT
ReportingDate
,PortfolioID
,DV.dmv_nme AS Currency
,RANK() OVER (PARTITION BY PortfolioID ORDER BY SUM(Percentage) DESC) AS [Rank]
,ISNULL(CAST(SUM(DurationContribution)/100.0 AS DECIMAL(22,1)),0) AS [Duration Contribution]
,CAST(SUM(Percentage) AS DECIMAL(22,1)) AS [Weight]
FROM #Worktable as WT
INNER JOIN dw_domain_value AS DV
ON DV.dmv_value = WT.Currency
AND DV.data_cls_num = 2
GROUP BY WT.ReportingDate
, WT.PortfolioID
, DV.dmv_nme
)
SELECT
ReportingDate
, PortfolioID
, CASE WHEN [Rank] <= 10 THEN Currency ELSE 'Total' END As Currency
, SUM([Duration Contribution]) As [Duration Contribution]
, SUM([Weight]) As [Weight]
FROM CTE
GROUP BY
ReportingDate
, PortfolioID
, CASE WHEN [Rank] <= 10 THEN Currency ELSE 'Total' END As Currency
WITH ROLLUP
ORDER BY ReportingDate, PortfolioID, [Rank], [Weight] DESC
For this i decided i couldn't use CTE, so ended up having to insert these section by section into a temp table as so:
/* Include only top 9 */
INSERT INTO #FinalOutput
SELECT
ReportingDate
, PortfolioID
, PortfolioNme
, Currency
, [Rank]
, DurationContribution
, [Weight]
FROM #WorktableGrouped
WHERE [Rank] <= 9
ORDER BY ReportingDate, PortfolioID, [Rank], [Weight] DESC
/* Aggregate everything outside the top 9 into other */
INSERT INTO #FinalOutput
SELECT
ReportingDate
, PortfolioID
, PortfolioNme
, 'Other' AS Currency
, 10 AS [Rank]
, SUM(DurationContribution) AS DurationContribution
, SUM([Weight]) AS [Weight]
FROM #WorktableGrouped
WHERE [Rank] > 9
GROUP BY ReportingDate, PortfolioID, PortfolioNme
ORDER BY ReportingDate, PortfolioID, [Rank], [Weight] DESC
SELECT * FROM #FinalOutput
/* Final Select with roll up for total per portfolio */
SELECT
ReportingDate
, PortfolioID
, PortfolioNme
, CASE
WHEN GROUPING_ID(ReportingDate, PortfolioID, PortfolioNme, Currency, [Rank]) = 3 THEN 'Total'
ELSE Currency
END AS Currency
, CASE
WHEN GROUPING_ID(ReportingDate, PortfolioID, PortfolioNme, Currency, [Rank]) = 3 THEN 11
ELSE [Rank]
END AS [Rank]
, ISNULL(CAST(SUM(DurationContribution) AS DECIMAL(22,1)),0) AS [Duration Contribution]
, CAST(SUM([Weight]) AS DECIMAL(22,1)) AS [Weight]
--, GROUPING_ID(ReportingDate, PortfolioID, PortfolioNme, Currency, [Rank])
FROM #FinalOutput
GROUP BY ReportingDate
, PortfolioID
, PortfolioNme
, Currency
, [Rank] WITH ROLLUP
HAVING GROUPING_ID(ReportingDate, PortfolioID, PortfolioNme, Currency, [Rank]) IN (0,3)
ORDER BY ReportingDate, PortfolioID, [Rank]

Remove nulls from sub totals / grand totals in tsql rollup

I currently have a script below which aggregates some data using rollup:
SELECT
CASE
WHEN GROUPING(Custodian) = 1
THEN 'Grand Total'
WHEN GROUPING(PortfolioID) = 1
THEN Custodian+''+'Total'
ELSE Custodian
END AS Custodian
, PortfolioID
, PortfolioBaseCCY
, [Date]
, SUM(AmountTotalBaseEquiv) AS AmountTotalBaseEquiv
, ExchangeRate
, AmountTotalBaseEquivUSD
, PortfolioNAVUSD
, SUM(TotalCashPctNAV) AS TotalCashPctNAV
FROM #ResultSet
WHERE TotalCashPctNAV > 5
GROUP BY Custodian
, PortfolioID
, PortfolioBaseCCY
, [Date]
, AmountTotalBaseEquiv
, ExchangeRate
, AmountTotalBaseEquivUSD
, PortfolioNAVUSD
, TotalCashPctNAV WITH ROLLUP
HAVING GROUPING_ID(Custodian
, PortfolioID
, PortfolioBaseCCY
, [Date]
, AmountTotalBaseEquiv
, ExchangeRate
, AmountTotalBaseEquivUSD
, PortfolioNAVUSD
, TotalCashPctNAV) IN (1,255,511)
ORDER BY CASE WHEN GROUPING(Custodian) = 1 THEN 2 ELSE 1 END, Custodian, TotalCashPctNAV DESC, PortfolioID
This returns data like as an example:
Custodian PortfolioID PortfolioBaseCCY Date AmountTotalBaseEquiv ExchangeRate AmountTotalBaseEquivUSD PortfolioNAVUSD TotalCashPctNAV
XXXX TEST USD 11/09/2012 85708860.21 1 85708860.21 370253861.3 23.15
XXXX Total NULL NULL NULL 85708860.21 NULL NULL NULL 23.15
ZZZZ TEST1 GBP 11/09/2012 48427.91 0.6225 77795.84 77795.84 100
ZZZZ TEST2 GBP 11/09/2012 7772.61 0.6225 12486.12 12486.12 100
ZZZZ TEST3 USD 11/09/2012 1832627.81 1 1832627.81 17343500.68 10.56
ZZZZ Total NULL NULL NULL 1888828.33 NULL NULL NULL 210.56
Grand Total NULL NULL NULL 310273031.4 NULL NULL NULL 1051.71
What i would like is for the NULLS to become '' so that only the Total label and the two summed totals are the only bits of data on that particular line, is this possible?
You can use the ISNull() function in SQL to replace NULL with a blank space like this:
SELECT
CASE
WHEN GROUPING(Custodian) = 1
THEN 'Grand Total'
WHEN GROUPING(PortfolioID) = 1
THEN Custodian+''+'Total'
ELSE Custodian
END AS Custodian
, isNUll(PortfolioID,'')
, isNull(PortfolioBaseCCY,'')
, isNull([Date],'')
, SUM(AmountTotalBaseEquiv) AS AmountTotalBaseEquiv
, isNull(ExchangeRate,'')
, isNull(AmountTotalBaseEquivUSD,'')
, isNull(PortfolioNAVUSD,'')
, SUM(TotalCashPctNAV) AS TotalCashPctNAV
If you are okay with NULL values appearing as empty strings in both data rows and total rows, you can convert all of the non-string columns to strings and use COALESCE (or ISNULL) like:
SELECT
CASE
WHEN GROUPING(Custodian) = 1
THEN 'Grand Total'
WHEN GROUPING(PortfolioID) = 1
THEN Custodian+' '+'Total'
ELSE Custodian
END AS Custodian
, COALESCE(PortfolioID,'') AS PortfolioID
, COALESCE(PortfolioBaseCCY,'') AS PortfolioBaseCCY
, COALESCE(CONVERT(char(10),[Date],101),'') AS [Date]
, SUM(AmountTotalBaseEquiv) AS AmountTotalBaseEquiv
, COALESCE(CONVERT(char(10),ExchangeRate),'') AS ExchangeRate
, COALESCE(CONVERT(char(20),AmountTotalBaseEquivUSD),'') AS AmountTotalBaseEquivUSD
, COALESCE(CONVERT(char(20),PortfolioNAVUSD),'') AS PortfolioNAVUSD
, SUM(TotalCashPctNAV) AS TotalCashPctNAV
FROM #ResultSet
WHERE TotalCashPctNAV > 5
GROUP BY Custodian
, PortfolioID
, PortfolioBaseCCY
, [Date]
, AmountTotalBaseEquiv
, ExchangeRate
, AmountTotalBaseEquivUSD
, PortfolioNAVUSD
, TotalCashPctNAV WITH ROLLUP
HAVING GROUPING_ID(Custodian
, PortfolioID
, PortfolioBaseCCY
, [Date]
, AmountTotalBaseEquiv
, ExchangeRate
, AmountTotalBaseEquivUSD
, PortfolioNAVUSD
, TotalCashPctNAV) IN (1,255,511)
ORDER BY CASE WHEN GROUPING(Custodian) = 1 THEN 2 ELSE 1 END, Custodian, TotalCashPctNAV
Otherwise, you can use a case expression, as you did with Custodian, to determine which rows are totals. For example:
CASE WHEN GROUPING(Custodian) + GROUPING(PortfolioId) > 0 THEN ''
ELSE PortfolioID END AS PortfolioID