Check if two products belong to same invoice - sql

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

Related

Sum Items from Multiple Tables in 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;

How to Count total rows on a shipment

I'm trying to count the total number of lines on each shipment:
SELECT Shipments.ShipmentId,
SalesOrders.SalesOrderId as OrderNumber,
Count(SalesOrderItems.SalesOrderItem) as NumberOfLines
FROM SalesOrders
INNER JOIN SalesOrderItems on SalesOrders.SalesOrder = SalesOrderItems.SalesOrder
INNER JOIN Shipments on SalesOrderItems.SalesOrder = Shipments.SalesOrder
GROUP BY SalesOrderItems.SalesOrderItem, SalesOrders.SalesOrderId, Shipments.ShipmentId
ORDER BY Shipments.ShipmentID ASC
Currently I'm getting:
ShipmentID | OrderNumber | NumberOfLines
SH00000001 | SO-0000001 | 1
SH00000001 | SO-0000001 | 1
SH00000002 | SO-0000007 | 1
SH00000003 | SO-0000006 | 1
SH00000003 | SO-0000006 | 1
And I should be getting:
ShipmentID | OrderNumber | NumberOfLines
SH00000001 | SO-0000001 | 1
SH00000001 | SO-0000001 | 2
SH00000002 | SO-0000007 | 1
SH00000003 | SO-0000006 | 1
SH00000003 | SO-0000006 | 2
Remove SalesOrderItems.SalesOrderItem from your group by clause, you don't want it (as can be deduced from it not existing on your sample result dataset).
Your GROUP BY clause should match the unaggregated columns in the SELECT:
SELECT s.ShipmentId,
so.SalesOrderId as OrderNumber,
Count(soi.SalesOrderItem) as NumberOfLines
FROM SalesOrders so INNER JOIN
SalesOrderItems soi
ON so.SalesOrder = soi.SalesOrder INNER JOIN
Shipments s
ON soi.SalesOrder = s.SalesOrder
GROUP BY soi.SalesOrderId, s.ShipmentId
ORDER BY s.ShipmentID ASC;
Note that I've added table aliases. These make the queries easier to write and to read.

Postgres tsvector with relational tables

I have two simple tables. First is brands and each brand may have a top brand. For example Elseve has a top brand which is Loreal Paris. But Avon has no top brand. And I have a simple products table.
Here is sqlfiddle
Here is brands table.
id | name | parent_id | depth
----+--------------+-----------+-------
3 | Loreal Paris | null | 0
1 | Avon | null | 1
2 | Elseve | 3 | 1
(3 rows)
And here is products table
id | brand_id | name
----+----------+-----------
1 | 1 | Product 1
2 | 2 | Product 2
(2 rows)
When I try to get tsvectors, Product 1 document returns null result. But I need to get at least Avon in the document.
product_id | product_name | document
------------+--------------+--------------------------------------------------
1 | Product 1 |
2 | Product 2 | '2':2 'elsev':3 'loreal':4 'paris':5 'product':1
(2 rows)
How to solve this problem ?
Thanks to Voa Tsun. I updated query a little bit. I don't need grouping anymore.
select
products.id as product_id,
products.name as product_name,
to_tsvector(products.name) ||
to_tsvector(brands.name) ||
to_tsvector(top_brands.name)
as document
from products
JOIN brands on brands.id = products.brand_id
LEFT JOIN brands as top_brands on coalesce(brands.parent_id,brands.id) = top_brands.id;
The basic idea is to join id not against null in "parent_id", but "at least against id", as I got from your post.
like here:
select
products.id as product_id,
products.name as product_name,
to_tsvector(coalesce(products.name,brands.name)) ||
to_tsvector(brands.name) ||
to_tsvector(coalesce(string_agg(top_brands.name, ' ')))
as document
from products
JOIN brands on brands.id = products.brand_id
LEFT JOIN brands as top_brands on coalesce(brands.parent_id,brands.id) =
top_brands.id
GROUP BY products.id,brands.id;
product_id product_name document
1 1 Product 1 '1':2'avon':3,4'product':1
2 2 Product 2 '2':2'elsev':3'loreal':4'pari':5'product':1

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

SQL Server - OR clause in join confusion

I have the table structure like below
Package
PACK_ID | DESCR | BRAND_ID
1 | Shoes | 20
2 | Cloths| NULL
ITEMS
ITEM_ID | PACK_ID | BRAND_ID
100 | 1 | 10
101 | 1 | NULL
102 | 1 | 10
BRANDS
NAME | BRAND_ID
A | 10
B | 20
I want to write a query to list how many items are there in a package grouped by same brand. If the brand is not defined in the item it should get it from package.
Note: Brand_id in both package and items are nullable
My query is this
SELECT count (*) as count,p.descr as descr,b.name FROM [items] item
inner join [package] p on item.pack_id= p.pack_id
inner join [brands] b on b.brand_id = item.brand_id or b.brand_id = p.brand_id
where p.pack_id = 1
group by b.name,p.descr
and my result is
COUNT | descr | NAME
2 | Shoes | a
3 | Shoes | B
whereas i expect the result to be something like this
COUNT | descr | NAME
2 | Shoes | a
1 | Shoes | B
could you please suggest what is wrong with my code? Thanks in advance.
Try using ISNULL on your join condition:
SELECT count (*) as count,p.pack_id as pack_id,b.name FROM [items] item
inner join [package] p on item.pack_id= p.pack_id
inner join [brands] b on b.brand_id = ISNULL(item.brand_id, p.brand_id)
where p.pack_id = 1
group by b.name,p.pack_id
Your OR was causing it to join to multiple rows, this should use the item by default and then fall back to the package.
I would tend to approach this by getting the brand for both the item and the package. Then decide which one to use in the select:
SELECT count(*) as count, p.descr as descr, coalesce(bi.name, bp.name) as name
FROM [items] item inner join
[package] p
on item.pack_id= p.pack_id left join
[brands] bi
on bi.brand_id = item.brand_id left join
brands bp
on b.brand_id = p.brand_id
where p.pack_id = 1
group by coalesce(bi.name, bp.name), p.descr;
One key advantage to this approach is performance. Databases tend to do a poor job when joins are on expression or or conditions.