Modifying an Aggregate - sql

I am trying to get my surgeries to calculate at different rates and I am struggling with it. For example, patient 58903 has 4 total surgeries as shown below. However, I would like the first surgery to calculate at 100% of the PPO SURG rate (so $4232), the second one at 50%, and all remaining surgeries at 25% of the main PPO SURG rate. My current code returns $16,929 for patient 5903 which is just $4232*4. My desired output for the SURG Total below is $8,464 (4232+2116+1058+1058).
My Current Code:
SELECT
DISTINCT PATNO,
SUM( PPOSURG) AS 'Surg Total',
SUM( PPONONSURG) AS 'Non Surg Total',
SUM( PPODRUG) AS 'Drug Total',
INSNME,
IIF( SUM( PPOSURG) IS NOT NULL,
SUM( PPOSURG) + SUM(CASE WHEN REV=278 THEN (AMT1)*0.446 END),
ISNULL(SUM( PPODRUG),0)+ISNULL( SUM( PPONONSURG),0)) AS 'Claim Total'
FROM
[OPGRACE$] AS GR --Main Table
LEFT JOIN [BCSURGOP$] AS SRG ON GR.CPTCDA=SRG.[CPTCODESURG] --SURG joined on cpt
LEFT JOIN [BCBSDRUG$] AS DRUG ON GR.CPTCDA=DRUG.[CPT CODE] --DRUG Schedules joined on cpt
LEFT JOIN [BCBSNONSURGOP$] AS NSRG ON GR.CPTCDA=NSRG.[CPT CODE] --Non-SURG joined on cpt
WHERE QTY>0 AND PATNO>0
GROUP BY PATNO,INSNME
ORDER BY PATNO ASC
I tried doing something like this but you can't have an aggregate in a SUM
SUM(CASE WHEN COUNT(CPTCODESURG)=1 THEN PPOSURG ELSE PPOSURG*0.5 END) + SUM(CASE WHEN REV=278 THEN (AMT1)*0.446 END),
Here is my output for just patient 58903
SURG Total
Non Surg Total
Drug Total
CLAIM Total
16929.472
3103
179
22598.84
Here is a blurb of my joined table showing how patient 58903 shows up:
PName
Rev
CPT
PPOSURG
58903
360
29882
4232.368
58903
360
29882
4232.368
58903
360
29882
4232.368
58903
360
29882
4232.368
Would a PARTITION be the way to go here? A subquery? Can I somehow use a case statement? I don't expect anyone to write my code but literally any ideas would be extremely helpful, I have been really stuck on this.

If I understand you correctly you just need a row number partitioned by the patient and then a CASE expression to convert that into a multiplier. I've added an id column to the sample data to allow for an order by (which you need for a row number).
declare #Test table (id int identity(1,1), PName int, Rev int, CPT int, PPOSURG money);
insert into #Test (PName, Rev, CPT, PPOSURG)
values
(58903, 360, 29882, 4232.368),
(58903, 360, 29882, 4232.368),
(58903, 360, 29882, 4232.368),
(58903, 360, 29882, 4232.368);
with cte as (
select *
, row_number() over (partition by PName order by id) rn
from #Test
)
select PName, Rev, CPT
, cast(sum(PPOSURG * case rn when 1 then 1.00 when 2 then 0.50 else 0.25 end) as decimal(9,2)) Total
from cte
group by PName, Rev, CPT;

Related

using case when in sum function is returning wrong results for an aggregated table that is joined to itself in SQL

