I have to register payments.
- There are two things to pay. thingC or thingD (they may not be paid).
- You can only pay one thingD per payment transaction.
- You can pay many thingC per payment transaction.
- You can pay for thingC or thingD per payment, not both.
- There are three codes that have to be generated for each thingC or thingD that was paid.
Existing tables
thingC
idthingC serial PRIMARY KEY
--more columns
thingD
idthingD serial PRIMARY KEY
--more columns
Tables I think I need:
payment
idpayment serial PRIMARY KEY
idthingD integer REFERENCES thingD -- the payment may be for a thingD
type character(1) NOT NULL --C or D
c1 integer
c2 integer
c3 integer
payment_C
idpaymentC serial NOT NULL
idpayment integer NOT NULL REFERENCES payment
idthingC integer NOT NULL REFERENCES thingC
c1 integer NOT NULL
c2 integer NOT NULL
c3 integer NOT NULL
In table payment
I'll have values in idthingD
, c1
, c2
and c3
if type
is 'D'. The columns mentioned above will be null if type
is 'C', and there will be values in table payment_C
.
I have to be able to say:
- idpayment 1 was for thingD 2 and have the codes 1, 4, 8.
- idpayment 2 was for thingC 5 with codes 2, 3, 6 AND thingC 8 with codes 4, 5, 8
What do you think? In which table should the codes be? Can the design be improved.
1 Answer 1
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:
-
The CONSTRAINT c_unique_per_payment should be for thingDKaz Miller– Kaz Miller2016年12月31日 16:31:02 +00:00Commented Dec 31, 2016 at 16:31
-
1@KazMiller: This constraint is meant to disallow the same
thingC
more than once in the same payment. I added a(?)
because I am not sure you need this constraint. You did not explicitly ask for it, but typically this would be nonsense. (There can still be multiple distinctthingC
per payment.) I renamed it toc_distinct_per_payment
now to clarify. The partial unique indexidx_one_d_per_idpayment
prevents more than onethingD
per payment.Erwin Brandstetter– Erwin Brandstetter2016年12月31日 19:34:31 +00:00Commented Dec 31, 2016 at 19:34
Explore related questions
See similar questions with these tags.
thingD
and severalthingC
? Or are both mutually exclusive?