Group by and inner join: how to select joined without a "max" trick - sql

Here is a simple query:
SELECT orders.id, customers.name, COUNT(order_product.id)
FROM orders
INNER JOIN order_product ON orders.id = order_product.order_id
INNER JOIN customers ON orders.customer_id = customers.id
GROUP BY orders.id;
In other words, I want:
The ID of an order.
The number of products (count) in each order.
The customer name of the order.
The problem is about selecting customers.name. I cannot select it directly because it's not in aggregate function nor group by. But there is only one, so I d'ont know why I have to aggregate it. I can do a trick like this to select its name:
SELECT MAX(customers.name)
But I think it's dirty, because I don't want the "max name of a customer for an order" but "the name of the customer for an order". What is the elegant way to do such a thing?
Hope it's clear and not a duplicate.
EDIT: an order have only one customer identified by orders.customer_id. That's why I asking why I have to do such a trick.

Add customers.name to the GROUP BY clause:
SELECT orders.id, customers.name, COUNT(order_product.id)
FROM orders
INNER JOIN order_product ON orders.id = order_product.order_id
INNER JOIN customers ON orders.customer_id = customers.id
GROUP BY orders.id, customers.name
Usually you can simply group by all selected columns that are not arguments to set functions!

Alternatively, you could use window functions
SELECT DISTINCT orders.id, customers.name, COUNT(order_product.id) OVER ( PARTITION BY orders.id)
FROM orders
INNER JOIN products ON orders.id = order_product.order_id
INNER JOIN customers ON orders.customer_id = customers.id;

Related

Order customers based on the purchases sum

I have this SQL
SELECT customers.first_name
FROM customers
INNER JOIN orders ON customer.id = orders.customer_id
GROUP BY first_name
HAVING SUM(orders.price) > 100;
But I want all customers to be listed in a table from the highest purchase price of their order to the lowest.
You can use next simple ORDER BY:
SELECT customers.first_name, SUM(orders.price) orders_price
FROM customers
INNER JOIN orders ON customers.id = orders.customer_id
GROUP BY first_name
ORDER BY orders_price DESC;
MySQL order by fiddfe
Also you can use LEFT JOIN and COALESCE function for select customers without orders:
SELECT
customers.first_name,
COALESCE(SUM(orders.price), 0) orders_price FROM customers
LEFT JOIN orders ON customers.id = orders.customer_id
GROUP BY first_name
ORDER BY orders_price DESC;
MySQL LEFT JOIN & COALESCE

Beginner: LEFT JOIN not doing what it should?

I'm having trouble with a really simple left join statement that's driving me nuts
I wanted to count the numbers of orders from each customer, that's fine, but I want to display the name, and I'm joining with the customers table and trying to select the name and it says that CustomerName is not part of an aggregate function, it's really weird.
SELECT Customers.CustomerName as 'Name',
COUNT(*) AS 'Order Count'
FROM Orders
LEFT JOIN Customers
ON Orders.CustomerID = Customers.CustomerID
GROUP BY Customers.CustomerID
Thanks for any tips.
You need to count the rows from the orders table, and the left join should be in the other direction:
SELECT c.customerid,
c.CustomerName as "Name",
COUNT(o.customerid) AS "Order Count"
FROM Customers c
LEFT JOIN Orders o ON o.CustomerID = cs.CustomerID
GROUP BY c.CustomerID, c.customername;
count() will ignore NULL values that come into the result due to the outer join so it will count the number of orders for each customers. Customers without orders will be show with a zero count.
Include CustomerName in Group BY instead of CustomerID
SELECT Customers.CustomerName as 'Name', COUNT(*) AS 'Order Count'
FROM Orders LEFT JOIN Customers ON Orders.CustomerID = Customers.CustomerID
GROUP BY Customers.CustomerName
If you are using SQL Server then try using OVER() without Group BY
SELECT Customers.CustomerName as 'Name', COUNT(*) OVER (PARTITION BY Customers.CustomerName ORDER BY Customers.CustomerName)AS 'Order Count'
FROM Orders LEFT JOIN Customers ON Orders.CustomerID = Customers.CustomerID
Modify as below. column used in group by clause should be in column queried in select clause
SELECT Customers.CustomerName as 'Name',
COUNT(*) AS 'Order Count'
FROM Orders
LEFT JOIN Customers
ON Orders.CustomerID = Customers.CustomerID
GROUP BY Customers.CustomerName
I have just reordered your query,please try this it will definitely work for you.
SELECT Customers.CustomerName as 'Name',
COUNT(*) AS 'Order Count'
FROM Customers
LEFT JOIN Orders
ON Customers.CustomerID=Orders.CustomerID
GROUP BY Customers.CustomerID
A simple approach to get all the columns in the customers table is to use a correlated subquery:
select c.*, -- or whatever columns you want
(select count(*)
from orders o
where o.CustomerID = c.CustomerID
) as order_count
from customers c;
Because this avoids the outer GROUP BY, this also has the advantage of having better performance in most databases, particularly with an index on orders(CustomerId). Plus, it returns 0 if the customer has no orders. And, it allows you to choose any or all of the columns from Customers.
The correct way to get the counts you want is to count a column from Orders:
SELECT c.CustomerName, c.CustomerID,
COUNT(o.CustomerId) AS Order_Count
FROM Customers c LEFT JOIN
Orders o
ON o.CustomerID = c.CustomerID
GROUP BY c.CustomerID, c.CustomerName;
Notes:
The Customers table goes first in the LEFT JOIN because presumably you want all rows in Customers.
Table aliases make the query easier to write and to read.
Do not use single quotes for column aliases, even if your database supports it. The best method is to choose aliases that do not need to be supported.
Include the CustomerId in the logic, just in case two customers have the same name.
Count a column from Orders so you get a count of 0 for customers with no orders.

