在sql查询中添加N个动态列

时间:2011-06-29 22:42:17

标签: sql sql-server sql-server-2005 tsql entity-attribute-value

我有一个名为datarecords的表,其中包含7个固定列,这些列在select查询中始终是必需的。用户可以添加他们想要的任意数量的自定义列。我将此信息存储在名为datacolumn的表中,并将值存储在另一个名为datavalue的表中。

现在我想创建一个查询,它带来来自datarecord的7个固定列,然后添加自定义列并从这些表中获取数据值,因为每个数据记录在数据值表中都有对应的值。

2 个答案:

答案 0 :(得分:2)

您可以尝试将PIVOT自定义属性从行添加到列中,但您会发现即使在Microsoft SQL Server中支持PIVOT,您也需要在编写查询之前了解这些属性,并且查询代码需要指定所有属性。 SQL中没有办法要求所有自定义属性根据需要神奇地填充尽可能多的列。

只能通过逐行提取任意数量的自定义属性来检索它们,因为它们存储在数据库中。然后编写应用程序代码以循环结果。如果需要,可以编写一个类,将多行自定义属性映射到应用程序中对象的字段。

使用SQL查询非关系数据很尴尬和不优雅。这是因为SQL旨在假设相同类型的每个逻辑实体具有固定数量的列,并且在编写查询之前就知道了这些列。如果您的实体具有变量属性,则根据定义,它不能存储为关系。

许多人尝试使用您正在使用的设计来扩展它,但他们发现它很难管理并且不能很好地扩展。此设计通常称为Entity-Attribute-Value模型或键值对。有关EAV设计陷阱的更多详细信息,请参阅我的书SQL Antipatterns

如果您需要支持自定义属性,可以选择以下几种方法:

  • 将所有自定义属性存储在BLOB中,并使用一些内部结构来分隔字段名称和值(Serialized LOB)。您可以选择创建反向索引,以帮助您查找给定字段具有给定值的行(请参阅How FriendFeed Uses MySQL)。

  • 使用面向文档的数据库(例如MongoDBSolr)作为动态数据。

  • 当用户需要自定义属性时,使用ALTER TABLE将常规列添加到表中。这意味着您需要为所有用户强制执行相同的自定义属性集,或者存储所有用户的自定义属性,并希望您的表不会过宽(Single Table Inheritance),或者为每个用户创建一个单独的表,适用于所有列(Concrete Table Inheritance)或仅适用于自定义列(Class Table Inheritance)。

答案 1 :(得分:1)

编辑:有关更多详细信息,请参阅底部的注释。

我遇到了同样的问题,我找到了一个缓慢的解决方案。也许其他人有一个加速我的发现的解决方案。在我的代码中,我有一个包含三列的表:Col1,Col2,Col3。 Col1是我的记录ID。 Col2是我的动态列的名称。 Col3是该列的值。因此,如果我想表示ID为1的记录,两列2和3,以及这些列的值:4和5,我会有以下内容:

Col1, Col2, Col3
1, 2, 4
1, 3, 5

然后我们转过第2列并选择MAX(或MIN或AVG,无关紧要,因为col2和col3组合是唯一的)col3在枢轴中。为了使用可变数量的列来完成数据透视,我们使用动态SQL生成来生成SQL。这适用于小输入数据(我相信动态SQL的FROM子句中的派生表)。一旦数据集变大,平均函数就会花费很长时间来执行。很长一段时间。它似乎从大约1000行开始,所以可能有一个提示或其他方法使它更短。

注意,由于Col2和Col3的值映射为1:1,我还尝试动态生成如下所示的SELECT语句:

SELECT Col1,
   CASE WHEN Col2 = '4' THEN Col3 END [4],
   CASE WHEN Col2 = '5' THEN Col3 END [5],
   CASE WHEN Col2 = '6' THEN Col3 END [6], -- ... these were dyanmically generated
FROM #example
GROUP BY Col1

这对我的数据集来说同样慢。你的里程可能会有所不同。以下是为SQL Server编写的工作原理的完整示例(2005+应该运行此命令)。

--DROP TABLE #example
CREATE TABLE #example
(
    Col1 INT,
    Col2 INT,
    Col3 INT
)

INSERT INTO #example VALUES (2,4,10)
INSERT INTO #example VALUES (2,5,20)
INSERT INTO #example VALUES (2,6,30)
INSERT INTO #example VALUES (2,7,40)
INSERT INTO #example VALUES (2,8,50)
INSERT INTO #example VALUES (3,4,11)
INSERT INTO #example VALUES (3,5,22)
INSERT INTO #example VALUES (3,6,33)
INSERT INTO #example VALUES (3,7,44)
INSERT INTO #example VALUES (3,8,55)

DECLARE @columns VARCHAR(100)
SET @columns = ''

SELECT @columns = @columns + '[' + CAST(Col2 AS VARCHAR(10)) + '],'
FROM (SELECT DISTINCT Col2 FROM #Example) a

SELECT @columns = SUBSTRING(@columns, 0, LEN(@columns) )

DECLARE @dsql NVARCHAR(MAX)

SET @dsql = '
select Col1, ' + @columns + '
from
    (select Col1, Col2, Col3 FROM #example e) a
PIVOT
(
    MAX(Col3)
    FOR Col2 IN (' + @columns + ')
) p'

print @dsql
EXEC sp_executesql @dsql
编辑:由于我这样做的独特情况,我设法使用两个表(一个包含实体,另一个包含属性 - 值对)以及在表上创建聚簇索引来提高速度。属性 - 值对,包括所有列(ID,属性,值)。如果你需要快速插入,大量列,大量数据行等,我建议你以另一种方式解决这个问题。我对数据的大小和增长率有一些已知的确定性,myy解决方案适合我的范围。

还有许多其他解决方案更适合解决此问题。例如,如果需要快速插入和单记录读取(或慢速读取无关紧要),则应考虑将XML字符串打包到字段中并在数据库使用者中序列化/反序列化。如果您需要超快速写入,则很少添加超快速读取和数据列,那么您可以考虑更改表格。在大多数实践中,这是一个糟糕的解决方案,但可能适合一些问题。如果您的列经常更改,但您还需要快速读取和写入不是问题,那么我的解决方案可能适用于您,直到某个数据集大小。

相关问题