MySQL query (CustomerID and dates of 1st, 2nd and 3rd purchases) - mysql

I am trying to create a dashboard with customer ID and dates of 1st, 2nd and 3rd purchases. I use MySQL, Northwind db.
My query works perfectly fine for the 1st purchase, but I do not understand how to find 2nd and 3rd purchase date for each customer.
Now I'm trying to do next: 2nd_purchase_date is the next MIN(OrderDate) after 1st_purchase_date but I get following error 'Invalid use of group function'
DROP TABLE IF EXISTS t1;
CREATE TABLE t1
(
CustomerID varchar(5),
OrderDate datetime,
OrderID int,
i int
);
INSERT INTO t1(CustomerID, OrderDate, OrderID, i)
SELECT CustomerID, min(OrderDate), min(OrderID),1
FROM Orders
GROUP BY CustomerID;
INSERT INTO t1(CustomerID, OrderDate, OrderID, i)
SELECT CustomerID,min(OrderDate), min(OrderID),2
FROM Orders
WHERE min(OrderDate)
NOT IN
(
SELECT CustomerID, min(OrderDate), min(OrderID)
FROM Orders
)
GROUP BY CustomerID;
INSERT INTO t1(CustomerID, OrderDate, OrderID, i)
SELECT CustomerID,min(OrderDate), OrderID,3
FROM Orders
WHERE min(OrderDate)
NOT IN
(
SELECT CustomerID, min(OrderDate), min(OrderID) FROM Orders
)
GROUP BY CustomerID;

As shown in the last example here, you can use the LIMIT offset, count feature in combination with ORDER BY column ASC to select the entry with the second purchase date for a single user (with the id XXX replaced with the respective value):
SELECT CustomerID, OrderDate, OrderID
FROM Orders
WHERE CustomerID = XXX
ORDER BY OrderDate ASC
LIMIT 1, 1;
or the entry with the third purchase date:
SELECT CustomerID, OrderDate, OrderID
FROM Orders
WHERE CustomerID = XXX
ORDER BY OrderDate ASC
LIMIT 2, 1;
... and so on.
You get the first entry via
SELECT CustomerID, OrderDate, OrderID
FROM Orders
WHERE CustomerID = XXX
ORDER BY OrderDate ASC
LIMIT 0, 1;
or the equivalent:
SELECT CustomerID, OrderDate, OrderID
FROM Orders
WHERE CustomerID = XXX
ORDER BY OrderDate ASC
LIMIT 1;
This should give you the first three orders for a single customer:
SELECT CustomerID, OrderDate, OrderID
FROM Orders
WHERE CustomerID = XXX
ORDER BY OrderDate ASC
LIMIT 3;
While the following is definitely not good from a performance perspective, it should still work to give you the first three orders for each customer:
SELECT o1.CustomerID, o1.OrderDate, o1.OrderID
FROM Orders o1
WHERE o1.OrderID IN
(SELECT o2.OrderID
FROM Orders o2
WHERE o1.CustomerID = o2.CustomerID
ORDER BY o2.OrderDate ASC LIMIT 3)
ORDER BY o1.CustomerID, o2.OrderDate ASC;

Related

Fastest way to join 2 tables to get the most recent record

