Get Product Onhand Quantity - sql

Using sqlite3, I have two tables: products, orders. I want to know how many products are left in the shop.
SELECT pid,
txt,
price,
qty-coalesce((SELECT SUM(qty)
FROM ORDERS
WHERE pid=?),0)
FROM PRODUCTS
WHERE pid=?
This works if I select 1 product, I would like a list of all my products ?

SELECT
P.pid, P.txt, P.price,
P.qty - coalesce((SELECT sum(O.qty) FROM orders O WHERE O.pid = P.pid), 0)
FROM products P

Try this:
SELECT
pid,
txt,
price,
qty-coalesce(
(SELECT sum(qty)
FROM orders
WHERE orders.pid = products.pid),0)
FROM products

I recommend using:
SELECT t.pid,
t.txt,
t.price,
t.qty - IFNULL(qs.qty_sold, 0) 'onhand_qty'
FROM PRODUCTS t
LEFT JOIN (SELECT o.pid,
SUM(o.qty) 'qty_sold'
FROM ORDERS o) qs ON qs."o.pid" = t.pid
WHERE t.pid = ?
While it works, using correllated SELECT statements in the SELECT clause will have the worst performance because they are executing once for every row returned in your query.
IFNULL is preferrable to use in this case compared to COALESCE. COALESCE is intended for checking 2+ values for being null, giving a false impression when someone else reads your code. There isn't any inherent benefit - per the documentation, they are the same.
Reference: SQLite Core Functions

Related

Multi Join Table, Multiple Sums

