Limit result depending on sum of value - sql

I´ve been trying to automize a list of addresses in SQL. I have multiple addresses and quantities and i need only the addresses that will fulfill the quantity i need
For example:
I have a table with
Item A qty 20
Item B qty 5
Item C qty 23
And a table with addresses and units
Address 1 item A 15units
Address 2 item A 10units
Address 3 item A 10units
Address 4 item A 13units
The result should show only
Address 2 item A 10units
Address 3 item A 10units

Assuming that your first table sales stores the sales made :
Item
qty
A
20
B
5
C
23
and your second table stocks indicates the addresses of the stored items :
Address
item
units
1
A
15
2
A
10
3
A
10
4
A
13
then you can select a subset of addresses so that the sum of the stored units is equal or greater than the quantity sold :
WITH list AS (
SELECT st.item
, sa.qty AS sales_qty
, array_agg(st.address) OVER w AS addresses
, array_agg(st.units) OVER w AS units
, sum(units) OVER w AS total_stored_units
FROM sales AS sa
INNER JOIN stocks AS st
ON st.item = sa.item
WINDOW w AS (PARTITION BY st.item ORDER BY st.address)
)
SELECT DISTINCT ON (item)
item, unnest(addresses) AS address, unnest(units) AS units
FROM list
WHERE total_stored_units >= sales_qty
ORDER BY item, total_stored_units ASC
Result
item
address
units
A
1
15
A
2
10
see dbfiddle

Thanks a lot #Edouard!
I ended up using something like
WITH r AS
(
SELECT
item,
address,
quantity,
sum(quantity)
OVER (
PARTITION BY item
ORDER BY quantity desc
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) as total_units
FROM st
GROUP BY 1,2,3
)
SELECT *
FROM r
GROUP BY 1,2,3,4
HAVING total_units <= qty_needed + avg(units)
ORDER BY units desc
I couldnt run your query in the bigquery console, but from it a re read some documentation of windows functions find this solution.
Again, thanks a lot!

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

Query to list every line item but only list total once

We have a sales report that pulls data from multiple tables and my query shows correct data except orders that have multiple line items, i.e., the Total from the Orders table is listed on every line item row.
How can we list the Order Total only once on the row that has the smallest line item ID (for that order) but still list every line item row? Thanks!
Data Structure:
Orders Table:
Order_ID
Total
Line Items Table:
ID
Order_ID
Line_Item_Price
Line_Item_Qty
Result should be:
Order_ID Total Line_Item_Price Line_item_Qty Line_Item_ID
---------- ------- ----------------- --------------- --------------
10001 200 100 2 32001
10002 150 150 1 32002
10003 210 55 1 32003
10003 30 2 32004
10003 95 1 32005
10004 125 125 1 32006
This should be done in the application not in SQL.
But you can do that using window functions
select o.order_id,
case row_number() over (partition by o.order_id order by line_item_id)
when 1 then o.total
end as total,
li.line_item_price,
li.line_item_qty,
li.line_item_id
from orders o
join line_item li on o.order_id = li.order_id
order by o.order_id, li.line_item_id;
row_number() assigns a unique row number for each line item for every order. When the number is 1 the total is displayed, otherwise it's not.
In a relational database there is no such thing as "the first row" unless you specify an order by - in this case the "first row" is the line item with the smallest line_item_id
Online example: http://rextester.com/TQOIX50171
Unrelated, but: storing the total in the orders table is not a terribly good idea. In a normalized design you shouldn't store information that can easily be derived from existing data.
What this does is get the orders with their items and ranks them. So that the smallest item_id is rank 1 for that order, and the latest is the last rank.
ROW_NUMBER() is the function that gives the index of the rows in the output of the query (row 1 = 1, row 2 = 2). Then we can combine this with OVER (PARTITION BY), which means get the row numbers within a certain window, a partition. In this case we want to number the rows for the windows of Order_IDs. We use ORDER BY alongside to say how we order the rows within the window
When we have this table, we can then write a query on it to show the total only on the rows where the item_rank = 1
WITH rank_items_for_orders AS (
SELECT
Order_ID,
Line_Item_Price,
Line_Item_Qty,
Line_Item_ID,
Total,
ROW_NUMBER()
OVER (PARTITION BY Order_ID, ORDER BY Line_Item_ID ASC)
AS order_the_items_IDs
FROM orders o
LEFT JOIN line_items li ON o.order_id = li.order_id
ORDER BY Order_ID ASC)
SELECT
Order_ID,
Line_Item_Price,
Line_Item_Qty,
Line_Item_ID,
CASE WHEN order_the_items_IDs = 1
THEN Total ELSE NULL END
AS Total
FROM
rank_items_for_orders

