问题
Our app has been customized to handle many different types of customers, with certain settings that will only apply to a few or one customer. Rather than continuously adding nullable columns to the customers table, I decided to add a [Settings] table to allow each setting to be a row.
[dbo].[Settings]
[SettingID] [int]
[SettingCode] [nchar](4)
[SettingDescription] [nvarchar](255)
Which is then linked to the [Customers] table through a many-to-many table
[dbo].[Customer_Settings]
[Customer_SettingsID] [int]
[CustomerID] [int]
[SettingID] [int]
My question is about how to handle the fact that many of these settings need an additional data type on the [Customer_Settings] table.
For example, we could have one setting be "Latest Delivery Time" requiring a time datatype, or another be "Minutes Until Expiration" requiring an int.
The two ways I can think of to handle this is to add nullable columns to the [Customer_Settings] table like:
[dbo].[Customer_Settings]
[Customer_SettingsID] [int]
[CustomerID] [int]
[SettingID] [int]
[ValueTime] [time] NULL
[ValueInt] [int] NULL
...
This seems like bad design.
The other way I can think of is to add child tables to [Customer_Settings] table like:
[dbo].[Customer_Settings_Int]
[Customer_Settings_Int_ID] [int]
[Customer_SettingsID] [int]
[Value] [int]
This seems like it is normalized but also cumbersome. Please let me know if one of these is clearly better or if there is another alternative. Thanks!
回答1:
The solution you have chosen is called Entity-Attribute-Value (EAV.) The most common approach is to store all values as strings. Some people add a validator column, containing a regex or like expression, that is verified by the client or t-sql function updating the value.
It is much much cleaner to use nullable columns.
回答2:
Instead of a list of singleton attributes, it looks like you may be able to gather sets of attributes into logical groupings. You hint at "something that is delivered" and "something that expires" that could be two such groupings. Create a list of these groupings in a lookup table that looks something like this:
ID Name Description
D Deliverable Something that is delivered
E Expirable Something that expires
Then create a kind of intersection table for customer and grouping (with type of grouping)
create table CustGrouping(
CustID int not null references Customer( ID ),
GroupID int auto_generated,
GroupType char( 1 ) not null references Groupings( ID ), -- the table above
constraint PK_CustGrouping primary key( CustID, GroupID ),
constraint UQ_Group_Type unique( GroupID, GroupType )
);
The PK will prevent accidentally pairing the a customer with the same grouping more than once. Why create a unique constraint on (GroupID, GroupType) when GroupID itself will be unique? So it can be the reference point of foreign keys.
You'll need a separate table for each grouping. Here is just one:
create table Deliverables(
ID int not null primary key,
TypeID char( 1 ) not null check( TypeID = 'D' ),
DeliveryDate date not null,
..., -- all other fields that are associated with deliverables
constraint FK_Deliverables_CustGrouping foreign key( ID, TypeID )
references CustGrouping( GroupID, GroupType )
);
The check constraint shows how only deliverable data can be written to that table.
Here is the sequence of operations:
- When a deliverable is generated for a customer, insert to CustGrouping with customer ID and grouping designator ('D'). This generates the ID of this deliverable.
- Using the generated ID, insert the deliverable data into the Deliverables table.
The same operations goes for other groupings. The grouping ID makes sure the FK for that group can only refer to a grouping of the correct type. It also lets you know which table contains the data and this data can be completely different for each kind of grouping. It is scalable in that to add a new type of grouping, insert the definition into the Groupings table, create a table to contain the data of whatever length and format needed and go from there.
I would further recommend creating views to show customer data with each kind of grouping. For example, a view CustomerDeliverables
shows customer data with deliverables. So when a part of the app is just working with deliverables, it doesn't need to know the details of how this is stored in the database. Triggers on the views can allow for easy creating, deleting, and manipulating grouped data.
来源:https://stackoverflow.com/questions/39633693/many-to-many-relationship-with-different-data-types