Simplify in sql [duplicate] - mysql

This question already has answers here:
SQL select only rows with max value on a column [duplicate]
(27 answers)
Closed 1 year ago.
I have some task that I should do it with sql.
Below is the function I currently use.
(SELECT ProductName, sum(Quantity*Price) Revenue, Country
FROM products p
JOIN orderdetails d
ON p.ProductID = d.ProductID
JOIN orders o
ON o.OrderID = d.OrderID
JOIN customers c
ON c.CustomerID = o.CustomerID
WHERE Country =
(
SELECT DISTINCT Country
FROM customers
LIMIT 1
)
GROUP BY ProductName
ORDER BY Revenue DESC
LIMIT 1)
UNION
(SELECT ProductName, sum(Quantity*Price) Revenue, Country
FROM products p
JOIN orderdetails d
ON p.ProductID = d.ProductID
JOIN orders o
ON o.OrderID = d.OrderID
JOIN customers c
ON c.CustomerID = o.CustomerID
WHERE Country =
(
SELECT DISTINCT Country
FROM customers
LIMIT 1,1
)
GROUP BY ProductName
ORDER BY Revenue DESC
LIMIT 1)
UNION
(SELECT ProductName, sum(Quantity*Price) Revenue, Country
FROM products p
JOIN orderdetails d
ON p.ProductID = d.ProductID
JOIN orders o
ON o.OrderID = d.OrderID
JOIN customers c
ON c.CustomerID = o.CustomerID
WHERE Country =
(
SELECT DISTINCT Country
FROM customers
LIMIT 2,1
)
GROUP BY ProductName
ORDER BY Revenue DESC
LIMIT 1)
My task is "Find best selling products based on revenue for each country!"
The result I want is below:
ProductName
Revenue
Country
Tofu
279
Argentina
Côte de Blaye
18445
Austria
You can access the data I use from this link RawDatabase
the sample data that I use is like this
ProdName
Country
Revenue
coco
Argentina
120
bread
Austria
10000
crunch
Austria
13265
Cote de Blaye
Austria
18445
milk
Argentina
254
Tofu
Argentina
279
From this data I want to select only the best product for each country by revenue. In the data there were 21 country. What should I do so I can get the result below
ProductName
Revenue
Country
Tofu
279
Argentina
Côte de Blaye
18445
Austria
The only way in my mind is only filtering the data by each country then get the best product then union all of them like the code I give on top. I wonder if there's another way.

Using row_number window function or compare to country maxrevenue
DROP TABLe if exists t;
create table t
(ProdName varchar(20), Country varchar(20), Revenue int);
insert into t values
('coco' ,'Argentina' ,120),
('bread' ,'Austria' ,10000),
('crunch','Austria' ,13265),
('Cote de Blaye' ,'Austria', 18445),
('milk' ,'Argentina' ,254),
('Tofu' ,'Argentina' ,279);
select *
from
(
select prodname,country,revenue,
row_number() over(partition by country order by revenue desc) rn
from t
) s
where rn = 1;
or
select *
from t
join (select t.country,max(t.revenue) maxrevenue from t group by t.country) t1
on t1.country = t.country and t1.maxrevenue = t.revenue;
+---------------+-----------+---------+----+
| prodname | country | revenue | rn |
+---------------+-----------+---------+----+
| Tofu | Argentina | 279 | 1 |
| Cote de Blaye | Austria | 18445 | 1 |
+---------------+-----------+---------+----+
2 rows in set (0.001 sec)

Related

MYSQL - select row based on condition, otherwise select other row

