Calculate the qty of items that were purchased more than once by a customer - but need to exclude the first order qty - sql

SELECT TOP 100 PERCENT soheader.custid,SOHeader.OrdNbr, SOLine.InvtID, SOLine.Descr,SOLine.QtyOrd
FROM SOHeader INNER JOIN
SOLine ON SOHeader.OrdNbr = SOLine.OrdNbr
WHERE (SOHeader.OrdDate >= CONVERT(DATETIME, '2013-06-01 00:00:00', 102)) AND (SOHeader.OrdDate <= GETDATE()) AND (SOHeader.CustID = '69065')
ORDER BY SOLine.InvtID, SOHeader.OrdNbr
here is my sample data
69065 WO0175279 69407 Jazzy Laces White 3
69065 WO0175393 69407 Jazzy Laces White 6
69065 WO0175393 69407 Jazzy Laces White 9
Now I want to know how to get the total qty of this item ordered after the first order. I do not want to include the qty of 3 in the first record above. I just want to include the qty of 6 in the first reorder and the qty 9 of the second reorder which equals the qty of 15.
69065 is the customer ID
WO##### is the order ID
69407 is the inventory ID

SELECT invtId, SUM(QtyOrd)
FROM (
SELECT invtId,
qtyOrd,
ROW_NUMBER() OVER (PARTITION BY invtId ORDER BY h.ordDate, h.ordNbr) rn
FROM soLine l
JOIN soHeader h
ON h.ordNbr = l.ordNbr
WHERE l.custId = 69065
) q
WHERE rn > 1
GROUP BY
invtId

WITH cl
as
(select *, ROW_NUMBER() OVER (PARTITION BY InvtID ORDER BY QtyOrd) rn
from t)
select InvtID, QtyOrd,
(select SUM(QtyOrd) from cl oo
where o.InvtID = oo.InvtID and o.rn -1 > 0
and rn between o.rn -1 and o.rn) as 'sm'
from cl o
Here's a Demo on SqlFiddle.

Related

Oracle SQL Hierarchy Summation

