-- Customer phone numbers are stored in one form, 62… without + (util.NormalizePhoneNumber), -- and every number a customer types is rewritten to it before it is looked up. This -- rewrites the numbers stored before, so 0812…, +62 812… and 812… become 62812…. -- -- Only one customer may have a number. When several stored numbers become the same one, -- the customer that already has it keeps it, else a registered one (with a password), -- else the oldest; the others are left as they are. A number that is not an Indonesian -- mobile number is left as it is too. Neither can log in until it is fixed by hand: -- -- SELECT id, name, phone_number FROM customers -- WHERE phone_number IS NOT NULL AND phone_number !~ '^628[0-9]{7,11}$'; WITH stripped AS ( SELECT id, phone_number, password_hash, created_at, regexp_replace(regexp_replace(phone_number, '[[:space:]().-]', '', 'g'), '^\+', '') AS p FROM customers WHERE phone_number IS NOT NULL ), normalized AS ( SELECT id, phone_number, password_hash, created_at, CASE WHEN p LIKE '620%' THEN '62' || substr(p, 4) WHEN p LIKE '0%' THEN '62' || substr(p, 2) WHEN p LIKE '8%' THEN '62' || p ELSE p END AS new_phone FROM stripped ), ranked AS ( SELECT id, new_phone, row_number() OVER ( PARTITION BY new_phone ORDER BY phone_number = new_phone DESC, password_hash IS NOT NULL DESC, created_at, id ) AS rank FROM normalized WHERE new_phone ~ '^628[0-9]{7,11}$' ) UPDATE customers c SET phone_number = r.new_phone, updated_at = NOW() FROM ranked r WHERE c.id = r.id AND r.rank = 1 AND c.phone_number <> r.new_phone; -- A registration started before this migration finishes with the number in its OTP -- session, and a resent OTP is found by it. UPDATE otp_sessions SET phone_number = n.new_phone, updated_at = NOW() FROM ( SELECT id, CASE WHEN p LIKE '620%' THEN '62' || substr(p, 4) WHEN p LIKE '0%' THEN '62' || substr(p, 2) WHEN p LIKE '8%' THEN '62' || p ELSE p END AS new_phone FROM ( SELECT id, regexp_replace(regexp_replace(phone_number, '[[:space:]().-]', '', 'g'), '^\+', '') AS p FROM otp_sessions ) stripped ) n WHERE otp_sessions.id = n.id AND n.new_phone ~ '^628[0-9]{7,11}$' AND otp_sessions.phone_number <> n.new_phone;