SQL Pivot and Grand total? - sql

I have a pivot table and I would like to know how to add an extra row that will be the grand total, for example,
Christmas, Hallowen
500 , 800
600 , 700
Total 1100 , 1500
Anything like this would help a lot, below is the code I've used to create my pivot table.
/*Pivot table */
SELECT * FROM
(SELECT Phase2.[Ref]
,Phase2.[Parties]
,Phase2.[TypeOfParty]
,Phase2.[Cost]
,Phase2.[DressCode]
,0 + SUM(Phase2.Cost) OVER (ORDER BY [REF]) AS 'Running Total'
FROM Stage2) AS BaseData
PIVOT(
SUM(BaseData.[Cost])
FOR BaseData.[Parties]
IN([Christmas], [Halloween])
) AS PivotTable
The Line below does give the running cost per Party but it would be good to have it as a new row and not as a column, looks a lot better.
,0 + SUM(Phase2.Cost) OVER (ORDER BY [REF]) AS 'Running Total'
Currently my results with the current pivot above are:
ref TypeOfParty, DressCode Christmas, Halloween, Running Total
1 Christmas, XmasWear, 500 , 500
2 christmas, XmasWear, 500 , , 1000
3 Halloween, HallWear, , 400 , 400
4 Halloween, HallWear, , 300 , 700
If anyone needs me to clarify something, I'm here. Hope there's enough information for you all to understand what I'm trying to do.

