EAV Modeling with PHP & MySQL

Update Nov 11th, 2013:

I’ve updated the code examples and provided them on github at:
https://github.com/dschreck/php-eav-example

I want to share with some of you an easy way to set up your database design in a very flexible and reliable EAV like model.

EAV stands for Entity Attribute Value, which is a common design for complex database structures that require many different Entities using many different attributes with, again, many different values. It’s very common to find this set up in medical offices or records.

Let’s go ahead and assume a situation, and we’ll work through it.

Let’s say you’re accepting a feed of some sort, and you need to save the items being fed to you into a database. The problem here, is that the content length of these items, attributes, and values varies.

So let’s consider the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Item 'Node1' ->
Attribute 'Main' ->
Value 1
Value 2
Value 3
Value 4
Item 'Node2' ->
Attribute 'Main' ->
Value 1
Value 2
Value 3
Value 4
Attribute 'Other' ->
Value 1
Value 2
Item 'Node3' ->
Attribute 'Main' ->
Value 1
Value 2
Value 3
Value 4
Value 5
Value 6
Value 7
Value 8

Now this is a very cheap and generic example of a data structure. But we’ll work with it for now.

So let’s go ahead and draw some conclusions.

The Item list will come in with a name, and have Attributes. These attributes will have values. But the number of attributes and the number of values varies.

So with that in mind, we shall come up with some SQL to create some times. For this example we’re going to need three tables: items, item_attributes, and attribute_values.

So here we go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- first our items table:
CREATE TABLE `items` (
`id` int(11) NOT NULL auto_increment,
`item_name` varchar(50) default NULL,
PRIMARY KEY (`id`)
);
-- now our item attributes
CREATE TABLE `item_attributes` (
`id` int(11) NOT NULL auto_increment,
`item_id` int(11) NOT NULL default '0',
`attribute_name` varchar(50) default NULL,
PRIMARY KEY (`id`),
KEY `item_id_attribute_name` (`item_id`,`attribute_name`)
);
-- now finally our attribute values
CREATE TABLE `attribute_values` (
`attribute_id` int(11) NOT NULL default '0',
`attribute_value` varchar(100) default NULL,
UNIQUE KEY `attribute_id` (`attribute_id`,`attribute_value`)
);

Now that we have our layout, let’s take a look at how this is going to work:

Let’s assume the following PHP array is a reprentation of our data…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
// start up our array
$data = array();
//
// Now, let's just load it with some test data
$data['item_1'] = array();
$data['item_1']['attribute_1'] = array();
$data['item_1']['attribute_1'][] = 'value1';
$data['item_1']['attribute_1'][] = 'value2';
$data['item_1']['attribute_1'][] = 'value3';
$data['item_1']['attribute_1'][] = 'value4';
// that's good for now.
/**
* Now let's insert this int our new schema
*
* Please note, for example sake, I will not be double checking queries
* but you SHOULD check each query for an error.
**/
foreach($data as $item_name =&gt; $attributes)
{
$sql = "INSERT INTO items (id, item_name) VALUES (NULL, '{$item_name}');";
mysql_query($sql);
$item_id = mysql_insert_id();
// now let's loop through our attributes
foreach($attributes as $attribute =&gt; $values)
{
// this is now our insert into the attributes...
$sql = "INSERT INTO item_attributes (id, item_id, attribute_name) VALUES (NULL, {$item_id}, '{$attribute}');";
mysql_query($sql);
$attribute_id = mysql_insert_id();
// now let's loop through the attribute values
foreach($values as $value)
{
$sql = "INSERT INTO attribute_values (attribute_id, attribute_value) VALUES ({$attribute_id}, '{$value}');";
mysql_query($sql);
}
}
}

And there you have it - that’s now how we can get our data into our database in a flexiable manner, without having to rely
on an ‘excel’ like database.

To get it out, we’ll simply use some joins…

1
2
3
4
5
6
7
8
9
10
11
12
$sql =
"SELECT
items.item_name,
ia.attribute_name,
av.attribute_value
FROM
attribute_values AS av
JOIN item_attributes AS ia
ON (ia.id = av.attribute_id)
JOIN items AS items
ON (items.id = ia.item_id);
";

You could also use a concat_ws to make a comma seperated list, but now that you have it in your database, you can do anything you want with it :)