Sum Quantity and Filter Results - sql

I have the following table with order id's and quantities. I need to be able to sum the quantity and retrieve the id's that that equal less than the provided number.
| id | quantity |
|------|----------|
| 100 | 1 |
| 200 | 25 |
| 300 | 15 |
For example, I need the id's where the sum of quantity equals less than 25.
When I try the following it only provides me the first id (100).
Select *
from (
select *,
SUM (Quantity) OVER (ORDER BY Id) AS SumQuantity
from dbo.Orders
) as A
where SumQuantity <= 25
Is it possible to adjust this query where it will provide me id 100 and 300, since the sum total of those orders is less than 25?
I know I can use a where clause on for quantity less than 25, but the important thing here is I need to be able to sum the quantity and pull id's that give me less than the provided number.
Thank you in advance!

Perhaps you want to order by the quantity instead of id?
Select o.*
from (select o.*, SUM (Quantity) OVER (ORDER BY quantity) AS SumQuantity
from dbo.Orders
) o
where SumQuantity <= 25;
This chooses the smallest values so you will get the most rows.

Group by Id and set the condition in the HAVING clause:
select Id, SUM(Quantity) AS SumQuantity
from Orders
group by Id
having SUM(Quantity) <= 25
See the demo.
Results:
Id | SumQuantity
100 | 1
200 | 25
300 | 15
If you want to include all the columns you can modify your query to not ORDER BY id but PARTITION BY id:
select *
from (
select *,
SUM (Quantity) OVER (PARTITION BY Id) AS SumQuantity
from Orders
) as A
where SumQuantity <= 25
For this dataset:
CREATE TABLE Orders([id] varchar(6), [quantity] int);
INSERT INTO Orders([id], [quantity])VALUES
('100', '1'), ('100', '2'),
('200', '25'), ('200', '3'),
('300', '15'), ('300', '5');
Results:
id | quantity | SumQuantity
100 | 1 | 3
100 | 2 | 3
300 | 15 | 20
300 | 5 | 20
See the demo.

Setup:
Your threshold can vary, so let's make it into a variable:
declare #threshold int = 25;
But I also imagine that your table values can vary, like if we add another row only having a quantity of 2:
declare #orders table (id int, quantity int)
insert #orders values (100,1), (200,25), (300,15), (400, 2);
Solution:
For this, we'll need a recursive kind of cross joining:
with
traverse as (
select ids = convert(nvarchar(255), id),
id,
quantity
from #orders
where quantity < #threshold
union all
select ids =
convert(nvarchar(255), tv.ids + ',' +
convert(nvarchar(255), o.id)),
o.id,
quantity = tv.quantity + o.quantity
from traverse tv
cross join #orders o
where tv.id < o.id
and tv.quantity + o.quantity < #threshold
)
select t.ids, t.quantity
from traverse t;
which will produce:
Explanation:
The above code is an algorithm that builds a tree. It starts with your base id's and quantities as nodes (the anchor part of the CTE). It trims anything not meeting the threshold.
It then adds edges by cross joining with orders table again (the recursive part of the CTE), but it only includes the following:
Id's that are greater than the last id considered in the current node (this is so that we avoid duplicate considerations, such as ids = '300,400' and ids = '400,300').
Ids where the sum of quantities is less than the threshold.
Warnings:
But beware, the type of problem you're considering will have computational complexity considerations. But because of the trimming conditions, it will be more efficient than doing all the cross joins first and then filtering the result set at the end.
Also, keep in mind that you may get rows in your table where there is no single set of numbers that will sum up to less than 25. Rather, you can get different paths to that sum. The way I produce the results here will help you identify such a situation.

cross join is perfect for this task, try:
declare #tbl table (id int, quantity int);
insert into #tbl values
(100, 1), (200, 25), (300, 15), (400, 10);
select distinct case when t1.id > t2.id then t1.id else t2.id end,
case when t1.id < t2.id then t1.id else t2.id end
from #tbl t1
cross join #tbl t2
where t1.id <> t2.id
and t1.quantity + t2.quantity < 25

Related

