66

Which of the following options, if any, is considered best practice when designing a table used to store user settings?

(OPTION 1)

USER_SETTINGS
-Id
-Code (example "Email_LimitMax")
-Value (example "5")
-UserId

(OPTION 2)

create a new table for each setting where, for example, notification settings would require you to create:

"USER_ALERT_SETTINGS"
-Id
-UserId
-EmailAdded (i.e true)
-EmailRemoved 
-PasswordChanged
...
...

"USER_EMAIL_SETTINGS"
-Id
-UserId
-EmailLimitMax
....

(OPTION 3)

"USER"
-Name
...
-ConfigXML
Cabrera
  • 1,670
  • 1
  • 16
  • 16
001
  • 62,807
  • 94
  • 230
  • 350
  • Here is described pros and cons about dynamic settings https://martinfowler.com/apsupp/properties.pdf – Kirill A Aug 30 '21 at 15:25

5 Answers5

128

Other answers have ably outlined the pros and cons of your various options.

I believe that your Option 1 (property bag) is the best overall design for most applications, especially if you build in some protections against the weaknesses of propety bags.

See the following ERD:

Property Bag ERD

In the above ERD, the USER_SETTING table is very similar to OP's. The difference is that instead of varchar Code and Value columns, this design has a FK to a SETTING table which defines the allowable settings (Codes) and two mutually exclusive columns for the value. One option is a varchar field that can take any kind of user input, the other is a FK to a table of legal values.

The SETTING table also has a flag that indicates whether user settings should be defined by the FK or by unconstrained varchar input. You can also add a data_type to the SETTING to tell the system how to encode and interpret the USER_SETTING.unconstrained_value. If you like, you can also add the SETTING_GROUP table to help organize the various settings for user-maintenance.

This design allows you to table-drive the rules around what your settings are. This is convenient, flexible and easy to maintain, while avoiding a free-for-all.


EDIT: A few more details, including some examples...

Note that the ERD, above, has been augmented with more column details (range values on SETTING and columns on ALLOWED_SETTING_VALUE).

Here are some sample records for illustration.

SETTING:
+----+------------------+-------------+--------------+-----------+-----------+
| id | description      | constrained | data_type    | min_value | max_value |
+----+------------------+-------------+--------------+-----------+-----------+
| 10 | Favourite Colour | true        | alphanumeric | {null}    | {null}    |
| 11 | Item Max Limit   | false       | integer      | 0         | 9001      |
| 12 | Item Min Limit   | false       | integer      | 0         | 9000      |
+----+------------------+-------------+--------------+-----------+-----------+

ALLOWED_SETTING_VALUE:
+-----+------------+--------------+-----------+
| id  | setting_id | item_value   | caption   |
+-----+------------+--------------+-----------+
| 123 | 10         | #0000FF      | Blue      |
| 124 | 10         | #FFFF00      | Yellow    |
| 125 | 10         | #FF00FF      | Pink      |
+-----+------------+--------------+-----------+

USER_SETTING:
+------+---------+------------+--------------------------+---------------------+
| id   | user_id | setting_id | allowed_setting_value_id | unconstrained_value |
+------+---------+------------+--------------------------+---------------------+
| 5678 | 234     | 10         | 124                      | {null}              |
| 7890 | 234     | 11         | {null}                   | 100                 |
| 8901 | 234     | 12         | {null}                   | 1                   |
+------+---------+------------+--------------------------+---------------------+

From these tables, we can see that some of the user settings which can be determined are Favourite Colour, Item Max Limit and Item Min Limit. Favourite Colour is a pick list of alphanumerics. Item min and max limits are numerics with allowable range values set. The SETTING.constrained column determines whether users are picking from the related ALLOWED_SETTING_VALUEs or whether they need to enter a USER_SETTING.unconstrained_value. The GUI that allows users to work with their settings needs to understand which option to offer and how to enforce both the SETTING.data_type and the min_value and max_value limits, if they exist.

Using this design, you can table drive the allowable settings including enough metadata to enforce some rudimentary constraints/sanity checks on the values selected (or entered) by users.

EDIT: Example Query

Here is some sample SQL using the above data to list the setting values for a given user ID:

-- DDL and sample data population...
CREATE TABLE SETTING
    (`id` int, `description` varchar(16)
     , `constrained` varchar(5), `data_type` varchar(12)
     , `min_value` varchar(6) NULL , `max_value` varchar(6) NULL)