I have 2 tables:
First one is bom
Article
AB
CD
EF
GH
CREATE TABLE bom
(
Article VARCHAR(250)
);
INSERT INTO bom (Article)
VALUES
('AB'),
('CD'),
('EF'),
('GH');
Second one is purchases
Article
OrderDate
Price
AB
'2020-01-10'
12
AB
'2020-01-05'
10
AB
'2020-01-03'
8
EF
'2020-01-01'
7
CREATE TABLE purchases
(
Article VARCHAR(250),
OrderDate DATE,
Price DOUBLE
);
INSERT INTO purchases (Article, OrderDate, Price)
VALUES
('AB', '2020-01-10', 12.0),
('AB', '2020-01-05', 10.0),
('AB', '2020-01-03', 8.0),
('EF', '2020-01-01', 7.0);
I want to extract the most recent price for each row of Article at a given date.
For instance, at #evalDay = '2020-01-04', I want to get
Article
OrderDate
Price
AB
'2020-01-03'
8
EF
'2020-01-01'
7
I've managed it to work using a window function (row_number() over), but the performance is not as good as I need. This is a simplified example, but my bom table has a few hundred of rows, whereas the purchases has about 1 million rows. On my computer, it takes approx. 50ms to execute. Of course I use indexes and compound indexes.
My solution:
set #evalDay = '2020-01-04';
with cte (Article, OrderDate, Price, rn) as (
select purchases.*,
row_number() over (
partition by bom.article
order by purchases.OrderDate desc
) as rn
from bom
join purchases on bom.Article = purchases.Article
where purchases.OrderDate <= #evalDay
)
select *
from cte
where rn = 1;
In this case, what's the fastest approach to get the answer?
I would try the following approaches:
Move the join to outside
with cte (Article, OrderDate, Price, rn) as (
select *,
row_number() over (partition by article order by OrderDate desc) as rn
from purchases
where OrderDate <= #evalDay)
select cte.*, bom.*
from cte
join bom
on cte.Article = bom.Article
where cte.rn = 1;
Remove the join if no additional columns needed from bom
with cte (Article, OrderDate, Price, rn) as (
select *,
row_number() over (partition by article order by OrderDate desc) as rn
from purchases
where OrderDate <= #evalDay)
select *
from cte
where rn = 1;
If the above still doesn't perform, consider creating a table to store the result (Article,OrderOdate,Price,evalDate) partitioned by evalDate.
If you have more than one row in purchases which has the same latest date for the same Article, this might not give you what you want but it's a fairly simple query....
set #evalDay = '2020-01-04';
Select a.*
From purchases a,
(select Article, Max(OrderDate) AS ODate
from purchases
where OrderDate <= #evalDay
group by Article) b
Where a.Article = b.Article
And a.OrderDate = b.ODate;

SQL Group by returning wrong results

I have a salary table in which I am trying to return determine the lowest salary earned and by which industry for each year however despite getting the correct lowest salary earned I am receiving the wrong industry name.
I am aware that it is due to the fact that I have utilized GROUP BY without placing a constraint(?) on it hence it is returning me the wrong value but I am not sure how I can solve it.
SALARY TABLE
salaryID
salaryAmount
salaryYear
industryName (ForeignKey)
Can someone please guide me on the right path?
**(Problem Code)**
SELECT MIN(S.salary), S.industryName, S.salaryYear
FROM salary
GROUP BY S.salaryYear;
**(Attempted solution)**
SELECT S.salary
FROM salary
INNER JOIN
SELECT (min(S1.amount)), S1.year, S1.industryName, S1.salaryId
FROM salary S1
GROUP BY S1.year
ON S.salaryId = S1.salaryId);
Use a proper GROUP BY. Any non-aggregated columns must be included in GROUP BY.
SELECT MIN(amount), year
FROM salary
GROUP BY year
If you want to include industryName,
SELECT amount, year, industryName, salaryId
FROM (
SELECT amount, year, industryName, salaryId
, ROW_NUMBER() OVER(PARTITION BY year ORDER BY amount) AS rn
FROM salary
) a
WHERE rn = 1
Pre-MySQL 8 version
SELECT *
FROM salary s
INNER JOIN (
SELECT MIN(amount) AS minAmount, year
FROM salary
GROUP BY year
) m ON m.minAmount = s.amount AND m.year = s.year
I think you need a self-join :
SELECT s1.industryName, s2.min_salary, s2.salaryYear
FROM salary s1
JOIN
(
SELECT MIN(salary) as min_salary, salaryYear
FROM salary
GROUP BY salaryYear
) s2
ON s1.salary = s2.min_salary
AND s1.salaryYear = s2.salaryYear;
The Demo of this query with your sample data

Why do i keep getting the same error message on SQL

Overview:Write a SELECT statement that summarizes the guitar shop’s orders
GROUP BY order_id
HAVING MAX(discount_amount)>500
ORDER BY order_id ASC
I keep getting this error message: Error code 1055. Expression #3 of select list is not
use sum(quantity) as you are using aggregated function you've to use this also in aggregated way other wise it's need to added in group by clause
SELECT order_id, COUNT(*) AS num_items, SUM(item_price - discount_amount) *
sum(quantity) AS order_total, MAX(discount_amount) AS max_item_discount
FROM order_items
GROUP BY order_id
HAVING MAX(discount_amount)>500
ORDER BY order_id ASC
use quantity in group by as your engine ONLY_FULL_GROUP_BY
SELECT order_id, COUNT(*) AS num_items, SUM(item_price - discount_amount) *
quantity AS order_total, MAX(discount_amount) AS max_item_discount
FROM order_items
GROUP BY order_id,quantity
HAVING MAX(discount_amount)>500
ORDER BY order_id ASC
other wise use quantity inside aggregation sum((item_price - discount_amount) * quantity)
You have the column quantity not in group by nut could be you need move the column inside te sum for item_price - discount_amount
SELECT order_id
, COUNT(*) AS num_items
, SUM((item_price - discount_amount) * quantity ) AS order_total
, MAX(discount_amount) AS max_item_discount
FROM order_items
GROUP BY order_id
HAVING MAX(discount_amount)>500
ORDER BY order_id ASC

How to get the ID of the max counted user in Order table

I have a table with
orderNumber(pk) , customerNumber , comment
I have to count the maximum order placed by a user and show its user ID and MAX count . I have following Query
It shows the count Right but it takes the first CustomerNumber in the table
SELECT maxCount.customerNumber , MAX(`counted`) FROM
(
SELECT customerNumber, COUNT(*) AS `counted`
FROM `orders`
GROUP BY `customerNumber`
)as maxCount
Thanks & regards
Just use ORDER BY with your inner query:
SELECT customerNumber, COUNT(*) AS `counted`
FROM `orders`
GROUP BY `customerNumber`
ORDER BY COUNT(*) DESC
LIMIT 1
If you want to return all customer numbers in the event of a tie, you can use a HAVING clause with a subquery which identifies the maximum count:
SELECT customerNumber, COUNT(*) AS counted
FROM orders
GROUP BY customerNumber
HAVING COUNT(*) = (SELECT MAX(t.counted) FROM (SELECT COUNT(*) AS counted
FROM orders
GROUP BY customerNumber) t)
Demo here:
SQLFiddle

making a query for stock/price trend in mysql

SQL Fiddle
Table scheme:
CREATE TABLE company
(`company_id` int,`name` varchar(30))
;
INSERT INTO company
(`company_id`,`name`)
VALUES
(1,"Company A"),
(2,"Company B")
;
CREATE TABLE price
(`company_id` int,`price` int,`time` timestamp)
;
INSERT INTO price
(`company_id`,`price`,`time`)
VALUES
(1,50,'2015-02-21 02:34:40'),
(2,60,'2015-02-21 02:35:40'),
(1,70,'2015-02-21 05:34:40'),
(2,120,'2015-02-21 05:35:40'),
(1,150,'2015-02-22 02:34:40'),
(2,130,'2015-02-22 02:35:40'),
(1,170,'2015-02-22 05:34:40'),
(2,190,'2015-02-22 05:35:40')
I'm using Cron Jobs to fetch company prices. In concatenating the price history for each company, how can I make sure that only the last one in each day is included? In this case, I want all of the price records around 05:30am concatenated.
This is the result I'm trying to get (I have used Date(time) to only get the dates from the timestamps):
COMPANY_ID PRICE TIME
1 70|170 2015-02-21|2015-02-22
2 120|190 2015-02-21|2015-02-22
I have tried the following query but it doesn't work. The prices don't correspond to the dates and I don't know how to exclude all of the 2:30 am records before applying the Group_concat function.
SELECT company_id,price,trend_date FROM
(
SELECT company_id, GROUP_CONCAT(price SEPARATOR'|') AS price,
GROUP_CONCAT(trend_date SEPARATOR'|') AS trend_date
FROM
(
SELECT company_id,price,
DATE(time) AS trend_date
FROM price
ORDER BY time ASC
)x1
GROUP BY company_id
)t1
Can anyone show me how to get the desired result?
Ok, so this should work as intended:
SELECT p.company_id,
GROUP_CONCAT(price SEPARATOR '|') as price,
GROUP_CONCAT(PriceDate SEPARATOR '|') as trend_date
FROM price as p
INNER JOIN (SELECT company_id,
DATE(`time`) as PriceDate,
MAX(`time`) as MaxTime
FROM price
GROUP BY company_id,
DATE(`time`)) as t
ON p.company_id = t.company_id
AND p.`time` = t.MaxTime
GROUP BY p.company_id
Here is the modified sqlfiddle.
This is a bit unorthodox but I think it solves your problem:
SELECT company_id,
GROUP_CONCAT(price SEPARATOR'|'),
GROUP_CONCAT(trend_date SEPARATOR'|')
FROM (
SELECT *
FROM (
SELECT company_id,
DATE(`time`) `trend_date`,
price
FROM price
ORDER BY `time` DESC
) AS a
GROUP BY company_id, `trend_date`
) AS b
GROUP BY company_id