I have a table of transactions from customers who buy credits for our products. Let's just say for this example it's for pizza products. When a customer buys credits a new row is added to the table with their customer_id, amount uploaded, date_time, note describing whether credits were bought or another type of transaction, their previous balance, voucher balance, and condition explaining whether the process was confirmed or not.
I want to make a new table by getting some stats for each user. So I want a table that consists only of one row for each user. Along with the stats I want to have their last balance included. In order to do this I have to get the last row for each user and join it back to itself or at least that was my impression from the answers I saw online. this is my attempt along with some sample data.
CREATE TABLE pizza_transactions
(customer_id int,
amount int,
date_time date,
note varchar,
previous_balance int,
previous_voucher_balance int,
condition1 varchar)
;
INSERT INTO pizza_transactions
(customer_id, amount, date_time, note,previous_balance, previous_voucher_balance, condition1)
VALUES
(1, 10, '2022-01-01','Pizza credits bought',100,50,'confirmed'),
(1, -45, '2022-02-02','something else',110,50, 'processing'),
(2, 70, '2022-05-1','Pizza credits bought',20,5,'confirmed'),
(3, 20, '2022-09-01','Pizza credits bought',10,15,'confirmed'),
(3, 10, '2022-09-02','Pizza credits bought',30,15,'confirmed'),
(3, -15, '2022-09-03','something else',40,15,'processing')
select u.customer_id,
sum(case when note like '%Pizza credits bought%' and condition1 = 'confirmed' then amount else 0 end) as total_bought,
avg(case when note like '%Pizza credits bought%' and condition1 = 'confirmed' then amount end) as avg_bought,
min(case when note like '%Pizza credits bought%' then date_time end) as first_purchased_date,
max(case when note like '%Pizza credits bought%' then date_time end) as last_purchased_date,
max(case when hu.rn1 = 1 then hu.previous_balance end) as last_balance,
max(case when hu.rn1 = 1 then hu.previous_voucher_balance end) as last_voucher_balance
from pizza_transactions as u
inner join (
select row_number() over (partition by customer_id order by date_time desc) as rn1,
previous_balance,
previous_voucher_balance,
customer_id
from pizza_transactions
) as hu
on u.customer_id = hu.customer_id
group by u.customer_id;
This query however returns a table with some right information except for the newly created column called total_bought. After playing around with the query I realized the join was causing duplicate rows and that is why the sum of the amount was wrong. I then tried to get rid of the duplicate rows by changing my SQL query to look like this
select u.customer_id,
sum(case when u.note like '%Pizza credits bought%' and u.condition1 = 'confirmed' then amount else 0 end) as total_bought,
avg(case when u.note like '%Pizza credits bought%' and u.condition1 = 'confirmed' then amount end) as avg_bought,
min(case when u.note like '%Pizza credits bought%' then u.date_time end) as first_purchased_date,
max(case when u.note like '%Pizza credits bought%' then u.date_time end) as last_purchased_date,
max(hu.previous_balance) as last_balance,
max(hu.previous_voucher_balance) as last_voucher_balance
from pizza_transactions as u
left join (select *
from (
select row_number() over (partition by customer_id order by date_time desc) as rn1,
previous_balance,
previous_voucher_balance,
customer_id
from pizza_transactions )t
where t.rn1 = 1
) as hu
on u.customer_id = hu.customer_id;
group by u.customer_id
But this returned ERROR: column "u.customer_id" must appear in the GROUP BY clause or be used in an aggregate function Position: 8. I did however get rid of the duplicate rows.
So my question is how can I aggregate a table and group by users and then add their last balances to this table? I can't seem to figure this out.

Datediff on 2 rows of a table with a condition

