SQL: How to group by with two tables? - sql

I have the tables products and history and I need to group by name:
products = (id_product, name)
history = (id_history, id_product, amount)
I tried this SQL query but it isn't grouped by name:
SELECT
products.name,
sum(history.amount)
FROM history
INNER JOIN products ON history.id_product = products.id_product
GROUP BY
products.name,
history.amount,
history.id_history;
This is the result:

You should only be grouping by the attributes you need to be aggregated. In this case, you need only products.name.
SELECT
products.name,
sum(history.amount) AS [Amount]
FROM history
INNER JOIN products ON history.id_product = products.id_product
GROUP BY
products.name;
If you need to include products without history (assuming sum should be 0 instead of null in this case), then you can use an OUTER JOIN instead of INNER JOIN to include all products:
SELECT
products.name,
COALESCE(sum(history.amount), 0) AS [Amount]
FROM history
RIGHT OUTER JOIN products ON history.id_product = products.id_product
GROUP BY
products.name;

This is no answer, but too long for a comment.
For readability's sake the product table should be first. After all it is products that we select from, plus a history sum that we can access via [left] join history ... followed by an aggregation, or [left] join (<history aggregation query>), or a subselect in the select clause.
Another step to enhance readability is the use of alias names.
Join the table, then aggregate
select p.name, coalesce(sum(h.amount), 0) as total
from products p
left join history h on h.id_product = p.id_product
group by p.name
order by p.name;
Aggregate, then join
select p.name, coalesce(h.sum_amount, 0) as total
from products p
left join
(
select sum(h.amount) as sum_amount
from history
group by id_product
) h on h.id_product = p.id_product
order by p.name;
Get the sum in the select clause
select
name,
(select sum(amount) from history h where h.id_product = p.id_product) as total
from products p
order by p.name;
And as you were confused on how to use GROUP BY, here is an explanation: GROUP BY ___ means you want one result row per ___. In your original query you had GROUP BY products.name, history.amount, history.id_history saying you wanted one result row per name, amount, and id, while you actually wanted one row per name only, i.e. GROUP BY products.name.

Related

SQL: Order by count return only the first row

I have two tables products and product_views.
I want to write a query that returns the products with the largest number of views.
products table has id and name columns. product_views has columns id, customer_id, product_id. The counting is based on the product_id column.
Here is my query:
SELECT *
FROM products
LEFT JOIN product_views ON products.id = product_views.product_id
ORDER BY COUNT(product_views.product_id) DESC;
The problem is it returns only the first product. I think the issue is that initially the product_views table is all empty (all values are null).
How to solve this issue?
Based on your criteria, the following should work -
SELECT
p.id,
p.name,
count(v.product_id) as views
FROM products p
LEFT JOIN product_views v
ON p.id = v.product_id
GROUP BY 1,2
ORDER BY views DESC;
The query ignores customer_id, and counts product_ids based on the id/name groupings.
Calculates count in separate script:
SELECT products.id, products.name, pv.count FROM products
JOIN (SELECT product_id, COUNT(*) as count FROM product_views GROUP BY product_id)
pv ON (products.id = product_id )
ORDER BY pv.count DESC;
Example: SQL Fiddle

SQL - Sum Up Results Of A Multiplication By Group

I have the following tables:
Orders (OID, Count, ProductID, TableNr)
Table (TableNr, Name, Number)
Products (ProductID, Prize)
Now I want to calculate how much was earned per table. I think to do this I have to Group By Orders.TableNr. But how can I multiply the Products.Prize with the Orders.Count and after that sum up this results within the group?
You are describing a join and aggregation:
select o.tableNr, sum(o.count * p.prize) totalEarned
from orders o
inner join products p on p.productId = o.productId
group by o.tableNr
If you want to display table information as well (say, the table name), then you can add another join:
select t.tableNr, t.name, sum(o.count * p.prize) totalEarned
from table t
inner join orders o on o.tableNr = t.tableNr
inner join products p on p.productId = o.productId
group by t.tableNr, t.name
Note that table is a SQL keyword, hence not a good choice for a table name.

Join two tables but only get most recent associated record

