I am using SQL Server 2008 R2 and T-SQL. These are abbreviated examples.
My table/view has the following fields
Table/View
ReportID,
UnitName,
UnitID,
CaseDefinitionID,
CaseDefinition,
DateOfDelivery,
YEAR(DateOfDelivery) AS [Year],
MONTH(DateOfDelivery) AS [Month],
DATENAME(m,DateOfDelivery) AS [Month name]
and my target report is:
Unit | Case type 1| Case type 2| Case total|
Unit A | 36| 40| 76|
2013| 20| 18| 38|
Jan| 10|
Feb| 10|
2014| 16|
Mar| 8|
Dec| 8|
Unit B | 12|
2013| 12|
Jan| 6|
May| 6|
Grand total| 48|
The month rows can be empty, NULL content is fine. The example report is incomplete to aid clarity.
Progress so far is the following query:
SELECT *
FROM
(
SELECT
PCT.[Year],
PCT.UnitName AS [Unit],
PCT.UnitID,
PCT.CaseDefinition AS [Case definition]
FROM
PivotBase AS PCT
) AS P1
PIVOT
(
COUNT(UnitID)
FOR [Case definition] IN ([Case type 1],[Case type 2])
) AS P2
This produces the following table
Year|Unit |Case type 1|Case type 2|
2013|Unit A | 20 | 18|
2014|Unit A | 16 | 22|
2013|Unit B | 6 | 8|
2014|Unit B | 6 | 8|
There is no need for dynamic SQL
My reading so far has covered many options but I still have no idea where to go next. How do I produce the required report.
You can get there by pivoting on Month and Year and then using WITH ROLLUP
WITH data
AS (SELECT *
FROM (SELECT Dateadd(month, Datediff(month, 0, PCT.dateofdelivery), 0
)
MonthYear,
PCT.unitid,
PCT.unitname
AS
[Unit],
PCT.casedefinition
AS
[Case definition]
FROM pivotbase AS PCT) AS P1
PIVOT ( Count(unitid)
FOR [case definition] IN ([Case type 1],
[Case type 2]) ) AS p2),
rollup
AS (SELECT Month(monthyear) Month,
unit unitX,
Year(monthyear) Year,
Sum([case type 1]) [case type 1],
Sum([case type 2]) [case type 2],
Grouping(unit) GUnit,
Grouping(Month(monthyear)) gm,
Grouping(Year(monthyear)) gy
FROM data
GROUP BY unit,
Year(monthyear),
Month(monthyear) WITH rollup)
SELECT COALESCE(Cast(month AS VARCHAR), Cast(year AS VARCHAR), unitx,
'Grand Total')
Unit,
[case type 1],
[case type 2]
FROM rollup
ORDER BY gunit,
unitx,
year,
gm DESC,
month
SQLFiddle
It's doable with some fancy rollup (SQL Fiddle)
;WITH
CTE1 AS
(
SELECT UnitName,
[Year],
[Month],
CaseDefinition,
GROUPING_ID(UnitName,[Year],[Month],CaseDefinition)
AS GroupingID,
CASE GROUPING_ID(CaseDefinition,UnitName,[Year],[Month])
WHEN 0 THEN UnitName + '-' + CAST(Year AS varchar(4)) + '-' + RIGHT('0' + CAST([Month] AS varchar(10)),2)
WHEN 1 THEN UnitName + '-' + CAST(Year AS varchar(4))
WHEN 3 THEN UnitName
WHEN 7 THEN 'zzz' -- So that grand total appears at the bottom
ELSE NULL
END AS GroupingLevel,
CASE GROUPING_ID(CaseDefinition,UnitName,[Year],[Month])
WHEN 0 THEN ' ' + CASE [Month]
WHEN 1 THEN 'Jan'
WHEN 2 THEN 'Feb'
WHEN 3 THEN 'Mar'
WHEN 4 THEN 'Apr'
WHEN 5 THEN 'May'
WHEN 6 THEN 'Jun'
WHEN 7 THEN 'Jul'
WHEN 8 THEN 'Aug'
WHEN 9 THEN 'Sep'
WHEN 10 THEN 'Oct'
WHEN 11 THEN 'Nov'
WHEN 12 THEN 'Dec'
END
WHEN 1 THEN ' ' + CAST([Year] AS varchar(4))
WHEN 3 THEN UnitName
WHEN 7 THEN 'Grand Total'
END AS DisplayName,
COUNT(UnitID) AS UnitCount
FROM PivotBase
GROUP BY GROUPING SETS(
(CaseDefinition),
(UnitName,CaseDefinition),
(UnitName,[Year],[CaseDefinition]),
(UnitName,[Year],[Month],CaseDefinition)
)
)
SELECT pvt.GroupingLevel,
pvt.DisplayName,
pvt.[Case Type 1],
pvt.[Case Type 2],
ISNULL(pvt.[Case Type 1],0) + ISNULL(pvt.[Case Type 2],0) AS [Case Total]
FROM CTE1
PIVOT (
SUM(UnitCount) FOR CaseDefinition IN ([Case Type 1],[Case Type 2])
) pvt
ORDER BY GroupingLevel
Explanation:
GROUPING SET( (set1), (set2), (set3) ) defines the roll up level. You will get a count of UnitID for each unique combination inside set1, then set2, then set3
GROUPING_ID is the most obscure function in here. Think of it as a bit mask. If a column is aggregated, its bit value is set to 1. For example: GROUPING_ID(field3, field2, field1, field0). If all 4 are not aggregated, the bit mask is 0000 = 0. If field0 is aggregated away, the returned value is 0001 = 1, and so on.
You can replace the last SELECT with SELECT * FROM CTE1 to see the inner working of the query.
Related
I'm trying to insert the followings columns below into a #table based off a time range.
For example if the query is run in August 2020, I'd need it to insert Months names from Jan - July 2020
Report_Date_Name| Report_Date_Month| DateYrmo| DateYear| DateMonth|
January 2020| January| 202001| 2020| 1|
February 2020| February| 202002| 2020| 2|
March 2020| March| 202003| 2020| 3|
April 2020| April| 202004| 2020| 4|
May 2020| May| 202005| 2020| 5|
June 2020| June| 202006| 2020| 6|
July 2020| July| 202007| 2020| 7|
________________________________________________________________________________
A recursive CTE seems like a simple approach:
with dates as (
select datefromparts(year(getdate()), 1, 1) as dte
union all
select dateadd(month, 1, dte)
from dates
where dte < getdate()
)
select concat(convert(char(12), datename(month, dte)), year(dte)) as report_date_name,
datename(month, dte) as report_date_month,
year(dte) * 100 + month(dte) as year_month,
year(dte) as year
from dates;
It is not clear if the last columns are strings or numbers. If strings, just cast them to the type you want.
Here is a db<>fiddle.
Try this:
DECLARE #SomeDate DATE = '2020-08-08';
WITH Dates ([Date]) AS
(
SELECT DATEADD(MONTH, -[number], '2020-08-08')
FROM
(
SELECT [number] + 1
FROM [master].[dbo].[spt_values]
WHERE [type] = 'P'
) n ([number])
WHERE DATEADD(MONTH, -[number], #SomeDate) <= DATEADD(MONTH, -1, #SomeDate)
AND YEAR(DATEADD(MONTH, -[number], #SomeDate)) = YEAR(#SomeDate)
)
SELECT CONCAT(DATENAME(MONTH, [Date]), ' ', YEAR([Date])) AS [Report_Date_Name]
,DATENAME(MONTH, [Date]) AS [Report_Date_Month]
,RIGHT(YEAR([Date]) * 100 + 10000000 + MONTH([Date]), 6) AS [DateYrmo]
,YEAR([Date]) AS [DateYear]
,MONTH([Date]) AS [DateMonth]
FROM Dates
ORDER BY [Date] ASC;
The CTE is used to generated one date from each previous month for the current year. Then we are using built-in functions to format the output.
I have some values in rows like :
Month | Product | SalesQty
-------+---------+---------
Jan-17 | ABC | 3
Feb-17 | ABC | 6
Apr-17 | ABC | 19
But i want to show the some values in columns like:
Model| Apr-17 | May-17 | Jun-17 | Jul-17
ABC 1 2 12 0
BCS 212 12 12 112
Months must be generated dynamically. Static month will not help me in this situation.
Why not Use pivot? it is simpler than other solutions like case expression:
SELECT *
FROM table
PIVOT
(
SUM(SalesQty)
FOR Month IN([Apr-17] ,[May-17], [Jun-17], [Jul-17])
) AS p;
To do it dynamically you can use the same query with dynamic sql like this:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' +QUOTENAME(CONCAT(LEFT(datename(month, Month), 3),
CAST(DATEPART(day, month) AS NVARCHAR(2))))
FROM table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = ' SELECT *
FROM
(
SELECT product, SalesQty,
CONCAT(LEFT(datename(month, Month), 3),
CAST(DATEPART(day, month) AS NVARCHAR(2))) AS Month
FROM table1
) AS t
PIVOT
(
SUM(SalesQty)
FOR Month IN( ' + #cols + ' )
) AS p';
execute(#query);
dynamic demo
If you don't want to use PIVOT then you can use CASE expression like this:
SELECT product,
SUM(CASE WHEN month = 'Jan17' THEN SalesQty ELSE 0 END) AS Jan17,
SUM(CASE WHEN month = 'Jan17' THEN SalesQty ELSE 0 END) AS Jun17,
SUM(CASE WHEN month = 'Jan17' THEN SalesQty ELSE 0 END) AS Jul17
FROM
(
SELECT product, SalesQty,
CONCAT(LEFT(datename(month, Month), 3),
CAST(DATEPART(day, month) AS NVARCHAR(2))) AS Month
FROM table1
) AS t
GROUP BY Product;
Then to do this dynamically, you just need to replace the case expression part to by dynamic in the cols names variable.
I have a table in #SQL server 2008 that has transaction data. The table looks like this. I would like to have this in a sql statement.
TransactionId|TransactionDate|TransactionType|Amount|Balance|UserId
The transaction type can be one of four types, Deposit, Withdrawals, Profit and Stake. I give an example how it can look like in the transaction table. The balance is the Sum of amount column.
TransactionId|TransactionDate|TransactionType|Amount|Balance|UserId
1| 2013-03-25| Deposit| 150| 150| 1
2| 2013-03-27| Stake| -20| 130| 1
3| 2013-03-28| Profit | 1500| 1630| 1
4 | 2013-03-29| Withdrawals| -700| 930| 1
5| 2013-03-29| Stake | -230 | 700 | 1
6| 2013-04-04| Stake| -150 | 550| 1
7| 2013-04-06| Stake | -150 | 400| 1
What I want now is to get a select statement that gives me all data grouped by week. The result should look like this.
Week|Deposit|Withdrawals|Stake|Profit|Balance|Year
13 | 150| -700 | -250 | 1500 | 700 | 2013
14 | 0 | 0 | -300| 0 | 400 | 2013
I have also problem with the weeks... I live in Europe an my first day in a week is monday. I have a solution for that but around the end of a year I get sometimes week 54 but there are only 52 weeks in a year...
I hope someone can help me out.
This is what I have so far.
SELECT transactionid,
transactiondate,
transactiontype,
amount,
(SELECT Sum(amount)
FROM transactions AS trans_
WHERE trans_.transactiondate <= trans.transactiondate
AND userid = 1) AS Balance,
userid,
Datepart(week, transactiondate) AS Week,
Datepart(year, transactiondate) AS Year
FROM transactions trans
WHERE userid = 1
ORDER BY transactiondate DESC,
transactionid DESC
Here's sample data and my query on sql-fiddle: http://www.sqlfiddle.com/#!3/79d65/92/0
In order to transform the data from the rows into columns, you will want to use the PIVOT function.
You did not specify what balance value you want to return but based on the final result, it looks like you want the final balance to be the value associated with the last transaction date for each day. If that is not correct, then please clarify what the logic should be.
In order to get the result you will want to use the DATEPART and YEAR functions. These will allow grouping by both the week and year values.
The following query should get the result that you want:
select week,
coalesce(Deposit, 0) Deposit,
coalesce(Withdrawals, 0) Withdrawals,
coalesce(Stake, 0) Stake,
coalesce(Profit, 0) Profit,
Balance,
Year
from
(
select datepart(week, t1.transactiondate) week,
t1.transactiontype,
t2.balance,
t1.amount,
year(t1.transactiondate) year
from transactions t1
cross apply
(
select top 1 balance
from transactions t2
where datepart(week, t1.transactiondate) = datepart(week, t2.transactiondate)
and year(t1.transactiondate) = year(t2.transactiondate)
and t1.userid = t2.userid
order by TransactionId desc
) t2
) d
pivot
(
sum(amount)
for transactiontype in (Deposit, Withdrawals, Stake, Profit)
) piv;
See SQL Fiddle with Demo. The result is:
| WEEK | DEPOSIT | WITHDRAWALS | STAKE | PROFIT | BALANCE | YEAR |
------------------------------------------------------------------
| 13 | 150 | -700 | -250 | 1500 | 700 | 2013 |
| 14 | 0 | 0 | -300 | 0 | 400 | 2013 |
As a side note, you stated that your start of the week is Monday, you might have to use the DATEFIRST function to set the first day of the week.
Another option, without using PIVOT, but rather with few CASEs
WITH CTE AS
(
SELECT
TransactionId
,TransactionDate
,DATEPART(WEEK, TransactionDate) AS Week
,CASE WHEN TransactionType='Deposit' THEN Amount ELSE 0 END AS Deposit
,CASE WHEN TransactionType='Stake' THEN Amount ELSE 0 END AS Stake
,CASE WHEN TransactionType='Profit' THEN Amount ELSE 0 END AS Profit
,CASE WHEN TransactionType='Withdrawals' THEN Amount ELSE 0 END AS Withdrawals
,Balance
,DATEPART(YEAR, TransactionDate) AS Year
FROM dbo.Transactions
)
SELECT
Week, SUM(Deposit) AS Deposit, SUM(Withdrawals) AS Withdrawals, SUM(Stake) AS Stake, SUM(Profit) AS Profit,
(SELECT Balance FROM CTE i WHERE i.TransactionID = MAX(o.TransactionID)) AS BAlance, Year
FROM CTE o
GROUP BY Week, Year
SQLFiddle Demo
http://www.sqlfiddle.com/#!3/79d65/89
;WITH cte AS
(
SELECT datepart(ww, transactiondate) wk,
sum(CASE WHEN TransactionType = 'Deposit' THEN Amount ELSE 0 END) AS D,
sum(CASE WHEN TransactionType = 'Withdrawals' THEN Amount ELSE 0 END) AS W,
sum(CASE WHEN TransactionType = 'Profit' THEN Amount ELSE 0 END) AS P,
sum(CASE WHEN TransactionType = 'Stake' THEN Amount ELSE 0 END) AS S,
sum(
CASE WHEN TransactionType = 'Deposit' THEN Amount ELSE 0 END +
CASE WHEN TransactionType = 'Withdrawals' THEN Amount ELSE 0 END +
CASE WHEN TransactionType = 'Profit' THEN Amount ELSE 0 END +
CASE WHEN TransactionType = 'Stake' THEN Amount ELSE 0 END +
CASE WHEN TransactionType = 'Balance' THEN Amount ELSE 0 END) AS wkTotal
FROM transactions
GROUP BY datepart(ww, transactiondate)),
cte1 AS
(
SELECT *, row_number() over (ORDER BY wk) AS rowNum
FROM cte)
SELECT wk, d, w, p, s, wktotal
+ coalesce((SELECT top 1 wktotal FROM cte1 x WHERE x.rownum < m.rownum ), 0) AS RunningBalance
FROM cte1 m
I have a table with a PROJECTNAME, ACTUALCOST, PROJECTSTART and PROJECTEND columns like so:
PROJECTNAME ACTUAL COST PROJECTSTART PROJECTEND
abc 1500 2011-12-01 2012-07-31
prj1 1170 2012-01-09 2012-06-30
xyz 5350 2012-01-30 2012-03-30
I am trying to get an output like below:
PRJNAME DEC11 JAN12 FEB12 MAR12 APR12 MAY12 JUN12 JUL12 ...
abc 187.5 187.5 187.5 187.5 187.5 187.5 187.5 187.5
prj1 117 195 195 195 195 195
I need the monthly cost breakdowns of each project name, depending on how many days left are in each month.
You can use the PIVOT function to get the result.
The way I would recommend doing this would be to write a hard-coded version of this first, then convert it to dynamic sql.
Note: I determined the cost per month by dividing the Actual Cost by the number of days per between the projectstart/projectend. This should at least get you started for your actual report.
The static version would be similar to this:
;with cte as
(
select projectname, [ACTUAL COST], [PROJECTSTART], [PROJECTEND]
from yourtable
union all
select projectname, [ACTUAL COST],
dateadd(d, 1, PROJECTSTART),
PROJECTEND
from cte
where dateadd(d, 1, PROJECTSTART) <= ProjectEnd
)
select *
from
(
select
my.projectname,
my.monthyear,
my.totaldayspermonth * a.perdaycost AmountPerMonth
from
(
select projectname,
left(datename(m, projectstart), 3) + cast(year(projectstart) as varchar(4)) monthyear,
count(*) TotalDaysPerMonth
from cte
group by projectname,
[actual cost],
left(datename(m, projectstart), 3) + cast(year(projectstart) as varchar(4))
) my
cross apply
(
select projectname,
round([actual cost] / (datediff(d, projectstart, projectend) *1.0), 2) PerDayCost
from yourtable a
where my.projectname = a.projectname
) a
) src
pivot
(
max(AMOUNTPERMONTH)
for monthyear in (Dec2011, Jan2012, Feb2012, Mar2012,
Apr2012, May2012, Jun2012, Jul2012, Aug2012)
) piv
OPTION(MAXRECURSION 0);
See SQL Fiddle with Demo.
Once you have a static version, it makes it much easier to convert it to dynamic SQL. The dynamic SQL would be:
;with cte as
(
select projectname, [ACTUAL COST], [PROJECTSTART], [PROJECTEND]
from yourtable
union all
select projectname, [ACTUAL COST],
dateadd(d, 1, PROJECTSTART),
PROJECTEND
from cte
where dateadd(d, 1, PROJECTSTART) <= ProjectEnd
)
select *
into #dates
from cte
OPTION(MAXRECURSION 0)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(left(datename(m, projectstart), 3) + cast(year(projectstart) as varchar(4)))
from #dates
group by datename(m, projectstart), year(projectstart), month(projectstart)
order by year(projectstart), month(projectstart)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = ';with cte as
(
select projectname, [ACTUAL COST], [PROJECTSTART], [PROJECTEND]
from yourtable
union all
select projectname, [ACTUAL COST],
dateadd(d, 1, PROJECTSTART),
PROJECTEND
from cte
where dateadd(d, 1, PROJECTSTART) <= ProjectEnd
)
select projectname, '+#cols+'
from
(
select
my.projectname,
my.monthyear,
my.totaldayspermonth * a.perdaycost AmountPerMonth
from
(
select projectname,
left(datename(m, projectstart), 3) + cast(year(projectstart) as varchar(4)) monthyear,
count(*) TotalDaysPerMonth
from cte
group by projectname,
[actual cost],
left(datename(m, projectstart), 3) + cast(year(projectstart) as varchar(4))
) my
cross apply
(
select projectname,
round([actual cost] / (datediff(d, projectstart, projectend) *1.0), 2) PerDayCost
from yourtable a
where my.projectname = a.projectname
) a
) src
pivot
(
max(AMOUNTPERMONTH)
for monthyear in ('+#cols+')
)piv
OPTION(MAXRECURSION 0)'
execute(#query)
See SQL Fiddle with Demo
The result of both queries is:
| PROJECTNAME | DEC2011 | JAN2012 | FEB2012 | MAR2012 | APR2012 | MAY2012 | JUN2012 | JUL2012 |
-----------------------------------------------------------------------------------------------
| abc | 191.27 | 191.27 | 178.93 | 191.27 | 185.1 | 191.27 | 185.1 | 191.27 |
| prj1 | (null) | 155.48 | 196.04 | 209.56 | 202.8 | 209.56 | 202.8 | (null) |
| xyz | (null) | 178.34 | 2585.93 | 2675.1 | (null) | (null) | (null) | (null) |
I need to build a query that will aggregate the # of units of blood that has been transfused so it can be compared to the # of units of blood that has been cross-matched. Blood (a precious resource) that is cross-matched, but not transfused is wasted.
Providers are supposed to check the system-of-record (Epic) for 'active' cross-match orders before creating new ones. Providers that don't do this are 'penalized' (provider 20). No penalty applies (it seems) for providers that don't transfuse all of the blood that they've cross-matched (provider 10).
Cross-match orders:
|ENC_ID|PROV_ID|ORDER_ID|ORDER_TIME |UNITS|
| 1| 10| 100|26-JUL-12 13:00| 4|
| 1| 20| 231|26-JUL-12 15:00| 2|
Transfusion orders:
|ENC_ID|PROV_ID|ORDER_ID|ORDER_TIME |UNITS|
| 1| 10| 500|26-JUL-12 13:05| 1|
| 1| 10| 501|26-JUL-12 13:25| 1|
| 1| 20| 501|26-JUL-12 15:00| 1|
| 1| 20| 501|26-JUL-12 15:21| 2|
Rules:
compare transfusions to cross-matches for same encounter (transfusion.END_ID=cross-match.ENC_ID)
transfusion orders are applied to cross-match orders in a FIFO manner
transfusion.ORDER_TIME >= cross-match.ORDER_TIME
a provider may transfuse more than their cross-match order, as long as all of the 'active' cross-match order still have available units (provider 20's second transfusion order)
Desired result:
|ENC_ID|PROV_ID|ORDER_ID|ORDER_TIME |CROSS-MATCHED|TRANSFUSED|
| 1| 10| 100|26-JUL-12 13:00| 4| 4|
| 1| 20| 231|26-JUL-12 15:00| 2| 1|
Provider 10 'credited' with Provider 20's transfusions.
Can this logic be implemented without resorting to a procedure?
You could do it in a single SQL query. Here's an example (tested on 11gR2, should work on 10g):
SETUP:
CREATE TABLE cross_match as (
SELECT 1 ENC_ID, 10 PROV_ID, 100 ORDER_ID,
to_date('2012-07-26 13', 'yyyy-mm-dd hh24') ORDER_TIME, 4 UNITS
FROM DUAL
UNION ALL SELECT 1, 20, 231, to_date('2012-07-26 15', 'yyyy-mm-dd hh24'), 2 FROM DUAL
);
CREATE TABLE transfusion as (
SELECT 1 ENC_ID, 10 PROV_ID, 500 ORDER_ID,
to_date('2012-07-26 13:05', 'yyyy-mm-dd hh24:mi') ORDER_TIME, 1 UNITS
FROM DUAL
UNION ALL SELECT 1, 10, 501, to_date('2012-07-26 13:25', 'yyyy-mm-dd hh24:mi'), 1 FROM DUAL
UNION ALL SELECT 1, 20, 501, to_date('2012-07-26 15:00', 'yyyy-mm-dd hh24:mi'), 1 FROM DUAL
UNION ALL SELECT 1, 20, 501, to_date('2012-07-26 15:21', 'yyyy-mm-dd hh24:mi'), 2 FROM DUAL
);
The following query will build a list of blood units numerically and join each unit from the cross_match table to the corresponding one (if it exists) in the transfusion table:
WITH cross_order as (
SELECT rownum rn FROM DUAL
CONNECT BY level <= (SELECT MAX(units) FROM cross_match)
),
transfusion_order as (
SELECT rownum rn FROM DUAL
CONNECT BY level <= (SELECT MAX(units) FROM transfusion)
)
SELECT c.enc_id, c.prov_id, c.order_id, c.order_time,
count(*) cross_matched,
count(t.enc_id) transfused
FROM (SELECT cm.*,
row_number() over (partition by cm.enc_id
order by cm.order_time) cross_no
FROM cross_match cm
JOIN cross_order co ON cm.units >= co.rn) c
LEFT JOIN (SELECT t.*,
row_number() over (partition by t.enc_id
order by t.order_time) trans_no
FROM transfusion t
JOIN transfusion_order tor ON t.units >= tor.rn) t
ON c.enc_id = t.enc_id
AND c.cross_no = t.trans_no
GROUP BY c.enc_id, c.prov_id, c.order_id, c.order_time;
ENC_ID PROV_ID ORDER_ID ORDER_TIME CROSS_MATCHED TRANSFUSED
-----------------------------------------------------------
1 20 231 07/26/2012 2 1
1 10 100 07/26/2012 4 4
This may be efficient if the maximum number of units remains small, otherwise this 1-to-1 relationship may become cumbersome.
This can be improved by using a running total of units on both sides instead of a basic 1-1. The join condition would be like an interval intersection between begin unit and end unit:
SELECT c.enc_id, c.prov_id, c.order_id, c.order_time,
sum(c.unit_end - nvl(c.unit_start,0))/count(*) cross_matched,
sum(least(c.unit_end, t.unit_end)
-greatest(nvl(c.unit_start, 0), nvl(t.unit_start, 0))) transfused
FROM (SELECT cm.*,
sum(cm.units) over (partition by cm.enc_id
order by cm.order_time
rows between unbounded preceding
and 1 preceding) unit_start,
sum(cm.units) over (partition by cm.enc_id
order by cm.order_time) unit_end
FROM cross_match cm) c
LEFT JOIN (SELECT t.*,
sum(t.units) over (partition by t.enc_id
order by t.order_time
rows between unbounded preceding
and 1 preceding) unit_start,
sum(t.units) over (partition by t.enc_id
order by t.order_time) unit_end
FROM transfusion t) t
ON c.enc_id = t.enc_id
AND c.unit_end > nvl(t.unit_start, 0)
AND t.unit_end > nvl(c.unit_start, 0)
GROUP BY c.enc_id, c.prov_id, c.order_id, c.order_time;
ENC_ID PROV_ID ORDER_ID ORDER_TIME CROSS_MATCHED TRANSFUSED
-----------------------------------------------------------
1 20 231 07/26/2012 2 1
1 10 100 07/26/2012 4 4