home
 
email me   home      
My Resume
My Recent Sites
My Project Examples
Etc...













An easier way to deal with reference / lookup tables in MS SQL
Wednesday, May 31, 2006

I have a project that contains dozens of tables that contain nothing more than simple descriptors and basic reference data for keeping main tables smaller. Some examples would be "User_Type", "Shipping_Status", "Order_Status", etc. Having a lot of these types of tables can become bothersome to deal with (even when creating them from templates), because ultimately you must create some sort of object that handles the CRUDS (create, update, delete, select, etc.) unless you are using dynamic SQL in your data objects.

Here's a handy little method I've been using for dealing with lots of reference data. It may not be the absolute perfect way, but I haven't found any drawbacks yet and these simple joins don't seem to be making much of an impact on my data retrieva. Most of the results are ultimately cached in the application as well.

Here's the rReference table which will hold all our reference data:
          CREATE TABLE [dbo].[rReference] (
               [id] [int] IDENTITY (1, 1) NOT NULL ,
               [date_created] [datetime] NOT NULL ,
               [createdby_id] [bigint] NOT NULL ,
               [ref_key] [varchar] (100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
               [ref_display] [varchar] (250) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
               [ref_display_desc] [varchar] (250) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
               [is_active] [bit] NOT NULL ,
               [is_deleted] [bit] NOT NULL ,
               [date_updated] [datetime] NOT NULL ,
               [updatedby_id] [bigint] NOT NULL ,
               [date_deleted] [datetime] NOT NULL ,
               [deletedby_id] [bigint] NOT NULL 
          )  ON [PRIMARY]
          GO
          
          ALTER TABLE [dbo].[rReference] WITH NOCHECK ADD 
               CONSTRAINT [PK_rReference] PRIMARY KEY  CLUSTERED
               (
                    [id]
               ) WITH  FILLFACTOR = 90  ON [PRIMARY] 
          GO
          
          ALTER TABLE [dbo].[rReference] ADD
               CONSTRAINT [DF_rReference_date_created] DEFAULT (GETDATE()) FOR [date_created],
               CONSTRAINT [DF_rReference_createdby_id] DEFAULT (1) FOR [createdby_id],
               CONSTRAINT [DF_rReference_is_active] DEFAULT (1) FOR [is_active],
               CONSTRAINT [DF_rReference_is_deleted] DEFAULT (0) FOR [is_deleted],
               CONSTRAINT [DF_rReference_date_updated] DEFAULT (1 / 1 / 1900) FOR [date_updated],
               CONSTRAINT [DF_rReference_updatedby_id] DEFAULT (0) FOR [updatedby_id],
               CONSTRAINT [DF_rReference_date_deleted] DEFAULT (1 / 1 / 1900) FOR [date_deleted],
               CONSTRAINT [DF_rReference_deletedby_id] DEFAULT (0) FOR [deletedby_id]
          GO
 

Basically what we are doing here is assigning a key (Column: ref_key) which will describe the reference data. The table's primary key (id) will be the actual value to join on. The value that will be presented after the join, and ultimately throughout the application's UI will be the "ref_display" column. So an example populated reference data row would look like this: "'1', '5/25/2005', '5', 'User Type', 'Registered Member', 'This is just a standard registered member of the site', '1', '0', '1/1/1900', '0', '1/1/1900', '0'"

An example join on this reference data table would look like:
          SELECT
               tUser.Name_LastFirst,
               tUser.status_id,
               A.ref_display AS status_name,
               tUser.type_id,
               B.ref_display AS type_name
          FROM
               tUser
               INNER JOIN rReference A ON tUser.status_id = A.id
               INNER JOIN rReference B ON tUser.type_id = B.id
          WHERE
               tUser.id = '1'
          GO
 

Next, you'd want to create some simple procedures for dealing with the rReference table. First, you'd need to display the various types of reference data that the user can manage. Well, that is very easy. All you'd need to do here is perform a distinct select on the ref_key which describes the reference data to store, and then pull your reference data rows by the associated key.

A couple of example stored procedures for managine the reference data:
          -- HERE'S WHERE YOU DECIDE WHAT IS AVAILABLE TO LIST
          CREATE PROCEDURE [rReference_sel_ListKeys]
          AS
          BEGIN
               SELECT
                   DISTINCT(ref_key) 
               FROM
                    rReference
               WHERE
                    rReference.is_deleted = 0
          GO
          
          -- AND HERE'S YOUR MAIN LIST PROCEDURE
          
          CREATE PROCEDURE [rReference_sel_ListByKey]
          (
               @ref_key VARCHAR(100),
               @full BIT,
               @is_active BIT
          )
          AS
          BEGIN
               -- DETERMINE WHETHER TO PULL ENTIRE ROW
               IF @full > 0
                    BEGIN
                         -- DETERMINE WHETHER TO PULL ACTIVE ROWS ONLY, OR ALL
                         IF @is_active > 0
                              BEGIN
                                   SELECT
                                        rReference.id,
                                        rReference.date_created,
                                        rReference.createdby_id,
                                        A.name_full AS createdby_name,
                                        rReference.ref_key,
                                        rReference.ref_display,
                                        rReference.ref_display_desc,
                                        rReference.is_active,
                                        rReference.is_deleted,
                                        rReference.date_updated,
                                        rReference.updatedby_id,
                                        B.name_full AS updatedby_name,
                                        rReference.date_deleted,
                                        rReference.deletedby_id,
                                        C.name_full AS deletedby_name
                                   FROM
                                        rReference
                                        INNER JOIN tUser A ON rReference.createdby_id = A.id
                                        LEFT OUTER JOIN tUser B ON rReference.createdby_id = B.id
                                        LEFT OUTER JOIN tUser C ON rReference.createdby_id = C.id
                                   WHERE
                                        rReference.ref_key = @ref_key AND
                                        rReference.is_active = 1 AND
                                        rReference.is_deleted = 0
                                   ORDER BY 
                                        rReference.ref_display ASC
                              END
                         ELSE
                              BEGIN
                                   SELECT
                                        rReference.id,
                                        rReference.date_created,
                                        rReference.createdby_id,
                                        A.name_full AS createdby_name,
                                        rReference.ref_key,
                                        rReference.ref_display,
                                        rReference.ref_display_desc,
                                        rReference.is_active,
                                        rReference.is_deleted,
                                        rReference.date_updated,
                                        rReference.updatedby_id,
                                        B.name_full AS updatedby_name,
                                        rReference.date_deleted,
                                        rReference.deletedby_id,
                                        C.name_full AS deletedby_name
                                   FROM
                                        rReference
                                        INNER JOIN tUser A ON rReference.createdby_id = A.id
                                        LEFT OUTER JOIN tUser B ON rReference.createdby_id = B.id
                                        LEFT OUTER JOIN tUser C ON rReference.createdby_id = C.id
                                    WHERE
                                        rReference.ref_key = @ref_key
                                    ORDER BY
                                        rReference.ref_display ASC
                               END
                    END
               -- WE ARE JUST PULLING LISTBOX DISPLAY ROWS
                ELSE
                    BEGIN
                          IF @is_active > 0
                               BEGIN
                                   SELECT
                                        rReference.id,
                                        rReference.ref_display
                                   FROM
                                        rReference
                                    WHERE
                                        rReference.ref_key = @ref_key AND
                                        rReference.is_active = 1 AND
                                        rReference.is_deleted = 0
                                    ORDER BY
                                        rReference.ref_display ASC
                               END
                         ELSE
                              BEGIN
                                   SELECT
                                        rReference.id,
                                        rReference.ref_display
                                   FROM
                                        rReference
                                   WHERE
                                        rReference.ref_key = @ref_key
                                   ORDER BY 
                                        rReference.ref_display ASC
                              END
                    END
          END