Group BY Postgresql - sql

I have two tables:
customers(id,first_name,last_name,email)
orders (id,order_date,amount,customer_id)
customer_id in the orders table is the foreign key for id in the customers table
I'm trying to run the following code on PostgreSQL and getting an error:
SELECT first_name,last_name,order_date, sum(amount)
FROM customers
INNER JOIN orders ON customers.id = orders.customer_id
GROUP BY orders.customer_id;
Can someone tell me what's wrong with my code?

Just aggregate by all the columns in the select that are not aggregated. Also, qualify the column names:
SELECT c.first_name, c.last_name, o.order_date, sum(o.amount)
FROM customers c INNER JOIN
orders o
ON c.id = o.customer_id
GROUP BY c.first_name, c.last_name, o.order_date;
If you want the sum per customer name, then remove the order_date:
SELECT c.first_name, c.last_name, sum(o.amount)
FROM customers c INNER JOIN
orders o
ON c.id = o.customer_id
GROUP BY c.first_name, c.last_name;

The following query is ANSI compliant and should run on Postgres without error:
SELECT c.id, c.first_name, c.last_name, SUM(o.amount)
FROM customers c
INNER JOIN orders o
ON c.id = o.customer_id
GROUP BY c.id;
I think the immediate cause of the error is that you were also selecting the order_date column while aggregating over customers. This does not make sense, because Postgres has no way of knowing which order you are referring to.
Typically it is not possible to select columns in a GROUP BY query which either do not appear in the clause itself or are inside an aggregate function such as MAX or SUM. However, we can select the customer columns in this case because the id functionally determines the values for the first and last names.
Note that if id is not a primary key column, then you should use the approach suggested by #Gordon.
Demo

Related

Query that will result in a list of customer names and the average order value made by each customer

I have a database and I need to create a query that will retrieve a list of customer names and the average order value made by each customer.
I tried:
SELECT c.customer_name, AVG(COUNT(o.order_id)*f.price) AS 'avgorderprice'
FROM Customers c
LEFT JOIN Orders o ON o.customer_id = c.customer_id
INNER JOIN F_in_Or fio ON o.order_id = fio.order_id
INNER JOIN Films f ON fio.film_id = f.film_id;
This is my database structure:
But I get an error, what can be wrong?
But I get an error, what can be wrong ?
You are trying to use an aggregate function Count within an aggregate function and getting a MISUSE OF AN AGGREGATE FUNCTION.
i.e. - > misuse of aggregate function COUNT()
Additionally you are not GROUP'ing your results and thus will receive the entire average rather than a per-customer average.
Aggregate functions work on a GROUP the default being all unless a GROUP BY clause places the rows into GROUPS.
You could instead of AVG(COUNT(o.order_id)*f.price) use sum(f.price) / count(*). However, there is an average aggregate function avg. So avg(f.price)' is simpler.
Additionally as you want an average per customer you want to use a GROUP BY c.customer clause.
Thus you could use :-
SELECT
c.customer_name,
avg(f.price) AS 'avgorderprice' --<<<<< CHANGED
FROM Customers c
LEFT JOIN Orders o ON o.customer_id = c.customer_id
INNER JOIN F_in_Or fio ON o.order_id = fio.order_id
INNER JOIN Films f ON fio.film_id = f.film_id
GROUP BY c.customer_name --<<<<< ADDED
;
This would result in something like :-
I think this should work
SELECT c.customer_name, AVG(f.price) AS 'avgorderprice' FROM Customers c LEFT JOIN Orders o ON o.customer_id = c.customer_id LEFT JOIN F_in_Or fio ON o.order_id = fio.order_id LEFT JOIN Films f ON fio.film_id = f.film_id;

Not in aggregate function or group by clause: org.hsqldb.Expression#59bcb2b6 in statement

I'm trying to group SUM(OrderDetails.Quantity) but keep getting the error Not in aggregate function or group by clause: org.hsqldb.Expression#59bcb2b6 in statement but since I already have an GROUP BY part I don't know what I'm missing
SQL Statement:
SELECT OrderDetails.CustomerID, Customers.CompanyName, Customers.ContactName, SUM(OrderDetails.Quantity)
FROM OrderDetails INNER JOIN Customers ON OrderDetails.CustomerID = Customers.CustomerID
WHERE OrderDetails.CustomerID = Customers.CustomerID
GROUP BY OrderDetails.CustomerID
ORDER BY OrderDetails.CustomerID ASC
I'm trying to create a table that shows customers and the amount of products they ordered, while also showing their CompanyName and ContactName.
Write this:
GROUP BY OrderDetails.CustomerID, Customers.CompanyName, Customers.ContactName
Unlike in MySQL, PostgreSQL, and standard SQL, in most other SQL dialects, it is not sufficient to group only by the primary key if you also want to project functionally dependent columns in the SELECT clause, or elsewhere. You have to explicitly GROUP BY all of the columns that you want to project.
Don't take the customer id from the orders table. Take it from the customers table. If you do so, this might work in your database:
SELECT c.CustomerID, c.CompanyName, c.ContactName, SUM(od.Quantity)
FROM OrderDetails od INNER JOIN
Customers c
ON od.CustomerID = c.CustomerID
GROUP BY c.CustomerID
ORDER BY c.CustomerID ASC;
Note that the WHERE clause does not need to repeat the conditions in the ON clause.
Your version won't work in standard SQL because od.CustomerId is not unique in OrderDetails. Many databases don't support this, so in these you need the additional columns:
SELECT c.CustomerID, c.CompanyName, c.ContactName, SUM(od.Quantity)
FROM OrderDetails od INNER JOIN
Customers c
ON od.CustomerID = c.CustomerID
GROUP BY c.CustomerID, c.CompanyName, c.ContactName
ORDER BY c.CustomerID ASC;
Even so, it is much, much better to take all columns from the same table. That would allow the SQL optimizer to use indexes on Customers.

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.

