如何转换一个MySQL实体 – 属性值模式

我需要devise存储所有文件元数据(即文件名,作者,标题,创builddate)和自定义元数据(已被用户添加到文件,例如CustUseBy,CustSendBy)的表格。 自定义元数据字段的数量不能事先设置。 事实上,确定在文件上添加了多less个自定义标签的唯一方法是检查表中存在的内容。

为了存储这个,我创build了一个基本表(具有文件的所有公共元数据),一个Attributes表(包含可以在文件上设置的附加可选属性)和一个FileAttributes表(它为文件的属性赋值)。

 CREAT TABLE FileBase ( id VARCHAR(32) PRIMARY KEY, name VARCHAR(255) UNIQUE NOT NULL, title VARCHAR(255), author VARCHAR(255), created DATETIME NOT NULL, ) Engine=InnoDB; CREATE TABLE Attributes ( id VARCHAR(32) PRIMARY KEY, name VARCHAR(255) NOT NULL, type VARCHAR(255) NOT NULL ) Engine=InnoDB; CREATE TABLE FileAttributes ( sNo INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, fileId VARCHAR(32) NOT NULL, attributeId VARCHAR(32) NOT NULL, attributeValue VARCHAR(255) NOT NULL, FOREIGN KEY fileId REFERENCES FileBase (id), FOREIGN KEY attributeId REFERENCES Attributes (id) ) Engine=InnoDB; 

示例数据:

 INSERT INTO FileBase (id, title, author, name, created) VALUES ('F001', 'Dox', 'vinay', 'story.dox', '2009/01/02 15:04:05'), ('F002', 'Excel', 'Ajay', 'data.xls', '2009/02/03 01:02:03'); INSERT INTO Attributes (id, name, type) VALUES ('A001', 'CustomeAttt1', 'Varchar(40)'), ('A002', 'CustomUseDate', 'Datetime'); INSERT INTO FileAttributes (fileId, attributeId, attributeValue) VALUES ('F001', 'A001', 'Akash'), ('F001', 'A002', '2009/03/02'); 

现在的问题是我想以这样的方式显示数据:

 FileId, Title, Author, CustomAttri1, CustomAttr2, ... F001 Dox vinay Akash 2009/03/02 ... F002 Excel Ajay 

什么查询会产生这个结果?

这个问题提到MySQL,实际上这个DBMS对于这种问题有一个特殊的function: GROUP_CONCAT(expr) 。 在逐个函数中查看MySQL参考手册 。 这个函数是在MySQL 4.1版本中添加的。 您将在查询中使用GROUP BY FileID

我不确定你想要结果如何。 如果你想要列出每个项目的每个属性(即使没有设置),这将是更困难的。 不过,这是我的build议,如何做到这一点:

 SELECT bt.FileID, Title, Author, GROUP_CONCAT( CONCAT_WS(':', at.AttributeName, at.AttributeType, avt.AttributeValue) ORDER BY at.AttributeName SEPARATOR ', ') FROM BaseTable bt JOIN AttributeValueTable avt ON avt.FileID=bt.FileID JOIN AttributeTable at ON avt.AttributeId=at.AttributeId GROUP BY bt.FileID; 

这给你所有的属性在相同的顺序,这可能是有用的。 输出将如下所示:

 'F001', 'Dox', 'vinay', 'CustomAttr1:varchar(40):Akash, CustomUseDate:Datetime:2009/03/02' 

这样你只需要一个数据库查询,而且输出很容易parsing。 如果你想在数据库中存储真正的date时间等属性,你需要使用dynamicSQL,但我会保持清楚,并存储在varchars的值。

如果您正在寻找比group-concat结果更可用的(并可join的),请尝试下面的解决scheme。 我已经创build了一些与您的示例非常相似的表格,以使其有意义。

这适用于:

  • 你想要一个纯粹的SQL解决scheme(没有代码,没有循环)
  • 你有一组可预测的属性(例如不是dynamic的)
  • 当需要添加新的属性types时,您可以更新查询
  • 你会喜欢一个结果,可以连接到,UNIONed,或嵌套作为子select

表A(文件)

 FileID, Title, Author, CreatedOn 

表B(属性)

 AttrID, AttrName, AttrType [not sure how you use type...] 

表C(Files_Attributes)

 FileID, AttrID, AttrValue 

