sql server : select rows who's sum matches a value [duplicate] - sql

This question already has answers here:
How to get rows having sum equal to given value
(4 answers)
Closed 9 years ago.
The community reviewed whether to reopen this question 1 year ago and left it closed:
Original close reason(s) were not resolved
here is table T :-
id num
-------
1 50
2 20
3 90
4 40
5 10
6 60
7 30
8 100
9 70
10 80
and the following is a fictional sql
select *
from T
where sum(num) = '150'
the expected result is :-
(A)
id num
-------
1 50
8 100
(B)
id num
-------
2 20
7 30
8 100
(C)
id num
-------
4 40
5 10
8 100
the 'A' case is most preferred !
i know this case is related to combinations.
in real world - client gets items from a shop, and because of an agreement between him and the shop, he pay every Friday. the payment amount is not the exact total of items
for example: he gets 5 books of 50 € ( = 250 € ), and on Friday he bring 150 €, so the first 3 books are perfect match - 3 * 50 = 150. i need to find the id's of those 3 books !
any help would be appreciated!

You can use recursive query in MSSQL to solve this.
SQLFiddle demo
The first recursive query build a tree of items with cumulative sum <= 150. Second recursive query takes leafs with cumulative sum = 150 and output all such paths to its roots. Also in the final results ordered by ItemsCount so you will get preferred groups (with minimal items count) first.
WITH CTE as
( SELECT id,num,
id as Grp,
0 as parent,
num as CSum,
1 as cnt,
CAST(id as Varchar(MAX)) as path
from T where num<=150
UNION all
SELECT t.id,t.num,
CTE.Grp as Grp,
CTE.id as parent,
T.num+CTE.CSum as CSum,
CTE.cnt+1 as cnt,
CTE.path+','+CAST(t.id as Varchar(MAX)) as path
from T
JOIN CTE on T.num+CTE.CSum<=150
and CTE.id<T.id
),
BACK_CTE as
(select CTE.id,CTE.num,CTE.grp,
CTE.path ,CTE.cnt as cnt,
CTE.parent,CSum
from CTE where CTE.CSum=150
union all
select CTE.id,CTE.num,CTE.grp,
BACK_CTE.path,BACK_CTE.cnt,
CTE.parent,CTE.CSum
from CTE
JOIN BACK_CTE on CTE.id=BACK_CTE.parent
and CTE.Grp=BACK_CTE.Grp
and BACK_CTE.CSum-BACK_CTE.num=CTE.CSum
)
select id,NUM,path, cnt as ItemsCount from BACK_CTE order by cnt,path,Id

If you restrict your problem to "which two numbers add up to a value", the solution is as follows:
SELECT t1.id, t1.num, t2.id,t2.num
FROM T t1
INNER JOIN T t2
ON t1.id < t2.id
WHERE t1.num + t2.num = 150
If you also want the result for three and more numbers you can achieve that by using the above query as a base for recursive SQL. Don't forget to specify a maximum recursion depth!

To find the id's of the books that the client is paying, you would need to have a table with your clients, and another one to store the orders of the client, and what products he bought.
Otherwise it would be impossible to know what product the payment refers to.

Related

How to avoid aggregate functions in recursive query's recursive term