Double check simple SQL query - customer and orders tables

I just wanted to double check this SQL query that I wrote. I want to return the top five customer first names based on the dollar amount ordered? I'm using a table called "customer" and table called "orders". However, I can't remember if I need to use a "max" somewhere.... Any help is appreciated!
SELECT TOP 5
customer.customerFirstName
FROM customer
LEFT JOIN orders
ON customer.customerID = orders.customerID
ORDER BY orders.orderCost DESC
You need a group by, I think:
SELECT TOP 5 c.customerFirstName
FROM customer c LEFT JOIN
orders o
ON c.customerID = o.customerID
GROUP BY c.customerFirstName
ORDER BY SUM(o.orderCost) DESC;
i think this should help answer your question, http://www.w3schools.com/sql/sql_join_left.asp
SELECT TOP 5 orders.orderid,
orders.customerid,
customers.customername
From customers
LEFT JOIN orders
ON customers.customerid=orders.customerid
ORDER by orders.orderid DESC

SQL Oracle (using AND clause)

when I use below code, I get the data of customers who ordered "Planned" or 'obsolete' products, but I want to get data of the customers who ordered both type, changing 'or' to 'and' does not work... please help
SELECT DISTINCT customers.CUST_EMAIL
,ORDERS.ORDER_ID
,PRODUCT_INFORMATION.PRODUCT_NAME
,PRODUCT_INFORMATION.PRODUCT_STATUS
FROM PRODUCT_INFORMATION
INNER JOIN ORDER_ITEMS ON PRODUCT_INFORMATION.PRODUCT_ID = ORDER_ITEMS.PRODUCT_ID
INNER JOIN ORDERS ON ORDER_ITEMS.ORDER_ID = ORDERS.ORDER_ID
INNER JOIN CUSTOMERS ON CUSTOMERS.CUSTOMER_ID = ORDERS.CUSTOMER_ID
WHERE PRODUCT_INFORMATION.PRODUCT_STATUS = 'planned'
OR PRODUCT_INFORMATION.PRODUCT_STATUS = 'obsolete'
ORDER BY CUSTOMERS.CUST_EMAIL;
I'm guessing that you want the following. If you want to get correct answer, rather than guesses, please provide a good representative set of sample data and your expected result based on that sample data.
First part of the query returns Customers that ordered planned products, second part of the query returns Customers that ordered obsolete products. INTERSECT operator returns only those that have ordered both planned and obsolete products.
You don't need explicit DISTINCT any more, because INTERSECT would do it anyway.
I've removed PRODUCT_INFORMATION.PRODUCT_STATUS from the list of returned columns, because with it the result set would be always empty.
I removed ORDERS.ORDER_ID and PRODUCT_INFORMATION.PRODUCT_NAME from result as well. I don't know what should be the correct query, but it is likely that INTERSECT should be done just on CUSTOMER_ID and then, once you get the list of IDs, you can join other tables to it fetching other related details if needed.
The performance of this method is beyond the scope of the question.
SELECT
CUSTOMERS.CUSTOMER_ID
,customers.CUST_EMAIL
FROM
PRODUCT_INFORMATION
INNER JOIN ORDER_ITEMS ON PRODUCT_INFORMATION.PRODUCT_ID = ORDER_ITEMS.PRODUCT_ID
INNER JOIN ORDERS ON ORDER_ITEMS.ORDER_ID = ORDERS.ORDER_ID
INNER JOIN CUSTOMERS ON CUSTOMERS.CUSTOMER_ID = ORDERS.CUSTOMER_ID
WHERE PRODUCT_INFORMATION.PRODUCT_STATUS = 'planned'
INTERSECT
SELECT
CUSTOMERS.CUSTOMER_ID
,customers.CUST_EMAIL
FROM
PRODUCT_INFORMATION
INNER JOIN ORDER_ITEMS ON PRODUCT_INFORMATION.PRODUCT_ID = ORDER_ITEMS.PRODUCT_ID
INNER JOIN ORDERS ON ORDER_ITEMS.ORDER_ID = ORDERS.ORDER_ID
INNER JOIN CUSTOMERS ON CUSTOMERS.CUSTOMER_ID = ORDERS.CUSTOMER_ID
WHERE PRODUCT_INFORMATION.PRODUCT_STATUS = 'obsolete'
ORDER BY CUST_EMAIL
without the script for you tables it's difficult to build a test case and a working query; i'll try with this step:
select order_id from (
SELECT customers.CUSTOMER_ID
,sum(decode(PRODUCT_INFORMATION.PRODUCT_STATUS, 'obsolete', 1, 0)) obsolete
,sum(decode(PRODUCT_INFORMATION.PRODUCT_STATUS, 'planned', 1, 0)) planned
FROM PRODUCT_INFORMATION
INNER JOIN ORDER_ITEMS ON PRODUCT_INFORMATION.PRODUCT_ID = ORDER_ITEMS.PRODUCT_ID
INNER JOIN ORDERS ON ORDER_ITEMS.ORDER_ID = ORDERS.ORDER_ID
INNER JOIN CUSTOMERS ON CUSTOMERS.CUSTOMER_ID = ORDERS.CUSTOMER_ID
WHERE PRODUCT_INFORMATION.PRODUCT_STATUS = 'planned'
OR PRODUCT_INFORMATION.PRODUCT_STATUS = 'obsolete'
group by customers.CUSTOMER_ID)
where obsolete>1 and planned>1
This query should return all the customer id that have items in orders with both the product status (the different product status may be in different orders), if you want to retrieve orders that have products with both status you must change the query removing customer.customer_id and adding orders.order_id. If you provide some script with sample data we can provide a better answer