I have a table TRANS that contains the following records:
TRANS_ID TRANS_DT QTY
1 01-Aug-2020 5
1 01-Aug-2020 1
1 03-Aug-2020 2
2 02-Aug-2020 1
The expected output:
TRANS_ID TRANS_DT BEGBAL TOTAL END_BAL
1 01-Aug-2020 0 6 6
1 02-Aug-2020 6 0 6
1 03-Aug-2020 6 2 8
2 01-Aug-2020 0 0 0
2 02-Aug-2020 0 1 1
2 03-Aug-2020 1 0 1
Each trans_id starts with a beginning balance of 0 (01-Aug-2020). For succeeding days, the beginning balance is the ending balance of the previous day and so on.
I can create PL/SQL block to create the output. Is it possible to get the output in 1 SQL statement?
Thanks.
Try this following script using CTE-
Demo Here
WITH CTE
AS
(
SELECT DISTINCT A.TRANS_ID,B.TRANS_DT
FROM your_table A
CROSS JOIN (SELECT DISTINCT TRANS_DT FROM your_table) B
),
CTE2
AS
(
SELECT C.TRANS_ID,C.TRANS_DT,SUM(D.QTY) QTY
FROM CTE C
LEFT JOIN your_table D
ON C.TRANS_ID = D.TRANS_ID
AND C.TRANS_DT = D.TRANS_DT
GROUP BY C.TRANS_ID,C.TRANS_DT
ORDER BY C.TRANS_ID,C.TRANS_DT
)
SELECT F.TRANS_ID,F.TRANS_DT,
(
SELECT COALESCE (SUM(QTY), 0) FROM CTE2 E
WHERE E.TRANS_ID = F.TRANS_ID AND E.TRANS_DT < F.TRANS_DT
) BEGBAL,
(
SELECT COALESCE (SUM(QTY), 0) FROM CTE2 E
WHERE E.TRANS_ID = F.TRANS_ID AND E.TRANS_DT = F.TRANS_DT
) TOTAL ,
(
SELECT COALESCE (SUM(QTY), 0) FROM CTE2 E
WHERE E.TRANS_ID = F.TRANS_ID AND E.TRANS_DT <= F.TRANS_DT
) END_BAL
FROM CTE2 F
You can as well do like this (I would assume it's a bit faster): Demo
with
dt_between as (
select mindt + level - 1 as trans_dt
from (select min(trans_dt) as mindt, max(trans_dt) as maxdt from t)
connect by level <= maxdt - mindt + 1
),
dt_for_trans_id as (
select *
from dt_between, (select distinct trans_id from t)
),
qty_change as (
select distinct trans_id, trans_dt,
sum(qty) over (partition by trans_id, trans_dt) as total,
sum(qty) over (partition by trans_id order by trans_dt) as end_bal
from t
right outer join dt_for_trans_id using (trans_id, trans_dt)
)
select
trans_id,
to_char(trans_dt, 'DD-Mon-YYYY') as trans_dt,
nvl(lag(end_bal) over (partition by trans_id order by trans_dt), 0) as beg_bal,
nvl(total, 0) as total,
nvl(end_bal, 0) as end_bal
from qty_change q
order by trans_id, trans_dt
dt_between returns all the days between min(trans_dt) and max(trans_dt) in your data.
dt_for_trans_id returns all these days for each trans_id in your data.
qty_change finds difference for each day (which is TOTAL in your example) and cumulative sum over all the days (which is END_BAL in your example).
The main select takes END_BAL from previous day and calls it BEG_BAL, it also does some formatting of final output.
First of all, you need to generate dates, then you need to aggregate your values by TRANS_DT, and then left join your aggregated data to dates. The easiest way to get required sums is to use analitic window functions:
with dates(dt) as ( -- generating dates between min(TRANS_DT) and max(TRANS_DT) from TRANS
select min(trans_dt) from trans
union all
select dt+1 from dates
where dt+1<=(select max(trans_dt) from trans)
)
,trans_agg as ( -- aggregating QTY in TRANS
select TRANS_ID,TRANS_DT,sum(QTY) as QTY
from trans
group by TRANS_ID,TRANS_DT
)
select -- using left join partition by to get data on daily basis for each trans_id:
dt,
trans_id,
nvl(sum(qty) over(partition by trans_id order by dates.dt range between unbounded preceding and 1 preceding),0) as BEGBAL,
nvl(qty,0) as TOTAL,
nvl(sum(qty) over(partition by trans_id order by dates.dt),0) as END_BAL
from dates
left join trans_agg tr
partition by (trans_id)
on tr.trans_dt=dates.dt;
Full example with sample data:
alter session set nls_date_format='dd-mon-yyyy';
with trans(TRANS_ID,TRANS_DT,QTY) as (
select 1,to_date('01-Aug-2020'), 5 from dual union all
select 1,to_date('01-Aug-2020'), 1 from dual union all
select 1,to_date('03-Aug-2020'), 2 from dual union all
select 2,to_date('02-Aug-2020'), 1 from dual
)
,dates(dt) as ( -- generating dates between min(TRANS_DT) and max(TRANS_DT) from TRANS
select min(trans_dt) from trans
union all
select dt+1 from dates
where dt+1<=(select max(trans_dt) from trans)
)
,trans_agg as ( -- aggregating QTY in TRANS
select TRANS_ID,TRANS_DT,sum(QTY) as QTY
from trans
group by TRANS_ID,TRANS_DT
)
select
dt,
trans_id,
nvl(sum(qty) over(partition by trans_id order by dates.dt range between unbounded preceding and 1 preceding),0) as BEGBAL,
nvl(qty,0) as TOTAL,
nvl(sum(qty) over(partition by trans_id order by dates.dt),0) as END_BAL
from dates
left join trans_agg tr
partition by (trans_id)
on tr.trans_dt=dates.dt;
You can use a recursive query to generate the overall date range, cross join it with the list of distinct tran_id, then bring the table with a left join. The last step is aggregation and window functions:
with all_dates (trans_dt, max_dt) as (
select min(trans_dt), max(trans_dt) from trans group by trans_id
union all
select trans_dt + interval '1' day, max_dt from all_dates where trans_dt < max_dt
)
select
i.trans_id,
d.trans_dt,
coalesce(sum(sum(t.qty)) over(partition by i.trans_id order by d.trans_dt), 0) - coalesce(sum(t.qty), 0) begbal,
coalesce(sum(t.qty), 0) total,
coalesce(sum(sum(t.qty)) over(partition by i.trans_id order by d.trans_dt), 0) endbal
from all_dates d
cross join (select distinct trans_id from trans) i
left join trans t on t.trans_id = i.trans_id and t.trans_dt = d.trans_dt
group by i.trans_id, d.trans_dt
order by i.trans_id, d.trans_dt

On same row: last purchase quantity + date, and total quantity in stock (several stock places) - SQL server

I am trying to get the following result in SQL server:
From the purchase order rows, last purchase quantity + date from all item codes in the order rows table and from the warehouse table amount in stock for the item codes I get from the rows table.
Order rows:
ORDER_DATE ITEM_CODE QTY
2019-03-01 A 5
2019-03-02 A 3
2019-03-05 A 4
2019-03-03 B 3
2019-03-04 B 10
Warehouse:
ITEM_CODE INSTOCK STOCKPLACE
A 10 VV
A 3 LP
A 8 XV
B 5 VV
B 15 LP
Wanted result (Latest order date, latest order qty and total in stock):
ORDER_DATE ITEM_CODE QTY INSTOCK
2019-03-05 A 4 21
2019-03-04 B 10 20
I have tried some queries but only failed. I have a steep learning curve ahead of me :) Thanks in advance for all the help!
Here is one method:
select o.*, wh.*
from (select wh.item_code, sum(wh.instock) as instock
from warehouse wh
group by wh.item_code
) wh outer apply
(select top (1) o.*
from orders o
where o.item_code = wh.item_code
order by o.order_date desc
) o;
You can use row_number() with apply :
select t.*, wh.instock
from (select o.*, row_number () over (partition by item_code order by o.order_date desc) as seq
from Order o
) t cross apply
( select sum(wh.instock) as instock
from warehouse wh
where wh.item_code = t.item_code
) wh
where t.seq = 1;
Your Orders aren't identified with a unique ID, and therefore if multiple Orders were to coincide on the same date, you have no way of telling which is the most recent order on that day.
Anyway, assuming that the database you posted is correct and an Order date + Item Code combines to form a unique key, you could use grouping and some CTE to get the desired output as follows.
;WITH MostRecentOrders (ITEM_CODE, ORDER_DATE)
AS (
SELECT
O.ITEM_CODE
, MAX(O.ORDER_DATE) AS ORDER_DATE
FROM
#Order O
GROUP BY ITEM_CODE
)
SELECT
O.ORDER_DATE
, O.ITEM_CODE
, O.QTY
, SUM(WH.INSTOCK) AS INSTOCK
FROM
#Warehouse WH
INNER JOIN #Order O ON O.ITEM_CODE = WH.ITEM_CODE
INNER JOIN MostRecentOrders MRO ON MRO.ITEM_CODE = O.ITEM_CODE
AND MRO.ORDER_DATE = O.ORDER_DATE
GROUP BY
O.ORDER_DATE
, O.ITEM_CODE
, O.QTY
ORDER BY O.ITEM_CODE