My data looks like the following
TicketID OwnedbyTeamT Createddate ClosedDate
1234 A
1234 A 01/01/2019 01/05/2019
1234 A 10/05/2018 10/07/2018
1234 B 10/04/2019 10/08/2018
1234 finance 11/01/2018 11/11/2018
1234 B 12/02/2018
Now, I want to calculate the datediff between the closeddates for teams A, and B, if the max closeddate for team A is greater than max closeddate team B. If it is smaller or null I don't want to see them. So, for example,I want to see only one record like this :
TicketID (Datediff)result-days
1234 86
and for another tickets, display the info. For example, if the conditions aren't met then:
TicketID (Datediff)result-days
2456 -1111111
Data sample for 2456:
TicketID OwnedbyTeamT Createddate ClosedDate
2456 A
2456 A 10/01/2019 10/05/2019
2456 B 08/05/2018 08/07/2018
2456 B 06/04/2019 06/08/2018
2456 finance 11/01/2018 11/11/2018
2456 B 12/02/2018
I want to see the difference in days between 01/05/2019 for team A, and
10/08/2018 for team B.
Here is the query that I wrote, however, all I see is -1111111, any help please?:
SELECT A.incidentid,
( CASE
WHEN Max(B.[build validation]) <> 'No data'
AND Max(A.crfs) <> 'No data'
AND Max(B.[build validation]) < Max(A.crfs) THEN
Datediff(day, Max(B.[build validation]), Max(A.crfs))
ELSE -1111111
END ) AS 'Days-CRF-diff'
FROM (SELECT DISTINCT incidentid,
Iif(( ownedbyteam = 'B'
AND titlet LIKE '%Build validation%' ), Cast(
closeddatetimet AS NVARCHAR(255)), 'No data') AS
'Build Validation'
FROM incidentticketspecifics) B
INNER JOIN (SELECT incidentid,
Iif(( ownedbyteamt = 'B'
OR ownedbyteamt =
'Finance' ),
Cast(
closeddatetimet AS NVARCHAR(255)), 'No data') AS
'CRFS'
FROM incidentticketspecifics
GROUP BY incidentid,
ownedbyteamt,
closeddatetimet) CRF
ON A.incidentid = B.incidentid
GROUP BY A.incidentid
I hope the following answer will be of help.
With two subqueries for the two teams (A and B), the max date for every Ticket is brought. A left join between these two tables is performed to have these information in the same row in order to perform DATEDIFF. The last WHERE clause keeps the row with the dates greater for A team than team B.
Please change [YourDB] and [MytableName] in the following code with your names.
--Select the items to be viewed in the final view along with the difference in days
SELECT A.[TicketID],A.[OwnedbyTeamT], A.[Max_DateA],B.[OwnedbyTeamT], B.[Max_DateB], DATEDIFF(dd,B.[Max_DateB],A.[Max_DateA]) AS My_Diff
FROM
(
--The following subquery creates a table A with the max date for every project for team A
SELECT [TicketID]
,[OwnedbyTeamT]
,MAX([ClosedDate]) AS Max_DateA
FROM [YourDB].[dbo].[MytableName]
GROUP BY [TicketID],[OwnedbyTeamT]
HAVING [OwnedbyTeamT]='A')A
--A join between view A and B to bring the max dates for every project
LEFT JOIN (
--The max date for every project for team B
SELECT [TicketID]
,[OwnedbyTeamT]
,MAX([ClosedDate]) AS Max_DateB
FROM [YourDB].[dbo].[MytableName]
GROUP BY [TicketID],[OwnedbyTeamT]
HAVING [OwnedbyTeamT]='B')B
ON A.[TicketID]=B.[TicketID]
--Fill out the rows on the max dates for the teams
WHERE A.Max_DateA>B.Max_DateB
You might be able to do with a PIVOT. I am leaving a working example.
SELECT [TicketID], "A", "B", DATEDIFF(dd,"B","A") AS My_Date_Diff
FROM
(
SELECT [TicketID],[OwnedbyTeamT],MAX([ClosedDate]) AS My_Max
FROM [YourDB].[dbo].[MytableName]
GROUP BY [TicketID],[OwnedbyTeamT]
)Temp
PIVOT
(
MAX(My_Max)
FOR Temp.[OwnedbyTeamT] in ("A","B")
)PIV
WHERE "A">"B"
Your sample query is quite complicated and has conditions not mentioned in the text. It doesn't really help.
I want to calculate the datediff between the closeddates for teams A, and B, if the max closeddate for team A is greater than max closeddate team B. If it is smaller or null I don't want to see them.
I think you want this per TicketId. You can do this using conditional aggregation:
SELECT TicketId,
DATEDIFF(day,
MAX(CASE WHEN OwnedbyTeamT = 'B' THEN ClosedDate END),
MAX(CASE WHEN OwnedbyTeamT = 'A' THEN ClosedDate END) as diff
)
FROM incidentticketspecifics its
GROUP BY TicketId
HAVING MAX(CASE WHEN OwnedbyTeamT = 'A' THEN ClosedDate END) >
MAX(CASE WHEN OwnedbyTeamT = 'B' THEN ClosedDate END)

