LOVs or Lists of values – one solution fits quite a few

Applications often have the need to have selection lists present and/or limit the possible choices for entry in a field. Some lists need to be static, and others need to be a bit more flexible in terms of adding new values. I’ve seen several solutions to this, and having been “stuck” on 11g for a long time, I thought I’d test out a “new” way of doing it using a composite foreign key and hidden columns.

List of values (LOV) often need to fulfill several requirements:

  • Limit the possible inputs
  • Present a list of possible inputs to the user
  • Display a describing text instead of codes in reports and user interface

To limit the inputs you can easily use a check-constraint on the column in the table:

create table mytable (
  id number primary key,
  my_coded_value number(1) not null,
  constraint my_coded_valud_chk check (my_coded_value between 1 and 5)
);

This is fast, easy, efficient and ensures data quality. I consider it most useful for few values that won’t change over time.

It is however not well suited for displaying the corresponding text to the user. You’d have to use a case-statement or something similar for that.

If you put the values in a separate table, you can add a foreign key to that table and it will make it easy to display the coded value’s text. It can also facilitate efficient searches based on the coded text value. However – this will potentially give you a lot of tables to create and maintain.

If you collect all the LOVs into one common table-structure, you don’t have to create a lot of tables and you can maintain these through the same API or GUI.

Based on composite foreign keys, virtual columns and invisible columns I thought I’d try a, for me, new twist on this in my current project. If I collect all my simple LOVs into a two-table structure:

create table lists (  
  list_id    number generated by default on null as identity  
             constraint lists_pk primary key,  
  list_name  varchar2(100) not null  
);

create table list_values (  
  list_id     number not null  
              constraint list_values_list_id_fk references lists,  
  code_id     number not null,  
  code_value  varchar2(100) not null,  
  in_use      varchar2(1) default 'Y',  
  constraint list_values_pk primary key (list_id, code_id),  
  constraint in_use_chk check (in_use in ('Y', 'N'))  
)  
organization index;

I’ve created the table as an index organized table (sorted by the primary key), to provide for efficient lookups of code_values.

Now – for my data table I can use a composite foreign key, but I don’t have to store the list_id – it can just be generated. And to avoid “cluttering” my output when I do a select * in ad-hoc queries, I can make it a hidden column. The hidden part is of course entirely optional.

create table customers (  
  customer_id           number generated by default on null as identity  
                        constraint customers_pk primary key,  
  status_id             number not null,  
  status_code_list_id   invisible generated always as (1),  
  constraint customer_status_lov_fk foreign key (status_code_list_id, status_id)   
  references list_values (list_id, code_id)  
);

This way, all new values in status_id must conform to a corresponding code_id in list_values with list_id = 1;

If you want to index on this foreign key, I’d suggest to use the compress-attribute to reduce the size, as status_code_list_id will always have just one value and the status_id will have a limited number of values. That way, the index won’t take any significant more space that an index on status_id alone:

create index customer_status_lov_fk
on customers (status_code_list_id, status_id) compress;

Using this technique ensures that old values can’t be deleted from the code list as long as they’re referenced in the customers table. If I want to retire some values from the list, I’ll tag them with in_use = ‘N’ and filter on that before presenting the list in listboxes in the GUI. This filtering part, I’ll hide behind a view, joining lists and list_values, so that it’s easily reusable for different GUI searches. These are also datasets that are easily cacheable on the application server and/or the client.

I’ve saved a script for this as a Live SQL session that can easily be reviewed and run:

Critique of this model:

Pros:

  • Simplistic.
  • Can use code_id’s that are easy to use and remember (can start from 1 for each value list). Of course I use constants in the code, but it’s better when doin ad-hoc queries.
  • Can drop having a check-constraint on the customers table.
  • Easy to introduce new code values.
  • Can avoid triggers.

Cons:

  • Have to include the codelist_id-column on every join to get the correct value. This can be remedied by using a view where the join for such columns is in place.
  • Tagging a code value as out of use won’t be validated by the constraint the way it is defined now. It will be possible to insert new rows with out-of-use-values. Depending on business needs it might still be okay.
  • More complex varieties like having codes with valid date-ranges may need other solutions. A trigger is a possibility here.

Anyway – I’ll test this out and see how I like it. I’ve added a descripton and a couple of attribute columns to the lists and list_values tables to improve them a bit.

I’ve created a template in my IDE to easily create the code for adding the columns, constraints, indexes and comments.

References:

Alex Zaballa and Daniel Da Meda’s blog post on invisible columns is worth taking a look at.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s