动态列上的T-SQL过滤器

时间:2015-02-24 09:44:37

标签: php sql-server tsql where-clause

之前可能已经提出过这个问题,但这是一个如此复杂的话题,我无法理解它。所以我想我会问具体细节。

我有以下“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”获取数据“表(相应地过滤),然后将数据转移到主数组中(如果不是所有数据都返回,我知道过滤器已经”过滤掉“该行并从数组中删除了行。)

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

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

有人可以就如何实施此解决方案或其他更好的解决方案提供任何见解或意见吗?我一直在这里拉头发来创造一个稳定,高效的解决方案,我无法相信它以前从未做过。提前谢谢!

2 个答案:

答案 0 :(得分:0)

首先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 

答案 1 :(得分:0)

虽然我建议在评论中放弃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一样,然后只使用属性可以即时添加的外围字段表。