How to query MAX(SUM(relation)) in Postgresql? - sql

I have read several related threads on StackOverflow but none of them solves my problem.
I have a Sales database as where I need to query for the customer who spent the most amount in buying stuff.
For that, I need to find who bought which product using
SELECT sum(qty*rate)
AS exp from salesdetails as s JOIN sales as ss on (ss.invno = s.invno)
JOIN customer as c ON (ss.customerno = c.custno) GROUP BY(c.name)
ORDER BY sum(qty*rate);
It returns a table with the name of the person and what he spent in ascending order as
Output of command above:
While what I actually need is to only print a tuple when sum(qty*rate) is maximum. Currently I'm getting the results by sub querying like:
SELECT name, sum(qty*rate) FROM salesdetails as s JOIN sales as ss on (ss.invno=s.invno)
JOIN customer as c ON (ss.customerno = c.custno) GROUP BY(c.name)
HAVING sum(qty*rate) IN (SELECT max(exp) FROM (SELECT sum(qty*rate)
AS exp from salesdetails as s JOIN sales as ss on (ss.invno = s.invno)
JOIN customer as c ON (ss.customerno = c.custno) GROUP BY(c.name) ORDER BY sum(qty*rate)) aa);
Expected Output:
Is there any shorter way to get to the output?

Are you looking for something like this:
select *
from (
SELECT c.Name, sum(qty*rate)
AS exp from salesdetails as s JOIN sales as ss on (ss.invno = s.invno)
JOIN customer as c ON (ss.customerno = c.custno)
GROUP BY(c.name)
ORDER BY sum(qty*rate) desc
) t
limit 1;

You want row_number() or distinct on:
SELECT DISTINCT ON (c.name) c.name, sum(qty*rate) AS exp
FROM salesdetails s JOIN
sales ss
on (ss.invno = s.invno) JOIN
customer c
ON (ss.customerno = c.custno)
GROUP BY c.name
ORDER BY c.name, sum(qty*rate) DESC;

Related

SQL how to retrieve last ordered 2 of the listed products from all customers?

This is my current query:
SELECT
c.name, c.email, c.phone, sol.description, so.orderDate
FROM
SalesOrderLine sol
JOIN
SalesOrder so ON sol.salesOrderID = so.id
JOIN
Customer c ON so.customerID = c.id
WHERE
(orderDate >= '2020-05-01' AND orderDate <= '2020-09-09')
AND (description LIKE '%Seed Mix%' OR description LIKE '%Sesame Seeds (Natural)%')
ORDER BY
c.name
Goal
I am aiming to retrieve where product is seed mix or sesame seeds. And between two dates. But only show the most recent date ordered for each customer for both of the products.
Output:
Question
How can I get earliest date they have ordered for both the 1st or 2nd product mentioned in the query?
Desired output:
You can use row_number():
SELECT *
FROM (
SELECT c.name, c.email, c.phone, sol.description, so.orderDate,
RANK() OVER(PARTITION BY c.id, sol.product_id ORDER BY so.orderDate DESC) rn
FROM SalesOrderLine sol
JOIN SalesOrder so ON sol.salesOrderID = so.id
JOIN Customer c ON so.customerID = c.id
WHERE
orderDate >= '20200501'
AND orderDate <= '20200909'
AND (description LIKE '%Seed Mix%' OR description LIKE '%Sesame Seeds (Natural)%')
) t
WHERE rn = 1
ORDER BY name
Note: it seems like you have exact matches on the description. If so, it is more efficient to use equality checks rather than pattern matches. So:
AND description IN ('Seed Mix', 'Sesame Seeds (Natural)')

need help to write a query about this db

