How to select data based on two tables - sql

I have two tables and want to select only the ones where a certain status is 'complete' in the second table. The most important factor is that all shipments need to be completed.
First Table (orders) includes a unique order id and several cells with customer details. For example
Order_id | Name
1001 | John
1002 | Paula
1003 | Ben
The second table (shipments) has all the items a customer ordered and the status whether they were delivered e.g.
Order_id | Shipment_number | Status
1001 | 8004 | complete
1001 | 8003 | processing
1002 | 8005 | complete
1003 | 8008 | processing
1003 | 8007 | processing
1003 | 8009 | complete
I tried it with the following code but unfortunately the results show all order ids where at least one of the associated shipments is 'complete'.
SELECT
order_id,
name
FROM orders
INNER JOIN shipments ON orders.order_id = shipments.order_id
WHERE
shipments.status = 'complete';
I'm pretty new to SQL and really struggling with this. Thanks in advance :)

SELECT Orders.OrderID
FROM orders
WHERE NOT EXISTS (SELECT OrderID
FROM shipments
WHERE status != 'complete' AND
shipments.OrderID = Orders.OrderID)

You can use nested queries to do this:
Write a query to take a count of the shipments.order_id group by order_id, this gives you the total shipments for each order, select the order_id and the count.
Write a query to take a count of the shipments.order_id group by order_id where shipments.status = 'complete', this gives you the completed shipments for each order, select the order_id and the count.
join the result of 1 and 2 with order on order id where (1.count = 2.count), select the name and the order_id.

You could try this
SELECT order_id FROM <orders>
WHERE order_id NOT IN (SELECT order_id FROM <shipments> WHERE status <> 'complete')
Even though, depending on your system I would consider adding a field in order table (effectively de-normalizing it, but benefits/drawbacks depend on how often you need this information) and update it with current order status.
If you need this information often, I might be worth it.

SELECT ship.order_id, ord.name
FROM shipments as ship
left join orders as ord on
ord.order_id = ship.order_id
where ship.status = 'complete'

Generally, the best way to do this is to have a level at the order itself that indicates whether or not the order is completely shipped.
But, this would work although it might not scale really well. To get it to scale better, you would have to do another join BACK to orders in the subqueries to limit the orders that you are looking through.
select order_id, name from
orders JOIN
(select order_id, count(*) from shipments where status = 'complete' group by order_id
INTERSECT
select order_id, count(*) from shipments group by order_id)
on order_id

Related

How can I get a snapshot view from an event-based table in SQL?

On MariaDB I have an orders table where I can have multiple records with the same order id, referring to events in the order lifecycle.
I need to extract a list of all orders that have not reached a "completed" state, meaning there is no record with that order id and state=completed.
So if I have the following records
Order Id | State
---------|---------
1 | new
2 | new
3 | new
1 | updated
2 | completed
1 | completed
4 | new
4 | updated
I would expect to get as a result orders 3 and 4
I need to extract a list of all orders that have not reached a "completed" state, meaning there is no record with that order id and state=completed.
Use aggregation like this:
select ol.order_id
from order_lifecycle ol
group by ol.order_id
having sum(ol.state = 'completed') = 0;
The expression state = 'completed' counts the number of rows where state = 'completed' for each order. The = 0 says that there are not any.
If you have a separate table of orders, you might find it faster to use not exists:
select o.*
from orders o
where not exists (select 1
from order_lifecycle ol
where ol.order_id = o.order_id and
ol.state = 'completed'
);
This can, in particular, take advantage of an index on order_lifecycle(order_id, state).

SQL Multiple INNER JOINS In One Select-Statement date-wise

