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

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)
;

Related

Only get rows until qty total is met

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

Conditionally use CASE...WHEN - Oracle SQL

I have two tables like so:
tblOrders: OrderNo (pk), CurrentStepNo (fk)
tblSteps: StepNo (pk), OrderNo (fk), StepName, StepType, StepStart, StepStop
tblOrders contains tons of information about our sales orders, while tblSteps contains tons of information regarding the proper sequential steps it takes to build the material we are selling.
I am trying to construct a query that follows this logic:
"For all orders, select the current step name from the step table. If
the Step Type is equal to 'XO', then select the most recently
completed (where StepStop is not null) regular step (where StepStop is
equal to 'YY')"
I have the following query:
SELECT
tblOrders.*,
tblSteps.StepName
FROM
tblOrders
INNER JOIN tblSteps
ON tblOrders.OrderNo = tblSteps.OrderNo
AND tblOrders.CurrentStepNo = tblSteps.StepNo
Which successfully returns to me the current step name for an in-process order. What I need to achieve is, when the tblOrders.CurrentStepNo is of type 'XO', to find the MAX(tblSteps.StepStop) WHERE tblSteps.StepType = 'YY'. However, I am having trouble putting that logic into my already working query.
Note: I am sorry for the lack of sample data in this example. I would normally post but cannot in this instance. This is also not a homework question.
I have reviewed these references:
Case in Select Statement
https://blogs.msdn.microsoft.com/craigfr/2006/08/23/subqueries-in-case-expressions/
But no luck so far.
I have tried this:
SELECT
tblOrders.*,
CASE
WHEN tblSteps.StepType = 'XO' THEN (-- Some logic here)
ELSE tblSteps.StepName
END AS StepName
FROM
tblOrders
INNER JOIN tblSteps
ON tblOrders.OrderNo = tblSteps.OrderNo
AND tblOrders.CurrentStepNo = tblSteps.StepNo
But am struggling to properly formulate the logic
Join all steps, rank them with ROW_NUMBER, and stay with the best ranked:
select *
from
(
select
o.*,
s.*,
row_number() over
(partition by o.orderno
order by case when s.steptype <> 'XO' and s.stepno = o.currentstepno then 1
when s.steptype <> 'YY' then 2
else 3 end, s.stepstop desc nulls last) as rn
from tblorders o
join tblsteps s on s.orderno = o.orderno
) ranked
where rn = 1
order by orderno;

Selecting average total where an associated table has id present

I'm fairly new to SQL and I'm trying to answer this question:
What is the average order total, where product X is present.
We have an orders table, and line items. An order has many line items, which again store the product_id.
SELECT
avg(total)
FROM
orders
WHERE
(shipment_state = 'shipped')
AND (delivery_date BETWEEN '2017-09-11' AND '2017-09-18');
is what I have now and that is working, however I do not know how to fetch and calculate it based on another table (in this case line_item)
You can use exists:
SELECT AVG(o.total)
FROM orders o
WHERE o.shipment_state = 'shipped' AND
o.delivery_date BETWEEN '2017-09-11' AND '2017-09-18' AND
EXISTS (SELECT 1
FROM orderlines ol
WHERE ol.order_id = o.order_id AND
ol.product_id = X
);
Notes:
When you have more than one table in a query, always use table aliases and qualified column names.
When working with dates, BETWEEN is not recommended. The recommended construct is o.delivery_date >= '2017-09-11' AND o.delivery_date < '2017-09-19'. This works for both dates and date/time values.

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;

Oracle find whether a corresponding record exists within a number of days

I am trying to subtract to date from each other
The question says that I have to create a query to display the orders that were not shipped within 30 days of ordering.
Here is my trying:
select orderno
from orders
where 30> (select datediff(dd,s.ship_date,o.odate )
from o.orders,s.shipment);
The error I get is
ERROR at line 1:
ORA-00942: table or view does not exist
These are the two tables :
SQL> desc orders
Name Null? Type
----------------------------------------- -------- ----------------------------
ORDERNO NOT NULL NUMBER(3)
ODATE NOT NULL DATE
CUSTNO NUMBER(3)
ORD_AMT NUMBER(5)
SQL> desc shipment
Name Null? Type
----------------------------------------- -------- ----------------------------
ORDERNO NOT NULL NUMBER(3)
WAREHOUSENO NOT NULL VARCHAR2(3)
SHIP_DATE DATE
You'd be wanting something along the lines of:
select ...
from orders o
where not exists (
select null
from shipments s
where s.orderno = o.orderno
and s.ship_date <= (o.odate + 30))
Date arithmetic is pretty easy if you just want a difference in days, as you can add or subtract days as integers. If it were months, quarters or years you'd want to use Add_Months().
Also, it's better in the query above to say "shipment_date <= (order_date + 30)" rather than "(shipment_date - order_date) <= 30)" as it lets indexes be used on the join key and shipment date combined. In practice you'd probably want an index on (s.orderno, s.ship_date) so that the shipment table does not have to be accessed for this query.
I used NOT EXISTS here because in the case that there might be multiple shipments per order you would want the query stop finding additional shipments if it has found a single one.
Here is one method, using Oracle syntax:
select o.orderno
from orders o
where 30 > (select o.date - s.ship_date
from shipment s
where s.orderno = o.orderno
);
Note the correlation clause in the subquery, but each table is only mentioned once.
The problem that you have is that an order could ship on more than on occasion -- and this would generate an error in the query, because the subquery would return more than one row. One solution is aggregation. You need to decide if the question is "the entire order does not ship within 30 days" or "no part of the order ships within 30 days". The latter would use MIN():
select o.orderno
from orders o
where (select MIN(o.date - s.ship_date)
from shipment s
where s.orderno = o.orderno
) > 30;
Your Syntax is wrong and you are trying to do a cross join implicitely. I think what you need is an INNER JOIN which i assume is going to return one row (if it returns multiple rows then use >ALL) like:
select orderno
from orders
where 30> (select s.ship_date - o.odate
from orders o INNER JOIN shipment s
ON o.orderNo = s.orderNo);