Sum Items from Multiple Tables in SQL - sql

I have two tables, one with a list of sales, and another with the product definitions. What I'm trying to do is calculate the total revenue for each product. In the sales table, there are multiple instances of a single product, so what I need to do is sum the quantity sold for each product type and multiply that by the price.
Here is a sample table to better explain:
Sales
name | qty | sid
-----+-----+------
p1 | 2 | 1
p2 | 3 | 2
p1 | 5 | 1
p3 | 1 | 3
products
name | price | pid
-----+-------+-----
p1 | 2.99 | 1
p2 | 5.00 | 2
p3 | 4.25 | 3
This is my attempt at solving this problem:
SELECT
MAX(product.name) AS product_name,
COUNT(sales.sid) AS Count_sales
FROM
products
LEFT JOIN
sales ON sales.sid = products.pid
GROUP BY
products.pid;
I've successfully been able to count the instances of each product in the sales list. However, I can't figure out how to also account for the quantity column as well as multiplying that by the price of each product.
Any help would be appreciated.

I think you just want:
SELECT MAX(p.name) AS product_name, SUM(p.price * s.sid) AS total_price
FROM products p LEFT JOIN
sales s
ON s.sid = p.pid
GROUP BY p.pid;

Related

SQL - select values from two different tables

I have got a problem with selecting values from two different tables. Tables below:
Material:
MaterialID | MaterialName
-------------------------
1111111 | Material1
2222222 | Material2
3333333 | Material3
Stock:
MaterialID | Location | Quantity
---------------------------------
1111111 | LocA | 10
1111111 | LocB | 20
2222222 | LocC | 15
2222222 | LocD | 10
My SQL query below:
SELECT
[Material].[MaterialName] as 'Material Name',
custom.quantity as 'Total Quantity',
FROM
[Material]
inner join (
SELECT
[MaterialId] as 'Materialcode',
SUM([Quantity]) as 'Quantity'
from
[Stock]
group by
[MaterialId]
) custom
on
custom.Materialcode = [Material].[MaterialId]
The result is:
Material Name | Total Quantity
------------------------------
Material1 | 30
Material2 | 25
The problem is that in the result there is no information about Material3 (I know that the quantity is equal to 0 as it`s not in Stock table, but I need a result that will show all of the materials - like below:
Material Name | Total Quantity
------------------------------
Material1 | 30
Material2 | 25
Material3 | 0
Is it possible?
You can left join and aggregate:
select m.materialName, coalesce(sum(s.quantity), 0) total_quantity
from material m
left join stock s on s.materialID = m.materialID
group by m.materialID, m.materialName
You may also aggregate, then left join (that was your original attempt, you just need to change the join type).
Actually, you might as well use a correlated subquery - with an index on stock(materialID, quantity), this may be an efficient solution (and you don't need coalesce() here):
select
m.materialName,
(select sum(quantity) from stock s where s.materialID = m.materialID) total_quantity
from material m
Another way to express this is to use a lateral join:
select m.materialName, s.total_quantity
from material m
outer apply (select sum(quantity) total_quantity from stock s where s.materialID = m.materialID) s

Check if two products belong to same invoice

I have a table of Invoices, InvocicesLines and Products, and I'd like to select all the invoices that have two different products.
How can I do this?
Example:
Invoices table:
InvoiceNo | CustomerId | ...
=============+===============+========+
1 | 1 |
2 | 2 |
3 | 5 |
4 | 7 |
InvoicesLines table:
InvoiceNo (FK) | Id (PK) | ProductId |
===============+===============+============+
1 | 1 | 3 |
2 | 2 | 1 |
2 | 3 | 2 |
4 | 4 | 5 |
I need the invoices which have product 1 and 2:
InvoiceNo |
============+
2 |
You can join twice your invoices with your lines, and return the ones having different products. You must group the result.
select Invoices.IdInvoice
from Inovices
inner join InvoicesLines as First on First.IdInvoice = Invoices.IdInvoice
inner join InvoicesLines as Second on Second.IdInvoice = Invoice.IdInvoice
where First.IdProduct <> Second.IdProduct
group by Invoices.IdInvoice
This is going to return all the invoices with at least two different products.
But if you want to return the invoices with two different products, and only two different products, you can ensure it with a having clausule.
select Invoices.IdInvoice
from Inovices
inner join InvoicesLines as First on First.IdInvoice = Invoices.IdInvoice
inner join InvoicesLines as Second on Second.IdInvoice = Invoice.IdInvoice
where First.IdProduct <> Second.IdProduct
group by Invoices.IdInvoice
having count(Invoices.IdInvoice) = 2
Group by Invoice number and use having clause to return only those invoices which has distinct count of products equal to 2 (or greater of 1, if you want "at least two products"):
select i.InvoiceNumber
from Inoices i
inner join InvoiceLines il on il.InvoiceId = i.InoiceId
group by i.InvoiceNumber
having count(distinct il.ProductId) = 2

SUM in multi-currency

I am trying to do SUM() in a multi-currency setup. The following will demonstrate the problem that I am facing:-
Customer
-------------------------
Id | Name
1 | Mr. A
2 | Mr. B
3 | Mr. C
4 | Mr. D
-------------------------
Item
-------------------------
Id | Name | Cost | Currency
1 | Item 1 | 5 | USD
2 | Item 2 | 2 | EUR
3 | Item 3 | 10 | GBP
4 | Item 4 | 5 | GBP
5 | Item 5 | 50 | AUD
6 | Item 6 | 20 | USD
7 | Item 3 | 10 | EUR
-------------------------
Order
-------------------------
User_Id | Product_Id
1 | 1
2 | 1
1 | 2
3 | 3
1 | 5
1 | 7
1 | 5
2 | 6
3 | 4
4 | 2
-------------------------
Now, I want the output of a SELECT query that lists the Customer Name and the total amount worth of products purchased as:-
Customer Name | Amount
Mr. A | Multiple-currencies
Mr. B | 25 USD
Mr. C | 15 GBP
Mr. D | 2 EUR
So basically, I am looking for a way to add the cost of multiple products under the same customer, if all of them have the same currency, else simply show 'multiple-currencies'. Running the following query will not help:-
SELECT Customer.Name, SUM(Item.Amount) FROM Customer
INNER JOIN Order ON Order.User_Id = Customer.Id
INNER JOIN Item ON Item.Id = Order.Product_Id
GROUP BY Customer.Name
What should my query be? I am using Sqlite
I would suggest two output columns, one for the currency and one for the amount:
SELECT c.Name,
(case when max(currency) = min(currency) then sum(amount)
end) as amount,
(case when max(currency) = min(currency) then max(currency)
else 'Multiple Currencies'
end) as currency
FROM Customer c INNER JOIN
Order o
ON o.User_Id = c.Id INNER JOIN
Item
ON i.Id = o.Product_Id
GROUP BY c.Name
If you want, you can concatenate these into a single string column. I just prefer to have the information in two different columns for something like this.
The above is standard SQL.
I think your query should looks like this
SELECT
Data.Name AS [Customer Name],
CASE WHEN Data.Count > 1 THEN "Multiple-currencies" ELSE CAST(Data.Amount AS NVARCHAR) END AS Amount
FROM
(SELECT
Customer.Name,
COUNT(Item.Currency) AS Count,
SUM(Item.Amount) AS Amount
FROM
Customer
INNER JOIN Order ON Order.User_Id = Customer.Id
INNER JOIN Item ON Item.Id = Order.Product_Id
GROUP BY
Customer.Name) AS Data
A subquery to get the count of currencies and then ask for them in the main query to show the total or the text "Multiple-currencies".
Sorry if there is any mistake or mistype but I don't have a database server to test it
Hope this helps.
IMO I would start by standardizing variable names. Why call ID in customer table USER_ID in order table? Just a pet peeve. Anyway, you should learn how to build queries.
start with joining the customer table to the order table on then join the result to the item table. The first join is on CUSTOMER_ID and the second join is on PRODUCT_ID. Once you have that working use SUM and GROUP BY
Ok, I managed to solve the problem this way:-
SELECT innerQuery.Name AS Name, (CASE WHEN innerQuery.Currencies=1 THEN (innerQuery.Amount || innerQuery.Currency) ELSE 'Mutliple-Currencies' END) AS Amount, FROM
(SELECT Customer.Name, SUM(Item.Amount), COUNT(DISTINCT Item.Currency) AS Currencies, Item.Currency AS Currency FROM Customer
INNER JOIN Order ON Order.User_Id = Customer.Id
INNER JOIN Item ON Item.Id = Order.Product_Id
GROUP BY Customer.Name)innerQuery

Stock calculation in Postgres

I have a table p1 with transactions in Postgres like this:
| id | product_id | transaction_date | quantity |
|----|------------|------------------|----------|
| 1 | 1 | 2015-01-01 | 1 |
| 2 | 1 | 2015-01-02 | 2 |
| 3 | 1 | 2015-01-03 | 3 |
and p2 table with products like this:
| id | product | stock |
|----|--------------|-------|
| 1 | Product A | 15 |
stock in p2' has been be reduced for every new record in p1.
How to reconstruct previous states to get this result?
| product | first_stock | quantity | last_stock |
|-----------|-------------|----------|------------|
| Product A | 21 | 1 | 20 |
| Product A | 20 | 2 | 18 |
| Product A | 18 | 3 | 15 |
I have tried using lead() to get the quantity after the current row.
SELECT p2.product, p1.quantity, lead(p1.quantity) OVER(ORDER BY p1.id DESC)
FROM p1 INNER JOIN p2 ON p1.product_id = p2.id;
But how to calculate leading rows from the current stock?
You don't just need lead() you need the running sum over all rows in between to reconstruct previous states from transaction data:
SELECT p2.product
, p2.stock + px.sum_quantity AS first_stock
, px.quantity
, p2.stock + px.sum_quantity - quantity AS last_stock
FROM p2
JOIN (
SELECT product_id, quantity, transaction_date
, sum(quantity) OVER (PARTITION BY product_id
ORDER BY transaction_date DESC) AS sum_quantity
FROM p1
) px ON px.product_id = p2.id
ORDER BY px.transaction_date;
Assuming the course of events actually indicated by transaction_date.
Use the aggregate function sum() as window-aggregate function to get the running sum. Use a subquery, since we use the running sum of quantities (sum_quantity) multiple times.
For last_stock subtract quantity of the current row (after adding it redundantly).
Nitpick
Theoretically, it would be cheaper to use a custom frame definition for the window frame to only sum quantities up to preceding row, so we don't add and subtract the quantity of the current row redundantly. But that's more complex and hardly faster in reality:
SELECT p2.id, p2.product, px.transaction_date -- plus id and date for context
, p2.stock + COALESCE(px.pre_sum_q + px.quantity, 0) AS first_stock
, px.quantity
, p2.stock + COALESCE(px.pre_sum_q, 0) AS last_stock
FROM p2
LEFT JOIN (
SELECT id, product_id, transaction_date
, quantity
, sum(quantity) OVER (PARTITION BY product_id
ORDER BY transaction_date DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS pre_sum_q
FROM p1
) px ON px.product_id = p2.id
ORDER BY px.transaction_date, px.id;
Explanation for the frame definition in this related answer:
Grouping based on sequence of rows
While being at it, also demonstrating how to prevent missing rows and NUll values with LEFT JOIN and COALESCE for products that don't have any related rows in p1, and a stable sort order if there are multiple transactions for the same product on the same day.
Still assuming all columns to be defined NOT NULL, or you need to do some more for corner cases with NULL values.

SUM on LEFT OUTER JOIN on 2 different child tables

I have the following table schema
What I want to do is return all invoices with the following summarized totals
Total of UnitPrice * Qty for all Line items (e.g. SUM(UnitPrice * Qty))
Total of Amount for all Additional Costs (e.g. SUM(Amount))
Given that an Invoice can exist without any Line Items or Additional Costs I thought all I would need to do here is use a LEFT OUTER JOIN i.e.
SELECT i.*, SUM(li.UnitPrice * li.Qty) As [Sub Total], SUM(ac.Amount) As [AdditionalCosts]
FROM Invoice i
LEFT OUTER JOIN LineItem li ON li.InvoiceId = i.Id
LEFT OUTER JOIN AdditionalCost ac ON ac.InvoiceId = i.Id
GROUP BY i.Id
However, the problem is that if both sub tables are of varied length (e.g. I have 4 line items, but only 1 additional cost) the data for the additional cost is repeated across the extra line item rows (and vice versa), you can verify this by removing the GROUP BY.
So effectively what happens is for the following record
Invoice
-------
400001
LineItem
---------
400001 | 2000 | 100 | 1
400001 | 2001 | 50 | 2
400001 | 2002 | 10 | 10
400001 | 2003 | 20 | 5
AdditionalCost
--------------
1 | 400001 | 30
2 | 400001 | 70
My result set would look like
Id | Sub Total | Additional Costs
--------------------------------------
40001 | 800 | 400 <-- this should be 100
How can I calculate the SUM of each table independently and combine them into a single master record?
This should work:
SELECT i.*, li.[Sub Total], ac.[AdditionalCosts]
FROM Invoice i
LEFT OUTER JOIN (SELECT InvoiceID, SUM(UnitPrice*Qty) As [Sub Total]
FROM LineItem
GROUP BY InvoiceID) li
ON li.InvoiceId = i.Id
LEFT OUTER JOIN (SELECT InvoiceID, SUM(Amount) As [AdditionalCosts]
FROM AdditionalCost
GROUP BY InvoiceID) ac
ON ac.InvoiceId = i.Id