I have this DB and I need help with this query
Find the customer ID, first name, last name and the movie Name
of the customer that bought the most ticket in that day
I found the customer who bought the highest number of tickets in that day, now I need to find the movies that he bought tickets for
SELECT c.*, COUNT(*) 'bought'
FROM customer c JOIN ticket t ON c.customerId=t.customerId
GROUP BY c.customerId
HAVING bought=(SELECT MAX(T1.CNT)
FROM (SELECT COUNT(*) AS CNT
FROM customer c JOIN ticket t ON c.customerId=t.customerId
GROUP BY c.customerId) AS T1)
So without building the database and populating it with dummy data I can't test this, but I think I found a solution for you.
SELECT C.CUSTOMERID, C.FIRSTNAME, C.LASTNAME, M.TITLE
FROM CUSTOMER C JOIN TICKET T ON C.CUSTOMERID = T.CUSTOMERID JOIN SHOWS S ON T.SHOWNUMBER = S.SHOWNUMBER JOIN MOVIE M ON M.MOVIEID = S.MOVIEID
WHERE C.CUSTOMERID IN(
SELECT C1.CUSTOMERID FROM(
SELECT CUST.CUSTOMERID, COUNT(*) 'BOUGHT'
FROM CUSTOMER CUST JOIN TICKET TICK ON CUST.CUSTOMERID = TICK.CUSTOMERID
GROUP BY CUST.CUSTOMERID
HAVING BOUGHT = (SELECT MAX(T1.CNT) FROM (SELECT COUNT(*) AS CNT FROM CUSTOMER CUSTO JOIN TICKET TICKE ON CUSTO.CUSTOMERID = TICKE.CUSTOMERID GROUP BY CUSTO.CUSTOMERID)AS T1) AS C1);

Get max value from another query

I have problems with some query. I need to get max value and product_name from that query:
select
products.product_name,
sum(product_invoice.product_amount) as total_amount
from
product_invoice
inner join
products on product_invoice.product_id = products.product_id
inner join
invoices on product_invoice.invoice_id = invoices.invoice_id
where
month(invoices.invoice_date) = 2
group by
products.product_name
This query returns a result like this:
product_name | total_amount
--------------+--------------
chairs | 70
ladders | 500
tables | 150
How to get from this: ladders 500?
Select product_name,max(total_amount) from(
select
products.product_name,
sum(product_invoice.product_amount) as total_amount
from product_invoice
inner join products
on product_invoice.product_id = products.product_id
inner join invoices
on product_invoice.invoice_id = invoices.invoice_id
where month(invoices.invoice_date) = 2
group by products.product_name
) outputTable
You can use order by and fetch first 1 row only:
select p.product_name,
sum(pi.product_amount) as total_amount
from product_invoice pi inner join
products p
on pi.product_id = p.product_id inner join
invoices i
on pi.invoice_id = i.invoice_id
where month(i.invoice_date) = 2 -- shouldn't you have the year here too?
group by p.product_name
order by total_amount
fetch first 1 row only;
Not all databases support the ANSI-standard fetch first clause. You may need to use limit, select top, or some other construct.
Note that I have also introduced table aliases -- they make the query easier to write and to read. Also, if you are selecting the month, shouldn't you also be selecting the year?
In older versions of SQL Server, you would use select top 1:
select top (1) p.product_name,
sum(pi.product_amount) as total_amount
from product_invoice pi inner join
products p
on pi.product_id = p.product_id inner join
invoices i
on pi.invoice_id = i.invoice_id
where month(i.invoice_date) = 2 -- shouldn't you have the year here too?
group by p.product_name
order by total_amount;
To get all rows with the top amount, use SELECT TOP (1) WITH TIES . . ..
If you are using SQL Server, then TOP can offer a solution:
SELECT TOP 1
p.product_name,
SUM(pi.product_amount) AS total_amount
FROM product_invoice pi
INNER JOIN products p
ON pi.product_id = p.product_id
INNER JOIN invoices i
ON pi.invoice_id = i.invoice_id
WHERE
MONTH(i.invoice_date) = 2
GROUP BY
p.product_name
ORDER BY
SUM(pi.product_amount) DESC;
Note: If there could be more than one product tied for the top amount, and you want all ties, then use TOP 1 WITH TIES, e.g.
SELECT TOP 1 WITH TIES
... (the same query I have above)

SQL Server : query with incorrect SUM result

My SQL Server query is supposed to get a count of each customer's number of orders, and the SUM of their reward points. For most customers the result is accurate (most people only have one or two orders). For a few people, the result is wildly off.
Here's the original query:
SELECT
c.email,
c.lastlogindate,
c.custenabled,
c.maillist,
d.GroupName,
COUNT(o.orderid) AS orders,
SUM(r.points) AS total_points
FROM
((customers c
LEFT JOIN orders o ON (c.contactid = o.ocustomerid AND o.ostep = 'step 5')
)
LEFT JOIN discount_group d ON c.discount = d.id
)
LEFT JOIN
customer_rewards r ON r.contactid = c.contactid
WHERE
c.last_update > '2014-02-01'
OR c.lastlogindate > '2014-02-01'
GROUP BY
c.email, c.custenabled, c.maillist, c.lastlogindate, d.GroupName;
For one example, customerid 1234 has placed 21 orders, totaling 2724 points. This will report that he has placed 441 orders (21 * 21) valued at 57204 points (2724 * 21). The raw data is fine, but each order row is being duplicated by the amount of orders they placed (but not for most customers...)
If I change the query to this:
SELECT
o.orderid,
c.email,
COUNT(o.orderid) AS orders,
SUM(r.points) AS total_points
FROM
((customers c
INNER JOIN orders o ON (c.contactid = o.ocustomerid AND o.ostep = 'step 5')
)
)
INNER JOIN
customer_rewards r ON r.contactid = c.contactid
WHERE
c.last_update > '2014-02-01'
OR c.lastlogindate > '2014-02-01'
GROUP BY
c.email, o.orderid;
The aggregate functions are calculated properly, but it will display one result for each order placed. So it will show "Customer 1234/21 orders/2724 points", 21 times.
I did remove the 'discount_group' join in the second query, but that was just to make it easier to read and change. That hasn't had any effect on results.
Here is a solution using common table expressions to aggregate your results.
Note: this will not show customers that have 0 orders or 0 rewards points. If you would like to show these, change the INNER JOINs to LEFT JOINs
WITH cteOrders AS
(
SELECT o.ocustomerid, orderCount = count(*)
FROM orders o
WHERE o.ostep = 'step 5'
GROUP BY o.ocustomerid
)
, cteRewards as
(
SELECT cr.contactid, total_points = SUM(cr.points)
FROM customer_rewards cr
GROUP BY cr.contactid
)
SELECT
c.email,
o.orderCount as orders,
r.total_points
FROM
customers c
INNER JOIN cteOrders o ON c.contactid = o.ocustomerid
INNER JOIN cteRewards r ON r.contactid = c.contactid
WHERE
c.last_update > '2014-02-01'
OR c.lastlogindate > '2014-02-01'
;
Or using subqueries:
SELECT
c.email,
o.orderCount as orders,
r.total_points
FROM
customers c
INNER JOIN
(
SELECT o.ocustomerid, orderCount = count(*)
FROM orders o
WHERE o.ostep = 'step 5'
GROUP BY o.ocustomerid
) o ON c.contactid = o.ocustomerid
INNER JOIN
(
SELECT cr.contactid, total_points = SUM(cr.points)
FROM customer_rewards cr
GROUP BY cr.contactid
) r ON r.contactid = c.contactid
WHERE
c.last_update > '2014-02-01'
OR c.lastlogindate > '2014-02-01'
;

Fetch data from more than one tables using Group By

I am using three tables in a PostgreSql database as:
Customer(Id, Name, City),
Product(Id, Name, Price),
Orders(Customer_Id, Product_Id, Date)
and I want to execute a query to get from them "the customers that have have ordered at least two different products alnong with the products". The query I write is:
select c.*, p.*
from customer c
join orders o on o.customer_id = c.id
join product p on p.id = o.product_id
group by (c.id)
having count(distinct o.product_id)>=2
It throws the error:
"column "p.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: select c.*, p.*".
However if I remove the the p.* from select statement (assuming that I one does not want the products, only the customers), it runs fine. How can I get the products as well?
Update: Having ordered two or more products, a customer must appear on the output as many times as its product he has ordered. I want as output a table with 5 columns:
Cust ID | Cust Name | Cust City | Prod ID | Prod Name | Prod Price
Is it possible in SQL given that group by should be used? Shoul it be used on more than one columns on different tables?
Try this out :
SELECT distinct c.* ,p.*
FROM Customer c
JOIN
(SELECT o.customer_id cid
FROM Product P
JOIN Orders o
ON p.id= o.product_id
GROUP BY o.customer_id
HAVING COUNT(distinct o.product_id)>=2) cp
ON c.id =cp.cid
JOIN Orders o
on c.id=o.customer_id
JOIN Product p
ON o.product_id =p.id
I hope it solves your problem.
I think you can use following query for this question -
SELECT C1.*, p1.*
FROM Customer C1
JOIN Orders O1 ON O1.Customer_Id = C1.Id
JOIN Product P1 ON P1.Id = O1.Product_Id
WHERE C1.Id IN (SELECT c.Id
FROM Customer c
JOIN Orders o ON o.Customer_Id = c.Id
GROUP BY (c.Id)
HAVING COUNT(DISTINCT o.Product_Id) >= 2)