Order customers based on the purchases sum - sql

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

Related

SQL query number of orders

I have two tables, customers and orders, and I want to get the number of orders made by each customer.
I tried
SELECT orders.order_id, customers.customer_id,
FROM customers
INNER JOIN orders ON orders.customer_id = customers.customer_id
but I can't count number of orders
You are describing aggregation:
SELECT c.customer_id, count(*) no_orders
FROM customers c
INNER JOIN orders o ON o.customer_id = c.customer_id
GROUP BY c.customer_id
If you also want customers that have no order, use a LEFT JOIN instead, or a correlated subquery:
SELECT
c.customer_id,
(SELECT COUNT(*) FROM orders o WHERE o.customer_id = c.customer_id) no_orders
FROM customers c

Return the rows that are above average

I'm trying to find the countries from which the number of orders is higher than the average. This is as far as I got. The problem is (see picture) that the count of orders for each country is off, as it should be different for everyone
SELECT avg(NumberOrders) as avg,
Customers.Country,
NumberOrders
FROM Customers,
(SELECT COUNT(Orders.OrderId) AS NumberOrders
FROM Customers JOIN
Orders ON Customers.CustomerID = Orders.CustomerID
GROUP BY Customers.Country) nested
GROUP BY Customers.Country
HAVING NumberOrders > avg;
Output
If your DBMS supports Windowed Aggregates (almost all besides MySQL & Access):
select *
from
(
SELECT Customers.Country,
COUNT(Orders.OrderId) AS NumberOrders, -- count per country
AVG(COUNT(Orders.OrderId)) OVER () AS avgOrders -- average count
FROM Customers
JOIN Orders
ON Customers.CustomerID = Orders.CustomerID
GROUP BY Customers.Country
) nested
WHERE NumberOrders > avgOrders
Edit:
For DBMSes not supporting Windowed Aggregates it's way more complicated:
SELECT Customers.Country,
COUNT(Orders.OrderId) AS NumberOrders -- count per country
FROM Customers
JOIN Orders
ON Customers.CustomerID = Orders.CustomerID
GROUP BY Customers.Country
HAVING COUNT(Orders.OrderId) >
( select avg(NumberOrders)
from
(
SELECT Customers.Country,
COUNT(Orders.OrderId) AS NumberOrders -- count per country
FROM Customers
JOIN Orders
ON Customers.CustomerID = Orders.CustomerID
GROUP BY Customers.Country
) AS dt
)
If the DBMS supports Common Table Expressions this can be simplified:
with cte as
(
SELECT Customers.Country,
COUNT(Orders.OrderId) AS NumberOrders -- count per country
FROM Customers
JOIN Orders
ON Customers.CustomerID = Orders.CustomerID
GROUP BY Customers.Country
)
select *
from cte
WHERE NumberOrders >
(
select avg(NumberOrders) from cte
)

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

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;

How to find result set from two different date comparisons?

How to find result set from two different date comparisons
How to find period1_label_cost, period2_label_cost in this query without using this query ?
Only period1_label_cost column value is getting
select distinct customers.id as customer_id, customers.first_name as first_name, customers.last_name as last_name, SUM(orders.total_cost) as period1_label_cost
from customers inner join orders
on customers.id= orders.customer_id
where
date(orders.created_at) between 'start_date1' and 'end_date1'
group by customers.id , customers.first_name, customers.last_name, customers.preferred
having(SUM(orders.total_cost) > sales_exceeded
intersect
select distinct customers.id as customer_id, customers.first_name as first_name, customers.last_name as last_name,
customers.preferred as preferred, SUM(orders.total_cost) as period2_label_cost
from customers inner join orders
on customers.id= orders.customer_id
where
date(orders.created_at) between start_date2 and end_date2
group by customers.id , customers.first_name, customers.last_name, customers.preferred
having( SUM(orders.total_cost) < sales_below) order by first_name asc
Are you looking for mere joins? Put your two queries in your FROM clause and join them on customer_id, then join with the customers table and show the results.
select
c.id as customer_id,
c.first_name,
c.last_name,
c.preferred,
period1.label_cost as period1_label_cost,
period2.label_cost as period2_label_cost
from
(
select
customer_id,
sum(total_cost) as label_cost
from orders
where date(created_at) between <start_date1> and <end_date1>
group by customer_id
having sum(total_cost) > <sales_exceeded>
) period1
join
(
select
customer_id,
sum(total_cost) as label_cost
from orders
where date(created_at) between <start_date2> and <end_date2>
group by customer_id
having sum(total_cost) < <sales_below>
) period2 on period2.customer_id = period1.customer_id
join customers c on c.id = period1.customer_id;

postgresql group by and inner join

I want a query in SQL which does INNER JOIN and GROUP BY at the same time. I tried the following which doesn't work:
SELECT customer.first_name, SUM(payment.amount)
FROM customer
GROUP BY customer.customer_id
INNER JOIN payment
ON payment.customer_id = customer.customer_id;
Thank you in advance!
First, GROUP BY comes at the end of the query (just before order by or having clauses if you have some).
Then, all fields in the select which are not in an aggregation function must be in the group by clause.
so
SELECT customer.first_name, SUM(payment.amount)
FROM customer
INNER JOIN payment
ON payment.customer_id = customer.customer_id
GROUP BY customer.first_name;
But customers with same first_name will be grouped, which is probably not really what you want.
so rather
SELECT customer.first_name, SUM(payment.amount)
FROM customer
INNER JOIN payment
ON payment.customer_id = customer.customer_id
GROUP BY customer.first_name, customer.customer_id;
You want to group by the customer_id, but get the first_name?
SELECT customer.first_name, SUM(payment.amount)
FROM customer
INNER JOIN payment
ON payment.customer_id = customer.customer_id
GROUP BY customer.customer_id, customer.first_name;
You might also do the aggregation in a Derived Table, then you can get additional columns from customer:
SELECT customer.first_name, SumPayment
FROM customer
INNER JOIN
(
SELECT customer_id,
SUM(payment.amount) AS SumPayment
FROM payment
GROUP BY customer_id
) AS payment
ON payment.customer_id = customer.customer_id