Only get rows until qty total is met - sql

I have an order table (product, qty_required) and a stock/bin location table (product, bin_location, qty_free) which is a one->many (a product may be stored in multiple bins).
Please, please (pretty please!) Does anybody know how to:
When producing a picking report, I only want to return the first x bins for each product ordered THAT SATISFIES the qty_required on the order.
For example
An order requires product 'ABC', QTY 10
Product 'ABC' is in the following locations (this is listed using FIFO rules so oldest first):
LOC1, 3 free
LOC2, 4 free
LOC3, 6 free
LOC4, 18 free
LOC5, 2 free
so. on the report, I'd ONLY want to see the first 3 locations, as the total of those (13) satisfies the order quantity of 10...
Ie:
LOC1, 3
LOC2, 4
LOC3, 6

Use sum(qty_free) over(partition by product order by placement_date desc, bin_location) to calculate running sum and filter rows by your threshold in outer query (select from select). Added location in order by to exclude sum of all locations where placement was in the same day.
with s as (
select st.*,
sum(qty_free) over(partition by product order by placement_date asc, bin_location) as rsum
from stock st
)
select
o.product,
s.bin_location,
s.qty_free,
o.qty_requested
from orders o
left join s
on o.product = s.product
and s.rsum <= o.qty_requested
UPD: Since that turned out that your SQL Server version is so old that there's no analytic function in it, here's another less performant way to do this (maybe need some fixes, didn't tested on real data).
And fiddle with some setup.
with ord_key as (
select stock.*,
/*Generate order key for FIFO*/
row_number() over(order by
placement_date desc,
bin_location asc
) as sort_order_key
from stock
)
, rsum as (
/*Calculate sum of all the items before current*/
select
b.product,
b.bin_location,
b.placement_date,
b.qty_free,
coalesce(sum(sub.item_sum), 0) as rsum
from ord_key as b
left join (
/*Group by partition key and orderby key*/
select
product,
sort_order_key,
sum(qty_free) as item_sum
from ord_key
group by
product,
sort_order_key
) as sub
on b.product = sub.product
and b.sort_order_key > sub.sort_order_key
group by
b.product,
b.bin_location,
b.placement_date,
b.qty_free
)
, calc_quantities as (
select
o.product,
s.placement_date,
s.bin_location,
s.qty_free,
s.rsum,
o.qty_requested,
case
when o.qty_requested > s.rsum + s.qty_free
then s.qty_free
else s.rsum + s.qty_free - o.qty_requested
end as qty_to_retrieve
from orders o
left join rsum s
on o.product = s.product
and s.rsum < o.qty_requested
)
select
s.*,
qty_free - qty_to_retrieve as stock_left
from calc_quantities s
order by
product,
placement_date desc,
bin_location desc

Related

Combining SQL queries into one with various having/group by/where rownum

I currently have three ORACLE SQL queries which are similar to this simplified example.
I get a list of customers which fulfill my requirements:
CREATE VIEW customerQRY AS
SELECT
o.customer_id,
o.order_id,
si.item_id,
o.price,
o.discount
FROM
Orders o
JOIN StockItems si ON o.order_id = si.order_id
WHERE
o.returned = 'N'
AND o.num_items = 1
AND o.completed = 'Y'
AND o.order_date BETWEEN TO_DATE('01-01-2019', 'DD-MM-YYYY') AND TO_DATE('01-01-2020', 'DD-MM-YYYY')
;
From those I get the top 1000 customers which have bought more than 10 items at max 10% discount:
CREATE TABLE CustomerSamples
SELECT
customer_id
FROM (
SELECT
customer_id
FROM
customerQRY
GROUP BY
customer_id
HAVING
COUNT(DISTINCT(order_id)) > 9
AND discount < 11
ORDER BY
COUNT(DISTINCT(order_id)) DESC,
discount DESC
)
WHERE
ROWNUM < 1001
;
Then I get all the data related to the order and items for this subset of customers:
(edit: this is actually not totally correct: I want the order details here to be a subset of the orders specified in CustomerSamples i.e. the ones which fall into the discount < 11 category; this can be done with a "where" clause here or however defined in a potential single query)
SELECT
Orders.*,
StockItems.*
FROM
CustomerSamples cs
JOIN Orders ON Orders.customer_id = cs.customer_id
JOIN StockItems ON StockItems.order_id = Orders.order_id
;
(please forgive any missed syntax errors as I've simplified the real ones - these run correctly in reality)
This is fair enough - it works - but I was asked to try and combine this into one query which makes sense for us with running on production boxes etc.
I have gone back and forth trying different things, but can't come up with a sensible solution!
Sure I can literally use customerQry as a subquery in the CustomerSamples, but this means I don't have the data from customerQRY and suddenly things get more complicated. I can't return order_ids from query 2 as we are grouping on the customer and counting the order_ids.
I can't see a way to get the 1000 customer_ids and their related order_ids in one go. I feel like I'm missing an obvious solution here, but I can't see it. Anyone have any ideas? Am I just fighting a waterfall?
If you use the texts of your request, then an example:
SELECT
Orders.*,
StockItems.*
FROM
Orders JOIN StockItems ON StockItems.order_id = Orders.order_id
WHERE
Orders.customer_id in (
SELECT
customer_id
FROM (
SELECT
customer_id
FROM
(
SELECT
o.customer_id,
o.order_id,
si.item_id,
o.price,
o.discount
FROM
Orders o
JOIN StockItems si ON o.order_id = si.order_id
WHERE
o.returned = 'N'
AND o.num_items = 1
AND o.completed = 'Y'
AND o.order_date BETWEEN TO_DATE('01-01-2019', 'DD-MM-YYYY') AND TO_DATE('01-01-2020', 'DD-MM-YYYY')
)
GROUP BY
customer_id
HAVING
COUNT(DISTINCT(order_id)) > 9
AND discount < 11
ORDER BY
COUNT(DISTINCT(order_id)) DESC,
discount DESC
)
WHERE
ROWNUM < 1001)
;

Find similar sales orders in SQL

This is my first post.
I work at a manufacturing company and most of the products we are making are custom made.
We believe we can find some commonalities in the products we sale.
To do this, we need to analyze sales orders and compare them to all the sales orders in our system to find identical ones.
Here's an example in form of a SQL result:
etc...
+------------------------------+
| OrderId ProductCode Qty |
+------------------------------+
| SS1234 Widget1 1 |
| SS1234 Widget2 3 |
| SS1234 Widget3 1 |
+------------------------------+
I would like to find orders similar to SS1234, ie orders with the same products (widget1, widget2 and widget3) and the same quantities.
How do I do this in SQL Server 2008R2?
Thanks for your help!
Raf
I won't be able to test this before I go to bed for the evening. This is an overly verbose approach, but I wanted to grind this out as quickly as possible so I tried to use structure / syntax that I know well, instead of trying to write more concise, efficient code that would require I lean on the documentation. Basically, we're counting the number of items in each order, selecting a pair of order ids every time we find two matching line items, then we count how many times an exact pair of order IDs appears. Use inner joins to filter out pairs that matched fewer times than there are products in the order.
WITH
ProductCounts AS (
SELECT COUNT(OrderID) AS ProductCodesInOrder, OrderID
FROM Table
GROUP BY OrderID
), MatchingLineItems AS (
SELECT A.OrderID AS FirstOrderID, B.OrderID AS SecondOrderID
FROM Table AS A
INNER JOIN Table AS B
ON A.ProductCode = B.ProductCode AND A.Qty = B.Qty
ORDER BY FirstOrderID, SecondOrderID
), MatchTotals AS (
SELECT
COUNT(FirstOrderID) AS Matches, FirstOrderID, SecondOrderID
FROM MatchingLineItems
GROUP BY FirstOrderID, SecondOrderID
), FirstMatches AS (
SELECT MatchTotals.FirstOrderID, MatchTotals.SecondOrderID, MatchTotals.Matches
FROM MatchTotals
INNER JOIN ProductCounts
ON MatchTotals.FirstOrderID = ProductCounts.OrderID
WHERE MatchTotals.Matches = ProductCounts.ProductCodesInOrder
)
SELECT FirstMatches.FirstOrderID, FirstMatches.SecondOrderID
FROM FirstMatches
INNER JOIN ProductCounts
ON FirstMatches.SecondOrderID = ProductCounts.OrderID
WHERE FirstMatches.Matches = ProductCounts.ProductCodesInOrder
Setup:
CREATE TABLE #ord (
OrderId VARCHAR(20),
ProductCode VARCHAR(40),
qty int
)
INSERT INTO #ord (OrderId, ProductCode, Qty)
VALUES
('SS1234','Widget1',1)
,('SS1234','Widget2',3)
,('SS1234','Widget3',1)
,('SS1234a','Widget1',1)
,('SS1234a','Widget2',3)
,('SS1234a','Widget3',1)
,('xSS1234','Widget1',1)
,('xSS1234','Widget2',3)
,('xSS1234','Widget3',1)
,('xSS1234','Widget4',1)
,('ySS1234','Widget1',10)
,('ySS1234','Widget2',3)
,('ySS1234','Widget3',1)
,('zSS1234','Widget2',3)
,('zSS1234','Widget3',1)
;
Query:
with CTE as (
select distinct
o.OrderID, ca.ProductString, ca.QtyString
from #ord o
cross apply (
SELECT
STUFF((
SELECT
', ' + o2.ProductCode
FROM #ord o2
WHERE o.OrderID = o2.OrderID
ORDER BY o2.ProductCode
FOR XML PATH ('')
)
, 1, 1, '')
, STUFF((
SELECT
', ' + cast(o2.Qty as varchar)
FROM #ord o2
WHERE o.OrderID = o2.OrderID
ORDER BY o2.ProductCode
FOR XML PATH ('')
)
, 1, 1, '')
) ca (ProductString, QtyString)
)
select
ProductString, QtyString, count(*) Num_Orders
from CTE
group by
ProductString, QtyString
having
count(*) > 1
order by
Num_Orders DESC
, ProductString
Result:
ProductString QtyString Num_Orders
Widget1, Widget2, Widget3 1, 3, 1 2
See: http://rextester.com/DJEN59714

SELECT TOP 10 rows

I have built an SQL Query that returns me the top 10 customers which have the highest outstanding. The oustanding is on product level (each product has its own outstanding).
Untill now everything works fine, my only problem is that if a certain customer has more then 1 product then the second product or more should be categorized under the same customer_id like in the second picture (because the first product that has the highest outstanding contagions the second product that may have a lower outstanding that the other 9 clients of top 10).
How can I modify my query in order to do that? Is it possible in SQL Server 2012?
My query is:
select top 10 CUSTOMER_ID
,S90T01_GROSS_EXPOSURE_THSD_EUR
,S90T01_COGNOS_PROD_NAME
,S90T01_DPD_C
,PREVIOUS_BUCKET_DPD_REP
,S90T01_BUCKET_DPD_REP
from [dbo].[DM_07MONTHLY_DATA]
where S90T01_CLIENT_SEGMENT = 'PI'
and YYYY_MM = '2017_01'
group by CUSTOMER_ID
,S90T01_GROSS_EXPOSURE_THSD_EUR
,S90T01_COGNOS_PROD_NAME
,S90T01_DPD_C
,PREVIOUS_BUCKET_DPD_REP
,S90T01_BUCKET_DPD_REP
order by S90T01_GROSS_EXPOSURE_THSD_EUR desc;
You need to calculate the top Customers first, then pull out all their products. You can do this with a Common Table Expression.
As you haven't provided any test data this is untested, but I think it will work for you:
with top10 as
(
select top 10 CUSTOMER_ID
,sum(S90T01_GROSS_EXPOSURE_THSD_EUR) as TOTAL_S90T01_GROSS_EXPOSURE_THSD_EUR
from [dbo].[DM_07MONTHLY_DATA]
where S90T01_CLIENT_SEGMENT = 'PI'
and YYYY_MM = '2017_01'
group by CUSTOMER_ID
order by TOTAL_S90T01_GROSS_EXPOSURE_THSD_EUR desc
)
select m.CUSTOMER_ID
,m.S90T01_GROSS_EXPOSURE_THSD_EUR
,m.S90T01_COGNOS_PROD_NAME
,m.S90T01_DPD_C
,m.PREVIOUS_BUCKET_DPD_REP
,m.S90T01_BUCKET_DPD_REP
from [dbo].[DM_07MONTHLY_DATA] m
join top10 t
on m.CUSTOMER_ID = t.CUSTOMER_ID
order by t.TOTAL_S90T01_GROSS_EXPOSURE_THSD_EUR desc
,m.S90T01_GROSS_EXPOSURE_THSD_EUR;

select least row per group in SQL

I am trying to select the min price of each condition category. I did some search and wrote the code below. However, it shows null for the selected fields. Any solution?
SELECT Sales.Sale_ID, Sales.Sale_Price, Sales.Condition
FROM Items
LEFT JOIN Sales ON ( Items.Item_ID = Sales.Item_ID
AND Sales.Expires_DateTime > NOW( )
AND Sales.Sale_Price = (
SELECT MIN( s2.Sale_Price )
FROM Sales s2
WHERE Sales.`Condition` = s2.`Condition` ) )
WHERE Items.ISBN =9780077225957
A little more complicated solution, but one that includes your Sale_ID is below.
SELECT TOP 1 Sale_Price, Sale_ID, Condition
FROM Sales
WHERE Sale_Price IN (SELECT MIN(Sale_Price)
FROM Sales
WHERE
Expires_DateTime > NOW()
AND
Item_ID IN
(SELECT Item_ID FROM Items WHERE ISBN = 9780077225957)
GROUP BY Condition )
The 'TOP 1' is there in case more than 1 sale had the same minimum price and you only wanted one returned.
(internal query taken directly from #Michael Ames answer)
If you don't need Sales.Sale_ID, this solution is simpler:
SELECT MIN(Sale_Price), Condition
FROM Sales
WHERE Expires_DateTime > NOW()
AND Item_ID IN
(SELECT Item_ID FROM Items WHERE ISBN = 9780077225957)
GROUP BY Condition
Good luck!

"Partitioned" sorting in a SQL query

The following SQL query that displays products sold sorted by cost and number of orders have to be sorted in a partitioned manner. Namely, products with the cost of under $100 should go first and then everything else that is > $100 should follow it. Adding HAVING TS.TotalSold < 100 to the query would accomplish this for the first partition, but would filter out other products. The operation should be atomic, so that this query can be executed only once.
NOTE: cost by which the query has to be partitioned is calculated as a max of two cost columns, which makes things a bit more complicated (the proposed solutions of CASE WHEN won't work as HighestCost is not a column)
SELECT PS.ProductName, TS.TotalSold,
((PS.Cost1 + PS.Cost2 + ABS(PS.Cost1-PS.Cost2)) / 2) as HighestCost
FROM Products as PS
CROSS APPLY
(SELECT
(SELECT COUNT(OrderId)
FROM Orders as OS
WHERE OS.ProductId=PS.ProductId)
as TotalSold) TS
ORDER BY HighestCost ASC, TS.TotalSold
EDIT: modified the query to include calculated cost by which the query has to be partitioned.
EDITED
SELECT *
FROM
(
SELECT PS.ProductName, TS.TotalSold,
((PS.Cost1 + PS.Cost2 + ABS(PS.Cost1-PS.Cost2)) / 2) as HighestCost
FROM Products as PS
CROSS APPLY
(SELECT COUNT(OrderId) as TotalSold
FROM Orders as OS
WHERE OS.ProductId=PS.ProductId) TS
) SQ
ORDER BY CASE WHEN HighestCost > 100 THEN 1 END ASC, TotalSold
original below
SELECT PS.ProductName, TS.TotalSold
FROM Products as PS
CROSS APPLY
(SELECT COUNT(OrderId) as TotalSold
FROM Orders as OS
WHERE OS.ProductId=PS.ProductId) TS
ORDER BY
CASE WHEN TS.TotalSold > 100 THEN 1 END, PS.Cost ASC, TS.TotalSold
You may notice I removed a subquery level since it was extraneous.
I don't know which dbms you are using but in mine I would use a calculated column to assign a partitionId, and sort by that. Something like this:
SELECT PS.ProductName, TS.TotalSold,
(if cost < 100 then 1 else 2 endif) as partition
FROM Products as PS
CROSS APPLY
(SELECT
(SELECT COUNT(OrderId)
FROM Orders as OS
WHERE OS.ProductId=PS.ProductId)
as TotalSold) TS
ORDER BY partition, PS.Cost ASC, TS.TotalSold