Using DISTINCT in SQL Query

How do i use DISTINCT command in a SQL query to show the supplier id, company name, and the number of distinct products from that supplier that were ordered prior to a specific date? I ran the code in Access but it doesn't translate over to SQL efficiently. The table appears.
[Supplier ID Company Name Product Name Order Date
1 Exotic Liquids Chang 17-Aug-94
1 Exotic Liquids Chang 22-Nov-94
1 Exotic Liquids Aniseed Syrup 26-Sep-94]
The code I have so far is the following. Where I get confused is where to put the DISTINCT statement. Should it be immediately after the Select? Should it go in Parentheses in addition to the SELECT? Excuse my lack of knowledge on this subject in advance.
SELECT Suppliers.SupplierID, Customers.CompanyName, Products.ProductName,
Orders.OrderDate
FROM Suppliers INNER JOIN
Products ON Suppliers.SupplierID = Products.SupplierID CROSS JOIN
Customers INNER JOIN
Orders ON Customers.CustomerID = Orders.CustomerID
WHERE Orders.OrderDate <='1/1/1999'
ORDER BY Suppliers.SupplierID
You can either distinct by all columns selected :
SELECT DISTINCT
Suppliers.SupplierID, Customers.CompanyName, Products.ProductName,
Orders.OrderDate
FROM
Suppliers INNER JOIN
Products ON Suppliers.SupplierID = Products.SupplierID CROSS JOIN
Customers INNER JOIN
Orders ON Customers.CustomerID = Orders.CustomerID
WHERE
Orders.OrderDate <='1/1/1999'
ORDER BY
Suppliers.SupplierID
or use group by instead if you need to distinct only by SupplierID. DISTINCT is not a function, hence DISTINCT(Suppliers.SupplierID) means the same as simply put DISTINCT word after SELECT in this case (see the 2nd reference below).
For Reference :
http://blog.sqlauthority.com/2007/12/20/sql-server-distinct-keyword-usage-and-common-discussion/
http://weblogs.sqlteam.com/jeffs/archive/2007/10/12/sql-distinct-group-by.aspx
I'm pretty sure it's this:
SELECT DISTINCT(Suppliers.SupplierID), Customers.CompanyName, Products.ProductName,Orders.OrderDate
FROM Suppliers INNER JOIN
Products ON Suppliers.SupplierID = Products.SupplierID CROSS JOIN
Customers INNER JOIN
Orders ON Customers.CustomerID = Orders.CustomerID
WHERE Orders.OrderDate <='1/1/1999'
ORDER BY Suppliers.SupplierID