You could rewrite with OR and two EXISTS subqueries and compare performance with your query:
SELECT a.* FROM a WHERE EXISTS ( SELECT FROM b WHERE a.type_id = b.id AND b.user_id = 123 AND a.type = 'dog' ) OR EXISTS ( SELECT FROM c WHERE a.type_id = c.type_id AND c.user_id = 123 AND a.type = 'cat' ) ; or:
SELECT a.* FROM a WHERE a.type = 'dog' AND EXISTS ( SELECT FROM b WHERE a.type_id = b.id AND b.user_id = 123 ) OR a.type = 'cat' AND EXISTS ( SELECT FROM c WHERE a.type_id = c.type_id AND c.user_id = 123 ) ; Your query seems correct, assuming that there are unique constraints or indexes on b (user_id, id) or on b (id) - and c (user_id, type_id).
It does however looks somewhat obfuscated - it is not clear without careful reading that it restricts the result to rows that have WHERE a.type_id IN ('dog', 'cat').