SQL Server query with multiple select

I have a query which select all car makes and count each make quantity in response
SELECT
q.Make, Count(q.ID)
FROM
(SELECT
cars.ID, cars.Make, cars.Model,
cars.Year1, cars.Month1, cars.KM, cars.VIN,
cars.Fuel, cars.EngineCap, cars.PowerKW,
cars.GearBox, cars.BodyType, cars.BodyColor,
cars.Doors, cars.FullName, Transport.address,
(DateDiff(second,Getdate(),cars.AuEnd)) as r,
cars.AuEnd, cars.BuyNowPrice, cars.CurrentPrice
FROM
cars
LEFT JOIN
Transport ON Cars.TransportFrom = Transport.ID
WHERE
Active = 'True'
AND AuEnd > GETDATE()
AND year1 >= 1900 AND year1 <= 2015
AND Make in ('AUDi', 'AIXAM', 'ALPINA')
ORDER BY
cars.make ASC, cars.model ASC
OFFSET 50 ROWS FETCH NEXT 50 ROWS ONLY) AS q
GROUP BY
q.make ORDER BY q.make ASC;
Now I need get in result as third field each make total count without offset,
so now I get result
Make CountInResponse
AIXAM 1
ALPINA 1
AUDI 48
But I need to get
Make CountInResponse Total
AIXAM 1 1
ALPINA 1 1
AUDI 48 100
I think I need something like
SELECT
q.Make, Count(q.ID),
(SELECT Make, Count(ID)
FROM cars
WHERE Active = 'True' AND AuEnd > GETDATE()
AND year1 >= 1900 AND year1 <= 2015
AND Make in ('AUDI', 'AIXAM', 'ALPINA')
GROUP BY Make) as q2
FROM
(SELECT
cars.ID, cars.Make, cars.Model,
cars.Year1, cars.Month1, cars.KM, cars.VIN,
cars.Fuel, cars.EngineCap, cars.PowerKW,
cars.GearBox, cars.BodyType, cars.BodyColor,
cars.Doors, cars.FullName, Transport.address,
(DateDiff(second,Getdate(),cars.AuEnd)) as r,
cars.AuEnd, cars.BuyNowPrice, cars.CurrentPrice
FROM
cars
LEFT JOIN
Transport ON Cars.TransportFrom = Transport.ID
WHERE
Active = 'True'
AND AuEnd > GETDATE()
AND year1 >= 1900 AND year1 <= 2015
AND Make in ('AUDi', 'AIXAM', 'ALPINA')
ORDER BY
cars.make ASC, cars.model ASC
OFFSET 50 ROWS FETCH NEXT 50 ROWS ONLY) AS q
But I get an error
Msg 116, Level 16, State 1, Line 10
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
How to write right syntax?
The problem is you are selecting two columns in q2 (ie) Make, Count(ID) you cannot do that in SQL Server.
Try something like this.
WITH cte AS
(
SELECT
row_number() OVER(order by cars.make ASC,cars.model ASC) AS rn,
cars.id, cars.make
FROM
cars
LEFT JOIN
transport ON cars.transportfrom = transport.id
WHERE
active = 'True'
AND auend > getdate()
AND year1 >= 1900 AND year1 <= 2015
AND make IN ('AUDI', 'AIXAM', 'ALPINA')
)
SELECT
make ,
count(CASE WHEN RN BETWEEN 50 AND 100 THEN 1 END) AS countinresponse,
count(1) AS total
FROM
cte
GROUP BY
make
Or you need to convert the sub-query in select to correlated sub-query
SELECT q.make,
Count(q.id) countinresponse,
(
SELECT Count(id)
FROM cars C1
WHERE c1.id = q.id
AND active='True'
AND auend > Getdate()
AND year1 >= 1900
AND year1 <= 2015
AND make IN ('AUDi',
'AIXAM',
'ALPINA')
GROUP BY make) AS total
FROM (
SELECT cars.id,
cars.make
FROM cars
LEFT JOIN transport
ON cars.transportfrom=transport.id
WHERE active='True'
AND auend > Getdate()
AND year1 >= 1900
AND year1 <= 2015
AND make IN ('AUDi',
'AIXAM',
'ALPINA')
ORDER BY cars.make ASC,
cars.model ASC offset 50 rowsfetch next 50 rows only ) AS q
GROUP BY q.make
ORDER BY q.make ASC;