I am having a hard time constructing an sql query that gets all the associated data with respect to another (associated) table and loops over into that set of data on which are considered as latest (or most recent).
The image below describes my two tables (Inventory and Sales), the Inventory table contains all the item and the Sales table contains all the transaction records. The Inventory.Id is related to Sales.Inventory_Id. And the Wanted result is the output that I am trying to work on to.
My objective is to associate all the sales record with respect to inventory but only get the most recent transaction for each item.
Using a plain join (left, right or inner) doesn't produce the result that I am looking into for I don't know how to add another category in which you can filter the most recent data to join to. Is this doable or should I change my table schema?
Thanks.
You can use APPLY
Select Item,Sales.Price
From Inventory I
Cross Apply(Select top 1 Price
From Sales S
Where I.id = S.Inventory_Id
Order By Date Desc) as Sales
WITH Sales_Latest AS (
SELECT *,
MAX(Date) OVER(PARTITION BY Inventory_Id) Latest_Date
FROM Sales
)
SELECT i.Item, s.Price
FROM Inventory i
INNER JOIN Sales_Latest s ON (i.Id = s.Inventory_Id)
WHERE s.Date = s.Latest_Date
Think carefully about what results you expect if there are two prices in Sales for the same date.
I would just use a correlated subquery:
select Item, Price
from Inventory i
inner join Sales s
on i.id = s.Inventory_Id
and s.Date = (select max(Date) from Sales where Inventory_Id = i.id)
select * from
(
select i.name,
row_number() over (partition by i.id order by s.date desc) as rownum,
s.price,
s.date
from inventory i
left join sales s on i.id = s.inventory_id
) tmp
where rownum = 1
SQLFiddle demo

SQL Compact Edition LEFT OUTER JOIN Average

I have a table with products and a table with ratings. I have the following query;
SELECT products.name, ratings.rating
FROM products
LEFT OUTER JOIN ratings
ON products.id = ratings.product_id;
Returning me a nice list of rows with the name of the product and every rating separately.
I am trying to achieve that it returns one row showing me the name of the product with its average rating. Can't seem to find out how though.
Thanks in advance.
You want to look into the GROUP BY statement. Example:
SELECT products.name AS Product_Name,
AVG(ratings.rating) AS Average_Rating
FROM products
LEFT OUTER JOIN ratings
ON products.id = ratings.product_id
GROUP BY products.name
ORDER BY products.name;
Whilst using the GROUP BY statement, you may wish to filter your resultset on the outcome of one or more of your aggregate functions. You use the HAVING statement to do so:
SELECT products.name AS Product_Name,
AVG(ratings.rating) AS Average_Rating
FROM products
LEFT OUTER JOIN ratings
ON products.id = ratings.product_id
GROUP BY products.name
HAVING AVG(ratings.rating) >= 3
ORDER BY products.name;

Using sum with a nested select

I'm using SQL Server. This statement lists my products per menu:
SELECT menuname, productname
FROM [web].[dbo].[tblMenus]
FULL OUTER JOIN [web].[dbo].[tblProductsRelMenus]
ON [tblMenus].Id = [tblProductsRelMenus].MenuId
FULL OUTER JOIN [web].[dbo].[tblProducts]
ON [tblProductsRelMenus].ProductId = [tblProducts].ProductId
LEFT JOIN [web].[dbo].[tblOrderDetails]
ON ([tblProducts].Id = [tblOrderDetails].ProductId)
GROUP BY [tblProducts].ProductName
Some products don't have menus and vice versa. I use the following to establish what has been sold of each product.
SELECT [tblProducts].ProductName, SUM([tblOrderDetails].Ammount) as amount
FROM [web].[dbo].[tblProducts]
LEFT JOIN [web].[dbo].[tblOrderDetails]
ON ([tblProducts].ProductId = [tblOrderDetails].ProductId)
GROUP BY [tblProducts].ProductName
What I want to do is complement the top table with an amount column. That is, I want a table with the same number of rows as in the first table above but with an amount value if it exists, otherwise null.
I can't figure out how to do this. Any suggestions?
If I am not missing anything, the second query could be simplified, then incorporated into the first query like this:
SELECT
m.menuname,
p.productname,
t.amount
FROM [web].[dbo].[tblMenus] m
FULL JOIN [web].[dbo].[tblProductsRelMenus] pm ON m.Id = pm.MenuId
FULL JOIN [web].[dbo].[tblProducts] p ON pm.ProductId = p.ProductId
LEFT JOIN (
SELECT ProductId, SUM(Amount) as amount
FROM [web].[dbo].[tblOrderDetails]
GROUP BY ProductId
) t ON p.ProducId = t.ProductId