SQL Columns to Rows- for a View

I have a view which has
ID INQCLASS INQDETAIL Period BAL
1233 GROSS water 12-3-2017 233.32
1233 GROSS ENergy 12-3-2017 122.00
ID,INQCLASS, Period is same. Except the INQDETAIL and BAL
I want to combine this into a single row which displays water and energy Bal.
Any Suggestions would be helpful. Thank you
SELECT ID,
INQCLASS,
Period,
MAX(CASE WHEN INQDETAIL = 'water' then BAL else 0 end) as WaterBal,
MAX(CASE WHEN INQDETAIL = 'ENergy' then BAL else 0 end) as ENergyBal
FROM View_Name
GROUP BY ID, INQLASS, Period
The case statement serves to show the BAL only when the condition is met. So with case alone, this would still return two rows for each item, but one would have a Waterbal value and no energybal value, and the other would be the reverse.
When you do GROUP BY, every field has to either be in the GROUP BY list (in this case, ID, INQCLASS, Period), or have an aggregate function like SUM, MAX, COUNT, etc. (in this case Waterbal and energyBal have aggregate functions).
The GROUP BY in this case collapses the common ID, INQLASS, Period into single rows, and then takes the largest (MAX) value for Waterbal and energyBal. Since one is always 0, it simply supplies the other one.
A simple pivot table ought to do it. As long as you know Inqdetail values ahead of time:
select ID,
INQCLASS,
[Period],
[Water] AS [Water Bal],
[Energy] as [Energy Bal]
from
(
select [ID],
[INQCLASS],
[INQDETAIL],
[Period],
[BAL]
from #util
) As Utilities
PIVOT
(
SUM([BAL])
FOR [inqdetail] IN ([Water],[Energy])
) AS Pvttbl
Try something like this:
SELECT INQDETAIL
, PERIOD
, SUM(BAL) AS energy_Bal
FROM your_view
WHERE INQDETAIL LIKE 'water'
GROUP BY INQDETAIL
, PERIOD;
Try this:
SELECT *
FROM
(SELECT * FROM #temp) AS P
PIVOT
(
max(bal) FOR INQDETAIL IN ([water], [ENergy])
) AS pv1

How can I add cumulative sum column?

I use SqlExpress
Following is the query using which I get the attached result.
SELECT ReceiptId, Date, Amount, Fine, [Transaction]
FROM (
SELECT ReceiptId, Date, Amount, 'DR' AS [Transaction]
FROM ReceiptCRDR
WHERE (Amount > 0)
UNION ALL
SELECT ReceiptId, Date, Amount, 'CR' AS [Transaction]
FROM ReceiptCR
WHERE (Amount > 0)
UNION ALL
SELECT strInvoiceNo AS ReceiptId, CONVERT(datetime, dtInvoiceDt, 103) AS Date, floatTotal AS Amount, 'DR' AS [Transaction]
FROM tblSellDetails
) AS t
ORDER BY Date
Result
want a new column which would show balance amount.
For example. 1 Row should show -2500, 2nd should -3900, 3rd should -700 and so on.
basically, it requires previous row' Account column's data and carry out calculation based on transaction type.
Sample Result
Well, that looks like SQL-Server , if you are using 2012+ , then use SUM() OVER() :
SELECT t.*,
SUM(CASE WHEN t.transactionType = 'DR'
THEN t.amount*-1
ELSE t.amount END)
OVER(PARTITION BY t.date ORDER BY t.receiptId,t.TransactionType DESC) as Cumulative_Col
FROM (YourQuery Here) t
This will SUM the value when its CR and the value*-1 when its DR
Right now I grouped by date, meaning each day will recalculate this column, if you want it for all time, replace the OVER() with this:
OVER(ORDER BY t.date,t.receiptId,t.TransactionType DESC) as Cumulative_Col
Also, I didn't understand why in the same date, for the same ReceiptId DR is calculated before CR , I've add it to the order by but if thats not what you want then explain the logic better.

Histogram: Counting orders with variable bins in SQL

I have a table containing orders, items, and prices. I am trying to generate histograms for each item based on the prices.
Create Table #Customer_Pricing
(
customer_id int,
item_id VARCHAR(10),
qty DECIMAL(5,2),
price DECIMAL(5,2),
)
;
GO
-- Insert Statements
Insert into #Customer_Pricing values(128456, 'SOM 555', 8, 2.50)
Insert into #Customer_Pricing values(123856, 'SOM 554', 1, 2.50)
Insert into #Customer_Pricing values(123456, 'SOM 554', 55, 2.00)
Insert into #Customer_Pricing values(123556, 'SOM 555', 2, 2.20)
Insert into #Customer_Pricing values(123456, 'SOM 553', 12, 2.13)
;
For each item, I wanted 3 bins so I determined the bin sizes by dividing the difference of the MAX-MIN by 3, then adding that value to the MIN.
WITH Stats_Table_CTE (item_id2,max_p, min_p, int_p, r1_upper, r2_lower, r2_upper, r3_lower)
AS
( SELECT item_id
,max(price)
,min(price)
,(max(price) - min(price))/3
,min(price)+(max(price) - min(price))/3-0.01
,min(price)+(max(price) - min(price))/3
,min(price)+((max(price) - min(price))/3)*2-0.01
,min(price)+((max(price) - min(price))/3)*2
FROM #Customer_Pricing
GROUP BY item_id)
Now, I need to count the frequencies for each range and each item. I have attempted to do so by using SUM(CASE...) but was unsuccessful.
SELECT item_id
,SUM(CASE WHEN price <= r1_upper, THEN 1 ELSE 0 END) AS r1_count
,SUM(CASE WHEN price >= r2_lower AND <= r2_upper, THEN 1 ELSE 0 END) AS r2_count
,SUM(CASE WHEN price >= r3_lower, THEN 1 ELSE 0 END) AS r3_count
FROM Stats_Table_CTE
GROUP BY item_id
I also attempted to use COUNT in the form
SELECT item_id, price
count(price <= r1_upper) AS r1_count.... but I got stuck
In one attempt, INNER JOINed the #Customer_Pricing table and Stats_Table_CTE but didn't know where to go from there.
Ideally, I would like the output table to appear as follows: *This is not the actual data, but I included it to show the desired format of the output.
Item ID min_p r1_upper (r2 bins) r3_lower max_p r1_count r2_ct
SOM 553 2.00 2.16 saving space 2.33 2.50 2 1
SOM 554 2.13 2.48 2.88 3.25 1 0
SOM 555 2.31 2.51 2.72 2.92 3 2
*The format of the output table is off, but I have item ID, the bins, and the counts across the top grouped by item
Here is my recommendation:
WITH Stats_Table_CTE AS (
SELECT item_id, max(price) as maxprice, min(price) as minprice,
(max(price) - min(price))/3 as binsize
FROM #Customer_Pricing
GROUP BY item_id
)
SELECT cp.item_id,
SUM(CASE WHEN price < minprice + binsize THEN 1 ELSE 0
END) AS r1_count
SUM(CASE WHEN price >= minprice + binsize AND price < minprice+ 2*binsize
THEN 1 ELSE 0
END) AS r2_count
SUM(CASE WHEN price >= minprice + 2*binsize
THEN 1 ELSE 0
END) AS r3_count
FROM #Customer_Pricing cp JOIN
Stats_Table_CTE st
ON st.item_id = cp.item_id
GROUP BY cp.item_id
The important part is the join back to #Customer_Pricing. Also important is the simplification of the logic -- you can define the bounds for the bins and use <, rather than having a lower and upper bound for each one. Also, your query had some syntax errors in it.
Note that in many databases, the CTE would not be necessary because you could just use window functions. Your question is not tagged with the database (although I could guess what it is), so that change seems unwarranted.