Join multiple tables Oracle - sql

I have 3 tables, a purchase (p), a refund (r) and a send to finance software batching table (b).
Set up like this:
purchases
=========
order_number VARHCAR(10)
order_id (number)
order_date (date)
refunds
=======
refund_id (number)
order_id <-- relates to purchases table (number)
refund_date (date)
send_to_finance_batch
=====================
generic_id (number) <-- this can be filled in by an number of tables
type (text)
status_reason (text)
satus_id (number)
(more or less)
What I need to figure out is how to do a left join (since the generic_id can come from several entities) refunds to purchases?
Right now I have this:
SELECT p.order_number,
b.generic_id,
b.status_id,
b.status_reason,
FROM send_to_finance_batching b
LEFT JOIN purchases p ON b.generic_id = p.order_id
WHERE...
refund_id OR order_id will equal the generic_id so I need to relate generic_id to refund_id and then from the refunds table order_id to orders table.
My desired output is to be able to fill in p.order_number, right now I have the other columns but since order_number only exists on the purchase table id like to link it back to the refunds table by joining on the batching table.
What I have right now is this:
Order number | type | generic_id | status_reason | status_id
==============================================================
| refund | 1234567890 | 'stuck' | 99
WH763HWW3499 | purch | 7534567654 | 'error' | 99
I would like to be able to fill in the order number on the refunds, but since generic_id relates to the refund_id I need to be able to use the order_id from refunds to get the info from purchases and have a complete query

Option 1: Using UNION
In this case you would lose rows that neither can be joined to purchases nor refunds.
SELECT p.order_number,
b.generic_id,
b.status_id,
b.status_reason
FROM send_to_finance_batching b
JOIN purchases p ON b.generic_id = p.order_id
WHERE...
UNION
SELECT p.order_number,
b.generic_id,
b.status_id,
b.status_reason
FROM send_to_finance_batching b
JOIN refunds r ON b.generic_id = r.refund_id
JOIN purchases p ON r.order_id = p.order_id
WHERE...
Option 2: JOIN purchases multiple times and using COALESCE
SELECT coalesce(p.order_number, p2.order_number) order_number,
b.generic_id,
b.status_id,
b.status_reason
FROM send_to_finance_batching b
LEFT JOIN purchases p ON b.generic_id = p.order_id
LEFT JOIN refunds r ON b.generic_id = r.refund_id
LEFT JOIN purchases p2 ON r.order_id = p2.order_id
WHERE...
Option 3: Use COALESCE on JOIN condition
SELECT p.order_number,
b.generic_id,
b.status_id,
b.status_reason
FROM send_to_finance_batching b
LEFT JOIN refunds r ON b.generic_id = r.refund_id
LEFT JOIN purchases p ON coalesce(r.order_id, b.generic_id) = p.order_id
WHERE...

Outer join the two tables, but consider the type column in order not to join the wrong entity. Then use COALESCE to display the order number from the referenced row.
SELECT
COALESCE(p.order_id, r.order_id) AS order_id,
fb.generic_id,
fb.status_id,
fb.status_reason
FROM send_to_finance_batching fb
LEFT JOIN purchases p ON fb.generic_id = p.order_id AND fb.type = 'purch'
LEFT JOIN refunds r ON fb.generic_id = r.refund_id AND fb.type = 'refund'
ORDER BY order_id;

Related

Selecting customers with multiple purchases

teble relationship
I have 4 tables for keeping track of purchases by clients. My goal is to select the names of clients that purchased 2 specific products (e.g. book and pencils)
The query which I thought of (and which is obviously incorrect) is:
FROM customer c
join purchase p on c.customer_id = p.customer_id
join product pr on p.product_id = pr.product_id
WHERE EXISTS (
SELECT 1
FROM Purchase
WHERE p.customer_id = c.customer_id
AND pr.product_name = 'Book')
AND EXISTS (
SELECT 1
FROM Purchase
WHERE p.customer_id = c.customer_id
AND pr.product_name = 'Pencils')
Which return nothing, when I know that there is at least 1 customer than fits the criteria.
Thank you in advance!
You can use in, group by, and having to fetch all customer_id which bought both pencil and book. The having is there to ensure both items actually bought.
with
relevant_purchases as
(
select customer_id
from purchase pu
left join product pr
on pu.product_id = pr.product_id
where product_name in ('book', 'pencil')
group by customer_id
having count(distinct pu.product_id) = 2
)
select *
from customer c
where exists (
select *
from relevant_purchases rp
where c.customer_id = rp.customer_id
);
Fiddle: https://www.db-fiddle.com/f/vdpUWSse7gcLi1CmnNrRgs/1