SQL Deduct value from multiple rows

I would like to apply total $10.00 discount for each customers.The discount should be applied to multiple transactions until all $10.00 used.
Example:
CustomerID Transaction Amount Discount TransactionID
1 $8.00 $8.00 1
1 $6.00 $2.00 2
1 $5.00 $0.00 3
1 $1.00 $0.00 4
2 $5.00 $5.00 5
2 $2.00 $2.00 6
2 $2.00 $2.00 7
3 $45.00 $10.00 8
3 $6.00 $0.00 9
The query below keeps track of the running sum and calculates the discount depending on whether the running sum is greater than or less than the discount amount.
select
customerid, transaction_amount, transactionid,
(case when 10 > (sum_amount - transaction_amount)
then (case when transaction_amount >= 10 - (sum_amount - transaction_amount)
then 10 - (sum_amount - transaction_amount)
else transaction_amount end)
else 0 end) discount
from (
select customerid, transaction_amount, transactionid,
sum(transaction_amount) over (partition by customerid order by transactionid) sum_amount
from Table1
) t1 order by customerid, transactionid
http://sqlfiddle.com/#!6/552c2/7
same query with a self join which should work on most db's including mssql 2008
select
customerid, transaction_amount, transactionid,
(case when 10 > (sum_amount - transaction_amount)
then (case when transaction_amount >= 10 - (sum_amount - transaction_amount)
then 10 - (sum_amount - transaction_amount)
else transaction_amount end)
else 0 end) discount
from (
select t1.customerid, t1.transaction_amount, t1.transactionid,
sum(t2.transaction_amount) sum_amount
from Table1 t1
join Table1 t2 on t1.customerid = t2.customerid
and t1.transactionid >= t2.transactionid
group by t1.customerid, t1.transaction_amount, t1.transactionid
) t1 order by customerid, transactionid
http://sqlfiddle.com/#!3/552c2/2
You can do this with recursive common table expressions, although it isn't particularly pretty. SQL Server stuggles to optimize these types of query. See Sum of minutes between multiple date ranges for some discussion.
If you wanted to go further with this approach, you'd probably need to make a temporary table of x, so you can index it on (customerid, rn)
;with x as (
select
tx.*,
row_number() over (
partition by customerid
order by transaction_amount desc, transactionid
) rn
from
tx
), y as (
select
x.transactionid,
x.customerid,
x.transaction_amount,
case
when 10 >= x.transaction_amount then x.transaction_amount
else 10
end as discount,
case
when 10 >= x.transaction_amount then 10 - x.transaction_amount
else 0
end as remainder,
x.rn as rn
from
x
where
rn = 1
union all
select
x.transactionid,
x.customerid,
x.transaction_amount,
case
when y.remainder >= x.transaction_amount then x.transaction_amount
else y.remainder
end,
case
when y.remainder >= x.transaction_amount then y.remainder - x.transaction_amount
else 0
end,
x.rn
from
y
inner join
x
on y.rn = x.rn - 1 and y.customerid = x.customerid
where
y.remainder > 0
)
update
tx
set
discount = y.discount
from
tx
inner join
y
on tx.transactionid = y.transactionid;
Example SQLFiddle
I usually like to setup a test environment for such questions. I will use a local temporary table. Please note, I made the data un-ordered since it is not guaranteed in a real life.
-- play table
if exists (select 1 from tempdb.sys.tables where name like '%transactions%')
drop table #transactions
go
-- play table
create table #transactions
(
trans_id int identity(1,1) primary key,
customer_id int,
trans_amt smallmoney
)
go
-- add data
insert into #transactions
values
(1,$8.00),
(2,$5.00),
(3,$45.00),
(1,$6.00),
(2,$2.00),
(1,$5.00),
(2,$2.00),
(1,$1.00),
(3,$6.00);
go
I am going to give you two answers.
First, in 2014 there are new windows functions for rows preceding. This allows us to get a running total (rt) and a rt adjusted by one entry. Give these two values, we can determine if the maximum discount has been exceeded or not.
-- Two running totals for 2014
;
with cte_running_total
as
(
select
*,
SUM(trans_amt)
OVER (PARTITION BY customer_id
ORDER BY trans_id
ROWS BETWEEN UNBOUNDED PRECEDING AND
0 PRECEDING) as running_tot_p0,
SUM(trans_amt)
OVER (PARTITION BY customer_id
ORDER BY trans_id
ROWS BETWEEN UNBOUNDED PRECEDING AND
1 PRECEDING) as running_tot_p1
from
#transactions
)
select
*
,
case
when coalesce(running_tot_p1, 0) <= 10 and running_tot_p0 <= 10 then
trans_amt
when coalesce(running_tot_p1, 0) <= 10 and running_tot_p0 > 10 then
10 - coalesce(running_tot_p1, 0)
else 0
end as discount_amt
from cte_running_total;
Again, the above version is using a common table expression and advanced windowing to get the totals.
Do not fret! The same can be done all the way down to SQL 2000.
Second solution, I am just going to use the order by, sub-queries, and a temporary table to store the information that is normally in the CTE. You can switch the temporary table for a CTE in SQL 2008 if you want.
-- w/o any fancy functions - save to temp table
select *,
(
select count(*) from #transactions i
where i.customer_id = o.customer_id
and i.trans_id <= o.trans_id
) as sys_rn,
(
select sum(trans_amt) from #transactions i
where i.customer_id = o.customer_id
and i.trans_id <= o.trans_id
) as sys_tot_p0,
(
select sum(trans_amt) from #transactions i
where i.customer_id = o.customer_id
and i.trans_id < o.trans_id
) as sys_tot_p1
into #results
from #transactions o
order by customer_id, trans_id
go
-- report off temp table
select
trans_id,
customer_id,
trans_amt,
case
when coalesce(sys_tot_p1, 0) <= 10 and sys_tot_p0 <= 10 then
trans_amt
when coalesce(sys_tot_p1, 0) <= 10 and sys_tot_p0 > 10 then
10 - coalesce(sys_tot_p1, 0)
else 0
end as discount_amt
from #results
order by customer_id, trans_id
go
In short, your answer is show in the following screen shot. Cut and paste the code into SSMS and have some fun.

