1

We have a client-server system that uses SQL Server 2012 as a database. The system is updated and enhanced on a regular basis.

We recently realized we have a recurring pattern of table design: a table that links the Users table to other tables in the database, forming a basis for views and permissions. Example:

CREATE TABLE Documents_Users (
    ID int not null identity(1,1) not for replication,
    UserID int not null, -- foreign key to the Users table
    DocumentID int not null, --- foreign key to the Documents table
    [...]
)

We were thinking, that instead of creating all of these tables, we might create one large table like this:

CREATE TABLE UserPermissions (
    ID int not null identity(1,1) not for replication,
    UserID int not null, -- foreign key to the Users table
    ObjectID int not null, -- pointer to any object ID in another table
    TableID int not null, -- actual id of the referenced table in sys.objects
    [...]
)

Pros: one table solves all of our permission needs for all current and future tables.

Cons:

  1. no foreign keys. we will have to create a job that runs every once in a while and deletes orphan pointers (if we don't just delete them in stored procedures).

  2. Views will need to query along the lines of

    select * from [some_table] where ID in ( select ObjectID from UserPermissions where TableID = object_id('someTableName') )

Is it worth it, and a good practice, to create a generalization like this? Would there be a performance gain from having one table and one index to read, instead of possibly many different tables?

user884248
  • 2,134
  • 3
  • 32
  • 57
  • In our ERP there is quite a lot such generalized tables (for example Attributes table; where attribute can be related to any object in any table). Pros - less tables (still there are a hundreds of tables), common triggers for Attributes table; You should use INSERT UPDATE trigger at generalized table instead of foreign key to check key integrity with any target tables and you should use DELETE trigger at targets tables to delete in common table (or to prevent delete of parent). – huhu78 Feb 04 '15 at 10:37
  • 1
    Please don't persist the value of a SQL Server sys table identifier (TableId in your example). Those key values are inherently fragile and subject to change for lots of reasons. Relying on them is likely to cause you a lot of pain. Use the object names or create your own surrogates. Your query obviously simplifies to a join: SELECT s.* FROM [some_table] s, UserPermissions u WHERE s.ID = u.ObjectID AND u.name = 'some_table';. In principle you could use a foreign key here if you use the same SEQUENCE object to generate all your ObjectiDs (instead of IDENTITY). – nvogel Feb 04 '15 at 12:49

3 Answers3

2

My gut feeling is that this is probably not a very good idea.

A better alternative would be to use inheritance1:

  • connect user to a base table,
  • then "inherit" other tables from the base table.2

Any future tables you add, you can just inherit from the existing base table and they will automatically "plug into" the security logic you have already implemented.

For what it's worth, we are currently in the process of implementing a rather powerful security mechanism on top of just such architecture and it works well so far.


1 Also known in ER modeling as: "category" or "generalization hierarchy" or "subclassing" etc.

2 Unfortunately, inheritance is not directly supported in relational databases today (not even PostgreSQL, which only partially implements it), so you'll need to emulate it (here is one example and you may also want to read this), which brings its own problems, but is still incredibly useful in certain scenarios.

Community
  • 1
  • 1
Branko Dimitrijevic
  • 50,809
  • 10
  • 93
  • 167
  • So, "inheritence" basically means adding more tables with foreign keys to the original, "base" table? – user884248 Feb 04 '15 at 13:14
  • Poor man's inheritance is discussed in two tags, [tag:single-table-inheritance] and [tag:class-table-inheritance]. – Walter Mitty Feb 04 '15 at 13:49
  • @user884248 In this case yes. If you read the links, you'l see that there are actually couple of other strategies, but this one if probably most appropriate for this case. – Branko Dimitrijevic Feb 04 '15 at 14:41
0

I'm facing the same choice right now, and I think I'll go for the generic solution. I'm not a fan of generic solutions, because the semantics are less obvious, and before you know it, you're in an incomprehensible mess. But I tend to think it is permitted if it serves a domain-wide, clearly defined purpose, such as logging, language, and like here, security.

For the objections I'd think that deleted orphans are not in the way of anything, so cleaning once in a while would be a good enough solution to start with. As for the queries, i'd create an inline table valued function, aka a parameterized view (instead of a view for each table) that takes the UserId as an argument. One could optionally restrict reading rights on the tables themselves, and only allow rights on the views.

I think the performance gain would be marginal, but still better with the generic solution; the main reason for me would be less cluttered diagrams.

nico boey
  • 389
  • 1
  • 5
0

I would not do it because what happens when a user-to-X permission needs attributes based on the type of X. i.e. UserDocumentPermission might have read, write, delete, add, etc while UserCarPermission might have drive, start and park permissions.

Who cares if you have a lot of tables. You can just use naming convention to make it easy to remember.

bpeikes
  • 3,495
  • 9
  • 42
  • 80
  • Thanks @bpeikes. This is one of the problem we had in mind. We are debating if it is worth the price of createing external tables for cases like these, or if that just means having the original problem of too many tables but in a different way. – user884248 Feb 04 '15 at 13:13