Select the matching/sum up total in SQL Server - sql

I want to get all the id's that matches/sum up my total qty.
Example if my total qty is 40 then my query will stops until it sums up all the qty at exactly or greater than 40.
See screenshots

If you are using sql server 2012 and above, you can use this script.
;WITH CTE AS (
SELECT PK_TRXNO, FK_iwItems, qty,
total = SUM(qty) OVER( PARTITION BY FK_iwItems ORDER BY PK_TRXNO DESC ROWS UNBOUNDED PRECEDING )
FROM #MyTable
)
, CTE2 AS (
SELECT *,
RN = ROW_NUMBER() OVER(PARTITION BY (CASE WHEN total > 40 THEN 1 ELSE 0 END) ORDER BY PK_TRXNO DESC)
FROM CTE
)
SELECT * FROM CTE2
WHERE total <= 40 OR ( total> 40 AND RN = 1)

Related

Get Earliest Date corresponding to the latest occurrence of a recurring name

I have a table with Name and Date columns. I want to get the earliest date when the current name appeared. For example:
Name
Date
X
30-Jan-2021
X
29-Jan-2021
X
28-Jan-2021
Y
27-Jan-2021
Y
26-Jan-2021
Y
25-Jan-2021
Y
24-Jan-2021
X
23-Jan-2021
X
22-Jan-2021
Now when I try to get the earliest date when current name (X) started to appear, I want 28-Jan, but the sql query would give 22-Jan-2021 because that's when X appeared originally for the first time.
Update: This was the query I was using:
Select min(Date) from myTable where Name='X'
I am using older SQL Server 2008 (in the process of upgrading), so do not have access to LEAD/LAG functions.
The solutions suggested below do work as intended. Thanks.
This is a type of gaps-and-islands problem.
There are many solutions. Here is one that is optimized for your case
Use LEAD/LAG to identify the first row in each grouping
Filter to only those rows
Number them rows and take the first one
WITH StartPoints AS (
SELECT *,
IsStart = CASE WHEN Name <> LEAD(Name, 1, '') OVER (ORDER BY Date DESC) THEN 1 END
FROM YourTable
),
Numbered AS (
SELECT *,
rn = ROW_NUMBER() OVER (PARTITION BY Name ORDER BY Date DESC)
FROM StartPoints
WHERE IsStart = 1 AND Name = 'X'
)
SELECT
Name, Date
FROM Numbered
WHERE rn = 1;
db<>fiddle
For SQL Server 2008 or earlier (which I strongly suggest you upgrade from), you can use a self-join with row-numbering to simulate LEAD/LAG
WITH RowNumbered AS (
SELECT *,
AllRn = ROW_NUMBER() OVER (ORDER BY Date ASC)
FROM YourTable
),
StartPoints AS (
SELECT r1.*,
IsStart = CASE WHEN r1.Name <> ISNULL(r2.Name, '') THEN 1 END
FROM RowNumbered r1
LEFT JOIN RowNumbered r2 ON r2.AllRn = r1.AllRn - 1
),
Numbered AS (
SELECT *,
rn = ROW_NUMBER() OVER (PARTITION BY Name ORDER BY Date DESC)
FROM StartPoints
WHERE IsStart = 1
)
SELECT
Name, Date
FROM Numbered
WHERE rn = 1;
This is a gaps and island problem. Based on the sample data, this will work:
WITH Groups AS(
SELECT YT.[Name],
YT.[Date],
ROW_NUMBER() OVER (ORDER BY YT.Date DESC) -
ROW_NUMBER() OVER (PARTITION BY YT.[Name] ORDER BY Date DESC) AS Grp
FROM dbo.YourTable YT),
FirstGroup AS(
SELECT TOP (1) WITH TIES
G.[Name],
G.[Date]
FROM Groups G
WHERE [Name] = 'X'
ORDER BY Grp ASC)
SELECT MIN(FG.[Date]) AS Mi
db<>fiddle
If i did understand, you want to know when the X disappeared and reappeared again. in that case you can search for gaps in dates by group.
this and example how to detect that
SELECT name
,DATE
FROM (
SELECT *
,DATEDIFF(day, lead(DATE) OVER (
PARTITION BY name ORDER BY DATE DESC
), DATE) DIF
FROM YourTable
) a
WHERE DIF > 1

How to query the V shaped data?