SQL: How to pull particular orders based on order details?

I have four tables namely customers, orders, orderDetails and Products.
Customer table
cId cName
1 James
2 Adam
3 Ed
Order table
oId cId
1 1
2 2
3 3
OrderDetails table
oId odId pId Quantity
1 1 1 50
1 2 2 45
2 3 2 52
3 4 1 44
Products table
pId PName
1 Apple
2 Orange
I want the list of customers who have never ordered Oranges. I am able to pull records of customers whose order details don't have oranges. But in one of the case, James has ordered both apples and oranges. So, the query should not pull James. I can do this with a larger query. But I want this with a smaller query where something I'm missing.
SQL
SELECT c.cId, c.cName, p.PName, od.Quantity FROM customers c
LEFT JOIN orders o ON c.cId = o.cId
LEFT JOIN orderDetails od ON o.oId = od.oId
JOIN products p ON od.pId = p.pId
WHERE od.pId != 2
I would do this using not exists:
with has_oranges as (
select o.*
from orders o join
orderlines ol
on o.oid = ol.oid
where ol.pid = 2
)
select c.*
from customers c
where not exists (select 1
from has_oranges ho
where ho.cid = c.cid
);
If you want customer information, I don't see what oid has to do with anything.
Notes:
The CTE determines who actually has oranges.
You don't need the products table, because you are using the pid.
Use NOT EXISTS
SELECT *
FROM Customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o
JOIN orderDetails od ON o.oId = od.oId
JOIN products p ON od.pId = p.pId
WHERE p.pName = 'oranges' AND c.cId = o.cId
)
You want all customers that have never ordered oranges. So select all customer IDs that ordered oranges and only show customers that are not in this data set.
select *
from customers c
where cid not in
(
select cid
from orderdetails
where pid = (select pid from products where pname = 'Orange'
);
select * from CustomerTbl where Id in (select t1.Id from CustomerTbl t1
left join OrderTbl t2 on t1.Id = t2.CustomerId
left join OrderDetailTbl t3 on t3.OrderId = t2.Id
left join ProductTbl t4 on t4.Id = t3.ProductId
where t4.Id != 2)
This will return Customers who not ordered Oranges.
This is the sqlfiddle link : http://sqlfiddle.com/#!6/c908d/6
SQL Server 2008 introduced the EXCEPT and INTERSECT keywords for doing this sort of thing. I tend to find that the queries are clearer than when using CTEs.
Microsoft Documentation
select c.cId
from Customer c
except
select o.cId
from Orders o
join OrderDetail od on o.oId = od.oId
and od.pId = 2
cId
-----------
3
You can add the name to the result set by joining to the Customer table in the second half of the query:
select c.cId, c.cName
from Customer c
except
select o.cId, c.cName
from Orders o
join OrderDetail od on o.oId = od.oId
join Customer c on c.cId = o.cId
and od.pId = 2
cId cName
----------- --------------------
3 Ed
We have to eliminate users who took orange. So in the below query i have used sub query
Select C.Cname,OH.oid,PM.Pname,OD.Quantity from Customers C
inner join OrderHeader OH ON C.cid=OH.Cid
inner join OrderDetails OD on oh.oid=od.oid
inner join ProductMast PM on PM.pid=OD.pid where OH.oid not in (select oid
from OrderDetails where pid = 2)

left join two tables on a non-unique column in right table

I have two tables in sql server and i wanna select and join some data from these table.the first tables have some customer like:
---------------
customer id
Dave 1
Tom 2
---------------
and second table i table of purchases that includes list of last purchases with cost and which customer bought that Product:
------------------
product date customer id
PC 1-1-2000 1
phone 2-3-2000 2
laptop 3-1-2000 1
------------------
i wanna select first table (customers info) with last date of their purchases!
i tried left join but that doesn't give me last purchases becuase customer id is not unique in second table! how can i do this function with SQL server query? Regards
If you just want the max date, use aggregation. I would recommend a left join for customers who have made no purchases:
select c.customer, c.id, max(p.date)
from customers c left join
purchases p
on c.id = p.customer_id
group by c.customer, c.id;
Use the not exists clause for the win!
select c.customer, p.*
from Customer as c
inner join Purchase as p
on p.customer_id = c.id
where not exists (
select 1
from Purchase as p2
where p2.customer_id = p.customer_id
and p2.date > p.date
)
I think you can use inner join and group by
select table1.customer, table1.id, table.max(date)
from table1
inner join table2 on table1.id = table2.id
group by table1.customer, table1.id

SQL aggregate query with one-to-many relationship with postgres

I'm trying to aggregate a list of product skus with a query that relates through a line_items table. I've abstracted a simple example of my use case:
my expected result would look like this:
id name skus
1 mike bar sku1,sku2,sku3
2 bort baz sku4
given a schema and data like:
products
id sku
1 sku1
2 sku2
3 sku3
4 sku4
line_items
id order_id product_id
1 1 1
2 1 2
3 1 3
4 2 4
addresses
id name
1 'bill foo'
2 'mike bar'
3 'bort baz'
orders
id address_id total
1 2 66
2 3 99
here's a working query, but it's not correct, i'm getting ALL products for each order. my WHERE should be using orders.id
http://sqlfiddle.com/#!15/70cd7/3/0
however, i can't seem to use orders.id? i'm guessing i need to use a JOIN or LEFT JOIN or somehow change the order of things in my query...
http://sqlfiddle.com/#!15/70cd7/4
http://sqlfiddle.com/#!15/70cd7/12
SELECT orders.id,
addresses.name,
array_agg(DISTINCT products.sku )
FROM orders
LEFT JOIN addresses
ON orders.address_id = addresses.id
LEFT JOIN line_items
ON line_items.order_id = orders.id
LEFT JOIN products
ON products.id = line_items.product_id
GROUP BY orders.id,addresses.name
You can use a correlated subquery with a JOIN to get the list of skus for each order
SELECT
o.id,
a.name,
(SELECT array_to_string(array_agg(sku), ',') AS Skus
FROM products p
INNER JOIN line_items li
ON li.product_id = p.id
WHERE li.order_id = o.id
) AS Skus
FROM orders o
INNER JOIN addresses a
ON a.id = o.address_id
ONLINE DEMO
One solution could be
SELECT orders.id,
addresses.name,
(SELECT string_agg(sku,',') AS skus
FROM products
WHERE id IN
(SELECT DISTINCT line_items.product_id
FROM line_items
WHERE line_items.order_id = orders.id))
FROM orders
inner join addresses
on orders.address_id = addresses.id
;
SQLFiddle

SQL Server query: Sum columns from 2 tables

I have these 3 tables:
APPOINTMENT: APPT_TIME, CUST_ID, STY_ID, SERVICE_ID, PROD_NUM
PRODUCT: PROD_NUM, PROD_TYPE, PROD_NAME, PROD_VENDOR, PRICE
SERVICE_: SERVICE_ID, SERVICE_DESC, EST_TIME, PRICE
What I need to do is sum the PRICE from PRODUCT table and the PRICE from the SERVICE_ table for each appointment/customer.
Here is what the result needs to look like:
CUST_ID final_price
------------
1234 45.16
4678 63.25
4587 78.58
7894 25.15
Assuming that prod_num and service_id are unique keys (or primary keys) in their respective tables, then aggregation is not necessary. To accomplish this you should use a left outer join, just in case some appointments don't have both fields:
select a.cust_id,
(coalesce(p.price, 0) + coalesce(s.price, 0)) as Final_Price
from appointment a left outer join
product p
on p.prod_num = a.prod_num left outer join
service s
on s.service_id = a.service_id;
If there could be multiple rows in the reference tables for a given product or service, then you should do the aggregation before the join.
SELECT A.CUST_ID
, SUM(ISNULL(P.PRICE,0) + ISNULL(S.PRICE,0)) Final_Price
FROM APPOINTMENT A
INNER JOIN PRODUCT P ON A.PROD_NUM = P.PROD_NUM
INNER JOIN SERVICE S ON S.SERVICE_ID = A.SERVICE_ID
GROUP BY A.CUST_ID