Handling EAV-data

 
In my line of work I often have to develop reports on all kinds of environments, but most of the time the underlying databases are limited to relational or dimensional models.
A few months ago, however, I was faced with the tedious task challenge to build a couple of reports on a system that was entirely based on an EAV-architecture.
Even though I was familiar with the technique, I have never put it to use for reporting purposes.
After doing some research on the internet, looking for best practices and guidelines, I noticed that the amount of information about reporting on EAV-data was limited at best.
That’s why I decided to dedicate a few articles on my personal experiences (and pitfalls) with the wonderful world of EAV.
There are a lot of interesting challenges to overcome when working with EAV-data, but I will try to start out with a few simple examples.
In this first article I will focus on the selection and formatting of EAV-data, the more complex techniques (like handling timelines) will be addressed in future articles.
First of all, let’s dive into some of the basics concerning EAV-data (since this type of datamodelling isn’t very common in a reporting environment).
EAV is an abbreviation for Entity – Attribute – Value. Data stored in this type of model usually consists out of three ‘base’ columns:
- Entity : An Entity is a ‘thing’, and object. Examples could be ‘Person’, ‘Employee’, ‘Contract’.
- Attribute : An Attribute describes a part of the Entity. If we take the Entity ‘Person’ as an example, attributes could be ‘Name’, ‘Birthday’, ‘Nationality’, etc.
- Value : The Value of the Attribute, i.e. ‘John’, ’12-12-1970’, ‘English’.
In a relational datamodel, an Entity would most likely be a table, a Attribute would correspond with a column and a Value would match a field.
Following table is a representation of a very simple EAV dataset.
Table: Entity
EntityID | Entity |
1 | Employee |
33 | Contract |
Table: Attribute
AttributeID | Attribute |
100 | Name |
101 | Birthday |
102 | Nationality |
360 | Type |
361 | Startdate |
Table: EAV_Data
ID | EntityID | AttributeID | Value |
1234 | 1 | 100 | John |
1234 | 1 | 101 | 12-12-1970 |
1234 | 1 | 102 | English |
5678 | 33 | 360 | Fulltime |
5678 | 33 | 361 | 01-01-2009 |
In the above example, we notice a few things.
First of all, in a relational datamodel we probably would have had a ‘Employee’ table and a ‘Contract’ table.
In our EAV example, most the data is stored in 1 single table. Both the tables (Entities) and columns (Attributes) are stored in a row-wise manner, therefore there is no ‘fixed’ datastructure. Second, all the ‘Values’ are stored in the same column, which implicates that we have 1 datatype to store everything (varchars, datetimes, ints, etc.).
To keep this example simple, ill ignore this ‘datatype-problem’ for now.
EAV-modelling works exceptionally well for systems that need a great amount of flexibility.
Both ‘tables’ and ‘columns’ can be dynamically added without changing the underlying model.
Systems that need this amount of flexibility are i.e. clinical applications.
So instead of having several (fixed) smaller tables, you end up with one (flexible) huge table.
While this is all fun and games for systems that storing information, things can get a bit challenging when you need to build reports.
First challenge: How do you retrieve EAV data?
To demonstrate the differences between a relational model and a EAV model when building queries, I’ll try to make an easy selection on all employee’s with the name ‘John’ that have the ‘English’ nationality.
The relation employee table in this example has the following columns: ID, Name, Last Name, Birthday, Nationality and Marriagal status.
Example 1: Relational table
1: SELECT *
2: FROM Employee
3: WHERE Name = 'John'
4: AND Nationality = 'English'
 
Result
ID | Name | Last name | Birthday | Nationality | Marriagal Status |
1234 | John | Doe | 12-12-1970 | English | Married |
365 | John | Cook | 01-08-1983 | English | Single |
All the information from an Employee is stored in 1 record, which makes it a very easy task to select all the information we need.
In contrary to the Employee table, the EAV table has every attribute (column) stored in a new record.
 
Example 2: EAV table
1: SELECT *
2: FROM EAV_Data
3: WHERE Attribute = 'Name'
4: AND Value = 'John'
Result
ID | EntityID | AttributeID | Value |
1234 | 1 | 100 | John |
365 | 1 | 100 | John |
Hmm, not exactly what we wanted to see. Even though this is a correct result, the other Attributes (like Last Name, Birthday, etc) are not returned by our query.
If we were to make a report on the Employees, we have to figure a way out to present the Attributes on the horizontal axe, as if they were columns. In short, we want the rows to be converted to columns.
One way to achieve this, is to access the same table several times by means of selfjoins.
 
Example 3: EAV table with selfjoins
1: SELECT t1.ID as 'ID'
2: , t1.Value AS 'Name'
3: , t2.Value AS 'Nationality'
4: , t3.Value AS Birthday
5: FROM EAV_Data t1
6: LEFT JOIN EAV_Data t2
7: ON t1.ID = t2.ID
8: LEFT JOIN EAV_Data t3
9: ON t1.ID = t3.ID
10: WHERE t1.Attribute = 'Name'
11: AND t1.Value = 'John'
12: AND t2.Attribute = 'Nationality'
13: AND t2.Value = 'English'
14: AND t3.Attribute = 'Birthday'
Result
ID | Name | Nationality | Birthday |
1234 | John | English | 12-12-1970 |
365 | John | English | 01-08-1983 |
While this works to some extent, you’ll notice that the query for the EAV-data is much more complex that the relational one. On top of that, in order to get the same amount of columns as the relational query, we need even more selfjoins! Needless to say, using lots and lots of selfjoins is stressfull for the database and has a negative impact on the performance.
A better (and more elegant) solution is to ‘rotate’ the table by making use of the PIVOT operator that was introduced with SQL Server 2005.
 
Example 4: EAV table with PIVOT
1: SELECT * FROM
2: (
3: SELECT ID
4: , [100] AS Name
5: , [101] AS Birthday
6: , [102] AS Nationality
7: FROM
8: (
9: SELECT ID, EntityID, AttributeID, Value
10: FROM EAV_Data
11: ) p
12: PIVOT
13: (
14: MAX (Value)
15: FOR AttributeID IN ([100], [101], [102])
16: ) AS pvt
17: ) AS result
18: WHERE Name = 'John'
19: AND Nationality = 'English'
Result
ID | Name | Birthday | Nationality |
1234 | John | 12-12-1970 | English |
365 | John | 01-08-1983 | English |
By making use of the PIVOT operator we are capable of ‘rotating’ the table, which makes it possible to query the data as if it was stored in a relational table.
Of course this is a very simple example, but combined with metadata it can be a powerful technique.
Systems that make extensive use of EAV-modelling are usually backed up with a great amount of metadata, which is used to describe properties like the datatypes of attributes, the attributes that belong to a certain entity, the relation between entities, etc.
In my current project we use the metadata to generate an extra layer in the database, based on certain combinations of attributes.
This way it’s like we have a couple of hundred different (relational) tables, while everything is effectively stored in 1 big table.
I hope this was an interesting read, in my next article I’ll elaborate on the subject of handling multiple timelines