TxnID RunningAmount MemberID
==================================
1 80000 20
2 90000 20
3 70000 20 //<==== Falls but previously never below 100k, hence ignore
4 90000 20
5 110000 20
6 60000 20 //<==== Falls below 100k, hence we want ID 8
7 80000 20
8 120000 20
9 85000 28
...
....
How to construct the query such that it group by members, get the first transactionID that formed the "V" shape. Even a pseudocode is fine, I can't share my attempt because I am totally clueless about how to do it.
UPDATES:
Sorry for the lack of explanations on the conditions. The base amount we looking is 100k. ID is random, definitely we need to have rownumber
We ignore all transactions before ID = 5 because their runningAmount is never exceeded 100k.
Now when ID=5, exceeded 100k, we check if transactions after ID=5 if there is a down trend in runningAmount that falls below 100k.
Immediately we see ID=6 falls below 100k, so we want to find the first transaction that exceed 100k again(if there is).
From the data sample above, the expected result is only one record, which is ID=8.
For every member, there will only be either one or zero record found based on the conditions I've mentioned
Try this query:
declare #tbl table(TxnID int, RunningAmount int, MemberID int);
insert into #tbl values
(1, 80000, 20),
(2, 90000, 20),
(3, 70000, 20),
(4, 90000, 20),
(5, 110000, 20),
(6, 60000, 20),
(7, 120000, 20),
(8, 85000, 28);
select TxnID, RunningAmount, MemberID,
LAG(VShape) over (partition by MemberID order by TxnID) VShape
from (
select TxnID, RunningAmount, MemberID,
case when rn < lagrn and rn < leadrn then 1 else 0 end VShape
from (
select *,
LAG(rn) over (partition by MemberID order by TxnID) lagRn,
LEAD(rn) over (partition by MemberID order by TxnID) leadRn
from (
select TxnID,
RunningAmount,
MemberID,
ROW_NUMBER() over (partition by MemberID order by RunningAmount) rn
from #tbl
) a
) a
) a
Last column VShape indicates if value in RunningAmount completes V shape (although you could be more clearer on what it means instead of everybody figuring it out). Now you can filter values based on RunningAmount (wheter they fall below or above 100k).
Here is version for earlier versions of SQL Server that don't have LAG and LEAD functions:
;with cte as (
select *,
ROW_NUMBER() over (partition by MemberID order by RunningAmount) rn
from #tbl
), cte2 as (
select c1.TxnID, c1.RunningAmount, c1.MemberID, c1.rn, c2.rn [lagRn] , c3.rn [leadRn]
from cte c1
left join cte c2 on c1.TxnID = c2.TxnID + 1 and c1.MemberID = c2.MemberID
left join cte c3 on c1.TxnID = c3.TxnID - 1 and c1.MemberID = c3.MemberID
), cte3 as (
select TxnID, RunningAmount, MemberID,
case when rn < lagrn and rn < leadrn then 1 else 0 end VShape
from cte2
), FinalResult as (
select c1.TxnID, c1.RunningAmount, c1.MemberID, c2.VShape
from cte3 c1
left join cte3 c2 on c1.TxnID = c2.TxnID + 1 and c1.MemberID = c2.MemberID
)
select fr.*, fr2.RunningAmount RunningAmountLagBy2 from FinalResult fr
left join FinalResult fr2 on fr.TxnID = fr2.TxnID + 2
where fr.RunningAmount > 100000 and fr2.RunningAmount > 100000 and fr.VShape = 1
UPDATE
After question update, here's solution:
select TxnID from (
select *, ROW_NUMBER() over (partition by VShape order by TxnID) CompletesVShape from (
select TxnID,
RunningAmount,
MemberID,
sum(case when RunningAmount >= 100000 then 1 else 0 end) over (partition by MemberID order by TxnID rows between unbounded preceding and current row) VShape
from #tbl
) a
) a where VShape > 1 and CompletesVShape = 1
Based on your question update and assuming for V shape necessary condition is to get above and below running amounts > 100000 and middle be smaller than above and below running amounts, below is a query showing how to do it in 2008 sql server.
also see live demo
; with firstlargeamount as
(
select MemberId, minTrxid=min(TxnID)
from t
where RunningAmount>100000
group by MemberId
)
,tbl as
(
select *,
rn=row_number() over( partition by MemberId order by TxnId)
from
t
)
select t3.*,f.*
from tbl t1
join tbl t2
on
t1.memberId=t2.memberid and t1.rn=t2.rn +1
and t1.RunningAmount<t2.RunningAmount
join tbl t3
on
t1.memberId=t3.memberid and t1.rn=t3.rn -1
and t1.RunningAmount<t3.RunningAmount
join firstlargeamount f
on
f.Memberid=t2.memberid and f.minTrxid>=t1.TxnID
Explanation:
First step is to generate a row number sequence at member level as cte tbl and min limiting transaction in cte firstlargeamount
Second step is double self join to find above and below records per row which satisfy the V shape criteria as well join with firstlargeamount to find rows which satisfy the 100000 criteria
Note that the above and below records are simply found using +1/-1 from the current records's row number computed in the step 1

MSSQL - OVER, ROW_NUMBER() and ORDER BY error

