Search code examples
mysqltime-seriescasewindow-functionsgaps-and-islands

How to create MySQL query that converts rows to columns?


I'm creating an employee tracking app.

I have a MySQL table which is as follows:

ID PersonID TypeID DateTime
1 001 IN 2022-09-01T13:21:12
2 001 OUT 2022-09-01T13:25:12
3 001 IN 2022-09-01T14:21:12
4 001 OUT 2022-09-01T14:25:12
5 002 IN 2022-09-03T13:21:12
6 002 OUT 2022-09-03T13:25:12
7 002 IN 2022-09-03T14:21:12
8 002 IN 2022-09-03T14:25:12
9 002 OUT 2022-09-03T14:25:12
10 002 OUT 2022-09-03T16:25:12
11 002 OUT 2022-09-03T17:25:12
12 002 IN 2022-09-04T16:25:12
13 002 IN 2022-09-05T17:25:12

I would like to create a view that returns records first sorted by PersonID and then by the ID but transforms the rows into columns.

Something like this:

PersonID InID In_DateTime OutID Out_DateTime
001 1 2022-09-01T13:21:12 2 2022-09-01T13:25:12
001 3 2022-09-01T14:21:12 4 2022-09-01T14:25:12
002 5 2022-09-03T13:21:12 6 2022-09-03T13:25:12
002 7 2022-09-03T14:21:12 null null
002 8 2022-09-03T14:25:12 9 2022-09-03T14:25:12
002 null null 10 2022-09-03T16:25:12
002 null null 11 2022-09-03T17:25:12
002 12 2022-09-04T16:25:12 null null
002 13 2022-09-05T17:25:12 null null

Does anyone have an idea how to do this in MySQL?

Thanks for any suggestions.


Solution

  • Use window functions LEAD() or LAG() to get for each row its pair row, depending on its TypeID and do a left join of the results to the table:

    WITH cte AS (
      SELECT *, 
         CASE 
           WHEN TypeID = 'IN' AND LEAD(TypeID) OVER w = 'OUT' THEN LEAD(ID) OVER w
           WHEN TypeID = 'OUT' AND LAG(TypeID) OVER w = 'IN' THEN LAG(ID) OVER w
         END other_ID  
      FROM tablename
      WINDOW w AS (PARTITION BY PersonID ORDER BY DateTime)
    )
    SELECT DISTINCT c.PersonID,
           CASE WHEN c.TypeID = 'IN' THEN c.ID ELSE t.ID END InID,
           CASE WHEN c.TypeID = 'IN' THEN c.DateTime ELSE t.DateTime END In_DateTime,
           CASE WHEN c.TypeID = 'IN' THEN t.ID ELSE c.ID END OutID,
           CASE WHEN c.TypeID = 'IN' THEN t.DateTime ELSE c.DateTime END Out_DateTime
    FROM cte c LEFT JOIN tablename t
    ON t.ID = c.other_ID
    ORDER BY c.PersonID, COALESCE(In_DateTime, Out_DateTime);
    

    See the demo.