I am using this code for inventory management system, in which i want to retrieve stock in hand date-wise from salestb using Three tables
Table Schema
Productmastertb
prod_id,
Product_name
salesdetailstb
sales_id,
Prod_id,
Prod_qty,
billno
salestb
billno
billdate
I need Result something like this
--------------------------------
Product ID | Product Name | Qty
--------------------------------
1 Mouse 10
2 Keyboard 60
3 Headphone 30
---------------------------------
Hope you will find like below solution (date wise qty):
SELECT pm.product_id
,pm.Product_Name
,SUM(sd.Prod_qty) as Qty
,s.billdate
FROM Productmastertb pm
JOIN salesdetailstb as sd ON sd.product_id = pm.product_id
JOIN salestb as s ON sd.billno = s.billno
GROUP BY pm.product_id, pm.Product_Name, s.billdate
Hope it would be helpful to you !
If its not as per your expectation, kindly provide sample output result with partition by date.

Sql query to calculate first occurrence of a sales order not fulfilled by stock

I have two tables:
Sales Orders (SO ) with fields:Part, Due_Date, Qty
Part with fields Part and Stock.
I an trying to write a query that will produce the first occurrence ( by date - SO.Due_Date) that a sales order (SO.Qty) cannot be fulfilled by the stock.
This is easy if there is no stock i.e. Part.Stock=0 or if there is only one sales order for the part (SO.Qty > Part.Stock)
If there are multiple sales orders I only want the first one shown e.g.
Part.Part = Box , Part.Stock = 250
SO.Part | SO.Due_Date | SO.Qty
Box | 26/10/2014 | 100
Box | 27/10/2014 | 100
Box | 28/10/2014 | 100 * Return this row
Box | 29/10/2014 | 100
I think I need a sub query or need to use CTE but I can't work it out unless I use a loop. The tables have thousands of parts and sales orders and I am trying to run this query as quickly as possible.
Many thanks for your help
I assume this is a learning exercise, as no real business would work this way.
Anyway, here is a query to do what you want:
select *
from sales_order as so1
where due_date =
(select min(due_date)
from sales_order as so2
inner join part as p on p.part = so2.part
where so1.part = so2.part
and stock < (
select sum(quantity)
from sales_order as so3
where so3.due_date <= so2.due_date
and so3.part = so2.part
)
)
Which I have put into a working fiddle here: http://sqlfiddle.com/#!2/bd8ab5/1
There are some assumptions such as one order per date, but I believe it answers the question.
A query that uses a self join to calculate the running quantity total for each row and selects the row with the smallest due date having a running total greater than p.stock
select so.part, so.due_date, so.quantity
from sales_order so
join part p on p.part = so.part
join sales_order so2 on so2.part = so.part
and so2.due_date <= so.due_date
where p.part = 'Box'
group by so.part, so.due_date, so.quantity
having sum(so2.quantity) > max(p.stock)
order by so.due_date limit 1

SQL Inner Join query

