【问题标题】:T-SQL Filter on dynamic columns动态列上的 T-SQL 过滤器
【发布时间】:2015-02-24 09:44:36
【问题描述】:

这可能以前被问过,但这是一个如此复杂的话题,我很难理解它。所以想问问具体情况。

我有以下“CampaignCalls”表,例如:

ID | contactName | contactNumber
1  | Joe Bloggs  | 123456789
2  | Simon Smith | 456987321
3  | Jane Doe    | 852936414

此外,我还有用户定义的“自定义列”的一对多表,“CampaignCall_Fields”,例如:

ID | fieldName
1  | Company
2  | Alternative Number
3  | Address 1 
4  | Address 2

以及定义自定义列(“CampaignCall_Field_values”)的每个对应值的相交表,例如:

CampaignCall_ID | field_ID | value
1               | 1        | ACME
1               | 2        | 789456123
1               | 3        | 123 Fake St
1               | 4        | London
2               | 1        | Initech
2               | 2        | 789456123
2               | 3        | 456 Fake St
2               | 4        | Paris
3               | 1        | Greendale
3               | 2        | 789456123
3               | 3        | 789 Fake St
3               | 4        | New York City

我有一个应用程序,它应该能够向用户显示所有行的报告,例如以下格式:

Name        | Number    | Company | Address 1   | Address 2
Joe Bloggs  | 123456789 | ACME    | 123 Fake St | London
Simon Smith | 456987321 | Initech | 456 Fake St | Paris

但我还想让用户能够在此示例中指定的任何列上创建过滤器,例如用户可以说“仅返回 Name = Joe Bloggs AND Company = ACME 的行”。

当前我这样做是通过从“CampaignCalls”表中提取所有数据(相应过滤),然后在 PHP 中遍历所有返回的行并从“CampaignCall_Field_values”中获取数据" 表(相应过滤),然后将数据旋转到主数组中(如果没有返回所有数据,我知道过滤器已“过滤掉”该行并从数组中删除该行)。

这是非常低效的,因为它需要很长时间并且为每一行打开不同的数据库连接。所以我想看看是否有办法减少数据库连接的数量和/或算法的复杂性。

我希望通过某种方式创建一些包含所有动态字段、具有适当索引等的数据库视图,然后针对该视图运行报告来理想地做到这一点。这似乎是最干净的方式,但不确定我将如何动态创建这样的视图。也不确定这对性能的影响。

有人可以就如何实施此解决方案或替代的更好的解决方案提供任何见解或意见吗?我一直在努力创造一个稳定、高效的解决方案,我不敢相信以前从未有人这样做过。提前致谢!

【问题讨论】:

  • 这正是 EAV 模型被视为 SQL 反模式的原因。它当然在 RDBMS 设计中占有一席之地,但除非您有大量经常变化的属性,否则通常最好采用关系设计。我不会说太多,因为我不想引发争论,谷歌搜索 SQL EAV vs Relational 有 26,100 个结果,我想我不能说任何尚未说的内容。
  • 好吧,我从来没有真正听说过 EAV...很高兴知道我在做什么有一个名字!我将如何将其重组为关系模式(不确定在这种情况下你所说的关系是什么意思)?或者对于单个评论来说太大了......
  • 在关系模型中,您将没有表CampaignCall_Field_values,而是在CampaignCalls 上只有附加列。因此,您的表格已经处于您想要实现的结构中。就像我说的,网上有很多关于这个的文章,我没有什么可以补充的了。我建议选择一个并阅读每种方法的优缺点。
  • 是的,这是有道理的。我快速阅读了您的链接和几篇文章,现在我已经开始思考了——只是想知道您对如何更改架构的看法。谢谢!
  • 如果这正是您想要避免的那种问题,请随意忽略它 - 但由于这里的自定义值是动态的(用户控制的,它们的数量可变)这不构成改变现场制作中的模式?我一直认为这是一件坏事?

标签: php sql-server tsql where-clause


【解决方案1】:

join三张表获取相关数据再使用pivot获取所需格式

SELECT *
FROM   (SELECT contactName,
               value,
               fieldName
        FROM   CampaignCalls C
               JOIN CampaignCall_Field_values CF
                 ON c.ID = cf.CampaignCall_ID
               JOIN [custom columns] Cs
                 ON cs.ID = cf.field_ID)A
       PIVOT (Max(value)
             FOR fieldname IN ([Company],
                               [Alternative Number],
                               [Address 1],
                               [Address 2]))piv 

