Adjacency list model duplicate parent & children - mysql

Hierarchical Data in MySQL Using the Adjacency List Model
I have this table named node_structur_data
CREATE TABLE node_structure_data (
id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(455) NOT NULL,
parent_id INT(10) UNSIGNED DEFAULT NULL,
PRIMARY KEY (id),
FOREIGN KEY (parent_id) REFERENCES node_structure_data (id)
ON DELETE CASCADE ON UPDATE CASCADE
);
Output:
id title parent_id
1 Division NULL
2 Site 1 1
3 Paper 2
4 ms1 3
How can I duplicate a node and its children?
For example Site 1
The id & parent_id should be unique but the title should stay the same.
Expected Output:
id title parent_id
1 Division NULL
2 Site 1 1
3 Paper 2
4 ms1 3
5 Site 1 1
6 Paper 5
7 ms1 6

The following approach first estimates the new max and then uses a recursive cte to find all children of the desired node 'Site 1' and determine their new possible parent_id if there were no other concurrent writes to the table.
I would recommend running the following in a transaction and locking the table during the operation to prevent concurrent table modifications.
To test this approach I added some additional sample data which I have included below, however you may see the approach in a demo with your initial sample data here
See output of working db fiddle below:
Schema (MySQL v8.0)
CREATE TABLE node_structure_data (
id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(455) NOT NULL,
parent_id INT(10) UNSIGNED DEFAULT NULL,
PRIMARY KEY (id),
FOREIGN KEY (parent_id) REFERENCES node_structure_data (id)
ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO node_structure_data
(`id`, `title`, `parent_id`)
VALUES
('1', 'Division', NULL),
('2', 'Site 1', '1'),
('3', 'Paper', '2'),
('4', 'ms1', '3'),
('5', 'ms2', '3'),
('6', 'os1', '4'),
('7', 'os2', '4'),
('8', 'gs1', '1'),
('9', 'hs1', '3'),
('10','js1','9');
Query #1
select 'Before Insert';
Before Insert
Before Insert
Query #2
select * from node_structure_data;
id
title
parent_id
1
Division
2
Site 1
1
3
Paper
2
4
ms1
3
5
ms2
3
6
os1
4
7
os2
4
8
gs1
1
9
hs1
3
10
js1
9
Query #3
select 'Possible Data Changes';
Possible Data Changes
Possible Data Changes
Query #4
with recursive max_id AS (
SELECT MAX(id) as id FROM node_structure_data
),
child_nodes AS (
SELECT
n.id,
title,
parent_id,
m.id+1 as new_id,
parent_id as new_parent_id
FROM
node_structure_data n
CROSS JOIN
max_id as m
WHERE
title='Site 1'
UNION ALL
SELECT
n.id,
n.title,
n.parent_id,
#row_num:=IF(#row_num=0,c.new_id,0) + 1 + #row_num as new_id,
c.new_id
FROM
child_nodes c
INNER JOIN
node_structure_data n ON n.parent_id = c.id
CROSS JOIN (
SELECT #row_num:=0 as rn
) as vars
)
SELECT * FROM child_nodes;
id
title
parent_id
new_id
new_parent_id
2
Site 1
1
11
1
3
Paper
2
12
11
4
ms1
3
13
12
5
ms2
3
14
12
9
hs1
3
15
12
6
os1
4
16
13
7
os2
4
17
13
10
js1
9
18
15
Query #5 - Performing actual insert
INSERT INTO node_structure_data (title,parent_id)
with recursive max_id AS (
SELECT MAX(id) as id FROM node_structure_data
),
child_nodes AS (
SELECT
n.id,
title,
parent_id,
m.id+1 as new_id,
parent_id as new_parent_id
FROM
node_structure_data n
CROSS JOIN
max_id as m
WHERE
title='Site 1'
UNION ALL
SELECT
n.id,
n.title,
n.parent_id,
#row_num:=IF(#row_num=0,c.new_id,0) + 1 + #row_num as new_id,
c.new_id
FROM
child_nodes c
INNER JOIN
node_structure_data n ON n.parent_id = c.id
CROSS JOIN (
SELECT #row_num:=0 as rn
) as vars
)
SELECT title,new_parent_id FROM child_nodes ORDER BY new_id;
There are no results to be displayed.
Query #6
select 'AFTER INSERT';
AFTER INSERT
AFTER INSERT
Query #7
select * from node_structure_data;
id
title
parent_id
1
Division
2
Site 1
1
3
Paper
2
4
ms1
3
5
ms2
3
6
os1
4
7
os2
4
8
gs1
1
9
hs1
3
10
js1
9
11
Site 1
1
12
Paper
11
13
ms1
12
14
ms2
12
15
hs1
12
16
os1
13
17
os2
13
18
js1
15
View on DB Fiddle
Let me know if this works for you.

Related

How can I use IF and CONCAT within SELECT

I have this Adjacency List Model table
Table:
CREATE TABLE node_structure_data (
id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(455) NOT NULL,
parent_id INT(10) UNSIGNED DEFAULT NULL,
PRIMARY KEY (id),
FOREIGN KEY (parent_id) REFERENCES node_structure_data (id)
ON DELETE CASCADE ON UPDATE CASCADE
);
Output:
id title parent_id
1 Division NULL
2 Site 1 1
3 Paper 2
4 ms1 3
5 Site 2 1
6 Paper 5
7 ms2 6
8 Site 3 1
9 Paper 8
10 ms3 9
So I have the following query that duplicates a Site 1 e.g. and its children.
In this case, the children are Paper with parent_id = 2 and ms1 with parent_id = 3
INSERT INTO node_structure_data (title,parent_id)
WITH recursive max_id AS (
SELECT MAX(id) AS id FROM node_structure_data
),
child_nodes AS (
SELECT
n.id,
title,
parent_id,
m.id+1 AS new_id,
parent_id AS new_parent_id
FROM
node_structure_data n
CROSS JOIN
max_id AS m
WHERE
title='Site 1'
UNION ALL
SELECT
n.id,
n.parent_id,
n.title,
#row_num:=IF(#row_num=0,c.new_id,0) + 1 + #row_num AS new_id,
c.new_id
FROM
child_nodes c
INNER JOIN
node_structure_data n ON n.parent_id = c.id
CROSS JOIN (
SELECT #row_num:=0 AS rn
) AS vars
)
SELECT title,new_parent_id FROM child_nodes ORDER BY new_id;
Output:
id title parent_id
1 Division NULL
2 Site 1 1
3 Paper 2
4 ms1 3
5 Site 2 1
6 Paper 5
7 ms2 6
8 Site 3 1
9 Paper 8
10 ms3 9
11 Site 1 1
12 Paper 11
13 ms1 12
As you can see Site 1 and its children got duplicated with a new unique id.
However for the duplicated Site title I want to have a prefix text Copy of for the DUPLICATED Site 1 title
I only want that prefix for a Site/parent_id = 1
So that the duplicated nodes should look like this:
id title parent_id
1 Division NULL
2 Site 1 1
3 Paper 2
4 ms1 3
5 Site 2 1
6 Paper 5
7 ms2 6
8 Site 3 1
9 Paper 8
10 ms3 9
11 Copy of Site 1 1
12 Paper 11
13 ms1 12
I have tried to implement the IF and CONCAT in the query but for some reason, it doesn't work, I don't get any errors but the output stays the same.
IF(n.title LIKE '%Site%', CONCAT("Copy of ", n.title), n.title),
If the title contains the text Site then I want to contact the prefix and the site title otherwise no concat.
Any ideas?
Any help is appreciated!!!
This solution shows how to insert a copy of a subtree and re-identify the descendants.
INSERT INTO node_structure_data (id, title, parent_id)
WITH RECURSIVE subtree AS (
SELECT
id,
(SELECT MAX(id) FROM node_structure_data) AS last_id,
CONCAT('Copy of ', title) AS title,
parent_id
FROM node_structure_data
WHERE id = 2 -- i.e. title = 'Site 1'
UNION ALL
SELECT
n.id,
s.last_id,
n.title,
n.parent_id
FROM subtree s
JOIN node_structure_data n ON s.id = n.parent_id
), new_id AS (
SELECT
id,
last_id + ROW_NUMBER() OVER (ORDER BY id) AS new_id,
title,
parent_id
FROM subtree
)
SELECT
n.new_id AS id,
n.title,
COALESCE(p.new_id, n.parent_id) AS parent_id
FROM new_id n
LEFT JOIN new_id p ON n.parent_id = p.id
Note that starting MySQL 8 setting user variables within expressions is deprecated and will be removed in a future release.
The following fiddle shows the results of each CTE - db<>fiddle

Aggregate rows by id comparing column values

I have the following table that groups users by their permissions
userIds permissions
4,5,7,8 100,1600,500,501,502,400,401,1500,1501
The numbers in the permissions column are the sections ids.
Some of these sections may have other data associated which I retrieved and stored in another table.
sectionId userId resourceId
100 4 NULL
1600 4 NULL
500 4 NULL
501 4 NULL
502 4 NULL
400 4 NULL
401 4 1
1500 4 NULL
1501 4 NULL
100 5 NULL
1600 5 NULL
500 5 NULL
501 5 NULL
502 5 NULL
400 5 NULL
401 5 1,2
1500 5 NULL
1501 5 NULL
100 7 NULL
1600 7 NULL
500 7 NULL
501 7 NULL
502 7 NULL
400 7 NULL
401 7 2
1500 7 NULL
1501 7 NULL
100 8 NULL
1600 8 NULL
500 8 NULL
501 8 NULL
502 8 NULL
400 8 NULL
401 8 1
1500 8 NULL
1501 8 NULL
My goal is to compare, for each user in the userIds column of the first table (splitted by comma), every row of the second table in order to check if each user has the same resourceId value for that specific sectionId.
If one or more users have the same resourceId value for each section I want to keep them group together, otherwise they need to be on different rows.
This is the output I'm expecting from the sample data provided:
userIds permissions
4,8 100,1600,500,501,502,400,401,1500,1501
5 100,1600,500,501,502,400,401,1500,1501
7 100,1600,500,501,502,400,401,1500,1501
UPDATE
I managed to get the desidered output in the following way:
-- Numbers table creation
DROP temporary TABLE IF EXISTS tally;
CREATE temporary TABLE tally
(
n INT NOT NULL auto_increment PRIMARY KEY
);
INSERT INTO tally
(n)
SELECT NULL
FROM (SELECT 0 AS N
UNION ALL
SELECT 1
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
UNION ALL
SELECT 5
UNION ALL
SELECT 6
UNION ALL
SELECT 7
UNION ALL
SELECT 8
UNION ALL
SELECT 9) a,
(SELECT 0 AS N
UNION ALL
SELECT 1
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
UNION ALL
SELECT 5
UNION ALL
SELECT 6
UNION ALL
SELECT 7
UNION ALL
SELECT 8
UNION ALL
SELECT 9) b;
-- Split users by comma from first table
DROP temporary TABLE IF EXISTS tmppermissions2;
CREATE temporary TABLE tmppermissions2
(
userid VARCHAR(255) NOT NULL,
permissions TEXT NOT NULL
);
INSERT INTO tmppermissions2
SELECT userid,
permissions
FROM (SELECT Substring_index(Substring_index(t.userids, ',', tally.n), ',', -1
)
userId,
t.permissions
permissions
FROM tally
INNER JOIN tmppermissions t
ON Char_length(t.userids) - Char_length(
REPLACE(t.userids, ',',
'')) >=
tally.n - 1
ORDER BY n) AS split;
-- Gets the users with the same permissions
DROP temporary TABLE IF EXISTS sharedprofiles;
CREATE temporary TABLE sharedprofiles
(
userids VARCHAR(255) NOT NULL,
permissions TEXT NOT NULL,
profileid INT(11)
);
INSERT INTO sharedprofiles
SELECT Group_concat(userid),
permissions,
NULL
FROM tmppermissions2
WHERE userid NOT IN (SELECT split.userid
FROM (SELECT Substring_index(Substring_index(r.userids,
',',
t.n), ',', -1)
userId
FROM tally t
INNER JOIN tmppermissions r
ON Char_length(r.userids)
- Char_length(
REPLACE(r.userids, ',',
'')) >=
t.n - 1
WHERE Position(',' IN r.userids) > 0
ORDER BY n) AS split
WHERE split.userid IN (SELECT *
FROM (SELECT Group_concat(userid
ORDER
BY userid ASC)
AS
users
FROM
tmpcurrentresources2
GROUP BY resourceid,
sectionid
ORDER BY users) b
WHERE Position(',' IN b.users) =
0))
GROUP BY permissions
ORDER BY Group_concat(userid);
-- Gets the users with specific permissions
DROP temporary TABLE IF EXISTS singleprofiles;
CREATE temporary TABLE singleprofiles
(
userid VARCHAR(255) NOT NULL,
permissions TEXT NOT NULL,
profileid INT(11)
);
INSERT INTO singleprofiles
SELECT userid,
permissions,
NULL
FROM tmppermissions2
WHERE userid IN (SELECT split.userid
FROM (SELECT Substring_index(Substring_index(r.userids, ',',
t.n),
',', -1)
userId
FROM tally t
INNER JOIN tmppermissions r
ON Char_length(r.userids) -
Char_length(
REPLACE(r.userids, ',',
'')) >=
t.n - 1
WHERE Position(',' IN r.userids) > 0
ORDER BY n) AS split
WHERE split.userid IN (SELECT *
FROM (SELECT Group_concat(userid
ORDER BY
userid ASC)
AS
users
FROM tmpcurrentresources2
GROUP BY resourceid,
sectionid
ORDER BY users) b
WHERE Position(',' IN b.users) = 0))
ORDER BY userid;
-- Merge the results
SELECT *
FROM sharedprofiles
UNION
SELECT *
FROM singleprofiles;
I'm wondering if there is a more concise way to accomplish the same result.
The solution (as I suspect you already know) is to normalise your schema.
So instead of...
userIds permissions
4,5 100,1600,500
...you might have
userIds permissions
4 100
4 1600
4 500
5 100
5 1600
5 500

mysql count on multiple columns from another table

I would like to get count of two columns from another table. For example, the source data is as below for the table called employee_master
emp_code dept_id sub_dept_id
1 10 22
2 11 20
3 10 22
4 10 22
5 11 20
6 10 21
Another source table called punching_data_table as shown below which is being joined with employee_master.
emp_code present
1 10:01 AM
2 10:02 AM
3 10:02 AM
4 10:03 AM
5 10:11 AM
6 10:09 AM //biometric punching time shown
The result is suppose to look like below.
dept_id dept_count sub_dept_id sub_dept_count
10 4 22 3
10 4 21 1
11 2 20 2
Tried with following code but it doesn't count seperately for both columns even though applied group by employee_master.dept_id, employee_master.sub_dept_id. Please note that there is only 1 column common between both table that is emp_code.
SELECT p.emp_code
, e.emp_code
, COUNT(e.dept_id) dept_id_count
, COUNT(e.sub_dept_id) sub_dept_id_count
FROM punching_data_table p
LEFT
JOIN employee_master e
ON e.emp_code = p.emp_code
GROUP
BY e.dept_id
, e.sub_dept_id
Any help on the subject with explanation will be highly appreciated.
This query (SQLFiddle) will give you the output that you specify:
SELECT em1.dept_id, em2.dept_count, em1.sub_dept_id, COUNT(em1.sub_dept_id) AS sub_dept_count
FROM employee_master em1
JOIN (SELECT dept_id, COUNT(dept_id) as dept_count
FROM employee_master e
JOIN punching_data_table p
ON e.emp_code = p.emp_code
GROUP BY dept_id) em2
ON em1.dept_id = em2.dept_id
WHERE EXISTS (SELECT * FROM punching_data_table pdt WHERE pdt.emp_code = em1.emp_code)
GROUP BY dept_id, sub_dept_id
Output:
dept_id dept_count sub_dept_id sub_dept_count
10 4 21 1
10 4 22 3
11 2 20 2
If you have a department names table e.g.
CREATE TABLE dept_master (dept_id INT, dept_name VARCHAR(20));
INSERT INTO dept_master VALUES
(10, 'Department 10'),
(11, 'Department 11');
You can join that as well using this query (SQLFiddle):
SELECT d.dept_name, em2.dept_count, em1.sub_dept_id, COUNT(em1.sub_dept_id) AS sub_dept_count
FROM employee_master em1
JOIN (SELECT dept_id, COUNT(dept_id) as dept_count
FROM employee_master e
JOIN punching_data_table p
ON e.emp_code = p.emp_code
GROUP BY dept_id) em2
ON em1.dept_id = em2.dept_id
JOIN dept_master d on d.dept_id = em1.dept_id
WHERE EXISTS (SELECT * FROM punching_data_table pdt WHERE pdt.emp_code = em1.emp_code)
GROUP BY d.dept_id, sub_dept_id
And then get department names in the output instead of ids:
dept_name dept_count sub_dept_id sub_dept_count
Department 10 4 21 1
Department 10 4 22 3
Department 11 2 20 2
Try this query
SELECT emp_code, MAX(dept_id_count) as dept_id_count, MAX(sub_dept_id_count) as sub_dept_id_count
FROM
(SELECT p.emp_code , COUNT(e.dept_id) dept_id_count, 0 as sub_dept_id_count
FROM punching_data_table p
LEFT JOIN employee_master e ON e.emp_code = p.emp_code
GROUP BY e.dept_id
UNION
SELECT p.emp_code, 0 as dept_id_count, COUNT(e.sub_dept_id) sub_dept_id_count
FROM punching_data_table p
LEFT JOIN employee_master e ON e.emp_code = p.emp_code
GROUP BY e.sub_dept_id) as subQuery
GROUP BY emp_code
There are some things which are not clear in your question for example do you want distinct employees or just a occurrences of employees, what happens if an employee does not exist in punching_data. I have made some assumptions in this answer but the crux of it is that you create 2 sub queries and join them on dept id which will force a 1 to many join
so given
drop table if exists employee_master,punching_data_table;
create table employee_master
(emp_code int, dept_id int, sub_dept_id int);
insert into employee_master values
(1 , 10 , 22),
(2 , 11 , 20),
(3 , 10 , 22),
(4 , 10 , 22),
(5 , 11 , 20),
(6 , 10 , 21 ),
(7 , 10 , 21);
create table punching_data_table(emp_code int);
insert into punching_data_table values
(1),
(2),
(3),
(4) ,
(5),
(6);
select a.dept_id,a.empcodes,s.sub_dept_id,s.empcodes from
(
select dept_id ,count(distinct emp_code) empcodes
from employee_master
where emp_code in (select emp_code from punching_data_table)
group by dept_id
) a
join
(select dept_id , sub_dept_id, count(distinct emp_code) empcodes
from employee_master
where emp_code in (select emp_code from punching_data_table)
group by dept_id,sub_dept_id) s
on s.dept_id = a.dept_id
order by a.dept_id,s.sub_dept_id
Result
+---------+----------+-------------+----------+
| dept_id | empcodes | sub_dept_id | empcodes |
+---------+----------+-------------+----------+
| 10 | 4 | 21 | 1 |
| 10 | 4 | 22 | 3 |
| 11 | 2 | 20 | 2 |
+---------+----------+-------------+----------+
3 rows in set (0.00 sec)
This result can be achieved without punching_data_table
SELECT t1.dept_id,
t2.sub_dept_id,
t1.count_dept,
t2.count_sub
FROM (SELECT dept_id,
sub_dept_id,
Count(dept_id) AS count_dept
FROM employee_master
GROUP BY dept_id)t1
LEFT JOIN (SELECT sub_dept_id,
dept_id,
Count(sub_dept_id) AS count_sub
FROM employee_master
GROUP BY sub_dept_id)t2
ON t1.dept_id = t2.dept_id

Insert records to a table with loop from an other table data

I have two tables like this:
SupplyList (IDSupply is the primary key)
IDSupply PartName Qty
--------- --------- ----
1 C 10
2 B 4
SupplyIndex (IDSupply and Index are the compound primary key)
IDSupply PartName Index
--------- --------- ------
1 C 2
1 C 3
1 C 7
1 C 9
1 C 10
These tables are related to each other with IDSupply.
I want to insert missed records to SupplyIndex table by a query in SQL. In other words, my expected result is SupplyIndex table like below (Index must include numbers from 1 to Qty from SupplyList table)
IDSupply PartName Index
--------- --------- ------ (result)
1 C 1
1 C 2
1 C 3
1 C 4
1 C 5
1 C 6
1 C 7
1 C 8
1 C 9
1 C 10
2 B 1
2 B 2
2 B 3
2 B 4
I did this job in my VB.Net application before and now I want to do it in SQL Server directly.
Would you please help me?
Thanks
Test Data:
create table #supplylist
(
idsupply int,
partname char(20),
qty int
)
insert into #supplylist
select 1,'a',10
union all
select 2,'c',4
create table #idsupply
(
idsupply int,
partname char(20),
indexx int
)
insert into #idsupply
select 1,'a',10
union all
select 2,'c',3
I used Numbers table to accomplish this
with cte
as
(
select
idsupply,
partname,n
from
#supplylist t
cross apply
(
select n from numbers where n <=qty
)b
--final part to check and isnert in other table..same query as above with insert and exists
with cte
as
(
select
idsupply,
partname,n
from
#supplylist t
cross apply
(
select n from numbers where n <=qty
)b
)
insert into #idsupply
select * from cte t1 where not exists (select 1 from #idsupply t2 where t2.indexx=t1.n)
Create Table #SupplyList(IDSupply int Primary Key, PartName char(10),Qty int);
Insert #SupplyList(IDSupply, PartName, Qty) Values
(1, 'C', 10),
(2, 'B', 4);
Create Table #SupplyIndex(IDSupply int, PartName char(10), [Index] int Primary Key (IdSupply, [Index]));
Insert #SupplyIndex(IDSupply, PartName, [Index]) Values
(1, 'C', 2),
(1, 'C', 3),
(1, 'C', 7),
(1, 'C', 9),
(1, 'C', 10);
;With cteMax As
(Select Max([Index]) As MaxIndex From #SupplyIndex),
cteNumbers As
(Select 1 As Number
Union All
Select Number + 1
From cteNumbers n
Cross Join cteMax m
Where n.Number < m.MaxIndex)
Merge #SupplyIndex t
Using (Select sl.IDSupply, sl.PartName, n.Number As Qty From #SupplyList sl Inner Join cteNumbers n On n.Number <= sl.Qty) s
On s.IDSupply = t.IDSupply And s.PartName = t.PartName And s.Qty = t.[Index]
When Not Matched Then Insert(IDSupply, PartName, [Index]) Values(s.IDSupply, s.PartName, s.Qty);
Select * From #SupplyIndex;
go
Drop Table #SupplyList;
go
Drop Table #SupplyIndex

Get the Top records from each group in MYSQL

I've a table event_log with the following columns in MYSQL,
CREATE TABLE IF NOT EXISTS `event_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`customer_id` varchar(50) DEFAULT NULL,
`event_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
The sample Data can be,
id, customer_id, event_time
1 100 '2015-03-22 23:54:37'
2 100 '2015-03-21 23:54:37'
3 100 '2015-03-20 23:54:37'
4 101 '2015-03-19 23:54:37'
5 102 '2015-03-19 23:54:37'
6 102 '2015-03-18 23:54:37'
7 103 '2015-03-17 23:54:37'
8 103 '2015-03-16 23:54:37'
9 103 '2015-03-15 23:54:37'
10 103 '2015-03-14 23:54:37'
I want to group on customer_id and then pick the top 2 records from each group using event_time column (whose time is greater)
Please, suggest
Thanks,
Faisal Nasir
Here is a version that doesn't use variables:
select el.*
from event_log el
where 2 >= (select count(*)
from event_log el2
where el2.customer_id = el.customer_id and
el2.event_time >= el.event_time
);
This could even have reasonable performance with an index on event_log(customer_id, event_time).
One way to use user defined variables to pick 2 recent entries per customer_id
SELECT `id`, `customer_id`, `event_time`,row_num
FROM (
SELECT *,
#r:= CASE WHEN #g = `customer_id` THEN #r +1 ELSE 1 END row_num,
#g:= `customer_id`
FROM event_log
CROSS JOIN(SELECT #g:= NULL,#r:=0) a
ORDER BY `customer_id`,`event_time` desc
) t
where row_num <= 2
DEMO