;

INSERT INTO SETTING
    (`id`, `description`, `constrained`, `data_type`, `min_value`, `max_value`)
VALUES
    (10, 'Favourite Colour', 'true', 'alphanumeric', NULL, NULL),
    (11, 'Item Max Limit', 'false', 'integer', '0', '9001'),
    (12, 'Item Min Limit', 'false', 'integer', '0', '9000')
;

CREATE TABLE ALLOWED_SETTING_VALUE
    (`id` int, `setting_id` int, `item_value` varchar(7)
     , `caption` varchar(6))
;

INSERT INTO ALLOWED_SETTING_VALUE
    (`id`, `setting_id`, `item_value`, `caption`)
VALUES
    (123, 10, '#0000FF', 'Blue'),
    (124, 10, '#FFFF00', 'Yellow'),
    (125, 10, '#FF00FF', 'Pink')
;

CREATE TABLE USER_SETTING
    (`id` int, `user_id` int, `setting_id` int
     , `allowed_setting_value_id` varchar(6) NULL
     , `unconstrained_value` varchar(6) NULL)
;

INSERT INTO USER_SETTING
    (`id`, `user_id`, `setting_id`, `allowed_setting_value_id`, `unconstrained_value`)
VALUES
    (5678, 234, 10, '124', NULL),
    (7890, 234, 11, NULL, '100'),
    (8901, 234, 12, NULL, '1')
;

And now the DML to extract a user's settings:

-- Show settings for a given user
select
  US.user_id 
, S1.description 
, S1.data_type 
, case when S1.constrained = 'true'
  then AV.item_value
  else US.unconstrained_value
  end value
, AV.caption
from USER_SETTING US
  inner join SETTING S1
    on US.setting_id = S1.id 
  left outer join ALLOWED_SETTING_VALUE AV
    on US.allowed_setting_value_id = AV.id
where US.user_id = 234

See this in SQL Fiddle.

