mysqlsqlmysql-5.6

How to combine the initial state of a row with the latest state in MySQL?


I need to get the initial state as well as the latest state from a MySQL database. This is over two tables:

customer

id name surname dob email telephone
10 Steve Bobbly 01-01-1970 uvw@xyz.com 0123456789
15 James Bond 01-01-1950 007@bond.com 0101010999

audit_log

id entity_id property old_value new_value
1 10 name John Steve
2 10 email abc@def.com uvw@xyz.com
3 10 telephone 0123456789

What I expect is output like this:

id name surname dob email telephone
10 Steve Bobbly 01-01-1970 uvw@xyz.com 0123456789
10_1 John Bobbly 01-01-1970 abc@def.com
15 James Bond 01-01-1950 007@bond.com 0101010999

I initially had a PHP script that runs through all the customer rows, and then matches them to the audit_log rows and generate output from there, but the speed is EXTREMELY slow and resource intensive.

Would something like this be possible directly in MySQL, and how would I do it?

EDIT

I've added additional rows to the customer and the output tables. The output table needs to contain all rows in customer, as well as a copy of the initial row, built from audit_log.


Solution

  • Try the following:

    SET @rn=0;
    SET @cust=null;
    SET @pr=null;
    
    SELECT id, name, surname, dob, email, telephone
    FROM customer 
      UNION ALL
    (
      SELECT CONCAT(T.id, '_', D.rownum) id,
            COALESCE(MAX(CASE D.property WHEN 'name' THEN D.old_value END), MAX(T.name)) name,
            COALESCE(MAX(CASE D.property WHEN 'surname' THEN D.old_value END), MAX(T.surname)) surname,
            COALESCE(MAX(CASE D.property WHEN 'dob' THEN D.old_value END), MAX(T.dob)) dob,
            COALESCE(MAX(CASE D.property WHEN 'email' THEN D.old_value END), MAX(T.email)) email,
            COALESCE(MAX(CASE D.property WHEN 'telephone' THEN D.old_value END), MAX(T.telephone)) telephone
      FROM customer T
      JOIN 
      (
        SELECT id, entity_id, property, old_value, new_value, 
        IF(@cust <> entity_id OR @pr <> property, @rn:=1, @rn:=@rn+1) rownum,
        @cust:=entity_id, @pr:=property
        FROM audit_log 
        ORDER BY entity_id, property, id
      ) D
      ON T.ID = D.entity_id
      WHERE D.rownum=1
      GROUP BY T.id, D.rownum
    )
    ORDER BY id
    

    See a demo.

    This query simulates ROW_NUMBER() OVER (PARTITION BY entity_id, property ORDER BY id) for the audit_log table to get the initial value for each customer/ property (where rownum = 1) .

    The COALESCE is used to get the value of a property from the customers table, if this property is not changed, i.e. the name is changed but the email is not, then for email get the lastest value (in this case the latest=initial, which is T.email in this query).