传统的查询会拉多个冗余行:

 SELECT * FROM Files F LEFT JOIN Files_Attributes FA USING (FileID) LEFT JOIN Attributes A USING (AttributeID); 
 AttrID FileID标题作者CreatedOn AttrValue AttrName AttrType
 50 1 TestFile Joe 2011-01-01 true ReadOnly bool
 60 1 TestFile Joe 2011-01-01 xls FileFormat文本
 70 1 TestFile Joe 2011-01-01 false私人布尔
 80 1 TestFile Joe 2011-01-01 2011-10-03 LastModifieddate
 60 2 LongNovel Mary 2011-02-01 json FileFormat文本
 80 2 LongNovel Mary 2011-02-01 2011-10-04 LastModifieddate
 70 2 LongNovel Mary 2011-02-01真正的私人布尔
 50 2 LongNovel Mary 2011-02-01 true ReadOnly bool
 50 3 ShortStory Susan 2011-03-01 false ReadOnly布尔
 60 3 ShortStory Susan 2011-03-01 ascii FileFormat文本
 70 3 ShortStory Susan 2011-03-01 false私人布尔
 80 3 ShortStory Susan 2011-03-01 2011-10-01 LastModifieddate
 50 4 ProfitLoss Bill 2011-04-01 false ReadOnly bool
 70 4 ProfitLoss Bill 2011-04-01 true私人布尔
 80 4 ProfitLoss账单2011-04-01 2011-10-02 LastModifieddate
 60 4 ProfitLoss账单2011-04-01文本FileFormat文本
 50 5月份预算George 2011-05-01 false ReadOnly bool
 60 5月份预算George 2011-05-01 binary FileFormat文本
 70 5月份预算George 2011-05-01 false Private bool
 80 5月份预算George 2011-05-01 2011-10-20 LastModified date

这个合并查询(使用MAX的方法)可以合并行:

 SELECT F.*, MAX( IF(A.AttrName = 'ReadOnly', FA.AttrValue, NULL) ) as 'ReadOnly', MAX( IF(A.AttrName = 'FileFormat', FA.AttrValue, NULL) ) as 'FileFormat', MAX( IF(A.AttrName = 'Private', FA.AttrValue, NULL) ) as 'Private', MAX( IF(A.AttrName = 'LastModified', FA.AttrValue, NULL) ) as 'LastModified' FROM Files F LEFT JOIN Files_Attributes FA USING (FileID) LEFT JOIN Attributes A USING (AttributeID) GROUP BY F.FileID; 
 FileID标题作者CreatedOn只读文件格式私人LastModified
 1 TestFile Joe 2011-01-01 true xls false 2011-10-03
 2 LongNovel Mary 2011-02-04真正的json真2011-10-04
 3 ShortStory Susan 2011-03-01 false ascii false 2011-10-01
 4 ProfitLoss Bill 2011-04-01 false text true 2011-10-02
 5月份预算George 2011-05-01假二元假2011-10-20

这样的查询的一般forms将是

 SELECT file.*, attr1.value AS 'Attribute 1 Name', attr2.value AS 'Attribute 2 Name', ... FROM file LEFT JOIN attr AS attr1 ON(file.FileId=attr1.FileId and attr1.AttributeId=1) LEFT JOIN attr AS attr2 ON(file.FileId=attr2.FileId and attr2.AttributeId=2) ... 

所以你需要从你需要的属性dynamic构build你的查询。 在php-ish伪代码中

 $cols="file"; $joins=""; $rows=$db->GetAll("select * from Attributes"); foreach($rows as $idx=>$row) { $alias="attr{$idx}"; $cols.=", {$alias}.value as '".mysql_escape_string($row['AttributeName'])."'"; $joins.="LEFT JOIN attr as {$alias} on ". "(file.FileId={$alias}.FileId and ". "{$alias}.AttributeId={$row['AttributeId']}) "; } $pivotsql="select $cols from file $joins"; 

这是SQL中标准的“行到列”问题。

在SQL之外最容易完成。

在您的应用程序中,执行以下操作:

  1. 定义一个简单的类来包含文件,系统属性和一组用户属性。 列表是这个客户属性集合的好select。 我们来调用这个类FileDescription。

  2. 在文件和文件的所有客户属性之间执行一个简单的连接。

  3. 编写一个循环来从查询结果中组装FileDescriptions。

    • 取第一行,创build一个FileDescription并设置第一个客户属性。

    • 虽然有更多的行要获取:

      • 取一行
      • 如果这一行的文件名与我们构build的FileDescription不匹配:完成构buildFileDescription; 将此附加到文件描述的结果集合; 用给定的名称和第一个客户属性创build一个新的,空的FileDescription。
      • 如果此行的文件名与我们正在构build的FileDescription相匹配:将另一个客户属性附加到当前的FileDescription

