Thanks for the suggestions. I couldn't make either work, but the ideas in them provided a way.
The CHECK constraint was my first idea, but I learned that CHECK constraints cannot depend on values from more than one row, so the SELECT which examines the whole table will not work.
The materialized view was a good idea but doesn't work, since materialized views do not permit UNION - the query has to be a subset query.
What I did get to work was to create a (regular) VIEW using the UNION roughly as you suggested, then create triggers for insert and update that throw an error if the key already exists in the union view. The view and two triggers is more complex than I hoped, but at this point I'm happy just to have a solution.
Thanks again for the help.