Can I SELECT the AVG of 'x' values from two tables but only select the values where the 'y' values match? (INNER JOIN)

I'm trying to get the average of two values which are in two different tables. I only want to get the average of the values where the in the same column the 'Week' Values of both tables are the same.
So e.g.:
Table1 Name= BicepsTable
Week | Biceps
1 | 33
2 | 33.2
3 | 34.1
.
Table2 Name=ThighTable
Week | Thigh
1 | 42.1
3 | 42.8
4 | 43
.
From these tables I want to have the values {(1, 37.55), (3, 38.45)}.
( . (33+42)/2=37.55 . . . . (34.1+42.8)/2=38.45 . )
I tried to get this with the following code but the following code gives me {(1, 37.55), (3, 37.55)} where the second value is wrong, the second average value should be the one of the next column.
sql = 'SELECT BicepsTable.Week,
((SELECT BicepsTable.Biceps FROM BicepsTable INNER JOIN ThighTable ON BicepsTable.Week = ThighTable.Week)
+
(SELECT ThighTable.Thigh FROM ThighTable INNER JOIN BicepsTable ON ThighTable.Week = BicepsTable.Week)) /2
FROM BicepsTable INNER JOIN ThighTable ON BicepsTable.Week = ThighTable.Week'
Please help, if you don't understand my problem, or got questions, feel free to ask:)
I suggest taking a union of the two tables, and then taking the average of each week:
SELECT Week, AVG(rating) AS avg_rating
FROM
(
SELECT Week, Biceps AS rating FROM BicepsTable
UNION ALL
SELECT Week, Thigh FROM ThighTable
) t
GROUP BY Week
HAVING COUNT(*) = 2
ORDER BY Week;
Aggregation, as used above, is a good option here, because the AVG will only operate on the values which are present. So, if only one or the other table has a value, then the average will reflect that.
declare #test1 as table
(id int,t1value float)
declare #test2 as table
(id int,t2value float)
insert into #test1
values(1,100),(2,150),(3,200)
insert into #test2
values(1,100),(3,150),(5,200)
select *,(a.t1value+b.t2value)/2 taverage from #test1 a
inner join #test2 b on a.[id]=b.[id]
group by a.id,a.t1value,b.id,b.t2value

Duplicated records from SELECT DISTINCT COUNT statement