I have following table structures,
cust_info
cust_id
cust_name
bill_info
bill_id
cust_id
bill_amount
bill_date
paid_info
paid_id
bill_id
paid_amount
paid_date
Now my output should display records (1 jan 2013 to 1 feb 2013) between two bill_dates dates as single row as follows,
cust_name | bill_id | bill_amount | tpaid_amount | bill_date | balance
where tpaid_amount is total paid for particular bill_id
For example,
for bill id abcd, bill_amount is 10000 and user pays 2000 one time and 3000 second time
means, paid_info table contains two entries for same bill_id
bill_id | paid_amount
abcd 2000
abcd 3000
so, tpaid_amount = 2000 + 3000 = 5000 and balance = 10000 - tpaid_amount = 10000 - 5000 = 5000
Is there any way to do this with single query (inner joins)?
You'd want to join the 3 tables, then group them by bill ids and other relevant data, like so.
-- the select line, as well as getting your columns to display, is where you'll work
-- out your computed columns, or what are called aggregate functions, such as tpaid and balance
SELECT c.cust_name, p.bill_id, b.bill_amount, SUM(p.paid_amount) AS tpaid, b.bill_date, b.bill_amount - SUM(p.paid_amount) AS balance
-- joining up the 3 tables here on the id columns that point to the other tables
FROM cust_info c INNER JOIN bill_info b ON c.cust_id = b.cust_id
INNER JOIN paid_info p ON p.bill_id = b.bill_id
-- between pretty much does what it says
WHERE b.bill_date BETWEEN '2013-01-01' AND '2013-02-01'
-- in group by, we not only need to join rows together based on which bill they're for
-- (bill_id), but also any column we want to select in SELECT.
GROUP BY c.cust_name, p.bill_id, b.bill_amount, b.bill_date
A quick overview of group by: It will take your result set and smoosh rows together, based on where they have the same data in the columns you give it. Since each bill will have the same customer name, amount, date, etc, we are fine to group by those as well as the bill id, and we'll get a record for each bill. If we wanted to group it by p.paid_amount, though, since each payment would have a different one of those (possibly), you'd get a record for each payment as opposed to for each bill, which isn't what you'd want. Once group by has smooshed these rows together, you can run aggregate functions such as SUM(column). In this example, SUM(p.paid_amount) totals up all the payments that have that bill_id to work out how much has been paid. For more information, please look at W3Schools chapter on group by in their SQL tutorials.
Hope I've understood this correctly and that this helps you.
This will do the trick;
select
cust_name,
bill_id,
bill_amount,
sum(paid_amount),
bill_date,
bill_amount - sum(paid_amount)
from
cust_info
left outer join bill_info
left outer join paid_info
on bill_info.bill_id=paid_info.bill_id
on cust_info.cust_id=bill_info.cust_id
where
bill_info.bill_date between X and Y
group by
cust_name,
bill_id,
bill_amount,
bill_date

Getting products from SQL query

I'm currently working on a proprietary shopping cart system and was having a few problems with getting products out with the correct pricing.
Basically my table structure is as follows:
Products table: (Only relevant columns are represented)
----------------------------------------------------
productid | product | descr | disporder| list_price|
----------------------------------------------------
1 name desc 1 0.00
2 name desc 4 0.00
3 name desc 2 2.45
Pricing table:
----------------------------------------
priceid | productid | price | variantid|
----------------------------------------
1 1 13.91 1
2 2 54.25 4
3 2 47.23 2
Variants Table:
-------------------------------
variantid | productid | active|
-------------------------------
1 1 Y
2 2 Y
3 2 Y
So, each product can have - and in most cases does have - multiple variants. My current SQL query I have managed to create thus far is:
SELECT
products.productid, product, descr, p.price, i.image_path
FROM
products
LEFT JOIN
pricing AS p
ON
p.variantid = (SELECT variantid FROM variants
WHERE productid = products.productid LIMIT 1)
LEFT JOIN
images_T AS i
ON
i.id = products.productid
GROUP BY
products.productid
ORDER BY
products.disporder
However, my problem arises when a product does not have a variant. If a product does not have a variant associated with it, the price will be in the list_price column of the products table. How would I go about performing a check to see if a product does indeed have a variant. If not, it should effectively bypass the variants table and get the pricing from list_price within the products table.
Yes, CASE is an option, or COALESCE:
SELECT
products.productid, product, descr,
COALESCE(products.list_price, p.price) AS price,
i.image_path
...
Just join both prices and when the first is NULL the other will be selected.
The simplest way is to use a CASE in the SELECT clause, like so:
SELECT
products.productid, product, descr,
CASE
WHEN p.price IS NULL
THEN products.list_price
ELSE p.price
END AS price,
i.image_path
[...]
Since you're left-joining on pricing/variants, p.price should reliably be NULL for products with no variants.
Hopefully that's what you meant by "bypassing" the variants table. :)
You can do a full join with variants table (which will ONLY give you producs which have variants), and then UNION it with a join of producs and pricing where there exists no varian (using AND NOT EXISTS (SELECT 1 from variants WHERE p.productid=v.productid and p.variantid =v.variantid)
Otherwise, use CASE on pricing.price