SQl - Select best price based on quantity

I have a table that contains prices for a particular item based upon the quantity being ordered and the type of client placing the order ...
ID Name Quantity ClientType Price/Unit ($)
========================================================
1 Cheese 10 Consumer 20
2 Cheese 20 Consumer 15
3 Cheese 30 Consumer 12
4 Cheese 10 Restaurant 18
5 Cheese 20 Restaurant 13
6 Cheese 30 Restaurant 10
I have having trouble with WHERE clause in the SQL to select the row where the customer gets the best price based upon the quantity that is ordered. The rule is they must at least meet the quantity in order to get the price for that pricing tier. If their order is below the minimum quantity then they get the Price for the first quantity (10 in this case) and if they order more than the largest quantity (30 in this example) they get that price.
For example ... If a Restaurant orders 26 units of cheese the row with ID = 5 should be chosen. If a Consumer ordered 9 units of cheese then the row returned should be ID = 1. If the Consumer orders 50 units of cheese then they should get ID = 3.
declare #SelectedQuantity INT;
SELECT *
FROM PriceGuide
WHERE Name = 'Cheese'
AND ClientType = 'Consumer'
AND Quantity <= #SelectedQuantity
What am I missing in the WHERE clause?
Edit
The first solution didn't handle the special case correctly, as mentioned in the comments.
Next try:
SELECT TOP 1 ID, Name, Quantity, ClientType, [Price/Unit]
FROM PriceGuide
WHERE Name = 'Cheese'
AND ClientType = 'Consumer'
ORDER BY CASE WHEN Quantity <= #SelectedQuantity THEN Quantity ELSE -Quantity END DESC
Assuming that Quantity is positive, the ORDER BY will return rows that meet Quantity <= #SelectedQuantity condition first, in a descending order.
For rows that do not match this condition, it uses -Quantity for ordering. So if no rows match the condition, the one with smallest quantity will be returned.
This is a little tricky because you need to deal with the quantities less than 10.
I think the best approach is:
SELECT TOP 1 *
FROM PriceGuide
WHERE Name = 'Cheese' AND ClientType = 'Consumer'
ORDER BY (CASE WHEN #SelectedQuantity >= Quantity THEN 1 ELSE 0 END) DESC,
(CASE WHEN #SelectedQuantity >= Quantity THEN PriceUnit END) ASC,
Quantity ASC;
This version handles the minimum quantity by keeping all the rows for a given Name/ClientType, using the ORDER BY for prioritization.
Your query will return all matching rows less than the #SelectedQuantity. You only want to return the row with highest quantity less than the selected quantity, so a subquery is needed to get this result:
SELECT *
FROM PriceGuide a
WHERE a.Name = 'Cheese'
AND a.ClientType = 'Consumer'
AND a.Quantity = (SELECT MAX(Quantity) FROM PriceGuide
WHERE ClientType = a.ClientType
AND Name = a.Name
AND Quantity <= #SelectedQuantity)
In your example of 26 unit, your query will return row 4 and 5 whereas you need it to return only row 5.
declare #SelectedQuantity INT;
SELECT *
FROM PriceGuide
WHERE Name = 'Cheese'
AND ClientType = 'Consumer'
AND Quantity <= #SelectedQuantity
ORDER BY Quantity DESC
LIMIT 1

How can I SELECT the max row in a table SQL?

I have a little problem.
My table is:
Bill Product ID Units Sold
----|-----------|------------
1 | 10 | 25
1 | 20 | 30
2 | 30 | 11
3 | 40 | 40
3 | 20 | 20
I want to SELECT the product which has sold the most units; in this sample case, it should be the product with ID 20, showing 50 units.
I have tried this:
SELECT
SUM(pv."Units sold")
FROM
"Products" pv
GROUP BY
pv.Product ID;
But this shows all the products, how can I select only the product with the most units sold?
Leaving aside for the moment the possibility of having multiple products with the same number of units sold, you can always sort your results by the sum, highest first, and take the first row:
SELECT pv."Product ID", SUM(pv."Units sold")
FROM "Products" pv
GROUP BY pv."Product ID"
ORDER BY SUM(pv."Units sold") DESC
LIMIT 1
I'm not quite sure whether the double-quote syntax for column and table names will work - exact syntax will depend on your specific RDBMS.
Now, if you do want to get multiple rows when more than one product has the same sum, then the SQL will become a bit more complicated:
SELECT pv.`Product ID`, SUM(pv.`Units sold`)
FROM `Products` pv
GROUP BY pv.`Product ID`
HAVING SUM(pv.`Units sold`) = (
select max(sums)
from (
SELECT SUM(pv2.`Units sold`) as "sums"
FROM `Products` pv2
GROUP BY pv2.`Product ID`
) as subq
)
Here's the sqlfiddle
SELECT SUM(pv."Units sold") as `sum`
FROM "Products" pv
group by pv.Product ID
ORDER BY sum DESC
LIMIT 1
limit 1 + order by
The Best and effective way to this is Max function
Here's The General Syntax of Max function
SELECT MAX(ID) AS id
FROM Products;
and in your Case
SELECT MAX(Units Sold) from products
Here is the Complete Reference to MIN and MAX functions in Query
Click Here

Individual percentage for each item, throughput

With the following code I get how many items that is not "Out", but it returns percentage for all the items and not for each individual. I know it has to do with the count(date) that counts all the date of the all the unitids. Is there any way to count each item individual so it doesn't show the total percentage?
SELECT unitid, (COUNT(date)* 100 / (SELECT COUNT(*) FROM items)) AS Percentage
FROM items
WHERE date !='Out'
GROUP BY unitid
EDIT1, clarification: Lets say I have 2 of each product, product a, b, c, d and e, one of each item is 'Out'. The result I get is:
unitid Percentage
1. a 10
2. b 10
3. c 10
4. d 10
5. e 10
I'd like it to show this instead:
unitid Percentage
1. a 50
2. b 50
3. c 50
4. d 50
5. e 50
Thanks :)
You need a link between the count items and the item selected.
SELECT
unitid,
COUNT(date) * 100
/ (SELECT COUNT(*) FROM items B WHERE B.unidid = A.unitid) AS Percentage
FROM items A
WHERE date !='Out'
GROUP BY unitid
You query does not require a subquery, just a conditional aggregation:
SELECT i.unitid, 100*sum(case when date <> 'Out' then 1 else 0 end)/count(date) as Percentage
FROM items i
GROUP BY unitid
Assuming that [date] is never NULL, you express this more simply as:
select i.unitid, 100*avg(case when date<>'out' then 1.0 else 0 end) as Percentage
from items i
group by unitid
Let's see if I understand this correctly. If you have one a, two b, three c, and four d's, one of each is "Out", whatever that is, your result set should be:
unitid Percentage
1. a 100.00
2. b 50.00
3. c 33.33
4. d 25.00
To do that, you can try this:
Select counts.unitId, 100.0 *outcounts.count/ counts.count as Percentage
from (select unitid, count(*) as count
from items
where items.date ='Out'
group by unitid) as outcounts
inner join (select unitid, count(*) as count
from items
group by unitid) as counts
on outcounts.unitId = counts.unitId
here's an SQL Fiddle with the setup