Joel Brown
  • 14,123
  • 4
  • 52
  • 64
  • 1
    Nice Diagram, ALLOWED_SETTINGS_VALUE, how are the attributes defined in here? why not option 3? :) – 001 Apr 21 '12 at 05:07
  • @001 - There's nothing wrong with option 3 if that's what you want. It lets you put anything at all in your settings and it's up to your app to make sense of it. It doesn't allow you to provide guidance to the user about what settings are available/allowed - at least it forces that guidance into your code instead of table-driving it. ALLOWED_SETTING_VALUE would probably be a varchar for the value and a varchar for a description/caption (optional). The actual type of the value would be according to SETTING.data_type, but it would be encoded as varchar for the sake of sanity. – Joel Brown Apr 21 '12 at 13:10
  • Hey Joel, I still don't really get your design? what about you had settings for item, and it has two settings itemMaxlimit, itsmMinLimit, how would you implement this into your design? Setting_group: item, setting: itemMaxlimit, itsmMinLimit, .....??? – 001 Apr 21 '12 at 23:29
  • Added a few more details for @001. I hope this helps to clarify. Let me know if you need more info. – Joel Brown Apr 22 '12 at 03:24
  • Have you used this design in production or just something you came up with? – 001 Apr 22 '12 at 08:27
  • @001 - I've used something very similar to this in a system some time ago. It was actually a little more complex because there were user-editable settings and system editable settings (updated automatically, not manually by user) and the user editable settings also had more GUI-related constraints in the `SETTING` table, including type of control, default value and edit mask. – Joel Brown Apr 22 '12 at 11:32
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/10362/discussion-between-001-and-joel-brown) – 001 Apr 22 '12 at 12:45
  • @PeterPenzov - Ask and ye shall receive. – Joel Brown May 12 '16 at 03:31
  • @JoelBrown For example can you show me how to insert data for `#0000FF` and query for getting the data? – Peter Penzov May 12 '16 at 06:41
  • @PeterPenzov You're going to have to be more specific about what you're having trouble with. There's an `INSERT INTO ALLOWED_SETTING_VALUE` and a `SELECT` that pulls data out of that table in the example/SQL Fiddle above. – Joel Brown May 12 '16 at 11:53
  • Thank you very much I will try to see how I can implement this solution. – Peter Penzov May 12 '16 at 12:13
  • By the way is it possible to populate all the tables with data using one SQL statement? – Peter Penzov May 12 '16 at 12:22
  • @PeterPenzov it isn't possible to do. See this question: http://stackoverflow.com/questions/3053593/mysql-insert-into-2-tables – Joel Brown May 12 '16 at 15:31
  • I found this http://stackoverflow.com/questions/20561254/insert-data-in-3-tables-at-a-time-using-postgres Maybe I can use it? – Peter Penzov May 13 '16 at 06:30
  • Can you show me some UPDATE SQL statement? How I can update values? – Peter Penzov May 15 '16 at 14:52
  • 1
    @PeterPenzov - Update what? Your questions are too vague for me to be able to help you. In general I can say that there is nothing exotic about the database design that makes updating records tricky. Is there some reason you feel regular SQL `UPDATE` statements won't work? – Joel Brown May 15 '16 at 18:43
  • Should I store everything in the `USER_SETTING` for example: `email`, `nickname`? Another question: what if I want to store something not editable like: `avatarID`, `token`? Still stores them in the `USER_SETTING` table? – Yami Odymel Oct 19 '16 at 15:52
  • 2
    @YamiOdymel - I wouldn't. If you have facts that are single valued (i.e. only one right answer at a time) and if these things have special meaning in your system (e.g. avatar, token, email,...) then store these things as attributes of the user (i.e. columns on the `USER` table). It's not a good idea to use a property bag structure if your application is more structured. – Joel Brown Oct 19 '16 at 17:50
  • You sir are a master @JoelBrown congratulations! – Andre Batista Aug 08 '17 at 14:14
  • Hi! What kind of tool have you used to draw these nice tables? Cheers! – Sarvar Nishonboyev Feb 04 '19 at 13:17
  • 2
    @SarvarNishonboev I use Visio with an ERD shape sheet that I created myself some time ago, but you could use generic boxes and lines with crow's foot notation "arrows" on each end. I also use a custom line texture to give it a hand-drawn look which I find is useful in draft/high level sketches to convey that it's a sketch and not a formal design. – Joel Brown Feb 04 '19 at 22:42
  • How would you factor in a boolean setting? I'm building settings for my app based on this now and I'm thinking that I'll have a SETTING.data_type = boolean and either have another column on the SETTING table "boolean_value", similar to how min/max_value works, or just throw the 0 or 1 in the USER_SETTING.unconstrained_value column. – Josh Dec 08 '20 at 21:24
  • @Josh you can certainly do it either way. It would work just fine. My inclination would be to have code that handles settings of data_type="boolean" without resorting to the constrained values. Each value of data_type will need to have some hard coding in a switch statement (or your language's equivalent) that knows how to encode/decode from a string representation (e.g. of unconstrained value) to a strongly typed native data type. Similarly, you'll need code that knows how to represent each native data_type as a string (usually .ToString() will do this). – Joel Brown Dec 09 '20 at 04:17
  • @JoelBrown the way that you would create settings for a new user would be executing a code (code first)/script with default values, when you are creating a new user? – Orion Jan 27 '22 at 14:22
  • 1
    @Orion It depends on your business rules. If there are settings that you expect to be there for every user then you could have either a script (or other code) that you execute to set up the defaults one by one or you could have model user that you copy, like the SQL Server model database or the Windows Default user folder structure. – Joel Brown Jan 28 '22 at 13:16
  • @JoelBrown the issue I faced in implementing your solution in my software is a case where the value of a specific setting should have relation for another table (one-to-one or one-to-many). e.g the user is selecting the list of services he want to provide. what do you think should be the way to handle such cases in your solution? – Amitk Jul 28 '22 at 10:19
  • @Amitk It sounds to me like what you are interested in tracking is not well suited to a property bag settings pattern. It sounds to me like you are maintaining the contents of an intersection table between users and a table containing a list of services (the other table that you mentioned). You should implement that intersection table as a separate, specific purpose table and relate it to users. This is probably a many-to-many table since more than one user can select a given service. I'm assuming also that users can select more than one service. – Joel Brown Jul 28 '22 at 17:20
17

Option 1 (as noted, "property bag") is easy to implement - very little up-front analysis. But it has a bunch of downsides.

  1. If you want to restrain the valid values for UserSettings.Code, you need an auxiliary table for the list of valid tags. So you have either (a) no validation on UserSettings.Code – your application code can dump any value in, missing the chance to catch bugs, or you have to add maintenance on the new list of valid tags.

  2. UserSettings.Value probably has a string data type to accommodate all the different values that might go into it. So you have lost the true data type – integer, Boolean, float, etc., and the data type checking that would be done by the RDMBS on insert of an incorrect values. Again, you have bought yourself a potential QA problem. Even for string values, you have lost the ability to constrain the length of the column.

  3. You cannot define a DEFAULT value on the column based on the Code. So if you wanted EmailLimitMax to default to 5, you can’t do it.

  4. Similarly, you can’t put a CHECK constraint on the Values column to prevent invalid values.

  5. The property bag approach loses validation of SQL code. In the named column approach, a query that says “select Blah from UserSettings where UserID = x” will get a SQL error if Blah does not exist. If the SELECT is in a stored procedure or view, you will get the error when you apply the proc/view – way before the time the code goes to production. In the property bag approach, you just get NULL. So you have lost another automatic QA feature provided by the database, and introduced a possible undetected bug.

  6. As noted, a query to find a UserID where conditions apply on multiple tags becomes harder to write – it requires one join into the table for each condition being tested.

  7. Unfortunately, the Property Bag is an invitation for application developers to just stick a new Code into the property bag without analysis of how it will be used in the rest of application. For a large application, this becomes a source of “hidden” properties because they are not formally modeled. It’s like doing your object model with pure tag-value instead of named attributes: it provides an escape valve, but you’re missing all the help the compiler would give you on strongly-typed, named attributes. Or like doing production XML with no schema validation.

  8. The column-name approach is self-documenting. The list of columns in the table tells any developer what the possible user settings are.

I have used property bags; but only as an escape valve and I have often regretted it. I have never said “gee, I wish I had made that explicit column be a property bag.”

Tom Wilson
  • 541
  • 3
  • 7
16

Consider this simple example.

If you have 2 tables, UserTable(contains user details) and SettingsTable(contains settings details). Then create a new table UserSettings for relating the UserTable and SettingsTable as shown below

user settings data base design

Hope you will found the right solution from this example.

Sajith
  • 2,842
  • 9
  • 37
  • 49
5

Each option has its place, and the choice depends on your specific situation. I am comparing the pros and cons for each option below:

Option 1: Pros:

  • Can handle many options
  • New options can easily be added
  • A generic interface can be developed to manage the options

Option 1: Cons

  • When a new option is added, its more complex to update all user accounts with the new option
  • Option names can spiral out of control
  • Validation of allowed option values is more complex, additional meta data is needed for that

Option 2: Pros

  • Validation of each option is easier than option 1 since each option is an individual column

Option 2: Cons

  • A database update is required for each new option
  • With many options the database tables could become more difficult to use
Stephen Senkomago Musoke
  • 3,528
  • 2
  • 29
  • 27
4

It's hard to evaluate "best" because it depends on the kind of queries you want to run.

Option 1 (commonly known as "property bag", "name value pairs" or "entity-attribute-value" or EAV) makes it easy to store data whose schema you don't know in advance. However, it makes it hard - or sometimes impossible - to run common relational queries. For instance, imagine running the equivalent of

select count(*) 
from USER_ALERT_SETTINGS 
where EmailAdded = 1 
and Email_LimitMax > 5

This would rapidly become very convoluted, especially because your database engine may not compare varchar fields in a numerically meaningful way (so "> 5" may not work the way you expect).

I'd work out the queries you want to run, and see which design supports those queries best. If all you have to do is check limits for an individual user, the property bag is fine. If you have to report across all users, it's probably not.

The same goes for JSON or XML - it's okay for storing individual records, but makes querying or reporting over all users harder. For instance, imagine searching for the configuration settings for email adress "bob@domain.com" - this would require searching through all XML documents to find the node "email address".

Neville Kuyt
  • 29,247
  • 1
  • 37
  • 52
  • Alternatively the configuration can be stored in the database as JSON, XML , what you think about this? – 001 Apr 18 '12 at 14:47
  • You can always query to check the limit, before you add a new record, therefore its not a problem. – 001 Apr 18 '12 at 14:51
  • I think you also can make some function/procedures to check the type of inputs and then execute the proper query based of the correct values, in that case ">5" will not evaluate against a varchar. – SdSaati Aug 20 '20 at 05:15