I have 2 tables with the following structure:
------------------------------------
| dbo.Katigories | dbo.Products |
|-----------------|------------------|
| product_id | product_id |
| Cat_Main_ID | F_material |
| Cat_Sub_ID | |
What I am trying to accomplish is the following:
I want to COUNT how many unique products (from the Products table) have as Cat_Main_ID=111, have as Cat_Sub_ID=222, and have as F_material=10
I have tried the following SELECT COUNT statement:
SELECT DISTINCT COUNT(*) AS METR_AN_EXEI_001
FROM dbo.Products P
INNER JOIN dbo.Katigories K
ON P.product_id = K.product_id
AND
K.Cat_Main_ID = 111
AND
P.F_material = 10
AND
K.Cat_Sub_ID = 222
The above statement is working, and counts the correct products, but is giving me duplicated records.
For example: When a product belongs to only 1 category the result of my count is correct and I am getting the number 1 as a result. But when a product belongs to more than one category then the result of the count is incorrect, depending on how many categories the product belongs. I know that the reason for the duplicates is that some of my products belong simultaneously to more than one category or sub category, so I am getting incorrect count results.
It will be truly appreciated if someone could help me with the syntax of my COUNT statement, in order to get the correct number of products (without duplicates) from my Products table.
!!!!!!!!!!!!!-----------------------------------------!!!!!!!!!!
Dear all.
Please forgive me for the inconvenience!
I'm so stupid, that I was placing your code in the wrong point in my page. Eventually almost all of the suggestions were correct.
Now I have a problem and I do not know which of the answers I choose to be right. All of them are correct!
A very big thank to all of you and I apologize again to all of you.
I suppose grouping should be approporate compared to distinct in this case
SELECT product_id, COUNT(product_id)
METR_AN_EXEI_001
FROM dbo.Products P
INNER JOIN dbo.Katigories K
ON P.product_id = K.product_id
AND
K.Cat_Main_ID = 111
AND
P.F_material = 10
AND
K.Cat_Sub_ID = 222
GROUP BY P.product_id
Can u try it using subselect to count, like this:
DECLARE #Katigories TABLE ( product_id int , Cat_Main_ID int , Cat_Sub_ID int )
DECLARE #Products TABLE( product_id int , F_material int )
INSERT INTO #Products (product_id,F_material) VALUES (1, 10)
INSERT INTO #Products (product_id,F_material) VALUES (2, 10)
INSERT INTO #Products (product_id,F_material) VALUES (3, 15)
INSERT INTO #Katigories ( product_id ,Cat_Main_ID ,Cat_Sub_ID ) VALUES ( 1,111, 222 )
INSERT INTO #Katigories ( product_id ,Cat_Main_ID ,Cat_Sub_ID ) VALUES ( 1,123, 223 )
INSERT INTO #Katigories ( product_id ,Cat_Main_ID ,Cat_Sub_ID ) VALUES ( 1,444, 222 )
INSERT INTO #Katigories ( product_id ,Cat_Main_ID ,Cat_Sub_ID ) VALUES ( 2,133, 223 )
SELECT
P.product_id,
(SELECT COUNT(*) FROM #Katigories WHERE product_id = P.product_id AND Cat_Main_ID=111 AND Cat_Sub_ID=222 AND P.F_material=10) AS METR_AN_EXEI_001
FROM #Products P
If you need to count distinct products use count(distinct ...). Something like this.
SELECT COUNT(DISTINCT P.product_id) AS METR_AN_EXEI_001
FROM dbo.Products P
INNER JOIN dbo.Katigories K
ON P.product_id = K.product_id
AND
K.Cat_Main_ID = 111
AND
P.F_material = 10
AND
K.Cat_Sub_ID = 222

Product Final Price after Many Discount given

I have two tables.
One table of Ids and their prices, and second table of discounts per Id.
In the table of discounts an Id can has many Discounts, and I need to know the final price of an Id.
What is the Best way to query it (in one query) ?
The query should be generic for many discounts per id (not only 2 as mentioned below in the example)
For example
Table one
id price
1 2.00
2 2.00
3 2.00
Table two
id Discount
1 0.20
1 0.30
2 0.40
3 0.50
3 0.60
Final result:
id OrigPrice PriceAfterDiscount
1 2.00 1.12
2 2.00 1.20
3 2.00 0.40
Here's another way to do it:
SELECT T1.ID, T1.Price, T1.Price * EXP(SUM(LOG(1 - T2.Discount)))
FROM T1 INNER JOIN T2 ON T1.ID = T2.ID
GROUP BY T1.ID, T1.Price
The EXP/LOG trick is just another way to do multiplication.
If you have entries in T1 without discounts in T2, you could change the INNER JOIN to a LEFT JOIN. You would end up with the following:
ID Price Discount
4 2.00 NULL
Your logic can either account for the null in the discounted price column and take the original price instead, or just add a 0 discount record for those.
Generally it can be done with a trick with LOG/EXP functions but it is complex.
Here is a basic example:
declare #p table(id int, price money)
declare #d table(id int, discount money)
insert into #p values
(1, 2),
(2, 2),
(3, 2)
insert into #d values
(1, 0.2),
(1, 0.3),
(2, 0.4),
(3, 0.5),
(3, 0.6)
select p.id,
p.price,
p.price * ca.discount as PriceAfterDiscount
from #p p
cross apply (select EXP(SUM(LOG(1 - discount))) as discount FROM #d where id = p.id) ca
For simpler(cursor based approach) you will need a recursive CTE, but in this case you need some unique ordering column in Discounts table to run it correctly. This is shown in #Tanner`s answer.
And finally you can approach this with a regular cursor
I believe this produces the desired results using a CTE to iterate through the discounts. The solution below is re-runnable in isolation.
Edited: to include data that might not have any discounts applied in the output with a left join in the first part of the CTE.
CREATE TABLE #price
(
id INT,
price DECIMAL(5, 2)
);
CREATE TABLE #discount
(
id INT,
discount DECIMAL(5, 2)
);
INSERT INTO #price
(
id,
price
)
VALUES
(1, 2.00),
(2, 2.00),
(3, 2.00),
(4, 3.50); -- no discount on this item
INSERT INTO #discount
(
id,
discount
)
VALUES
(1, 0.20),
(1, 0.30),
(2, 0.40),
(3, 0.50),
(3, 0.60);
-- new temporary table to add a row number to discounts so we can iterate through them
SELECT d.id,
d.discount,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY d.discount) rn
INTO #GroupedDiscount
FROM #discount AS d;
-- note left join in first part of cte to get prices that aren't discounted included
WITH cte
AS (SELECT p.id,
p.price,
CASE
WHEN gd.discount IS NULL THEN
p.price
ELSE
CAST(p.price * (1.0 - gd.discount) AS DECIMAL(5, 2))
END AS discountedPrice,
gd.rn
FROM #price AS p
LEFT JOIN #GroupedDiscount AS gd
ON gd.id = p.id
AND gd.rn = 1
UNION ALL
SELECT cte.id,
cte.price,
CAST(cte.discountedPrice * (1.0 - gd.discount) AS DECIMAL(5, 2)) AS discountedPrice,
cte.rn + 1 AS rn
FROM cte
INNER JOIN #GroupedDiscount AS gd
ON gd.id = cte.id
AND gd.rn = cte.rn + 1
)
SELECT cte.id,
cte.price,
MIN(cte.discountedPrice) AS discountedPrice
FROM cte
GROUP BY id,
cte.price;
DROP TABLE #price;
DROP TABLE #discount;
DROP TABLE #GroupedDiscount;
Results:
id price discountedPrice
1 2.00 1.12
2 2.00 1.20
3 2.00 0.40
4 3.50 3.50 -- no discount
As others have said, EXP(SUM(LOG())) is the way to do the calculation. Here is basically same approach from yet another angle:
WITH CTE_Discount AS
(
SELECT Id, EXP(SUM(LOG(1-Discount))) as TotalDiscount
FROM TableTwo
GROUP BY id
)
SELECT t1.id, CAST(Price * COALESCE(TotalDiscount,1) AS Decimal(18,2)) as FinalPRice
FROM TableOne t1
LEFT JOIN CTE_Discount d ON t1.id = d.id
SQLFIddle Demo

How would l write SQL to label quantities until they run out?

I would like to label quantities (in the quantity table) using the labels assigned (see label assignment table) until the quantity goes to 0. Then I know that I am done labeling that particular ID.
label assignment table is as follows:
ID | Label | Quantity
1 aaa 10
1 bbb 20
2 ccc 20
And my quantity table:
ID | Total Quantity
1 60
2 20
And I would like to get the following result:
ID | Label | Quantity
1 aaa 10 (read from reference table, remaining 50)
1 bbb 20 (read from reference table, remaining 30)
1 [NULL] 30 (no label in reference table, remaining 0)
2 ccc 20 (read from reference table, remaining 0)
You can do it with a simple JOIN and UNION operation so as to include 'not covered' quantities:
SELECT la.ID, la.Label, la.Quantity
FROM label_assignment AS la
INNER JOIN quantity AS q ON la.ID = q.ID
UNION
SELECT q.ID, NULL AS Label, q.TotalQuantity - la.TotalQuantity
FROM quantity AS q
INNER JOIN (
SELECT ID, SUM(Quantity) AS TotalQuantity
FROM label_assignment
GROUP BY ID
) AS la ON q.ID = la.ID AND q.TotalQuantity > la.TotalQuantity
Demo here
DECLARE #PerLabelQuantity TABLE(Id int, Label varchar(10), Quantity int);
INSERT INTO #PerLabelQuantity
VALUES (1, 'aaa', 10), (1, 'bbb', 20), (2, 'ccc', 20);
DECLARE #QuantityRequired TABLE(Id int, TotalQuantity int);
INSERT INTO #QuantityRequired
VALUES (1, 60), (2, 20);
SELECT t.Id,
CASE WHEN o.Overflowed = 1 THEN NULL ELSE t.Label END AS Label,
CASE WHEN o.Overflowed = 1 THEN t.QuantityStillNeeded
WHEN t.QuantityStillNeeded < 0 THEN t.Quantity + t.QuantityStillNeeded
ELSE t.Quantity END AS Quantity
FROM (
SELECT p.Id, p.Label, p.Quantity,
MAX(p.Label) OVER (PARTITION BY p.Id) AS LastLabel,
r.TotalQuantity - SUM(p.Quantity)
OVER (PARTITION BY p.Id
ORDER BY Label
ROWS UNBOUNDED PRECEDING) AS QuantityStillNeeded
FROM #PerLabelQuantity p
INNER JOIN #QuantityRequired r ON p.Id = r.Id) t
INNER JOIN (VALUES (0), (1)) o(Overflowed)
ON t.LastLabel = t.Label AND t.QuantityStillNeeded > 0 OR Overflowed = 0
WHERE t.QuantityStillNeeded > -t.Quantity; -- Remove this if you want labels with
-- 0 quantity used, but you'll need to tweak
-- the CASE expression for Quantity
The subquery calculates a set of used up labels and how many items remain afterward. If there is any quantity remaining after the last label, then we need to insert a row in the result set. To do this, I join on a two-element table but the join condition is only true when we are at the last label and there is quantity remaining. This is probably a confusing way to do this, and we could combine the UNION from George's answer with the subquery from mine to avoid this Overflow table.
Here's the changed (and probably preferable) query:
SELECT Id,
Label,
CASE WHEN QuantityStillNeeded < 0 THEN Quantity + QuantityStillNeeded
ELSE Quantity END AS Quantity
FROM (SELECT p.Id, p.Label, p.Quantity,
r.TotalQuantity - SUM(p.Quantity)
OVER (PARTITION BY p.Id
ORDER BY Label
ROWS UNBOUNDED PRECEDING) AS QuantityStillNeeded
FROM #PerLabelQuantity p
INNER JOIN #QuantityRequired r ON p.Id = r.Id) t
WHERE t.QuantityStillNeeded > -t.Quantity
UNION ALL
SELECT q.Id, NULL AS Label, q.TotalQuantity - la.TotalQuantity AS Quantity
FROM #QuantityRequired AS q
INNER JOIN (
SELECT Id, SUM(Quantity) AS TotalQuantity
FROM #PerLabelQuantity
GROUP BY Id) la ON q.ID = la.ID
WHERE q.TotalQuantity > la.TotalQuantity
Simplest answer I think, after getting ideas from the other answers: Just create a "FAKE" label for the missing amount:
DECLARE #PerLabelQuantity TABLE(Id int, Label varchar(10), Quantity int);
INSERT INTO #PerLabelQuantity
VALUES (1, 'aaa', 10), (1, 'bbb', 20), (2, 'ccc', 20);
SELECT *
FROM #PerLabelQuantity
DECLARE #QuantityRequired TABLE(Id int, TotalQuantity int);
INSERT INTO #QuantityRequired
VALUES (1, 60), (2, 20);
SELECT *
FROM #QuantityRequired
-- MAKE A FAKE LABEL LET'S CALL IT [NULL] WITH THE AMOUNT THAT IS NOT LABELED
-- i.e. WITH THE REMAINING AMOUNT
-- Probably should be done by copying the original data and the following
-- into a temp table but this is just for proof of concept
INSERT INTO #PerLabelQuantity( Id, Label, Quantity )
SELECT q.ID,
NULL,
ISNULL(q.TotalQuantity - p.TotalQuantityLabeled, q.TotalQuantity)
FROM #QuantityRequired q
LEFT JOIN (SELECT p.ID, SUM(Quantity) AS TotalQuantityLabeled
FROM #PerLabelQuantity p
GROUP BY p.Id) p ON
p.ID = q.ID
AND q.TotalQuantity - p.TotalQuantityLabeled > 0
SELECT *
FROM #PerLabelQuantity p

how to get query value from 1 row to use to another row?

This is example query:
payment_Type payment_value cost_type cost value
Cost I 100 Registration 40
Cost I 100 books 40
Cost I 100 Lab 40
The COST I has 3 elements Cost_Type that have their own Cost_value.
I want to manipulate like this:
payment_Type payment_value cost_type cost value Payment_by_cost_type
Cost I 100 Registration 40 40
Cost I 100 books 40 40
Cost I 100 Lab 40 20
The point is the I want to divided the payment_value into each cost_value. In the example the payment_by_cost becomes 40, 40, 20 = 100.
The lab cost_value is 40 but it can assign value is 20 because remains from the divided 2 cost type above.
Is it possible that I can use the value from Payment_by_cost_type in the next row record? I have been trying to insert the value Payment_by_cost_type to a temporary table but select cannot have insert statement.
Does anyone have any ideas on how to solve this? I've been consulting to DWH he said it must using Store procedure it cannot done by query.
I guess your table contains not only "Cost I" but other values so here is a query to output results for all groups (by Payment_type) in the table:
;with table1 as
(select
t.*,
row_number()
OVER
(PARTITION BY payment_Type order by cost_type) rn
from t
)
,table2 as
( select t4.*,isnull((select sum(cost_value) from table1
where table1.payment_type=t4.payment_type and rn<t4.rn),0) CumSum
from table1 t4
)
select payment_type,payment_value,cost_type,cost_value,
case when cost_value+CumSum<=payment_value then cost_value
else
payment_value-Cumsum
end
from table2
order by Payment_type,rn;
SQLFIDDLE demo
You need to define some kind of order for your records to define in which order the payments should be applied
Once you have done that (i'm using ID in this example)...
select *
, case
when payment_value-(select isnull(SUM(cost_value),0) from yourtable t2 where t2.id<t1.id)<cost_value
then payment_value-(select isnull(SUM(cost_value),0) from yourtable t2 where t2.id<t1.id)
else cost_value
end
from yourtable t1
Doing it step by step using common table expressions.
declare #t table (
payment_type varchar(20),
payment_value int,
cost_type varchar(20),
cost_value int,
cost_id int --for the ordering
)
insert #t values
('Cost I',100,'Registration',40,1),
('Cost I',100,'books',40,2),
('Cost I',100,'Lab',40,3),
('Cost 2',100,'Registration',40,4),
('Cost 2',100,'books',50,5),
('Cost 2',100,'Lab',40,6)
--get count for each payment_type to determine last row
;with payment_value_cte(payment_type,payment_value,count) as
(
select payment_type,payment_value,COUNT(*) from #t group by payment_type,payment_value
),
--use sequential index for each row in payment type
payment_value_index_cte(
payment_type ,
payment_value,
cost_type,
cost_value,
cost_id,
row) as
(
select *,ROW_NUMBER() OVER(PARTITION BY payment_type ORDER BY cost_id) from #t --assumes order is by an id
),
--get sum of each row for payment type except last row
payment_value_sum_except_last_cte(
payment_type,
payment_value,
current_sum) as
(
select pi.payment_type,pi.payment_value,SUM(cost_value)
from payment_value_index_cte pi
inner join payment_value_cte pt on pt.payment_type = pi.payment_type
where pi.row < pt.count
group by pi.payment_type,pi.payment_value
)
select
pi.payment_type,pi.payment_value,pi.cost_type,pi.cost_value,
--if last row calculate difference, else use the cost_value
case when pi.row = pt.count then pt.payment_value - pe.current_sum else pi.cost_value end [Payment_by_cost_type]
from payment_value_index_cte pi
inner join payment_value_cte pt on pt.payment_type = pi.payment_type
inner join payment_value_sum_except_last_cte pe on pe.payment_type = pi.payment_type
SELECT payment_Type, payment_value, cost_type, cost_value,
CASE WHEN ROW_NUMBER() OVER (ORDER BY(SELECT 1)) = COUNT(*) OVER (PARTITION BY payment_Type)
THEN SUM(cost_value) OVER (PARTITION BY payment_Type) - payment_value
ELSE cost_value END AS Payment_by_cost_type
FROM dbo.your_table
Demo on SQLFiddle