I have a table products which have an association with product_prices.
Each product can have multiple prices for different countries.
Desired outcome: When I run the query, I want to receive all products with 1 single price based on country condition.
Logic
If product has price for the specified country-> then country price
will be displayed.
If product does not have price for the specified
country-> then price for with country_id 400 needs to be displayed.
If product has no price for specified country and for country with id 400 -> then the price with country_id 500 needs to be displayed.
If product has not price for specified country and id 400, and id 500 -> then price which has country with biggest amount of total users will be
displayed.
I did something like this for 1 single product. But I don't know how to achieve these for all products.
select p.id, pp.price, pp.country_id,
(CASE WHEN (pp.country_id=:countryId) then 0 else
(CASE WHEN (pp.country_id=400) then 1 else
(case when (pp.country_id=500) then 2 else 3 END) END) END) as priorityIndex
from products p
inner join product_prices pp on p.id = pp.product_id
inner join countries c on pp.country_id = c.id
where p.id = '00057c218b154d5b838b928a0189ff9f'
order by priorityIndex limit 1;
My data:
Country table
country_id
country_code
total_users
2
FR
10
10
US
100
27
UK
200
400
EU
160
500
GLOBAL
150
Product price table
product_id
price_id
price
country_id
product1
1a
19.99
27
product1
1b
20.99
400
product1
1c
30.99
500
product2
2a
199.99
10
product2
2b
299.99
400
product3
3a
50.99
500
product4
4a
40
2
product4
4a
45
10
My expected output when inserting country 27:
product_id
price
country_id
product1
19.99
27
product2
20.99
400
product3
50.99
500
product4
45
10
I did something like this. But I am not sure only about the last part. In case product price needs to be choosed based on total dealers.
select A.id, A.price_id, A.price, A.country from
(select p.id, pp.id as price_id, pp.price, pp.country_id as country,
ROW_NUMBER() over (PARTITION BY p.id order by (CASE WHEN (pp.country_id=:countryId) then 0 else
(CASE WHEN (pp.country_id=400) then 1 else
(case when (pp.country_id=500) then 2 else 3 END) END) END), c.total_dealer_users desc) AS rowNumber
from products p
inner join product_prices pp on p.id = pp.product_id
inner join countries c on pp.country_id = c.id
where p.id in ('00057c218b154d5b838b928a0189ff9f','054a8caf911e4ff3a594990af20a9611')) as A
where rowNumber=1;
SELECT
*
FROM
(
SELECT
pp.product_id,
pp.price_id,
pp.price,
pp.country_id as country,
ROW_NUMBER() OVER (
PARTITION BY pp.product_id
ORDER BY FIELD(
c.country_id,
27,
400,
500,
c.country_id
),
c.total_users DESC
)
AS rowNumber
FROM
product_price pp
INNER JOIN
country c
ON pp.country_id = c.country_id
)
AS ranked_price
WHERE
rowNumber=1;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=94f8851be7f309d8c4fd1946230d7999
With MySQL 8.0+ ROW_NUMBER() allows to drop all rows except first per product:
SELECT * FROM (
SELECT p.id,
pp.price,
pp.country_id,
ROW_NUMBER() OVER (PARTITION BY p.id ORDER BY FIELD(pp.country_id, 500, 400, 27) DESC) AS `rn`
FROM products AS p
JOIN product_prices AS pp
ON p.id = pp.product_id
WHERE p.id IN (...)
) AS t WHERE t.rn=1

Mysql query select customers with no orders in each year