I am having trouble getting around a sum() in the recursive term. Basically my problem is this.
Lets say 3 different finish products. 'ABC1', 'ABC2', 'ABC3' every one of them is made from 'ABC'. Every 'ABC' is made from 'AB'. Every 'AB' is made from 'A'. I went out and sold 10 of each 'ABC1', 'ABC2', 'ABC3'
I am trying to make a query give me a list of each item and how much I need of that item based on how much I have sold.
This is an example of the return that I am looking for
Item
Level
Sold
On Hand
Required
A
0
0
0
15
AB
1
0
10
25
ABC
2
10
0
25
ABC1
3
10
5
10
ABC2
3
10
5
10
ABC3
3
10
5
10
For a general table structure you would have
Item
item_id
item_onhand
AND
BOM
bom_product_id
bom_material_id
AND
Sales
sale_id
sale_item_id
sale_qty
I cant start at the top and go down in my case. because the dataset takes too long to process. So I have to start with all the sales and work up the tree from there.
My idea was to create a result for each level.
And then recursively go up the material tree. Something along the lines of
WITH RECURSIVE sales_req AS(
SELECT item_id,
SUM(sale_qty) AS sales_req_sold,
item_onhand AS sales_req_qoh
FROM sales JOIN item ON sales_item_id = item_id
GROUP BY item_id
UNION
SELECT
item_id,
SUM(sales_req_sold - sales_req_qoh),
item_onhand
FROM
bom
JOIN sales_req ON bom_product_id = sales_req.item_id
JOIN item mat ON bom_material_id = mat.item_id
WHERE sales_req_sold > sales_req_qoh
The first Query Returning Something Like this
Item
Required
ABC
10
ABC1
10
ABC2
10
ABC3
10
And The recursive portion returning something like this
Item
Required
Notes
ABC
15
( The sum of sales for "ABC1,ABC2,ABC3" minus the inventory for each one)
AB
25
( The sum of ABC requirements from 1,2 and 3 Plus the requirement for the sale of ABC)
A
15
( AB Minus the inventory on hand for AB)
I need some sort of alternate solution to sum function. However there are a few constraints. I have to start with the sales table. I cannot put a limit on the levels. In this example I have 4 levels and only one level has multiple parts on it. But there could be 7 levels and each level could have 3 parts on it. I can assume the top level to be 1 single item.
try this :
WITH RECURSIVE req AS(
SELECT item_id, item_onhand, SUM(sale_qty) AS item_sales
FROM sales INNER JOIN item ON sale_item_id = item_id
GROUP BY item_id, item_onhand
), accum (item_id, item_onhand, item_sales, item_req, level) AS (
SELECT item_id, item_onhand, item_sales, item_sales, 0
FROM req
UNION ALL
SELECT b.bom_product_id, a.item_onhand, a.item_sales, a.item_sales - a.item_onhand, a.level - 1
FROM accum AS a
INNER JOIN bom AS b ON b.bom_material_id = a.item_id
)
SELECT r.item_id, min(a.level) AS level, r.item_onhand AS on_hand, r.item_sales AS sold, sum(item_req) AS required
FROM accum AS a
INNER JOIN req AS r ON r.item_id = a.item_id
GROUP BY r.item_id, r.item_onhand, r.item_sales
ORDER BY level
see test result in https://dbfiddle.uk/J7PMY1fZ[enter link description here]1

greatest N per group with padding

I've been trying to solve this problem over the weekend, without luck so far. I have two tables:
TopOffers:
OfferId RetailerId Order
1 38 0
2 8 3
3 17 2
4 22 1
And Offers:
Id RetailerId Name Description etc...
1 3 Strawberry Red and smelly
2 38 Cookie Crunchy
3 17 Onion Of the nice kind
4 22 Apple Cheap
5 8 Toothbrush Lasts extra long!
My goal is to get the top 10 Offers for each Retailer ID. The order in which they should be listed is specified by the Order field in the TopOffer table (Sort order is Ascending). On top of that, the result should be padded to 10 offers when there are less than 10 TopOffer records for a retailer. The TopOffer table always contains 10 or less records per retailer.
So far I've managed to get this going, which works (I realize it doesn't get the top 10, but rather everything that's in the TopOffer table, which is alright, since the TopOffer table is always equal to or smaller than the top 10 for any retailer):
SELECT b.*
FROM
(
SELECT o.Id, to.`Order` FROM Offer AS o
LEFT JOIN TopOffer AS to
ON o.Id = to.OfferId
) AS a,
(
SELECT o.*, to.`Order` FROM Offer AS o
LEFT JOIN TopOffer AS to
ON o.Id = to.OfferId
) AS b
WHERE a.`Order` >= b.`Order` AND a.Id = b.Id
GROUP BY b.RetailerId, b.Id
HAVING Count(1) BETWEEN 1 AND 10
ORDER BY RetailerId, `Order` ASC
Unfortunately I can't seem to find any way of padding the result of this query with offers that don't have an entry in the TopOffer table if there aren't 10 TopOffer records for that retailer.
My sincerest thanks in advance for any help!
If you create a virtual table with numbers 1-10 you can left join to your results to get 10 of each
select number, results.*
from
(select 1 as number union select 2 union select 3 ... union select 10) numbers
left join
(your query here) results
on numbers.number = results.rank

How do I get multiple rows when value > 1 [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Select Records multiple times from table
I want to have my query return (multiple) rows for the value of TABLE_B.QTY.
TABLE A
SALESNR ITEMNR LINENR
100 B2001 1
101 B2002 2
102 A1021 3
TABLE B
LINENR COLOR QTY
1 WHITE 3
2 BLACK 1
3 BROWN 8
For instance, with the following query:
SELECT TABLE_A.SALESNR, TABLE_A.ITEMNR, TABLE_B.COLOR, TABLE_B.QTY
FROM TABLE_A INNER JOIN TABLE_B ON TABLE_B.LINENR = TABLE_A.LINENR
I get:
100 B2001 White 3
What I need is:
100 B2001 White 3
100 B2001 White 3
100 B2001 White 3
Is there a way to do this?
Can't think of the right keywords to Google this...
Thnx,
Mike
This will work as long as QTY is less than 2047
SELECT TABLE_A.SALESNR, TABLE_A.ITEMNR, TABLE_B.COLOR, TABLE_B.QTY
FROM TABLE_A
INNER JOIN TABLE_B ON TABLE_B.LINENR = TABLE_A.LINENR
INNER JOIN master..spt_values ON type = 'P' AND number < TABLE_B.QTY
use this if QTY exceeds 2047:
;WITH a AS
(
SELECT TABLE_A.SALESNR, TABLE_A.ITEMNR, TABLE_B.COLOR, TABLE_B.QTY, 1 row
FROM TABLE_A
INNER JOIN TABLE_B ON TABLE_B.LINENR = TABLE_A.LINENR
WHERE QTY > 0
union all
SELECT SALESNR, ITEMNR, COLOR, QTY, row+1
FROM a
WHERE QTY > row
)
SELECT SALESNR, ITEMNR, COLOR, QTY from a
OPTION (MAXRECURSION 0)
The cross join won't do it if you have one row in each table, which I interpret it as. I would suggest, if possible, to re-design your data model to solve this - or loop in code where you use this data.
You can loop in the T-SQL if absolutely needed.
regards, Olle
Can't seem to comment other peoples posts, just wanted to say, nice solution, to t-clausen.dk!

SQL server count and Sum Query?

I have a Question table with the fields (QuestionID,QuestionMarks), and with the data fields look like -
QuestionID QuestionMarks
1 1
2 4
5 1
9 1
12 2
which means at the moment Question table has 5 Question of Total 9 marks, Now my problem is that i want to know that a combination of 4 Questions with 8 marks is possible and fetch out that combination ( in general a combination of "x" questions of "y" marks is possible) ?
I was thinking to use CTE, but was afraid that it will take lot of time to execute the query if i have tens of thousand question.
please suggest some idea how to get the data. I am using SQL Server version 2008
This is a start. It's going to have poor performance:
declare #Qs table (QuestionID int not null, QuestionMarks int not null)
insert into #Qs (QuestionID,QuestionMarks) values
(1,1), (2,4), (5,1), (9,1), (12,2)
declare #TargetMarks int = 8
declare #TargetCount int = 4
;with Build as (
select QuestionID as MinID,QuestionID as MaxID,QuestionMarks as Total,1 as Cnt
,'/' + CONVERT(varchar(max),QuestionID) + '/' as QPath
from #Qs
union all
select MinID,q.QuestionID,Total+q.QuestionMarks,Cnt+1,QPath + CONVERT(varchar(max),q.QuestionID) + '/'
from
Build b
inner join
#Qs q
on
b.MaxID < q.QuestionID and
b.Total + q.QuestionMarks <= #TargetMarks and
b.Cnt < #TargetCount
)
select * from Build where Cnt = #TargetCount and Total = #TargetMarks
Result set:
MinID MaxID Total Cnt QPath
--------------------------------------------------------------------------------
2 12 8 4 /2/5/9/12/
1 12 8 4 /1/2/9/12/
1 12 8 4 /1/2/5/12/
The tricky part is that the QPath value isn't exactly the greatest way of storing ID values.
I think you're right in that tens of thousands of questions can slow down execution, so I'd start by limiting the potential rows being queried. You already know for certain that even with millions of rows, you never need more than four with identical QuestionMarks and you can reduce this further, e.g. (sorry for not knowing whether SQL Server accepts this syntax)
WITH LimitPotentialRows AS
(SELECT m1.QuestionID, m1.QuestionMarks,
(SELECT SUM(m2.QuestionMarks)
FROM MyTable m2
WHERE m1.QuestionMarks = m2.QuestionMarks
AND m1.PrimaryKeyID <= m2.PrimaryKeyID) CurrentMarks,
(SELECT COUNT(*)
FROM MyTable m3
WHERE m1.QuestionMarks = m3.QuestionMarks
AND m1.PrimaryKeyID <= m3.PrimaryKeyID) TotalQuestions
FROM MyTable m1
WHERE m1.QuestionMarks <= :DesiredTotalQuestionMarks - :TotalNoOfQuestions + 1
HAVING CurrentMarks <= :DesiredTotalQuestionMarks
AND TotalQuestions <= :TotalNoOfQuestions)
Desiring 4 questions with a total of 8 marks, the result of this CTE will leave you with only
QuestionMarks NumberOfQuestions
1 4
2 4
3 2
4 1
5 1
Having limited the number of rows from tens of thousands to maximum 12, you're unlikely to have performance problems in your further calculations.

Sqlite: Selecting records spread over total records

I have a sql / sqlite question. I need to write a query that select some values from a sqlite database table. I always want the maximal returned records to be 20. If the total selected records are more than 20 I need to select 20 records that are spread evenly (no random) over the total records. It is also important that I always select the first and last value from the table when sorted on the date. These records should be inserted first and last in the result.
I know how to accomplish this in code but it would be perfect to have a sqlite query that can do the same.
The query Im using now is really simple and looks like this:
"SELECT value,date,valueid FROM tblvalue WHERE tblvalue.deleted=0 ORDER BY DATE(date)"
If I for example have these records in the talbe and to make an easier example the maximum result I want is 5.
id value date
1 10 2010-04-10
2 8 2010-04-11
3 8 2010-04-13
4 9 2010-04-15
5 10 2010-04-16
6 9 2010-04-17
7 8 2010-04-18
8 11 2010-04-19
9 9 2010-04-20
10 10 2010-04-24
The result I would like is spread evenly like this:
id value date
1 10 2010-04-10
3 8 2010-04-13
5 10 2010-04-16
7 8 2010-04-18
10 10 2010-04-24
Hope that explain what I want, thanks!
Something like this should work for you:
SELECT *
FROM (
SELECT v.value, v.date, v.valueid
FROM tblvalue v
LEFT OUTER JOIN (
SELECT min(DATE(date)) as MinDate, max(DATE(date)) as MaxDate
FROM tblvalue
WHERE tblvalue.deleted = 0
) vm on DATE(v.date) = vm.MinDate or DATE(v.date) = vm.MaxDate
WHERE tblvalue.deleted = 0
ORDER BY vm.MinDate desc, Random()
LIMIT 20
) a
ORDER BY DATE(date)
I think you want this:
SELECT value,date,valueid FROM tblvalue WHERE tblvalue.deleted=0
ORDER BY DATE(date), Random()
LIMIT 20
In other words you want select rows with date column, so that date is from the sorted list of dates, from where we take every odd element? And add the last recorded element (with the latest date)? And everything limited to max 20 rows?
If that's the case, then I think this one should do:
SELECT id,value,date FROM source_table WHERE date IN (SELECT date FROM source_table WHERE (rowid-1) % 2 = 0 OR date = (SELECT max(date) FROM source_table) ORDER BY date) LIMIT 20