Summing and then getting min and max of top 70% in SQL

I have data for the purchases of a product formatted like this:
Item | Price | Quantity Bought
ABC 10.10 4
DEF 8.30 12
DEF 7.75 8
ABC 10.50 20
GHI 15.4 1
GHI 15.2 12
ABC 10.25 8
... ... ...
Where each row represents an individual purchasing a certain amount at a certain price. I would like to aggregate this data and eliminate the prices below the 30th percentile for total quantity bought from my table.
For example, in the above data set the total amount of product ABC bought was (4+20+8) = 32 units, with average price = (4*10.10 + 8*10.25 + 20*10.50)/32 = 10.39.
I would like to organize the above data set like this:
Item | VWP | Total Vol | 70th %ile min | 70th %ile max
ABC 10.39 32 ??? ???
DEF ... 20 ??? ???
GHI ... 13 ??? ???
Where VWP is the volume weighted price, and the 70th %ile min/max represent the minimum and maximum prices within the top 70% of volume.
In other words, I want to eliminate the prices with the lowest volumes until I have 70% of the total volume for the day contained in the remaining prices. I would then like to publish the min and max price for the ones that are left in the 70th %ile min/max columns.
I tried to be as clear as possible, but if this is tough to follow along with please let me know which parts need clarification.
Note: These are not the only columns contained in my dataset, and I will be selecting and calculating other values as well. I only included the columns that are relevant to this specific calculation.
EDIT:
Here is my code so far, and I need to incorporate my calculation into this (the variables with the '#' symbol before them are inputs that are given by the user:
SELECT Item,
SUM(quantity) AS Total_Vol,
DATEADD(day, -#DateOffset, CONVERT(date, GETDATE())) AS buyDate,
MIN(Price) AS MinPrice,
MAX(Price) AS MaxPrice,
MAX(Price) - MIN(Price) AS PriceRange,
ROUND(SUM(Price * quantity)/SUM(quantity), 6) AS VWP,
FROM TransactTracker..CustData
-- #DateOffset (Number of days data is offset by)
-- #StartTime (Time to start data in hours)
-- #EndTime (Time to stop data in hours)
WHERE DATEDIFF(day, TradeDateTime, GETDATE()) = (#DateOffset+1)
AND DATEPART(hh, TradeDateTime) >= #StartTime
AND HitTake = ''
OR DATEDIFF(day, TradeDateTime, GETDATE()) = #DateOffset
AND DATEPART(hh, TradeDateTime) < #EndTime
AND HitTake = ''
GROUP BY Item
EDIT 2:
FROM (SELECT p.*,
(SELECT SUM(quantity) from TransactTracker..CustData p2
where p2.Series = p.Series and p2.Size >= p.Size) as volCum
FROM TransactTracker..CustData p
) p
EDIT 3:
(case when CAST(qcum AS FLOAT) / SUM(quantity) <= 0.7 THEN MIN(Price) END) AS min70px,
(case when CAST(qcum AS FLOAT) / SUM(quantity) <= 0.7 THEN MAX(Price) END) AS max70px
FROM (select p.*,
(select SUM(quantity) from TransactTracker..CustData p2
where p2.Item = p.Item and p2.quantity >= p.quantity)
as qcum from TransactTracker..CustData p) cd
There is some ambiguity on how you define 70 % when something goes over the threshold. However, the challenge is two fold. After identifying the cumulative proportion, the query also needs to choose the appropriate row. This suggests using row_number() for selection.
This solution using SQL Server 2012 syntax calculates the cumulative sum. It then takes assigns a sequential value based on how close the ratio is to 70%.
select item,
SUM(price * quantity) / SUM(quantity) as vwp,
SUM(quantity) as total_vol,
min(case when seqnum = 1 then price end) as min70price,
max(case when seqnum = 1 then price end) as max70price
from (select p.*,
ROW_NUMBER() over (partition by item order by abs(0.7 - qcum/qtot) as seqnum
from (select p.*,
SUM(quantity) over (partition by item order by vol desc) as qcum,
SUM(quantity) over (partition by item) as qtot
from purchases p
) p
) p
group by item;
To get the largest value less than 70%, then you would use:
max(case when qcum < qtot*0.7 then qcum end) over (partition by item) as lastqcum
And then the case statements in the outer select would be:
min(case when lastqcum = qcum then price end) . .
In earlier versions of SQL Server, you can get the same effect with the correlated subquery:
select item,
SUM(price * quantity) / SUM(quantity) as vwp,
SUM(quantity) as total_vol,
min(case when seqnum = 1 then price end) as min70price,
max(case when seqnum = 1 then price end) as max70price
from (select p.*,
ROW_NUMBER() over (partition by item order by abs(0.7 - qcum/qtot) as seqnum
from (select p.*,
(select SUM(quantity) from purchases p2 where p2.item = p.item and p2.quantity >= p.quantity
) as qsum,
SUM(quantity) over (partition by item) as qtot
from purchases p
) p
) p
group by item
Here is the example with your code:
SELECT Item,
SUM(quantity) AS Total_Vol,
DATEADD(day, -#DateOffset, CONVERT(date, GETDATE())) AS buyDate,
MIN(Price) AS MinPrice,
MAX(Price) AS MaxPrice,
MAX(Price) - MIN(Price) AS PriceRange,
ROUND(SUM(Price * quantity)/SUM(quantity), 6) AS VWP,
min(case when seqnum = 1 then price end) as min70price,
max(case when seqnum = 1 then price end) as max70price
from (select p.*,
ROW_NUMBER() over (partition by item order by abs(0.7 - qcum/qtot) as seqnum
from (select p.*,
(select SUM(quantity) from TransactTracker..CustData p2 where p2.item = p.item and p2.quantity >= p.quantity
) as qsum,
SUM(quantity) over (partition by item) as qtot
from purchases TransactTracker..CustData
) p
) cd
-- #DateOffset (Number of days data is offset by)
-- #StartTime (Time to start data in hours)
-- #EndTime (Time to stop data in hours)
WHERE DATEDIFF(day, TradeDateTime, GETDATE()) = (#DateOffset+1)
AND DATEPART(hh, TradeDateTime) >= #StartTime
AND HitTake = ''
OR DATEDIFF(day, TradeDateTime, GETDATE()) = #DateOffset
AND DATEPART(hh, TradeDateTime) < #EndTime
AND HitTake = ''
GROUP BY Item