I'm trying to make a query that outputs a list with the company data and the number of Products and Discounts of each Company and order by product_count.
Also i need to limit the output to groups of 30 rows
SELECT * FROM (
SELECT *, (
SELECT COUNT(*) FROM Products WHERE Product_Comp_id = Comp_id
) as product_count, (
SELECT COUNT(*) FROM Discount WHERE Disc_Comp_id = Comp_id
) as discount_count , ROW_NUMBER() OVER (
ORDER BY product_count ASC
) AS RowNum FROM Company
) AS finalTable WHERE finalTable.RowNum BETWEEN 0 AND 30
But i get this error
Invalid column name 'product_count'.
Table Structure
Products
|-Product_id
|-Product_Name
|-Product_Description
|-Product_Comp_id
Discount
|-Disc_id
|-Disc_Name
|-Disc_Comp_id
|-Disc_Ammount
Company
|-Comp_id
|-Comp_Name
|-Comp_Address
You need an additional level of subquery to give you product_count.
SELECT * FROM (
SELECT * , ROW_NUMBER() OVER (ORDER BY product_count ASC) AS RowNum
FROM
(
SELECT *, (SELECT COUNT(*) FROM Products WHERE Product_Comp_id = Comp_id) as product_count,
(SELECT COUNT(*) FROM Discount WHERE Disc_Comp_id = Comp_id) as discount_count
FROM Company
) C
) AS finalTable WHERE finalTable.RowNum BETWEEN 0 AND 30

Sorting top ten vendors and showing remained vendors as "other"

Please consider a table of vendors having two columns: VendorName and PayableAmount
I'm looking for a query which returns top ten vendors sorted by PayableAmount descending and sum of other payable amounts as "other" in 11th row.
Obviously, sum of PayableAmount from Vendors table should be equal to sum of PayableAmount from Query.
Technically, it's possible to do in one query:
declare #t table (
Name varchar(50) primary key,
Amount money not null
);
-- Dummy data
insert into #t (Name, Amount)
select top (20) sq.*
from (
select name, max(number) as [Amount]
from master.dbo.spt_values
where number between 100 and 100000
and name is not null
group by name
) sq
order by newid();
-- The table itself, for verification
select * from #t order by Amount desc;
-- Actual query
select top (11)
case when sq.RN > 10 then '<All others>' else sq.Name end as [VendorName],
case
when sq.RN > 10 then sum(sq.Amount) over(partition by case when sq.rn > 10 then 1 else 0 end)
else sq.Amount
end as [Value]
from (
select t.Name, t.Amount, row_number() over(order by t.Amount desc) as [RN]
from #t t
) sq
order by sq.RN;
It will even work on any SQL Server version starting with 2005. But, in real life, I would prefer to calculate these 2 parts separately and then UNION them.
This would perform the query you're looking for. Firstly extracting those in the top 10, then UNION ing that result with the higher ranked vendors, but calling those 'Other'
WITH rank AS (SELECT
VendorName,
PayableAmount,
ROW_NUMBER() OVER (ORDER BY PayableAmount DESC) AS rn
FROM vendors)
SELECT VendorName,
rn,
PayableAmount
FROM
rank WHERE rn <= 10
UNION
SELECT VendorName, 11 AS rn, PayableAmount
FROM
(
SELECT 'Other' AS VendorName,
SUM(PayableAmount) AS PayableAmount
FROM
rank WHERE rn > 10
) X11
ORDER BY rn
This has been tested in SQLFiddle.
this is for the 11th row
i didnt check it
declare #i int
set #i=
(select sum(x.PayableAmount)
from
(select * from table
except
select top 10 *from table
order by PayableAmount desc) as x)
select 'another',#i

Grab top 5 rows and combine the rest SQL Server

I have a column for group name and a column for amount spent.
I need to sum the amounts group them based on the group name and then grab the highest five. After that, I need to combine the the rest into it's own group w/ a total of their amount spent. This is what i have right now
SELECT groupName, SUM(amount) AS theAmountSpent
FROM purchases
GROUP BY groupName
ORDER BY theAmountSpent DESC
This groups and orders them, but i dont know how to then grab the remaining groups to combine them. Any help would be appreciated.
Alternate CTE-approach using row_number() (SQL Server 2005+):
WITH cte AS (
SELECT ROW_NUMBER() OVER (ORDER BY (SUM(amount)) DESC) AS num,
groupName, SUM(amount) AS theAmountSpent
FROM purchases
GROUP BY groupName
)
SELECT groupName, theAmountSpent FROM cte WHERE num BETWEEN 1 AND 5 --top 5
UNION ALL
SELECT 'Sum rest', SUM(theAmountSpent) FROM cte WHERE num > 5 -- sum of rest
If I'm understanding you correctly, this should do it:
SELECT top 5 groupName, SUM(amount) AS theAmountSpent
into #tempSpent FROM purchases
GROUP BY groupName
ORDER BY theAmountSpent DESC
Select * from #tempSpent -- get the top 5
--get sum for the rest
SELECT SUM(amount) AS theAmountSpent
FROM purchases
where groupName not in (select groupName from #tempSpent)
Drop table #tempSpent
Another idea from Larsts code:
WITH cte
AS
(
SELECT case
when ROW_NUMBER() OVER (ORDER BY (SUM(amount)) DESC) <=5
then ROW_NUMBER() OVER (ORDER BY (SUM(amount)) DESC)
else 6 end AS num
, groupName
, SUM(amount) AS theAmountSpent
FROM purchases
GROUP BY groupName
)
SELECT num
, max(groupName)
, sum(theAmountSpent )
FROM cte
group by num