我一直在试验不同的答案,而Methai的答案对我来说是最方便的。 我目前的项目,虽然它使用与MySQL的教条,有不less松散的表格。

以下是我对Methai解决scheme的经验的结果:

创build实体表

 DROP TABLE IF EXISTS entity; CREATE TABLE entity ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, title VARCHAR(255), author VARCHAR(255), createdOn DATETIME NOT NULL ) Engine = InnoDB; 

创build属性表

 DROP TABLE IF EXISTS attribute; CREATE TABLE attribute ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255) NOT NULL, type VARCHAR(255) NOT NULL ) Engine = InnoDB; 

创build属性值表

 DROP TABLE IF EXISTS attributevalue; CREATE TABLE attributevalue ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, value VARCHAR(255) NOT NULL, attribute_id INT UNSIGNED NOT NULL, FOREIGN KEY(attribute_id) REFERENCES attribute(id) ) Engine = InnoDB; 

创buildentity_attributevalue连接表

 DROP TABLE IF EXISTS entity_attributevalue; CREATE TABLE entity_attributevalue ( entity_id INT UNSIGNED NOT NULL, attributevalue_id INT UNSIGNED NOT NULL, FOREIGN KEY(entity_id) REFERENCES entity(id), FOREIGN KEY(attributevalue_id) REFERENCES attributevalue(id) ) Engine = InnoDB; 

填充实体表

 INSERT INTO entity (title, author, createdOn) VALUES ('TestFile', 'Joe', '2011-01-01'), ('LongNovel', 'Mary', '2011-02-01'), ('ShortStory', 'Susan', '2011-03-01'), ('ProfitLoss', 'Bill', '2011-04-01'), ('MonthlyBudget', 'George', '2011-05-01'), ('Paper', 'Jane', '2012-04-01'), ('Essay', 'John', '2012-03-01'), ('Article', 'Dan', '2012-12-01'); 

填充属性表

 INSERT INTO attribute (name, type) VALUES ('ReadOnly', 'bool'), ('FileFormat', 'text'), ('Private', 'bool'), ('LastModified', 'date'); 

填充属性值表

 INSERT INTO attributevalue (value, attribute_id) VALUES ('true', '1'), ('xls', '2'), ('false', '3'), ('2011-10-03', '4'), ('true', '1'), ('json', '2'), ('true', '3'), ('2011-10-04', '4'), ('false', '1'), ('ascii', '2'), ('false', '3'), ('2011-10-01', '4'), ('false', '1'), ('text', '2'), ('true', '3'), ('2011-10-02', '4'), ('false', '1'), ('binary', '2'), ('false', '3'), ('2011-10-20', '4'), ('doc', '2'), ('false', '3'), ('2011-10-20', '4'), ('rtf', '2'), ('2011-10-20', '4'); 

填充entity_attributevalue表

 INSERT INTO entity_attributevalue (entity_id, attributevalue_id) VALUES ('1', '1'), ('1', '2'), ('1', '3'), ('1', '4'), ('2', '5'), ('2', '6'), ('2', '7'), ('2', '8'), ('3', '9'), ('3', '10'), ('3', '11'), ('3', '12'), ('4', '13'), ('4', '14'), ('4', '15'), ('4', '16'), ('5', '17'), ('5', '18'), ('5', '19'), ('5', '20'), ('6', '21'), ('6', '22'), ('6', '23'), ('7', '24'), ('7', '25'); 