更新:如果您不知道fields,请使用Dynamic Pivot

DECLARE @sql  NVARCHAR(max),
        @cols VARCHAR(max)

SET @cols = (SELECT DISTINCT Quotename(fieldName) + ','
             FROM   [custom columns]
             FOR xml path(''))

SELECT @cols = LEFT(@cols, Len(@cols) - 1)

SET @sql='SELECT *
FROM   (SELECT contactName,
               value,
               fieldName
        FROM   CampaignCalls C
               JOIN CampaignCall_Field_values CF
                 ON c.ID = cf.CampaignCall_ID
               JOIN [custom columns] Cs
                 ON cs.ID = cf.field_ID)A
       PIVOT (Max(value)
             FOR fieldname IN (' + @cols
         + '))piv '

EXEC Sp_executesql @sql 

【讨论】:

  • 好的,但问题是我不知道动态字段是什么。有没有办法动态运行这个 PIVOT?
  • @Raiden616 - 现在更新检查
  • 哦,我明白了,没关系。我已经让它工作了,非常感谢 - 除了它需要很长时间。我能做些什么来提高效率吗?特别是因为我必须把它放在一个视图中,然后查询它。
  • @Raiden616 - 不,您不能在视图内使用动态查询。尝试将其设为SP
  • 好的 - 问题。如果我以某种方式安排此存储过程每 10 分钟运行一次并使用此结果填充一个表,然后从中提取报告,那会是不好的做法吗?我必须动态确定列...
【解决方案2】:

虽然我建议在 cmets 中远离 EAV,但我仍会尝试帮助解决它带来的问题。我建议您在透视数据之前应用任何过滤器,为了有效地执行此操作,您首先需要创建一个新类型:

CREATE TYPE dbo.StringPair AS TABLE
(
    Value1 NVARCHAR(MAX) NOT NULL,
    Value2 NVARCHAR(MAX) NOT NULL
);

这是保存过滤器所需的对(字段名称和字段值),然后您可以创建一个将其作为参数的存储过程:

CREATE PROCEDURE dbo.GetCampaignCalls @Filter dbo.StringPair READONLY
AS
BEGIN

    DECLARE @Cols NVARCHAR(MAX) = STUFF((SELECT ',' + QUOTENAME(FieldName)
                                        FROM CampaignCall_Fields
                                        FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, '');

    DECLARE @SQL NVARCHAR(MAX) = '
    WITH CampaignCallFields AS
    (   SELECT  cc.ID,
                cc.contactName,
                cc.contactNumber,
                f.FieldName,
                v.Value
        FROM    CampaignCalls AS cc
                INNER JOIN CampaignCall_Field_values AS v
                    ON cc.ID = v.CampaignCall_ID
                INNER JOIN CampaignCall_Fields AS f
                    ON f.ID = v.field_ID
    )
    SELECT  pvt.*
    FROM    (   SELECT  *
                FROM    CampaignCallFields AS c
                WHERE   EXISTS
                        (   SELECT  1
                            FROM    CampaignCallFields AS c2
                                    INNER JOIN @Filter AS f
                                        ON f.Value1 = c2.FieldName
                                        AND f.Value2 = c2.Value
                            WHERE   c2.ID = c.ID
                            GROUP BY c2.ID
                            HAVING COUNT(*) = (SELECT COUNT(*) FROM @Filter)
                        )
            ) AS c
            PIVOT 
            (   MAX(Value)
                FOR FieldName IN (' + @Cols + ')
            ) AS pvt;';

    EXECUTE sp_executesql @SQL, N'@Filter dbo.StringPair READONLY', @Filter;
END

这将被称为:

DECLARE @Filter dbo.StringPair;
INSERT @Filter VALUES ('Company', 'ACME');
EXECUTE dbo.GetCampaignCalls @Filter;

Example on SQL Fiddle

我认为你在评论中提到的可能是前进的方向,你当然应该在主表中存储尽可能多的核心字段,就像你对contactNumber和contactName所做的那样,然后只需使用属性可以动态添加的外围字段的表。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-14
    • 1970-01-01
    相关资源
    最近更新 更多