I've got 3 tables I need to work with:
CREATE TABLE invoices (
id INTEGER,
number VARCHAR(256)
)
CREATE TABLE items (
invoice_id INTEGER,
total DECIMAL
)
CREATE TABLE payments (
invoice_id INTEGER,
total DECIMAL
)
I need a result set along the lines of:
invoices.id
invoices.number
item_total
payment_total
oustanding_balance
00001
i82
42.50
42.50
00.00
00002
i83
89.99
9.99
80.00
I tried
SELECT
invoices.*,
SUM(items.total) AS item_total,
SUM(payments.total) AS payment_total,
SUM(items.total) - SUM(payments.total) AS oustanding_balance
FROM
invoices
LEFT OUTER JOIN items ON items.invoice_id = invoices.id
LEFT OUTER JOIN payments ON payments.invoice_id = invoices.id
GROUP BY
invoices.id
But that fails. The sum for payments ends up wrong since I'm doing 2 joins here and I end up counting payments multiple times.
I ended up with
SELECT
invoices.*,
invoices.item_total - invoices.payment_total AS oustanding_balance
FROM
(
SELECT invoices.*,
(SELECT SUM(items.total FROM items WHERE items.invoice_id = invoices.id) AS item_total,
(SELECT SUM(payments.total FROM payments WHERE payments.invoice_id = invoices.id) AS payment_total
) AS invoices
But ... that feels ugly. Now I've got subqueries going on everywhere. It DOES work, but I'm concerned about performance?
There has to be some good way to do this with joins - I'm sure I'm missing something super obvious?
As you say the sum behavior with multiple joins is normal and working with sub queries (Or CTE for SQl Server) is not a bad practice.
Doing such GOUP BY on an ID and a total in sub queries won't significantly downgrade your performance (depending on your tables sizes).
Another solution could be doing one SUM sub query for each column you need. It would be easier to understand this way I think :
SELECT
invoices.id
, i_total.total as item_total
, p_total.total aspayment_total
, ( i_total.total - p_total.total) as outstanding_balance
FROM
invoices
LEFT JOIN (
SELECT invoice_id, SUM(total) as total FROM items GROUP BY invoice_id
) i_total
ON i_total.invoice_id = invoices.id
LEFT JOIN (
SELECT invoice_id, SUM(total) as total FROM payments GROUP BY invoice_id
) p_total
ON p_total.invoice_id = invoices.id
I think a common table expression (or in this case two CTEs) will give you what you want. You are using something called a scalar, which is precisely speaking not wrong, but as you correctly identified is ugly, hard to read, hard to maintain and can be non-performant in many situations.
CTE essentially take a query and makes it "behave" like a table. We define it once and then we can refer to it later.
with item_data as (
SELECT invoice_id, SUM(total) as item_total
FROM items
group by invoice_id
),
payment_data as (
SELECT invoice_id, SUM(total) as payment_total
FROM payments
group by invoice_id
)
select
i.*,
id.item_total - pd.payment_total as outstanding_balance
from
invoices i
join item_data id on i.invoice_id = id.invoice_id
join payment_data pd on i.invoice_id = pd.invoice_id
Untested, but hopefully you get the idea.

sql select with group by and join for lookup

Imagine I have a product table and an orders table. I want to list out the most recent order for each product
I imagine something like this
select name, description, price, max(date)
from product
join order on order.item = product.name
group by order.item
But my postgres DB complains that I cant have raw fields (sqlite doesnt complain) I need to have aggregate function. I can put min() for each column but that seems like a waste, given that all the values for a particular product are always the same. I wondered about 'distinct' but that doesnt seem to help here
NOTE - I need standard portable SQL , not specific to any given engine.
In Postgres, you can use distinct on:
select distinct on (o.item) p.name, description, price, date
from product p join
order o
on o.item = p.name
order by o.item, date desc;
I added aliases into the query. I strongly advise you to always qualify all column names. I would do that but I don't know where they come from in most cases.
If you require standard ANSI SQL you can use a window function:
select *
from (
select p.name, p.description, p.price,
o.date,
max(o.date) over (partition by o.item) as last_date
from product p
join "order" o on o.item = p.name
) t
where date = last_date;
But in Postgres distinct on () is usually a lot faster.
If it was Oracle or MS you would need to group by all the fields in your select that aren't aggregate functions.
It would be an extra line before "order by" with "group by p.name, description, price, date" ...
About Postgres I am not so sure, but probably it will work.
You can use correlated subquery :
select p.name, p.description, p.price, o.date
from product p inner join
order o
on o.item = p.name
where o.date = (select max(o1.date)
from order o1
where o1.item = p.name
);

Using SQL query to find details of customers who ordered > x types of products

Please note that I have seen a similar query here, but think my query is different enough to merit a separate question.
Suppose that there is a database with the following tables:
customer_table with customer_ID (key field), customer_name
orders_table with order_ID (key field), customer_ID, product_ID
Now suppose I would like to find the names of all the customers who have ordered more than 10 different types of product, and the number of types of products they ordered. Multiple orders of the same product does not count.
I think the query below should work, but have the following questions:
Is the use of count(distinct xxx) generally allowed with a "group by" statement?
Is the method I use the standard way? Does anybody have any better ideas (e.g. without involving temporary tables)?
Below is my query
select T1.customer_name, T1.customer_ID, T2.number_of_products_ordered
from customer_table T1
inner join
(
select cust.customer_ID as customer_identity, count(distinct ord.product_ID) as number_of_products_ordered
from customer_table cust
inner join order_table ord on cust.customer_ID=ord.customer_ID
group by ord.customer_ID, ord.product_ID
having count(distinct ord.product_ID) > 10
) T2
on T1.customer_ID=T2.customer_identity
order by T2.number_of_products_ordered, T1.customer_name
Isn't that what you are looking for? Seems to be a little bit simpler. Tested it on SQL Server - works fine.
SELECT customer_name, COUNT(DISTINCT product_ID) as products_count FROM customer_table
INNER JOIN orders_table ON customer_table.customer_ID = orders_table.customer_ID
GROUP BY customer_table.customer_ID, customer_name
HAVING COUNT(DISTINCT product_ID) > 10
You could do it more simply:
select
c.id,
c.cname,
count(distinct o.pid) as `uniques`
from o join c
on c.id = o.cid
group by c.id
having `uniques` > 10

SQL Selecting Distinct Count of items where 2 conditions are met

I am struggling to get a DISTINCT COUNT working with SQL DISTINCT SELECT
Not sure if I should even be using distinct here, but I have got it correct using a subquery, though it is very heavy processing wise.
This query does what I ultimately want results wise (without the weight)
SELECT DISTINCT
product_brandNAME,
product_classNAME,
(SELECT COUNT(productID) FROM products
WHERE products.product_classID = product_class.product_classID
AND products.product_brandID = product_brand.product_brandID) as COUNT
FROM products
JOIN product_brand
JOIN product_class
ON products.product_brandID = product_brand.product_brandID
AND products.product_classID = product_class.product_classID
GROUP BY productID
ORDER BY product_brandNAME
This gets close, and is much more efficient, but I can't get the count working, it only counts (obviously) the distinct count which is 1.
SELECT DISTINCT product_brandNAME, product_classNAME, COUNT(*) as COUNT
FROM products
JOIN product_brand
JOIN product_class
ON products.product_brandID = product_brand.product_brandID
AND products.product_classID = product_class.product_classID
GROUP BY productID
ORDER BY product_brandNAME
Any suggestions, I'm sure its small, and have been researching the net for hours for an answer to no avail for 2 conditions to match.
Thanks,
Have you tried following query
Edit
SELECT product_brandNAME
, product_classNAME
, COUNT(*)
FROM products
JOIN product_brand ON products.product_brandID = product_brand.product_brandID
JOIN product_class ON products.product_classID = product_class.product_classID
GROUP BY
product_brandNAME
, product_classNAME
When using GROUP BY you do not need to use a DISTINCT clause. Try the following:
SELECT productID,
product_brandNAME,
product_classNAME,
COUNT(*) as COUNT
FROM products JOIN product_brand ON products.product_brandID = product_brand.product_brandID
JOIN product_class ON products.product_classID = product_class.product_classID
GROUP BY productID,
product_brandNAME,
product_classNAME
ORDER BY product_brandNAME

passing a parameter into a subquery

i was wondering if it is possible to pass a parameter into a select subquery.
What i want to do is collect some product data from one table and then crossref the weight of the item to it's carriage cost in a shipping table to return a cost.
something like:
select cataloguenumber, productname,
(select shipping.carriagecost
from shipping
where shipping.carriageweight = weight) as carriagecost
from products
Regards
DPERROTT
While the subquery would work, a better, more readable and efficient way to define this would be as follows:
SELECT p.cataloguenumber
, p.productname,
, s.carriagecost
FROM products p
INNER JOIN
shipping s
ON p.weight = s.carriageweight
This assumes that all product weights have a corresponding entry in the shipping table. If that is not the case then change from INNER JOIN to LEFT JOIN and deal with any nulls.
select cataloguenumber, productname, shipping.carriagecost as carriagecost
from products, shipping
where shipping.carriageweight = products.weight
or am I missing something?
SELECT DISTINCT cataloguenumber, productname, shipping.carriagecost
FROM products
LEFT OUTER JOIN shipping
ON shipping.carriageweight = products.weight
Your subquery should only return 1 row, if it returns more then that your query will throw an error in run-time.
This is possible I think, but then you should retrieve the column you want to pass in your parent query.
select cataloguenumber, productname, weight
(select shipping.carriagecost
from shipping
where shipping.carriageweight = weight) as carriagecost
from products
SELECT DISTINCT products.cataloguenumber, products.productname, shipping.carriagecost
FROM products
LEFT JOIN shipping ON shipping.carriageweight = products.weight