Tive uma situação em que precisei atualizar ou inserir em uma tabela de acordo com dois campos (ambas as chaves estrangeiras) em que não consegui definir uma restrição UNIQUE (então INSERT ... ON DUPLICATE KEY UPDATE não funcionou). Aqui está o que acabei usando:
replace into last_recogs (id, hasher_id, hash_id, last_recog)
select l.* from
(select id, hasher_id, hash_id, [new_value] from last_recogs
where hasher_id in (select id from hashers where name=[hasher_name])
and hash_id in (select id from hashes where name=[hash_name])
union
select 0, m.id, h.id, [new_value]
from hashers m cross join hashes h
where m.name=[hasher_name]
and h.name=[hash_name]) l
limit 1;
Este exemplo foi extraído de um de meus bancos de dados, com os parâmetros de entrada (dois nomes e um número) substituídos por [hasher_name], [hash_name] e [new_value]. O SELECT ... LIMIT 1 aninhado puxa o primeiro do registro existente ou um novo registro (last_recogs.id é uma chave primária de incremento automático) e usa isso como o valor de entrada no REPLACE INTO.