显示所有logging

 SELECT * FROM `entity` e LEFT JOIN `entity_attributevalue` ea ON ea.entity_id = e.id LEFT JOIN `attributevalue` av ON ea.attributevalue_id = av.id LEFT JOIN `attribute` a ON av.attribute_id = a.id; 
 id标题作者createdOn entity_id attributevalue_id id值attribute_id id名称types
 1 TestFile Joe 2011-01-01 00:00:00 1 1 1 true 1 1 ReadOnly bool
 1 TestFile Joe 2011-01-01 00:00:00 1 2 2 xls 2 2 FileFormat文本
 1 TestFile Joe 2011-01-01 00:00:00 1 3 3 false 3 3私人布尔
 1 TestFile Joe 2011-01-01 00:00:00 1 4 4 2011-10-03 4 4 LastModifieddate
 2 LongNovel Mary 2011-02-01 00:00:00 2 5 5 true 1 1 ReadOnly bool
 2 LongNovel Mary 2011-02-01 00:00:00 2 6 6 json 2 2 FileFormat文本
 2 LongNovel Mary 2011-02-01 00:00:00 2 7 7真3 3私人布尔
 2 LongNovel Mary 2011-02-01 00:00:00 2 8 8 2011-10-04 4 4 LastModified date
 3 ShortStory Susan 2011-03-01 00:00:00 3 9 9 false 1 1 ReadOnly bool
 3 ShortStory Susan 2011-03-01 00:00:00 3 10 10 ascii 2 2 FileFormat文本
 3 ShortStory Susan 2011-03-01 00:00:00 3 11 11 false 3 3私人布尔
 3 ShortStory Susan 2011-03-01 00:00:00 3 12 12 2011-10-01 4 4 LastModifieddate
 4 ProfitLoss Bill 2011-04-01 00:00:00 4 13 13 false 1 1 ReadOnly bool
 4 ProfitLoss Bill 2011-04-01 00:00:00 4 14 14 text 2 2 FileFormat文本
 4 ProfitLoss Bill 2011-04-01 00:00:00 4 15 15 true 3 3私人布尔
 4 ProfitLoss Bill 2011-04-01 00:00:00 4 16 16 2011-10-02 4 4 LastModifieddate
 5月份预算George 2011-05-01 00:00:00 5 17 17 false 1 1 ReadOnly bool
 5 MonthlyBudget George 2011-05-01 00:00:00 5 18 18二进制文件2 FileFormat文本
 5月份预算George 2011-05-01 00:00:00 5 19 19 false 3 3私人布尔
 5月份预算George 2011-05-01 00:00:00 5 20 20 2011-10-20 4 4 LastModified date
 6张纸Jane 2012-04-01 00:00:00 6 21 21二进制文件2 FileFormat文本
 6纸简2012-04-01 00:00:00 6 22 22假3 3私人布尔
 6纸简2012-04-01 00:00:00 6 23 23 2011-10-20 4 4 LastModifieddate
 7 Essay John 2012-03-01 00:00:00 7 24 24二进制文件2 FileFormat文本
 7 Essay John 2012-03-01 00:00:00 7 25 25 2011-10-20 4 4 LastModified date
 8文章2012-12-01 00:00:00 NULL NULL NULL NULL NULL NULL NULL NULL

数据透视表

 SELECT e.*, MAX( IF(a.name = 'ReadOnly', av.value, NULL) ) as 'ReadOnly', MAX( IF(a.name = 'FileFormat', av.value, NULL) ) as 'FileFormat', MAX( IF(a.name = 'Private', av.value, NULL) ) as 'Private', MAX( IF(a.name = 'LastModified', av.value, NULL) ) as 'LastModified' FROM `entity` e LEFT JOIN `entity_attributevalue` ea ON ea.entity_id = e.id LEFT JOIN `attributevalue` av ON ea.attributevalue_id = av.id LEFT JOIN `attribute` a ON av.attribute_id = a.id GROUP BY e.id; 
编号标题作者createdOn只读文件格式私人LastModified
 1 TestFile Joe 2011-01-01 00:00:00 true xls false 2011-10-03
 2 LongNovel Mary 2011-02-01 00:00:00 true json true 2011-10-04
 3 ShortStory Susan 2011-03-01 00:00:00 false ascii false 2011-10-01
 4 ProfitLoss Bill 2011-04-01 00:00:00 false text 2011-10-02
 5月份预算George 2011-05-01 00:00:00假二元假2011-10-20
 6张纸Jane 2012-04-01 00:00:00 NULL二进制假2011-10-20
 7作文约翰2012-03-01 00:00:00空二进制NULL 2011-10-20
 8文章2012-12-01 00:00:00 NULL NULL NULL NULL

但是有解决scheme使用行作为列,也就是转置数据。 它涉及到在纯SQL中执行查询技巧,或者使用Pivot表(或Cross表),您将不得不依赖某些特定数据库中的特定function。

例如,您可以在Oracle(11g)中看到如何执行此操作。

编程版本会更简单,维护和制作,而且可以和任何数据库一起工作。

部分答案,因为我不知道MySQL(好)。 在MSSQL中,我会查看数据透视表或将在存储过程中创build一个临时表。 这可能很难…