Some background story: Company A gives out vouchers to winners of a challenge. The SQL that I am currently writing needs to decide the required voucher denomination that sums to the value awarded to a person. I have a table that stores the denominations available for vouchers, depending on the country and currency.
In the example below, a particular person is awarded with €80 worth of vouchers.
The query below displays results of a lookup table for voucher denominations available for a particular country.
SELECT * FROM tblDenominationScheme WHERE CountryCode IN ('AT', 'US')
Result:
No. | CountryCode | VoucherName | VoucherValue
-------------------------------------------------
1 | AT | €50 Shop A | 50
2 | AT | €25 Shop A | 25
3 | AT | €15 Shop A | 15
4 | AT | €10 Shop A | 10
5 | US | $50 Store B | 50
6 | US | $10 Store B | 10
7 | US | $5 Store B | 5
My current SQL is as below to determine the required voucher denominations for €80 voucher:
DECLARE #CountryCode1 VARCHAR(2) = 'AT'
DECLARE #ChallengerID INT = 1172
DECLARE #RoundedAmount1 INT = 80
DECLARE #Vouchers INT
DECLARE #AmountAwarded INT = 0
SET #AmountAwarded = #RoundedAmount1
DROP TABLE IF EXISTS #tempVoucher
CREATE TABLE #tempVoucher
(
CountryCode VARCHAR(2),
ChallengerID INT,
AmountAwarded INT,
Vouchers INT,
)
WHILE (#RoundedAmount1 > 0)
BEGIN
SET #Vouchers = 0
SELECT TOP 1 #Vouchers = VoucherValue FROM tblDenominationScheme WHERE CountryCode = #CountryCode1 AND VoucherValue <= #RoundedAmount1 ORDER BY VoucherValue DESC
IF (#Vouchers > 0)
BEGIN
SET #RoundedAmount1 = #RoundedAmount1 - #Vouchers
END
ELSE
BEGIN
SELECT TOP 1 #Vouchers = VoucherValue FROM tblDenominationScheme WHERE CountryCode = #CountryCode1 ORDER BY VoucherValue
SET #RoundedAmount1 = #RoundedAmount1 - #RoundedAmount1
END
INSERT INTO #tempVoucher VALUES (#CountryCode1,#ChallengerID, #AmountAwarded, #Vouchers)
END
SELECT * FROM #tempVoucher
Result from the SQL above:
No. | CountryCode | ChallengerID | AmountAwarded | Vouchers
--------------------------------------------------------------
1 | AT | 1172 | 80 | 50
2 | AT | 1172 | 80 | 25
3 | AT | 1172 | 80 | 10
NOTE: The value in AmountAwarded column will be the same for all 3 rows. The amount in the Vouchers column for the 3 rows should sum up to 80.
The result above is obviously incorrect, because if you sum up the values in the Vouchers column, it gives you 85, which is 5 more than the AmountAwarded
Expected result (or at least closest):
No. | CountryCode | ChallengerID | AmountAwarded | Vouchers
--------------------------------------------------------------
1 | AT | 1172 | 80 | 50
2 | AT | 1172 | 80 | 10
3 | AT | 1172 | 80 | 10
4 | AT | 1172 | 80 | 10
Anyone able to help?
This might be an expensive query, but gets you different options to deliver up to 7 vouchers to get you the expected result. This, however, will generate a huge amount of reads if the rows increase or the amount of vouchers can be greater.
DECLARE #CountryCode1 VARCHAR(2) = 'AT'
DECLARE #RoundedAmount1 INT = 80;
WITH cteDenominations AS(
SELECT No, VoucherValue
FROM tblDenominationScheme
WHERE CountryCode = #CountryCode1
UNION ALL
SELECT 10000, 0
),
ctePermutations AS(
SELECT a.No AS a_No,
a.VoucherValue AS a_Value,
b.No AS b_No,
b.VoucherValue AS b_Value,
c.No AS c_No,
c.VoucherValue AS c_Value,
d.No AS d_No,
d.VoucherValue AS d_Value,
e.No AS e_No,
e.VoucherValue AS e_Value,
f.No AS f_No,
f.VoucherValue AS f_Value,
g.No AS g_No,
g.VoucherValue AS g_Value,
ROW_NUMBER() OVER(ORDER BY a.No, b.No, c.No, d.No) Permutation
FROM cteDenominations a
JOIN cteDenominations b ON a.VoucherValue >= b.VoucherValue
JOIN cteDenominations c ON b.VoucherValue >= c.VoucherValue
JOIN cteDenominations d ON c.VoucherValue >= d.VoucherValue
JOIN cteDenominations e ON d.VoucherValue >= e.VoucherValue
JOIN cteDenominations f ON e.VoucherValue >= f.VoucherValue
JOIN cteDenominations g ON f.VoucherValue >= g.VoucherValue
WHERE #RoundedAmount1 = a.VoucherValue
+ b.VoucherValue
+ c.VoucherValue
+ d.VoucherValue
+ e.VoucherValue
+ f.VoucherValue
+ g.VoucherValue
)
SELECT Permutation,
u.No,
u.VoucherValue
FROM ctePermutations
CROSS APPLY (VALUES(a_No, a_Value),
(b_No, b_Value),
(c_No, c_Value),
(d_No, d_Value),
(e_No, e_Value),
(f_No, f_Value),
(g_No, g_Value))u(No, VoucherValue)
WHERE VoucherValue > 0
AND Permutation = 1 --Remove this to get all possibilities
;
Looks like you need to solve a equation:
80 = n1*v1 + k2*n2...
where v1,v2 ... are values which you store in database
And you need to find n1, n2 ... , which are in {0, N}
There is no way how to implement it in SQL. Except - over all possible values, but it's not the smarter way.
Also, see this info:
https://math.stackexchange.com/questions/431367/solving-a-first-order-diophantine-equation-with-many-terms
Logic
Find the largest amount (that is less than or equal to starting amount) vouchers of 1 denomination can make.
Subtract this value from starting amount to get remainder,
Find the largest amount (that is less than or equal to remainder) a number of vouchers of 1 smaller denomination can make.
Subtract this value from previous remainder.
Go back to step 3
Features:
Handles multiple best combinations.
Small number of combinations are searched.
On my laptop: 100 runs take about 3 seconds
Notes
Performance may be improved by saving output of VoucherCombinations to a table variable and then using it in subsequent CTEs.
Code:
DECLARE #Vouchers TABLE( CountryCode CHAR( 2 ), VoucherValue DECIMAL( 10, 2 ))
INSERT INTO #Vouchers VALUES( 'AT', 50 ), ( 'AT', 40 ), ( 'AT', 25 ), ( 'AT', 20 ), ( 'AT', 15 ), ( 'AT', 10 ), ( 'US', 50 ), ( 'US', 10 ), ( 'US', 5 );
-- Small number table
-- Limits maximum count of Vouchers of a given denomination.
DECLARE #Numbers TABLE( Num INT )
INSERT INTO #Numbers VALUES( 1 ), ( 2 ), ( 3 ), ( 4 ), ( 5 ), ( 6 ), ( 7 ), ( 8 ), ( 9 ), ( 10 )
DECLARE #TargetAmount DECIMAL( 10, 2 ) = 60;
DECLARE #CountryCode CHAR( 2 ) = 'AT';
;WITH VoucherCombinations
AS (
-- Anchor
SELECT ROW_NUMBER() OVER( ORDER BY VoucherValue DESC ) AS ParentGroupID,
ROW_NUMBER() OVER( ORDER BY VoucherValue DESC ) AS SubGroupID,
1 AS IterationID,
VoucherValue, Num AS VoucherCumulativeCount,
CAST( VoucherValue * Num AS DECIMAL( 10, 2 )) AS TotalDenominationValue,
CAST( #TargetAmount - ( VoucherValue * Num ) AS DECIMAL( 10, 2 )) AS Remainder
FROM #Vouchers
-- Find the largest amount a given Voucher denomination can produce that is less than or equal to #TargetAmount
INNER JOIN #Numbers ON ( VoucherValue * Num ) <= #TargetAmount AND #TargetAmount - ( VoucherValue * Num ) < VoucherValue
WHERE CountryCode = #CountryCode
UNION ALL
-- Recursive query
SELECT SubGroupID,
SubGroupID * 10 + ROW_NUMBER() OVER( ORDER BY V.VoucherValue DESC ) AS SubGroupID,
IterationID + 1,
V.VoucherValue, VoucherCumulativeCount + N.Num AS VoucherCount,
CAST( V.VoucherValue * N.Num AS DECIMAL( 10, 2 )) AS TotalDenominationValue,
CAST( Remainder - ( V.VoucherValue * N.Num ) AS DECIMAL( 10, 2 )) AS Remainder
FROM VoucherCombinations AS VP
-- For each denomination look at the smaller denominations
INNER JOIN #Vouchers AS V ON VP.VoucherValue > V.VoucherValue
INNER JOIN #Numbers AS N ON V.VoucherValue * N.Num <= Remainder AND Remainder - ( V.VoucherValue * N.Num ) < V.VoucherValue
WHERE CountryCode = #CountryCode
),
-- Discard invalid combinations i.e. remainder is not 0
VoucherPoolValid AS(
SELECT *, DENSE_RANK() OVER( ORDER BY VoucherCumulativeCount ASC ) AS BestCombos
FROM VoucherCombinations
WHERE Remainder = 0
),
-- Find best combinations i.e. smallest number of Vouchers; Note: logic supports having more than 1 best combination
VoucherPoolBestCombos AS(
SELECT *, ROW_NUMBER() OVER( ORDER BY BestCombos ASC ) AS ComboID
FROM VoucherPoolValid
WHERE BestCombos = 1
),
-- Return all denominations for each combination
VoucherPoolAllDetails AS(
SELECT *
FROM VoucherPoolBestCombos
UNION ALL
SELECT Parent.*, BestCombos, ComboID
FROM VoucherPoolAllDetails AS Child
INNER JOIN VoucherCombinations AS Parent ON Child.ParentGroupID = Parent.SubGroupID
WHERE Child.SubGroupID <> Child.ParentGroupID
)
SELECT * FROM VoucherPoolAllDetails
ORDER BY ComboID
Related
I have a situation about writing a query to find and insert into table B all combinations of rows from table A, where the condition is:
a x b=total from row1
c x d=total from row2 ...etc where count(total)<=X
"a" price of item
"b" quantity of item
Idea is to have all combinations like example
For 100$ dollars i can buy:
2 tshirt, 1 jacket, 1 pants
or
1 tshirt, 2 jacket, 1 pants
...etc
Creating a cursor will help me run the query for each row, but how to split the number in col.quantity in the same time ?
I will first write what I understood,
we would have a table of items, each item would have a price,
we have an amount of money and we want to buy as many as possible
items
we want the items to have the same weight as the two examples
provided "2 tshirt, 1 jacket, 1 pants or 1 tshirt, 2 jacket, 1 pants"
did not specify a solution with one item but tried to use all the
items.
So how to determine the Qty for each item to utilize most of the money that we have.
I think this can be described in a different way to be more clear, like for example:- one person goes in a shop and would like to buy each of the items available but if he has some more money left he want to know what other items he can buy with it. if the items are not a lot and the money is not a lot, this can be easy, but if the items are a lot and the money a lot too, I can see that this may be a problem. so lets find a solution.
Declare #Items Table (
Item varchar(250),Price decimal
)
insert into #Items values
('tshirt',30)
,('jacket',30)
,('pants' ,10)
--,('shoe' ,15) ---extra items for testing
--,('socks',5) ---extra items for testing
Declare #total int=100 -- your X
Declare #ItemsCount int
Declare #flag int
Declare #ItemsSum decimal
Declare #AllItmsQty int
select #ItemsCount=count(*),#ItemsSum=sum(price),#flag=POWER(2,count(*)) From #Items
select #AllItmsQty=#total/cast(#ItemsSum as int)
;with Numbers(n) as (
--generat numbers from 1,2,3,... #flag
select 1 union all
select (n+1) n from Numbers where n<#flag
),ItemsWithQty as (
select *,Price*n [LineTotal] from #Items,Numbers
),Combination as (
select items.*,Numbers.n-1 [CombinationId] from #Items items,Numbers
),CombinationWithSeq as (
select *
,ROW_NUMBER() over (Partition by [CombinationId] order by [CombinationId]) [seq]
from Combination
),CombinationWithSeqQty as (
select *,case when (CombinationId & power(2,seq-1))>0 then 1 else 0 end +#AllItmsQty [qty]
from CombinationWithSeq
),CombinationWithSeqQtySubTotal as (
select *,Price*qty [SubTotal] from CombinationWithSeqQty
)
select
--CombinationId,
sum(subtotal) [Total],
replace(
replace(
STRING_AGG(
case when (Qty=0) then 'NA' else (cast(Qty as varchar(5))+' '+Item)
end
,'+')
,'+NA','')
,'NA+','') [Items]
from CombinationWithSeqQtySubTotal
group by CombinationId
having sum(subtotal)<=#total
The result would be as follow:-
Total Items
===== ===========================
100 2 tshirt+1 jacket+1 pants
100 1 tshirt+2 jacket+1 pants
80 1 tshirt+1 jacket+2 pants
70 1 tshirt+1 jacket+1 pants
if I add the other two items we would get
Total Items
===== ===========================
100 1 tshirt+1 jacket+2 pants+1 shoe+1 socks
95 1 tshirt+1 jacket+1 pants+1 shoe+2 socks
90 1 tshirt+1 jacket+1 pants+1 shoe+1 socks
ok so the query is giving the final result not the table B, that you described to have a x b or item price multiplied by qty and sub total , well we can display that one very easily by filtering witch combination we selected, if we are selecting the first one that would be the nearest to the amount we can change the last part of the query to show table B you need.
),CombinationWithSeqQtySubTotal as (
select *,Price*qty [SubTotal] from CombinationWithSeqQty
),Results as (
select
CombinationId,
sum(subtotal) [Total],
replace(
replace(
STRING_AGG(
case when (Qty=0) then 'NA' else (cast(Qty as varchar(5))+' '+Item)
end
,'+')
,'+NA','')
,'NA+','') [Items]
from CombinationWithSeqQtySubTotal
group by CombinationId
having sum(subtotal)<=#total
--order by [Total] desc
)
select item, price, qty, SubTotal from CombinationWithSeqQtySubTotal t where t.CombinationId in
(select top(1) CombinationId from Results order by [Total] desc)
The result would be as below:-
item price qty SubTotal
===== ===== === =======
tshirt 30 1 30
jacket 30 1 30
pants 10 2 20
shoe 15 1 15
socks 5 1 5
or if we run it with only the items you provided the result would be as below:-
item price qty SubTotal
====== === === =======
tshirt 30 2 60
jacket 30 1 30
pants 10 1 10
if we dont want to use 'STRING_AGG' or we dont have it, we can manage its same function by adding some CTE's that will do the same job, as the 'STRING_AGG' was only combining the results in a (qty + item + comma), so the below solution may help.
Declare #Items Table (Item varchar(250),Price decimal)
insert into #Items values
('tshirt',30)
,('jacket',30)
,('pants' ,10)
--,('shoes' ,15) ---extra items for testing
--,('socks',5) ---extra items for testing
Declare #total int=100 -- your X
Declare #ItemsCount int
Declare #flag int
Declare #ItemsSum decimal
Declare #AllItmsQty int
select #ItemsCount=count(*),#ItemsSum=sum(price),#flag=POWER(2,count(*)) From #Items
select #AllItmsQty=#total/cast(#ItemsSum as int)
;with Numbers(n) as (
--generat numbers from 1,2,3,... #flag
select 1 union all
select (n+1) n from Numbers where n<#flag
),ItemsWithQty as (
select *,Price*n [LineTotal] from #Items,Numbers
),Combination as (
select items.*,Numbers.n-1 [CombinationId] from #Items items,Numbers
),CombinationWithSeq as (
select *,ROW_NUMBER() over (Partition by [CombinationId] order by [CombinationId]) [seq] from Combination
),CombinationWithSeqQty as (
select *,case when (CombinationId & power(2,seq-1))>0 then 1 else 0 end +#AllItmsQty [qty] from CombinationWithSeq
),CombinationWithSeqQtySubTotal as (
select *,Price*qty [SubTotal] from CombinationWithSeqQty
),CombinationWithTotal as (
--to find only the combinations that are less or equal to the Total
select
CombinationId,
sum(subtotal) [Total]
from CombinationWithSeqQtySubTotal
group by CombinationId
having sum(subtotal)<=#total
),DetailAnswer as (
select s.*,t.Total,cast(s.qty as varchar(20))+' ' +s.Item QtyItem from CombinationWithTotal t
inner join CombinationWithSeqQtySubTotal s on s.CombinationId=t.CombinationId
),DetailAnswerFirst as (
select *,cast(QtyItem as varchar(max)) ItemList from DetailAnswer t where t.seq=1
union all
select t.*,cast((t.QtyItem+'+'+x.ItemList) as varchar(max)) ItemList from DetailAnswer t
inner join DetailAnswerFirst x on x.CombinationId=t.CombinationId and x.seq+1=t.seq
)
select CombinationId,Total,ItemList from DetailAnswerFirst where seq=#ItemsCount order by Total desc
--select * from DetailAnswer --remark the above line and unremark this one for the details that you want to go in Table B
if any of the assumptions are wrong or if you need some description I would be happy to help.
Maybe the easiest way to get the possible combinations is via self-joins and joins to numbers.
If you want combinations of 3, then use 3 self-joins.
And 3 joins to a number table or CTE for each joined "Items" table.
The way the ON criteria are used, is to minimize the impact of all that joining.
You could also take the SQL from the COMBOS CTE, and use it to first insert it into a temporary table.
For example:
declare #PriceLimit decimal(10,2) = 100;
WITH COMBOS AS
(
SELECT
i1.id as id1, i2.id as id2, i3.id as id3,
n1.n as n1, n2.n as n2, n3.n as n3,
(n1.n + n2.n + n3.n) AS TotalItems,
(i1.Price * n1.n + i2.Price * n2.n + i3.Price * n3.n) as TotalCost
FROM Items i1
JOIN Items i2 ON i2.id > i1.id AND i2.Price < #PriceLimit
JOIN Items i3 ON i3.id > i2.id AND i3.Price < #PriceLimit
JOIN Nums n1
ON n1.n between 1 and FLOOR(#PriceLimit/i1.Price)
AND (i1.Price * n1.n) < #PriceLimit
JOIN Nums n2
ON n2.n between 1 and FLOOR(#PriceLimit/i2.Price)
AND (i1.Price * n1.n + i2.Price * n2.n) < #PriceLimit
JOIN Nums n3
ON n3.n between 1 and FLOOR(#PriceLimit/i3.Price)
AND (i1.Price * n1.n + i2.Price * n2.n + i3.Price * n3.n) <= #PriceLimit
AND (i1.Price * n1.n + i2.Price * n2.n + i3.Price * (n3.n+1)) > #PriceLimit
WHERE i1.Price < #PriceLimit
)
SELECT
c.TotalItems, c.TotalCost,
CONCAT (c.n1,' ',item1.Name,', ',c.n2,' ',item2.Name,', ',c.n3,' ',item3.Name) AS ItemList
FROM COMBOS c
LEFT JOIN Items item1 ON item1.id = c.id1
LEFT JOIN Items item2 ON item2.id = c.id2
LEFT JOIN Items item3 ON item3.id = c.id3
ORDER BY c.TotalCost desc, c.TotalItems desc, c.id1, c.id2, c.id3;
A test on db<>fiddle here
Test result:
TotalItems | TotalCost | ItemList
---------- | --------- | ---------------------------
7 | 100.00 | 1 pants, 1 tshirt, 5 socks
6 | 100.00 | 1 jacket, 1 tshirt, 4 socks
6 | 100.00 | 1 pants, 2 tshirt, 3 socks
5 | 100.00 | 1 jacket, 1 pants, 3 socks
5 | 100.00 | 1 jacket, 2 tshirt, 2 socks
5 | 100.00 | 1 pants, 3 tshirt, 1 socks
5 | 100.00 | 2 pants, 1 tshirt, 2 socks
3 | 90.00 | 1 jacket, 1 pants, 1 tshirt
My exact requirement is that if the output of the query 'select amount, quantity from temp_table where type = 5;' is:
amount | quantity
10 | 5
20 | 7
12 | 10
Then, the output should be displayed as:
amount1 | amount2 | amount3 | quantity1 | quantity2 | quantity3
10 | 20 | 12 | 5 | 7 | 10
A possible solution might be:
SELECT LISTAGG(amount, '|') WITHIN GROUP (order by amount)
|| LISTAGG(quantity, '|') WITHIN GROUP (order by amount) as result
FROM temp_table where type = 5;
*Have in mind, that the values of the columns amount and quantity are separated by spaces, thus the ' ' in the listagg() expression. You can change it to '|' or anything else if you like.
Cheers
Use a PIVOT:
SELECT "1_AMOUNT" AS Amount1,
"2_AMOUNT" AS Amount2,
"3_AMOUNT" AS Amount3,
"4_AMOUNT" AS Amount4,
"5_AMOUNT" AS Amount5,
"1_QUANTITY" AS Quantity1,
"2_QUANTITY" AS Quantity2,
"3_QUANTITY" AS Quantity3,
"4_QUANTITY" AS Quantity4,
"5_QUANTITY" AS Quantity5
FROM ( SELECT amount, quantity, ROWNUM rn FROM temp_table WHERE type = 5 )
PIVOT ( MAX( amount ) AS amount,
MAX( quantity ) AS quantity
FOR rn IN ( 1, 2, 3, 4, 5 ) );
I'm just trying to understand CTE and recursion to solve an issue that I would previously have used a cursor for.
create table ##ACC (
AccNo int,
Property char
)
Insert into ##ACC
VALUES (1,'A'),(1,'B'),(2,'A'),(2,'C'),(3,'C'),(4,'D')
What I'm trying to achieve is to get a list of all AccNo's, and all AccNo's they're related to via Property. So my expected results are
PrimaryAccNo | LinkedAccNo
1 | 1
1 | 2
1 | 3
2 | 1
2 | 2
2 | 3
3 | 1
3 | 2
3 | 3
4 | 4
I've attempted the following code and variations but I either get 4 results (PrimaryAccNo=LinkedAccNo) only or I hit 100 recursions.
WITH Groups(PrimaryAccNo, LinkedAccNo)
AS
(
Select distinct AccNo, AccNo from ##ACC
UNION ALL
Select g.PrimaryAccNo, p.AccNo from
##ACC p inner join Groups g on p.AccNo=g.LinkedAccNo
inner join ##ACC pp on p.Property=pp.Property
where p.AccNo<> pp.AccNo
)
Select PrimaryAccNo,LinkedAccNo
from Groups
What am I doing wrong?
You're running into an infinite loop caused by cycles within your data, e.g.: 1 > 2 > 3 > 2 > ... . The solution is to keep track of the rows that have already been "consumed". Due to limitations in CTEs, this has to be done by including the history within each CTE row, e.g. by assembling the path followed to arrive at each row. You can uncomment the , Path on the final select to see what is going on.
-- Sample data.
declare #ACC as Table ( AccNo Int, Property Char );
insert into #ACC values
( 1, 'A' ), ( 1, 'B' ), ( 2, 'A' ), ( 2, 'C' ), ( 3, 'C' ), ( 4, 'D' );
select * from #ACC;
-- Recursive CTE.
with Groups as (
select distinct AccNo, AccNo as LinkedAccNo,
Cast( '|' + Cast( AccNo as VarChar(10) ) + '|' as VarChar(1024) ) as Path
from #ACC
union all
select G.AccNo, A.AccNo, Cast( Path + Cast( A.AccNo as VarChar(10) ) + '|' as VarChar(1024) )
from Groups as G inner join -- Take the latest round of new rows ...
#ACC as AP on AP.AccNo = G.LinkedAccNo inner join -- ... and get the Property for each ...
#ACC as A on A.Property = AP.Property -- ... to find new linked rows.
where G.Path not like '%|' + Cast( A.AccNo as VarChar(10) ) + '|%' )
select AccNo, LinkedAccNo -- , Path
from Groups
order by AccNo, LinkedAccNo;
Another approach similar to yours but differs in the following:
The property value is included in the recursive CTE so that it can be used later
The < is used to prevent duplicates and the resulting infinite recursion
Another CTE is added AccGroups to provide the mirror of the relations
A demo fiddle has been included below:
CREATE TABLE ##ACC (
AccNo int,
Property char
);
INSERT INTO ##ACC
VALUES (1,'A'),(1,'B'),(2,'A'),(2,'C'),(3,'C'),(4,'D');
WITH Groups(PrimaryAccNo, LinkedAccNo, Property) AS (
SELECT AccNo, AccNo, Property FROM ##ACC
UNION ALL
SELECT g.PrimaryAccNo, pp.AccNo, pp.Property
FROM Groups g
INNER JOIN ##ACC p ON g.Property=p.Property AND
g.LinkedAccNo < p.AccNo
INNER JOIN ##ACC pp ON p.AccNo = pp.AccNo
),
AccGroups AS (
SELECT DISTINCT * FROM (
SELECT PrimaryAccNo, LinkedAccNo FROM Groups
UNION ALL
SELECT LinkedAccNo, PrimaryAccNo FROM Groups
) t
)
SELECT * FROM AccGroups
ORDER BY PrimaryAccNo,LinkedAccNo
GO
PrimaryAccNo | LinkedAccNo
-----------: | ----------:
1 | 1
1 | 2
1 | 3
2 | 1
2 | 2
2 | 3
3 | 1
3 | 2
3 | 3
4 | 4
db<>fiddle here
Assume Table1:
|PaymentID|CashAmount|
----------------------
| P1 | 3000|
| P2 | 5000|
| P3 | 8000|
| P4 | 700|
| P5 | 5500|
| P6 | 1900|
If I want to sum of CashAmount to be 'at least' 9000. PaymentID order should be the same.
Expected Result:
|PaymentID|CashAmount|
----------------------
| P1 | 3000|
| P2 | 5000|
| P3 | 8000|
If I want to sum of CashAmount to be 'at least' 4000. PaymentID order should be the same.
Expected Result:
|PaymentID|CashAmount|
----------------------
| P1 | 3000|
| P2 | 5000|
I had a look at limiting the rows to where the sum a column equals a certain value in MySQL. But the accepted answer is not working with MSSQL and is not exactly what I'm looking for. Most of the answers there I've tested and they return only rows that the total amount is less than, not at least specific value.
SQL Server 2005 and Later
SELECT *
FROM TableName t
CROSS APPLY (SELECT SUM(Amount)
FROM TableName
WHERE [Date] <= t.[DATE]) c(AmtSum)
WHERE AmtSum <= 13
SQL Server 2012 and Later
SELECT *
FROM (
SELECT *
,SUM(Amount) OVER (ORDER BY [Date], Amount) AmtSum
FROM TableName
)t
WHERE AmtSum <= 13
According to your new input I changed my approach slightly. Hope this is what you need...
EDIT: Here's the version with SUM(x) OVER(...):
DECLARE #payment TABLE(PaymentID VARCHAR(10),CashAmount INT);
INSERT INTO #payment VALUES
('P1',3000)
,('P2',5000)
,('P3',8000)
,('P4',700)
,('P5',5500)
,('P6',1900);
DECLARE #myMinToReach INT=9000;
WITH SortedPayment AS
(
SELECT ROW_NUMBER() OVER(ORDER BY PaymentID) AS inx
,SUM(CashAmount) OVER(ORDER BY PaymentID) AS Summa
FROM #payment
)
SELECT * FROM SortedPayment
WHERE inx<=(SELECT TOP 1 x.inx
FROM SortedPayment AS x
WHERE Summa>#myMinToReach
ORDER BY Summa ASC);
And that's the old version for SQL-Server < 2012
DECLARE #payment TABLE(PaymentID VARCHAR(10),CashAmount INT);
INSERT INTO #payment VALUES
('P1',3000)
,('P2',5000)
,('P3',8000)
,('P4',700)
,('P5',5500)
,('P6',1900);
DECLARE #myMinToReach INT=4000;
WITH SortedPayment AS
(
SELECT ROW_NUMBER() OVER(ORDER BY PaymentID) AS inx,*
FROM #payment
)
,Accumulated AS
(
SELECT tbl.*
FROM
(
SELECT SortedPayment.*
,Accumulated.Summa
FROM SortedPayment
CROSS APPLY
(
SELECT SUM(ps2.CashAmount) AS Summa
FROM SortedPayment AS ps2
WHERE ps2.inx<=SortedPayment.inx
) AS Accumulated
) AS tbl
)
SELECT * FROM Accumulated
WHERE inx<=(SELECT TOP 1 x.inx
FROM Accumulated AS x
WHERE Summa>#myMinToReach
ORDER BY Summa ASC);
declare #s int;
update table set rc=row_count() over (order by date)
declare #i int;
set #i=1;
while #s<=12 or #i<100000
set #s=#s+(select amount from table where rc=#i+1);
set #i=#i+1;
end
// #s has at least 12
I am trying to select data based on some parameters passed to my stored procedure. I have problems with the age, I am trying to do something like this:
If my stored procedure parameter #Age = 1 then I select age between 15 to 18, #Age = 2 then 19 - 25..., apparently this is incorrect, anyone can help. Thanks.:
SELECT
User
FROM
[Member] m
WHERE
((m.Gender = #Gender) or #Gender IS NULL)
and ((DATEDIFF(hour,m.DOB,GETDATE())/8766) Between
CASE
WHEN #Age = 1 THEN (SELECT DATEDIFF(hour, m.DOB, GETDATE())/8766 WHERE (SELECT DATEDIFF(hour, m.DOB, GETDATE())/8766) between 15 and 18)
WHEN #Age = 2 THEN (SELECT DATEDIFF(hour,m.DOB,GETDATE())/8766 WHERE (SELECT DATEDIFF(hour,m.DOB,GETDATE())/8766) between 19 and 25)
END)
I think this is what you are after (probably with some superfluous parenthesis):
Select
[User]
From
[Member] m
Where (
(m.Gender = #Gender) or
#Gender Is Null
) And (
(#Age = 1 And DateDiff(hour, m.Dob, GetDate())/8766 Between 15 and 18) Or
(#Age = 2 And DateDiff(hour, m.Dob, GetDate())/8766 Between 19 and 25)
)
If you've got a lot of clauses, it might be easier to read as (assuming a MemberID Primary Key)
Select
[User]
From
[Member] m
Inner Join (
Select
MemberID,
DateDiff(hour, m.Dob, GetDate())/8766 As Years
From
[Member]
) As y
On m.MemberID = y.MemberID
Where (
(m.Gender = #Gender) or
#Gender Is Null
) And (
(#Age = 1 And y.Year Between 15 and 18) Or
(#Age = 2 And y.Year Between 19 and 25)
)
Even better, you could add the ranges to a separate table called AgeRanges
+-------+------------+----------+
| AgeID | StartYears | EndYears |
+-------+------------+----------+
| 1 | 15 | 18 |
| 2 | 19 | 25 |
| ... | ... | ... |
+-------+------------+----------+
Select
[User]
From
[Member] m
Inner Join
[AgeRanges] a
On DateDiff(hour, m.Dob, GetDate())/8766 Between a.StartYears and a.EndYears And
a.AgeID = #Age
You could also make DateDiff(hour, m.Dob, GetDate())/8766 a computed column on your members table to simplify things (and make indexing possible if performance became an issue).