Does this do what you want?
SELECT s.Ref, s.TypeOfParty, s.DressCode,
SUM(CASE WHEN Parties = 'Christmas' THEN s.cost ELSE 0 END) as Christmas,
SUM(CASE WHEN Parties = 'Halloween' THEN s.cost ELSE 0 END) as Halloween
FROM Stage2 s
GROUP BY GROUPING SETS ( (s.ref), () );
This will include an overall total row in the result set.
If you want running totals, you can include a separate column for each party (one column doesn't make sense with a total row):
SUM(SUM(CASE WHEN Parties = 'Christmas' THEN s.cost ELSE 0 END)) OVER (ORDER BY s.ref) as Christmas,

Related

Window function or Recursive query in Redshift

I try to classify customer based on monthly sales. Logic for calculation:
new customer – it has sales and appears for first time or has sales and returned after being lost (after 4 month period, based on sales_date)
active customer - not new and not lost.
lost customer - no sales and period (based on sales_date) more than 4 months
This is the desired output I'm trying to achieve:
The below Window function in Redshift classify however it is not correct.
It classified lost when difference between month > 4 in one row, however it did not classify lost if it was previous lost and revenue 0 until new status appear. How it can be updated?
with customer_status (
select customer_id,customer_name,sales_date,sum(sales_amount) as sales_amount_calc,
nvl(DATEDIFF(month, LAG(reporting_date) OVER (PARTITION BY customer_id ORDER by sales_date ASC), sales_date),0) AS months_between_sales
from customer
GROUP BY customer_id,customer_name,sales_date
)
select *,
case
WHEN months_between_sales = 0 THEN 'New'
WHEN months_between_sales > 0 THEN 'Active'
WHEN months_between_sales > 0 AND months_between_sales <= 4 and sales_amount_calc = 0 THEN 'Active'
WHEN /* months_between_sales > 0 and */ months_between_sales > 4 and sales_amount_calc = 0 THEN 'Lost'
ELSE 'Unknown'
END AS status
from customer_status
One way to solve to get cumulative sales amount partitioned on subset of potentially lost customers ( sales_amount = 0).
Cumulative amount for the customer partitioned
sum(months_between_sales) over (PARTITION BY customer_id ORDER by sales_date ASC rows unbounded preceding) as cumulative_amount,
How can it be changed to get sub-partitioned, for example when sales amount= 0 , in order to get lost correctly?
Does someone have ideas to translate the above logic into an
recursive query in Redshift?
Thank you

Cumulative tiered rate calculation in SQL Server for DML UPDATE/INSERT trigger?

Essentially, using SQL Server, I want to take the "Gross Amt" from the current table below (which is derived from a computed column upon INSERT or UPDATE) and then have that "Gross Amt" run through the "Tiered Table" to derive the "Total A $" in the desired output table.
I figured this would likely need to be done with a trigger (maybe a function?) since this calculation would happen upon INSERT or UPDATE and because the conditional logic could be incorporated into it since there are different tier tables with different Min/Max values and percentage thresholds for different tiers.
The example below is, of course, cumulative, and functions like marginal income tax rates, the first 10000 is at 90% (for Total A), the second tier calculates the 19999 at 60%, the third 69999 at 40%, and so on, etc. There are other regions with different tiers that are just simple lookup reference values.
Tiered table:
RegionID
TierNo
Min
Max
Total A
Total B
3
1
0
10000
.90
.10
3
2
10001
30000
.60
.40
3
3
30001
100000
.40
.60
3
4
100001
500000
.40
.60
3
5
500001
999999999999
.20
.80
Current table sample:
TransID
RegionID
GrossAmt
Total A %
Total A $
Net Amt
100001
3
125000
Desired output:
TransID
RegionID
GrossAmt
Total A %
Total A $
Net Amt
100001
3
125000
0.47
59000
66000
Any ideas or guidance would be extremely helpful and appreciated.
Firstly, your tiers aren't quite right. For example, the first one begins at 0 and ends at 10000, but the next one starts at 10001, leaving anything between 10000 and 10001 unaccounted for. Instead, have your tiers abutting each other, and pick your intervals carefully using > AND <=.
Secondly, this doesn't need a trigger at all. You should just calculate this on the fly when you need it. Create a view or an inline Table Valued Function if you need to.
It looks like a fairly simple grouped join, which you can do neatly using CROSS APPLY. You just need to calculate how much to multiply for each tier: the lower of GrossAmt or Max, subtracting Min
SELECT
c.*,
[Total A %] = t.Total / c.GrossAmt,
[Total A $] = t.Total,
[Net Amt] = c.GrossAmt - t.Total
FROM CurrentData c
CROSS APPLY (
SELECT
Total = SUM((v.ActualMax - t.Min) * t.[Total A])
FROM Tiers t
CROSS APPLY (VALUES(
CASE WHEN c.GrossAmt < t.Max THEN c.GrossAmt ELSE t.Max END
)) v(ActualMax)
WHERE c.GrossAmt > t.Min
) t;
db<>fiddle
You can do this as an inline Table Valued Function if you want.
CREATE FUNCTION dbo.GetTieredTotal (#GrossAmt decimal(18,9))
RETURNS TABLE
AS RETURN
SELECT
Total = SUM((CASE WHEN #GrossAmt < t.Max THEN #GrossAmt ELSE t.Max END - t.Min) * t.[Total A])
FROM Tiers t
WHERE #GrossAmt > t.Min
;
You can then change the main query to
SELECT
c.*,
[Total A %] = t.Total / c.GrossAmt,
[Total A $] = t.Total,
[Net Amt] = c.GrossAmt - t.Total
FROM CurrentData c
CROSS APPLY dbo.GetTieredTotal (c.GrossAmt) t;

I need write a query pivot or your solution on SQL Server 2019

I'm working SQLServer 2019 and I have orders table. Orders table has columns orderid, userid, country, site, count, price. I need help write query. You can see below with details.
Question:
Show me that how much user ordered from 2010 to present once, twice, etc in 5 stripe ([1], [2], [3], [4-10], [10-100]) based on country
Example Result:
Country 1 2 3 4-10 10-100
---------------------------------------------------------
US 0 0 3 4 5
GB 10 10 8 50 60
NL 20 20 20 100 30
....
My query:
I used pivot table from 1 to 3 and I have correct result. But, I couldn't write the ranges ten to four and ten to one hundred in the pivot table.
I ran query for below;
select * from (
SELECT Country,
count(*) as total,
count as totalpay
FROM [CRM].[dbo].[Orders]
where date like '%2010%'
group by Country,count
) countrytotalcnt
pivot
(
sum(total) for totalpay in ([1],[2],[3],[4-10],[10-100])
)countrytotal;
I have error for below;
Msg 8114, Level 16, State 1, Line 24
Error converting data type nvarchar to int.
Msg 473, Level 16, State 1, Line 24
The incorrect value "4-10" is supplied in the PIVOT operator.
Completion time: 2021-10-13T13:55:47.1067875+03:00
As I mentioned in the comments, use conditional aggregation here, it's far more versatile that the PIVOT operator.
Also, your WHERE will error as '%2010%' cannot be converted to a date and time data type. If the WHERE is "working" the problem is your design and you are storing date and time values as a string based data type; a fatal flaw and needs to be fixed. varchar is NOT a one size fits all data type. I assume your database isn't fundamentally flawwed and use a date boundaries.
I can't test this, as we have no sample data, but this is likely what you want. Note the comments in the SQL as well (especially on your ranges 4-10 and 10-100).
WITH Counts AS(
SELECT O.Country,
COUNT(*) AS Total
O.[Count] AS TotalPay--COUNT is a reserved word, I suggest against naming your columns this
FROM dbo.Orders O
WHERE [date] >= '20200101'
AND [date] < '20210101'
GROUP BY O.Country)
SELECT C.Country,
SUM(CASE TotalPay WHEN 1 THEN Total END) AS [1],
SUM(CASE TotalPay WHEN 2 THEN Total END) AS [2],
SUM(CASE TotalPay WHEN 3 THEN Total END) AS [3],
SUM(CASE WHEN TotalPay BETWEEN 4 AND 10 THEN Total END) AS [4-10], --note this includes 10
SUM(CASE WHEN TotalPay BETWEEN 10 AND 100 THEN Total END) AS [10-100] --Note this includes 10 again
FROM Counts C
GROUP BY C.Country;

sql multiplying a sum

I'm struggling with the below code. i'm trying to work out the total balance of a folio by using its total charges and total payments. i'm almost there, however with the below code the payments is incorrect, it seems to be multiplying the total sum of payments by the number of entries in charges. i'm guessing this is because I've connected the payments and charges, which I needed to do to filter out the checkin date which is only on pms sales.
can anyone help?
thanks
SELECT DISTINCT 'PMS AD' AS APP,
FOLIO_ID,
SUM(TOTAL_CHARGES) as TOTAL_CHARGES,
SUM(TOTAL_PAYMENTS) as TOTAL_PAYMENTS
FROM ((SELECT DISTINCT P1.FOLIO_ID AS FOLIO_ID, SUM(P1.CHARGE_CODE_AMOUNT) AS TOTAL_CHARGES, 0 AS TOTAL_PAYMENTS
FROM DEV.VR_PMS_SALES P1
WHERE P1.CHARGE_CODE_AMOUNT <> 0 AND
P1.ITEM_OPERATING_DAY IS NOT NULL
AND P1.ITEM_OPERATING_DAY <= '03-DEC-2014'
AND P1.CHECKIN_DATE <= '03-DEC-2014'
GROUP BY P1.FOLIO_ID
) UNION ALL
(SELECT DISTINCT P2.FOLIO_ID AS FOLIO_ID, 0 AS TOTAL_CHARGES, SUM(P2.AMOUNT) AS TOTAL_PAYMENTS
FROM DEV.VR_PMS_PAYMENTS P2,
DEV.VR_PMS_SALES P3
WHERE P2.FOLIO_ID = P3.FOLIO_ID
AND P2.AMOUNT <> 0
AND P2.PMS_OPERATING_DAY <= '03-DEC-2014'
AND P3.CHECKIN_DATE <= '03-DEC-2014'
GROUP BY P2.FOLIO_ID
)
) F
GROUP BY FOLIO_ID
EDIT:
Sorry I didn't provide examples. table data below
VR_PMS_PAYMENTS
VR_PMS_SALES
The issue I am having is that when running the sql it is multiplying the sum of p1.amount by the number of entries in VR_PMS_SALES. eg folio 4 is returning as 165 instead of 55. I need it to return the below...
desired outcome
I hope this is clearer.
thank you

How to subtract totals of 2 different groupings of the same column per month

I am writing a query in which I am supposed to subtract total revenue by discounts per month. Problem is that there are multiple codes which represent either revenue or discounts.
To illustrate exactly what I mean, please see the following example.
Month Code Amount
May 4001 $50.05
May 4002 $49.95
May 6005 $15.00
May 6006 $5.00
March 4003 $65.00
Codes for revenue are 4001, 4002 and 4003. Discounts are 6005 and 6006.
In the end I should be seeing:
Month TotalRevenue TotalDiscount Total
May $100.00 $20.00 $80.00
March $65.00 $0.00 $65.00
I have tried CASE but it tells me I can only use 1 argument. I have also tried creating a sub query in the select statement, but I can't seem to be able to use 2 SUM statements (1 in main and 1 in sub). I guess it would work if I had a possibility to use 'join' but there is nothing to join it with.
Well, the first issue is to construct your query so that the Discount Codes are distinguished from the Revenue Codes. If you have table(s) containing the Codes (either one combined Codes table with an indicator to distinguish the two types, or separate tables), this table (or tables) should be used.
Since it's hard to tell the full set of Codes from your question, let's just pretend that Codes 6000-6999 are Discounts and all others are Revenue. Then a query that produces your desired results could look like this:
select Month,
Revenue = sum(
case when Code between 6000 and 6999 then
0
else
Amount
end
),
Discounts = sum(
case when Code between 6000 and 6999 then
Amount
else
0
end
),
Total = sum(
case when Code between 6000 and 6999 then
-1 * Amount
else
Amount
end
)
from MyTable
group by Month
Depending on whatever your actual criteria is for distinguishing the two types of codes, you just need to change the case statements to match and it should work.
This should get you pretty close to the desired result:
Setup (it would be really appreciated if provided in the question :) )
-- drop table Code
create table Code
(
Code INT,
IsRevenue BIT
)
insert into Code VALUES (4001, 1), (4002, 1), (4003, 1), (6005, 0), (6006, 0)
GO
create table MonthData
(
TheMonth VARCHAR(16),
Code INT,
Amount NUMERIC(18, 2)
)
GO
insert into MonthData values ('May', 4001, 50.05), ('May', 4002, 49.95),
('May', 6005, 15.00), ('May', 6006, 5.00), ('March', '4003', 65.00)
GO
select * from MonthData
GO
The query:
SELECT TheMonth,
SUM((CASE WHEN C.IsRevenue = 1 THEN MD.Amount ELSE -MD.Amount END)) AS TotalRevenue
FROM MonthData MD
JOIN Code C ON C.Code = MD.Code
GROUP BY TheMonth