Select all orders by one customer

I have three tables, orders, orders_details and customers. I need to select orders by one customer for the orders table so I did this
orders columns:
id
customer_id
created
vat
discount
amount
paid
orders_details columns:
id
order_id
cost
qty
product
The SQL I used
SELECT
orders.*,
SUM(orders_details.qty*orders_details.cost) as amount,
SUM(orders_details.qty) AS qty
FROM
orders,
orders_details,
customers
WHERE
orders.customer_id = customers.id
AND orders_details.order_id = orders.id
AND orders.customer_id = 1
but I am getting a wrong qty of 30 instead of 20 and the amount is wrong
If you want to aggregate per order you need a GROUP BY clause. Also you should use proper JOIN syntax, and might consider using aliases to make the query more compact.
SELECT
o.*,
SUM(od.qty * od.cost) AS amount,
SUM(od.qty) AS qty
FROM orders o
INNER JOIN orders_details od ON od.order_id = o.id
INNER JOIN customers c ON o.customer_id = c.id -- not used, might be excluded
WHERE o.customer_id =1
GROUP BY o.id
Depending on what database system you are using you might need to include all columns referenced in o.* in the GROUP BY:
GROUP BY o.id, o.customer_id, o.created, o.vat, o.discount, o.amount, o.paid
Last note: as you don't seem to use any data from the customers table you probably could exclude that table altogether.
You're missing a GROUP BY clause which I'm guessing should be on orders.id.

Need hints on seemingly simple SQL query

I'm trying to do something like:
SELECT c.id, c.name, COUNT(orders.id)
FROM customers c
JOIN orders o ON o.customerId = c.id
However, SQL will not allow the COUNT function. The error given at execution is that c.Id is not valid in the select list because it isn't in the group by clause or isn't aggregated.
I think I know the problem, COUNT just counts all the rows in the orders table. How can I make a count for each customer?
EDIT
Full query, but it's in dutch... This is what I tried:
select k.ID,
Naam,
Voornaam,
Adres,
Postcode,
Gemeente,
Land,
Emailadres,
Telefoonnummer,
count(*) over (partition by k.id) as 'Aantal bestellingen',
Kredietbedrag,
Gebruikersnaam,
k.LeverAdres,
k.LeverPostnummer,
k.LeverGemeente,
k.LeverLand
from klanten k
join bestellingen on bestellingen.klantId = k.id
No errors but no results either..
When using an aggregate function like that, you need to group by any columns that aren't aggregates:
SELECT c.id, c.name, COUNT(orders.id)
FROM customers c
JOIN orders o ON o.customerId = c.id
GROUP BY c.id, c.name
If you really want to be able to select all of the columns in Customers without specifying the names (please read this blog post in full for reasons to avoid this, and easy workarounds), then you can do this lazy shorthand instead:
;WITH o AS
(
SELECT CustomerID, CustomerCount = COUNT(*)
FROM dbo.Orders GROUP BY CustomerID
)
SELECT c.*, o.OrderCount
FROM dbo.Customers AS c
INNER JOIN dbo.Orders AS o
ON c.id = o.CustomerID;
EDIT for your real query
SELECT
k.ID,
k.Naam,
k.Voornaam,
k.Adres,
k.Postcode,
k.Gemeente,
k.Land,
k.Emailadres,
k.Telefoonnummer,
[Aantal bestellingen] = o.klantCount,
k.Kredietbedrag,
k.Gebruikersnaam,
k.LeverAdres,
k.LeverPostnummer,
k.LeverGemeente,
k.LeverLand
FROM klanten AS k
INNER JOIN
(
SELECT klantId, klanCount = COUNT(*)
FROM dbo.bestellingen
GROUP BY klantId
) AS o
ON k.id = o.klantId;
I think this solution is much cleaner than grouping by all of the columns. Grouping on the orders table first and then joining once to each customer row is likely to be much more efficient than joining first and then grouping.
The following will count the orders per customer without the need to group the overall query by customer.id. But this also means that for customers with more than one order, that count will repeated for each order.
SELECT c.id, c.name, COUNT(orders.id) over (partition by c.id)
FROM customers c
JOIN orders ON o.customerId = c.id