i have 2 table's one with customers and one with orders
SELECT customers.customer_name, orders.order_date
FROM customers
Left
Join orders on (customers.customer_id = orders.customer_id)
WHERE not orders.customer_id IN (SELECT customer_id from orders where Year(order_date) = Year(#Parameter1))
and not orders.order_date is null
this works but i want to do this for each year to get somthing like this as result
|Year | customer_id |
|2010 | 1 |
|2010 | 2 |
|2011 | 2 |
|2011 | 3 |
|2012 | 1 |
You want a list showing customers and years that are not present in the orders table. So get a list of all customers combined with all years and then subtract the customers and years that you find in the orders table:
select o.yr, c.customer_id
from customers c
cross join (select distinct year(order_date) as yr from orders) o
where (c.customer_id, o.yr) not in (select customer_id, year(order_date) from orders);
Scorpio is sort of right, you do have to use the year function:
SELECT Year(orders.order_date), customers.customer_id
FROM customers
LEFT JOIN orders
ON (customers.customer_id = orders.customer_id)
WHERE NOT orders.customer_id
IN (
SELECT customer_id
FROM orders
WHERE Year(order_date) = Year(#Parameter1)
)
AND
NOT orders.order_date is NULL
You can use the year method in the SELECT section of your query
Update:
To display all the customers who didn't order in a year, you don't need the join. You can do it with a single subselect:
SELECT #Parameter1 AS Year, customer_id
FROM customers
WHERE customers.customer_id NOT IN (
SELECT customer_id
FROM orders
WHERE Year(order_date) = Year(#Parameter1)
)

SQL Inner Join Group By

Table - Customers
id FirstName LastName
23 James Smith
24 Tom Raven
25 Bob King
Table - Orders
id CustomerID
30 23
31 24
32 23
33 25
Table - Products
OrderID Product1 Product2 Product3
30 1 0 0
31 2 0 0
32 0 1 1
33 2 1 0
I want to count the total number of Products for each Customer so answers would be:
CustomerID FirstName LastName Total
23 James Smith 3
24 Tom Raven 2
25 Bob King 3
So far I've got for the SQL query:
SELECT Customers.id, Orders.id,
FROM Customers
INNER JOIN Orders ON Customers.id = Orders.CustomerID
INNER JOIN Products ON Orders.id = Products.OrderID
Not sure how to count up the products though.
Any help would be much appreciated.
select c.id as CustomerID
,(sum(p.Product1) + sum(p.Product2) + sum(p.Product3)) as ProductCount
from Customers c
inner join Orders o on c.id = o.CustomerID
inner join Products p on o.id = p.OrderID
group by c.id
You could use
SELECT c.id as CustomerID, c.firstname, c.lastname,
sum(p.Product1 + p.Product2 + p.Product3) as total
FROM Customers c
INNER JOIN Orders o
ON c.id=o.CustomerID
INNER JOIN Products p
ON o.id=p.OrderID
group by c.id,c.firstname, c.lastname;
And as #Thorsten Kettner's comment, you should consider normalizing your table design.
Add a GROUP BY clause! Normally it includes the selected columns that are not arguments to a set function.
SELECT O.CustomerID, C.FirstName, C.LastName, count(*) as Total
FROM Customers C
INNER JOIN Orders O ON C.id = O.CustomerID
INNER JOIN Products P ON O.id = P.OrderID
GROUP BY O.CustomerID, C.FirstName, C.LastName
PS. Now using table aliases, to save some typing.
SELECT Customers.id, Customers.firstname, Customers.lastname,count(*) as total FROM Customers
INNER JOIN Orders
ON Customers.id=Orders.CustomerID
INNER JOIN Products
ON Orders.id=Products.OrderID
group by Customers.id,Customers.firstname, Customers.lastname
A more conventional approach might be as follows:
DROP TABLE IF EXISTS customers;
CREATE TABLE customers
(customer_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,firstname VARCHAR(20) NOT NULL
,lastname VARCHAR(20) NOT NULL
);
INSERT INTO customers VALUES
(23,'James','Smith'),
(24,'Tom','Raven'),
(25,'Bob','King');
DROP TABLE IF EXISTS orders;
CREATE TABLE orders
(order_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,customer_id INT NOT NULL
);
INSERT INTO orders VALUES
(30 ,23),
(31 ,24),
(32 ,23),
(33 ,25);
DROP TABLE IF EXISTS order_detail;
CREATE TABLE order_detail
(order_id INT NOT NULL
,product_id INT NOT NULL
,quantity INT NOT NULL
,PRIMARY KEY(order_id,product_id)
);
INSERT INTO order_detail VALUES
(30 ,1 ,1),
(31 ,1 ,2),
(33 ,1 ,2),
(32 ,2 ,1),
(33 ,2 ,1),
(32 ,3 ,1);
SELECT c.*
, SUM(od.quantity) total
FROM customers c
JOIN orders o
ON o.customer_id = c.customer_id
JOIN order_detail od
ON od.order_id = o.order_id
GROUP
BY c.customer_id;
+-------------+-----------+----------+-------+
| customer_id | firstname | lastname | total |
+-------------+-----------+----------+-------+
| 23 | James | Smith | 3 |
| 24 | Tom | Raven | 2 |
| 25 | Bob | King | 3 |
+-------------+-----------+----------+-------+
SELECT DISTINCT CustomerID, FirstName, LastName, Total
FROM ( SELECT id AS CustomerID, FirstName, LastName FROM Customers ) AS c
NATURAL JOIN
( SELECT id AS OrderID, CustomerID FROM Orders ) AS o
NATURAL JOIN
( SELECT OrderID, SUM( Product1 + Product2 + Product3 ) AS Total
FROM Products
GROUP
BY OrderID ) AS p;

MySQL: Getting the MIN and the MAX of a table and both of their titles

I have a large database of products. It has a one to many relationship to another table of prices. I can easily get, with one query, the MIN, MAX and AVG of a particular category.
SELECT
MIN(gbp.price) AS min,
ROUND(AVG(gbp.price),2) AS ave,
MAX(gbp.price) AS max
FROM sku AS s
INNER JOIN price gbp ON gbp.sid = s.id
However, I also want to be able to get the title of the product it relates to as well - I cannot get this resolved despite multiple searches and rewrites.
My data is similar to...
prod_id | title
===============
1 | prod1
2 | prod2
3 | prod3
4 | prod4
5 | prod5
6 | prod6
7 | prod7
price_id | prod_id | price | price_date
=======================================
1 | 1 | 2.99 | 2015/02/01
2 | 1 | 3.99 | 2015/02/12
3 | 2 | 12.99 | 2015/02/01
4 | 3 | 15.99 | 2015/02/01
5 | 4 | 29.99 | 2015/02/01
6 | 5 | 29.99 | 2015/02/01
7 | 5 | 24.99 | 2015/02/12
8 | 6 | 2.99 | 2015/02/01
9 | 7 | 99.99 | 2015/02/01
10 | 7 | 89.99 | 2015/02/12
I am going to presume that other people may want a query writing similar to this, so I am going to ask for two answers.
First one "simply" to return this...
min | min_title | ave | max | max_title
============================================
2.99 | prod1 | 31.39 | 99.99 | prod7
However, the real answer I want (despite the fact I cannot even solve the above) is where it gets even trickier.
The actual results I want is in the table below...
min | min_title | ave | max | max_title
============================================
2.99 | prod6 | 25.85 | 89.99 | prod7
The min is 2.99 for prod6 as the 2.99 price for prod1 has expired.
The max is 89.99 for prod7 as the 99.99 price for prod7 has expired.
The ave is 25.85 because of above and because the price for prod5 is 24.99.
I am not expecting answers for everything, just answering the first question (in bold) will likely lead me to the answer for the second part (as I have similar queries that get the latest price etc).
SELECT t1.min, s.title AS min_title, t1.ave, t1.max, s2.title AS max_title
FROM (SELECT
MIN(gbp.price) AS min,
ROUND(AVG(gbp.price),2) AS ave,
MAX(gbp.price) AS max
FROM sku AS s
INNER JOIN price gbp ON (gbp.sid = s.id)
) t1
INNER JOIN (SELECT gbp.price, MAX(gbp.prod_id) AS MaxProdID
FROM price gbp
WHERE NOT EXISTS(
SELECT p2.price_id
FROM price p2
WHERE p2.price_id > gbp.price_id
AND p2.prod_id = gpb.prod_id
)
GROUP BY gbp.price
) minprice ON (minprice.price = t1.min)
INNER JOIN sku s ON (s.id = minprice.MaxProdID)
INNER JOIN (SELECT gbp.price, MAX(gbp.prod_id) AS MaxProdID
FROM price gbp
WHERE NOT EXISTS(
SELECT p2.price_id
FROM price p2
WHERE p2.price_id > gbp.price_id
AND p2.prod_id = gpb.prod_id
)
GROUP BY gbp.price
) maxprice ON (maxprice.price = t1.max)
INNER JOIN sku s2 ON (s2.id = maxprice.MaxProdID)
To solve your first output just use join to get those values:
SELECT min, mint.title, ave, max, maxt.title
FROM (
SELECT
MIN(gbp.price) AS min,
ROUND(AVG(gbp.price),2) AS ave,
MAX(gbp.price) AS max
FROM (SELECT price
FROM price AS gbp
INNER JOIN sku s2 ON gbp.sid = s2.id
ORDER BY prdate DESC
LIMIT 0, 1) AS s
INNER JOIN price gbp ON gbp.sid = s.id
) inq
JOIN price minp ON inq.min = minp.price
JOIN price maxp on inq.max = maxp.price
JOIN prod mint ON minp.prod_id = mint.prod_id
JOIN prod maxt ON maxp.prod_id = maxt.prod_id
I don't understand the rules for your second output.
This is essentially two different queries (or three if you count the average). The cross join is just horizontally splicing together the two results for min and max. They could all obviously be separated and executed individually.
with current_prices as (
select price_id, prod_id, price
from prices
where price_date = (
select max(price_date)
from prices as prices2
where prices2.prod_id = prices.prod_id
)
),
min_current_prices as (
select price, min(prod_id) as prod_d /* arbitrary selected representative */
from current_prices
where price = (
select min(price)
from current_prices
)
group by price
),
max_current_prices as (
select price, min(prod_id) as prod_id /* arbitrary selected representative */
from current_prices
where price = (
select max(price)
from current_prices
)
group by price
)
select
m1.price, prod1.title,
(select avg(price) from current_prices) as ave,
m2.price, prod2.title
from
min_current_prices as m1 inner join products as prod1 on prod1.prod_id = m1.prod_id
max_current_prices as m2 inner join products as prod2 on prod2.prod_id = m2.prod_id
I feel that this seems too complicated and yet you're asking for something very unusual. There clearly could be products with the same min/max price so this is going to cause problems when there is more than one at either end.
If your platform doesn't support WITH then just substitute the full query instead:
select
min_current_price.price as min_price, min_prod.title as min_title,
(
select avg(price)
from prices
where price_date = (
select max(price_date)
from prices as prices2
where prices2.prod_id = prices.prod_id
)
) as ave,
max_current_price.price as max_price, max_prod.title as max_title
from
(
select price, min(prod_id) as prod_id /* arbitrarily selected representative */
from (
select *
from prices
where price_date = (
select max(price_date)
from prices as prices2
where prices2.prod_id = prices.prod_id
)
) as current_prices
where price = (
select min(price)
from prices
where price_date = (
select max(price_date)
from prices as prices2
where prices2.prod_id = prices.prod_id
)
)
group by price
) as min_current_price
cross join
(
select price, min(prod_id) as prod_id /* arbitrarily selected representative */
from (
select *
from prices
where price_date = (
select max(price_date)
from prices as prices2
where prices2.prod_id = prices.prod_id
)
) as current_prices
where price = (
select max(price)
from prices
where price_date = (
select max(price_date)
from prices as prices2
where prices2.prod_id = prices.prod_id
)
)
group by price
) as max_current_price
inner join products as min_prod on min_prod.prod_id = min_current_price.prod_id
inner join products as max_prod on max_prod.prod_id = max_current_price.
Here's a hack for doing it in mysql using limits and sorting methods:
select
minprice.price as min_price, minprod.title as min_title,
(
select avg(price)
from prices
where price_date = (
select max(price_date)
from prices as prices2
where prices2.prod_id = prices.prod_id
)
) as ave,
maxprice.price as max_price, maxprod.title as max_title
from
(
select price_id, price, prod_id
from prices
where not exists ( /* another way of excluding expired prices */
select 1 from prices as p2
where p2.prod_id = prices.prod_id and p2.price_date > prices.prod_id
)
order by price asc
limit 0, 1
) as minprice,
(
select price_id, price, prod_id
from prices
where not exists (
select 1 from prices as p2
where p2.prod_id = prices.prod_id and p2.price_date > prices.prod_id
)
order by price desc
limit 0, 1
) as maxprice
inner join prod as minprod on minprod.prod_id = minprice.prod_id
inner join prod as maxprod on min.prod_id = maxprice.prod_id

Complicated sum() with group and order by date

There are two tables:
* ORDER
- id
- pay_type
* ORDER_PRICE
- order_id
- dt
- price
Order price can be changed, for example:
order_id | price | dt
1 | 100.3 | 2013-10-25
1 | 105.7 | 2013-10-28
2 | 207.4 | 2013-09-13
4 | 98.0 | 2013-10-03
I can select price history for any date like that:
SELECT
o.`id`,
(SELECT op.`price` FROM `order_price` op
WHERE op.`order_id`=o.`id` AND op.`dt` <= '2013-10-26'
ORDER BY op.`dt` DESC LIMIT 1) order_price
FROM `order` o
It gives me right prices for given date
order_id | price | dt
1 | 100.3 | 2013-10-25
2 | 207.4 | 2013-09-13
4 | 98.0 | 2013-10-03
But i need the sum of the second column (no matter what order number, only one number - 405.7 in this case).
Is there a solution for such a situation? There can be thousands of orders, so i think it will be wrong to sum records outside the mysql. Maybe it's the wrong all the way from the start and i need other structure? Thank you for your time and help.
I suspect the core query should look more like this...
SELECT o.order_id
, op.price
, op.dt
FROM orders o
JOIN order_price op
ON op.order_id = o.order_id
JOIN
( SELECT order_id, MAX(dt) max_dt FROM order_price WHERE dt < '2013-10-26' GROUP BY order_id ) x
ON x.order_id = op.order_id
AND x.max_dt = op.dt;
...which can be rewritten this way, to give totals
SELECT o.order_id
, SUM(op.price) price
, op.dt
FROM orders o
JOIN order_price op
ON op.order_id = o.order_id
JOIN
( SELECT order_id, MAX(dt) max_dt FROM order_price WHERE dt < '2013-10-26' GROUP BY order_id ) x
ON x.order_id = op.order_id
AND x.max_dt = op.dt
GROUP
BY order_id,dt
WITH ROLLUP;
...or this way
SELECT SUM(price) total
FROM
( SELECT o.order_id
, op.price
, op.dt
FROM orders o
JOIN order_price op
ON op.order_id = o.order_id
JOIN
( SELECT order_id, MAX(dt) max_dt FROM order_price WHERE dt < '2013-10-26' GROUP BY order_id ) x
ON x.order_id = op.order_id
AND x.max_dt = op.dt
) z;
...or just pull the total at the application level.