You obviously put some thought into this design. But you would regret that you have to search for codes in two tables. And what if you want to add details for codes?
I suggest to store all codes in the same table like this:
CREATE TABLE payment (
idpayment serial PRIMARY KEY
-- more payment details?
);
CREATE TABLE payment_code (
idpayment_code serial PRIMARY KEY
, idpayment int NOT NULL REFERENCES payment
, idthingC int REFERENCES thingC -- can be NULL
, idthingD int REFERENCES thingD -- can be NULL
, c1 int NOT NULL
, c2 int NOT NULL
, c3 int NOT NULL
, CONSTRAINT either_c_or_d -- reference either to c or to d
CHECK (idthingC IS NULL AND idthingD IS NOT NULL
OR idthingD IS NULL AND idthingC IS NOT NULL)
, CONSTRAINT c_distinct_per_payment -- distinct idthingC per payment (?)
UNIQUE (idpayment, idthingC)
);
CREATE UNIQUE INDEX idx_one_d_per_idpayment ON payment_code (idpayment)
WHERE idthingD IS NOT NULL; -- only one idthingD per payment
(Depending on the nature of the codes, maybe even just 1 code per row.)
The partial unique index idx_one_d_per_idpayment disallows more than one reference to thingD per payment.
This allows at most 1 thingD per payment and any number of distinct thingC.
Mutually exclusive payment types
You later clarified, those are mutually exclusive.
This allows at most 1 thingD per payment or any number of distinct thingC (but not both):
CREATE TABLE payment (
idpayment serial PRIMARY KEY
, type_cd "char" NOT NULL DEFAULT 'C' -- optional default
-- more payment details?
, CONSTRAINT type_cd_valid CHECK (type_cd = 'C' OR type_cd = 'D')
, CONSTRAINT redundant_uni_for_fk_constraint UNIQUE (idpayment, type_cd)
);
CREATE TABLE payment_code (
idpayment_code serial PRIMARY KEY
, idpayment int NOT NULL -- multicolumn ...
, type_cd "char" NOT NULL -- ... FK reference
, idthingC int REFERENCES thingC
, idthingD int REFERENCES thingD
, c1 int NOT NULL
, c2 int NOT NULL
, c3 int NOT NULL
, CONSTRAINT either_c_or_d
CHECK (type_cd = 'C' AND idthingC IS NOT NULL
OR type_cd = 'D' AND idthingD IS NOT NULL)
, CONSTRAINT c_distinct_per_payment
UNIQUE (idpayment, idthingC)
, CONSTRAINT payment_c_or_d
FOREIGN KEY (idpayment, type_cd) REFERENCES payment(idpayment, type_cd)
);
CREATE UNIQUE INDEX idx_one_d_per_idpayment ON payment_code (idpayment)
WHERE type_cd = 'D'; -- slightly simpler now
To enforce your rules, add a type column in table payment (I named it type_cd since "type" is too generic, and I chose data type "char" - with double quotes - efficient for tiny enumerations). Include that column in the FK reference (redundantly). This allows a CHECK constraint (either_c_or_d) in table payment_code to enforce valid references.
The UNIQUE constraint redundant_uni_for_fk_constraint seems redundant, but is required for the multicolumn FK reference on (idpayment, type_cd). Related, with more explanation:
Both FK columns (idpayment, type_cd) must be NOT NULL to enforce referential integrity. Or (if you need rows without assigned payment) declare the FK constraint as MATCH FULL. Details: