2014-02-20 33 views
4

我已经搜索了这个,并试图在过去几天自己做,但我不能。我在搜索范围内得到的最接近的是这个答案,也是堆栈溢出:EAV Select query from spreaded value tables如何查询实体属性值(EAV)模型中实体的所有信息?

所以我在这里,把自己变成了互联网!

因此,我有一个使用EAV(Entity-Attribute-Value)模型的数据库。但是这里有一个问题:实际的实体并不直接连接到其他EAV表。让我更具体一点;说有一个Person和一个Site表,并且他们只有他们的主键:person_idsite_id,分别。

因为这些实体(即人和站点)的属性(在我的模式中称为“属性”)必须是动态的,它们必须全部存储在它们各自的表(即EAV表)之外。以下是数据库模式的EAV部分(我不确定它是否完全正确,因此如果您有任何建议,请告诉我)。 - http://i.stack.imgur.com/EN3dy.png

架构的EAV部分主要有以下表格:

  • property
  • property_value_varchar
  • property_value_text
  • property_value_number
  • property_value_boolean
  • property_value_datetime
  • entity_tables

好了,所以,由于实体不是“直接连接”到EAV部分,我使用的entity_tables表一样,以实际的表的引用,因此,与上面的例子中,entity_tables表应该是这个样子:

 
--------------------------------------- 
|entity_table_id | entity_table_name | 
|  1   |  person  | 
|  2   |  site   | 
|  .   |  .   | 
|  .   |  .   | 
--------------------------------------- 

property表是实际持有任何实体都可以持有,说“PERSON_FIRST_NAME”或“LOCATION_NAME”,或其他任何性质不同的一个。

property_value_*表格除property_value的数据类型外都完全相同。这些是保存每个实体对象属性的实际值的映射,它们由entity_table_identity_object_id映射。

让我给你的数据库的一个可能的情况下,为了清楚:

 
Person table 
------------- 
| person_id | 
|  1  | 
|  2  | 
------------- 

Site table 
----------- 
| site_id | 
| 1 | 
| 2 | 
----------- 

entity_tables table 
--------------------------------------- 
|entity_table_id | entity_table_name | 
|  1   |  person  | 
|  2   |  site   | 
--------------------------------------- 

property table 
------------------------------------- 
| property_id | property_code | 
|  1  | PERSON_FIRST_NAME | 
|  2  | PERSON_LAST_NAME | 
|  3  | PERSON_BIRTH_DATE | 
|  4  | SITE_NAME   | 
|  5  | SITE_PHONE_NR_1 | 
|  6  | SITE_PHONE_NR_2 | 
|  7  | SITE_LATITUDE  | 
|  8  | SITE_LONGITUDE | 
|  9  | SITE_CITY   | 
|  10  | SITE_COUNTRY  | 
|  11  | SITE_ZIP_CODE  | 
------------------------------------- 

property_value_varchar table 
----------------------------------------------------------------------------------------- 
| property_value_id | property_id | entity_table_id | entity_object_id | property_value | 
|   1   |  1  |  1  |   1  |  Richard | 
|   2   |  2  |  1  |   1  |  Hammer  | 
|   3   |  1  |  1  |   2  |  Bruce  | 
|   4   |  2  |  1  |   2  |  Heaton  | 
|   5   |  4  |  2  |   1  |  BatCave | 
|   6   |  5  |  2  |   1  | +49123456789 | 
|   7   |  4  |  2  |   2  | BigCompany | 
|   8   |  5  |  2  |   2  | 987654321 | 
|   9   |  6  |  2  |   2  | 147852369 | 
|  10   |  9  |  2  |   2  |  Berlin | 
|  11   |  10  |  2  |   2  |  Germany | 
|  12   |  11  |  2  |   2  |  14167  | 
----------------------------------------------------------------------------------------- 

property_value_datetime table 
----------------------------------------------------------------------------------------- 
| property_value_id | property_id | entity_table_id | entity_object_id | property_value | 
|   1   |  3  |  1  |   1  | 1985-05-31 | 
----------------------------------------------------------------------------------------- 

property_value_number table 
----------------------------------------------------------------------------------------- 
| property_value_id | property_id | entity_table_id | entity_object_id | property_value | 
|   1   |  7  |  2  |   1  | 1.402636 | 
|   2   |  8  |  2  |   1  | 7.273922 | 
----------------------------------------------------------------------------------------- 

(property_value_text and property_value_boolean tables are empty) 

正如你可以看到,不是每个实体的所有对象都具有一定相同属性(属性)。这个领域真的很宽松。

因此,现在像我之前的很多人一样,我不确定如何以可读的方式检索所有这些信息,即如何获取有关人员表或记录的所有信息地点表?

也就是说,我怎么能得到这样的:

 Person table view 
---------------------------------------------------- 
| Person ID |  Property code | Property value | 
|  1  | PERSON_FIRST_NAME | Richard  | 
|  1  | PERSON_LAST_NAME | Hammer  | 
|  1  | PERSON_BIRTH_DATE | 1985-05-31 | 
|  2  | PERSON_FIRST_NAME | Bruce  | 
|  2  | PERSON_LAST_NAME | Heaton  | 
---------------------------------------------------- 

Site table view 
------------------------------------------------ 
| Site ID | Property code | Property value | 
| 1 | SITE_NAME  | Batcave  | 
| 1 | SITE_PHONE_NR_1 | +49123456789 | 
| 1 | SITE_LATITUDE | 1.402636  | 
| 1 | SITE_LONGITUDE | 7.273922  | 
| 2 | SITE_NAME  | BigCompany  | 
| 2 | SITE_PHONE_NR_1 | 987654321  | 
| 2 | SITE_PHONE_NR_2 | 147852369  | 
| 2 | SITE_CITY  | Berlin   | 
| 2 | SITE_COUNTRY | Germany  | 
| 2 | SITE_ZIP_CODE | 14167   | 
------------------------------------------------ 

也不像这个,如果是比较容易:

 
Person table view 
------------------------------------------------------------------------ 
| Person ID | PERSON_FIRST_NAME | PERSON_LAST_NAME | PERSON_BIRTH_DATE | 
|  1  |  Richard  |  Hammer  |  1985-05-31 | 
|  2  |  Bruce  |  Heaton  |     | 
------------------------------------------------------------------------ 

Site table view 
---------------------------------------------------------------------------------------------------------------------------------------- 
| Site ID | SITE_NAME | SITE_PHONE_NR_1 | SITE_PHONE_NR_2 | SITE_LATITUDE | SITE_LONGITUDE | SITE_CITY | SITE_COUNTRY | SITE_ZIP_CODE | 
| 1 | Batcave | +49123456789 |     | 1.402636 | 7.273922 |   |    |    | 
| 2 | BigCompany | 987654321 | 147852369  |    |    | Berlin | Germany |  14167  | 
---------------------------------------------------------------------------------------------------------------------------------------- 

我意识到这可能会相当混乱。请让我知道我还能如何帮助您,例如获取更多信息或更好地解释某些部分。

我也不期望1个SQL查询(每个实体)来做这个把戏。我意识到可能有超过1个查询,并且它/他们很可能需要由PHP(例如)“组合”,以便真正实现动态化。所以,即使有人甚至可以解释我如何才能获得所有这些信息,只是为了我上面的假设属性(属性),我已经非常感激!

谢谢你的帮助!

+1

不幸的是,EAV通常是反模式。它使得你想要的各种查询非常繁琐 - 你必须将许多你认真避免的“硬编码”的东西“硬编码”到一个模式中,从而失去任何好处。特别是,'SELECT'查询列的* type *不能依赖于表的*内容* - 它必须在查询时已知,或者可以从表列或表达式的* type *中发现,所以只有这样才能编写一个能够产生像第一个例子那样的输出的查询,就是加入* every *'result_value _...'表并合并... –

+0

...将结果合并到具有'COALESCE()的单个字段中'或类似的。但是,那么这个列的类型必然会变成一些最小公因分母类型(可能是无界的'VARCHAR'),所以你不能对它们执行最明智的操作(例如,即使你不能为每个值添加5知道它们最初是数字的,没有先显式转换为数字类型)。一些RDBMS(可能MySQL,我不知道)有“pivoting”扩展,可能允许行按照你以后的代码片段转换成列,但这是TTBOMK而不是标准的SQL。 –

+0

如果您首先使用EAV来允许用户编写高度可定制的查询,那么我的建议是以通常的方式重新设计您的数据库模式(即使用一组固定编码的列),并通过以下方式为用户提供查询灵活性:允许他们从通过其特殊元数据系统表(所有数据库提供此信息,例如通过“INFORMATION_SCHEMA”提供)中从数据库中提取的列表中选择字段,表等,然后从中创建一个定制的“SELECT”命令。 –

回答

1

这是一个有趣的问题!这可以通过动态sql来处理。在下面的代码中,模式已经用临时表重新创建。代码可以变成一个存储过程,将一个entity_table_id作为参数,然后选择entity_object_id作为entity_table_name +'id',然后选择每个property_value作为具有相应property_code作为标题的列。

-- load EAV tables 
if object_id('tempdb..#entity_tables') is not null 
    drop table #entity_tables 
create table #entity_tables(entity_table_id int,entity_table_name varchar(255)) 
insert into #entity_tables values 
    (1,'person'), 
    (2,'site') 
if object_id('tempdb..#property') is not null 
    drop table #property 
create table #property(property_id int,property_code varchar(255)) 
insert into #property values 
    (1,'PERSON_FIRST_NAME'), 
    (2,'PERSON_LAST_NAME'), 
    (3,'PERSON_BIRTH_DATE'), 
    (4,'SITE_NAME'), 
    (5,'SITE_PHONE_NR_1'), 
    (6,'SITE_PHONE_NR_2'), 
    (7,'SITE_LATITUDE'), 
    (8,'SITE_LONGITUDE'), 
    (9,'SITE_CITY'), 
    (10,'SITE_COUNTRY'), 
    (11,'SITE_ZIP_CODE') 
if object_id('tempdb..#property_value_varchar') is not null 
    drop table #property_value_varchar 
create table #property_value_varchar(property_value_id int,property_id int,entity_table_id int,entity_object_id int,property_value varchar(255)) 
insert into #property_value_varchar values 
    (1,1,1,1,'Richard'), 
    (2,2,1,1,'Hammer'), 
    (3,1,1,2,'Bruce'), 
    (4,2,1,2,'Heaton'), 
    (5,4,2,1,'BatCave'), 
    (6,5,2,1,'+49123456789'), 
    (7,4,2,2,'BigCompany'), 
    (8,5,2,2,'987654321'), 
    (9,6,2,2,'147852369'), 
    (10,9,2,2,'Berlin'), 
    (11,10,2,2,'Germany'), 
    (12,11,2,2,'14167') 
if object_id('tempdb..#property_value_datetime') is not null 
    drop table #property_value_datetime 
create table #property_value_datetime(property_value_id int,property_id int,entity_table_id int,entity_object_id int,property_value datetime) 
insert into #property_value_datetime values 
(1,3,1,1,'1985-05-31') 
if object_id('tempdb..#property_value_number') is not null 
    drop table #property_value_number 
create table #property_value_number(property_value_id int,property_id int,entity_table_id int,entity_object_id int,property_value float) 
insert into #property_value_number values 
(1,7,2,1,1.402636), 
(2,8,2,1,7.273922) 

-- create dynamic sql to get all data conditioned on #entity_tables.table_id value 
declare @tableid int,@sql varchar(max) 
set @tableid = 1 -- this could be passed as a parameter 

-- get pivot code with #ColumnList# placeholders to be added below 
select @sql = 'select entity_object_id ' + entity_table_name + 'id, 
    #ColumnListCast# 
from (
     select 
      e.entity_table_name, 
      pv.entity_object_id, 
      pv.property_value, 
      p.property_code 
     from #entity_tables e 
      inner join (
         select entity_table_id,entity_object_id,property_id,property_value from #property_value_varchar union all 
         select entity_table_id,entity_object_id,property_id,cast(property_value as varchar(255)) from #property_value_datetime union all 
         select entity_table_id,entity_object_id,property_id,cast(property_value as varchar(255)) from #property_value_number 
         ) pv 
       on pv.entity_table_id = e.entity_table_id 
      inner join #property p 
       on p.property_id = pv.property_id 
     where e.entity_table_id = ' + cast(@tableid as varchar(5)) + ' 
     ) p 
    pivot (
      max(property_value) 
      for property_code in (
            #ColumnList# 
            ) 
      ) piv' from #entity_tables where entity_table_id = @tableid 

-- get column list with cast version for diffferent data types 
declare @ColumnList varchar(max), 
     @ColumnListCast nvarchar(max) 
set @ColumnList = '' 
set @ColumnListCast = '' 
select @ColumnList = @ColumnList + '[' + p.property_code + ']' + case row_number() over(order by p.property_id desc) when 1 then '' else ',' end, 
     @ColumnListCast = @ColumnListCast + 'cast([' + p.property_code + '] as ' + t.CastValue + ') [' + p.property_code + ']' + case row_number() over(order by p.property_id desc) when 1 then '' else ',' end 
from #property p 
    inner join (
       select property_id,'varchar(255)' CastValue from #property_value_varchar where entity_table_id = @tableid union 
       select property_id,'datetime' CastValue from #property_value_datetime where entity_table_id = @tableid union 
       select property_id,'float' CastValue from #property_value_number where entity_table_id = @tableid 
       ) t 
     on t.property_id = p.property_id 
order by p.property_id 

set @sql = replace(replace(@sql,'#ColumnList#',@ColumnList),'#ColumnListCast#',@ColumnListCast) 

exec(@sql) 
相关问题