SQL Cross选项卡查询

时间:2014-08-29 14:40:28

标签: sql sql-server performance tsql sql-server-2012

需要帮助确定如何在一个查询中执行交叉表格报告。涉及3-4个表,但由于我们只需要计数,因此可能不需要在查询中包含用户表。

我已经将表架构和数据的屏幕截图放在一起作为示例,如下所示:

我需要它返回的是一个查询结果,如下所示:

所以我可以制作一份如下所示的报告:

我尝试过光标循环,因为它是我用我的基本知识做到这一点的唯一方法,但它太慢了。

我尝试生成的一个特定报告包含32行和64列,大约有70,000个答案,因此所有这些都是关于将其降低到一个查询并尽可能快地执行的性能。

我明白这可能取决于索引等等但如果有人可以帮我弄清楚如何在1个查询中完成这个(有多个连接?),那就太棒了!

谢谢!

3 个答案:

答案 0 :(得分:1)

我想我看到了问题。我知道您无法修改架构,但是您需要一个交叉表信息的概念表,例如哪个questionID是rowHeader,哪个是colHeader。您可以在外部数据源中创建它并与现有源连接,或者只是对SQL中的表值进行硬编码。

您需要有2个问题/选项/答案关系实例,每个交叉表的每个rowHeader和colHeader一个。这两个关系由userID连接。

这个版本有你的外连接: sqlFiddle

此版本没有crossTab表,只有行和col questionIDs硬编码: sqlFiddleNoTbl

答案 1 :(得分:1)

SELECT MIN(ro.OptionText) RowOptionText, MIN(co.OptionText) RowOptionText, COUNT(ca.AnswerID) AnswerCount
FROM tblQuestions rq 
CROSS JOIN tblQuestions cq 
JOIN tblOptions ro ON rq.QuestionID = ro.QuestionID
JOIN tblOptions co ON cq.QuestionID = co.QuestionID
LEFT JOIN tblAnswers ra ON ra.OptionID = ro.OptionID
LEFT JOIN tblAnswers ca ON ca.OptionID = co.OptionID AND ca.UserID = ra.UserID
WHERE rq.questionText = 'Gender'
AND cq.questionText = 'How happy are you?'
GROUP BY ro.OptionID, co.OptionID
ORDER BY ro.OptionID, co.OptionID

这应该至少接近你的要求。将其转换为数据透视表将需要动态SQL,因为SQL Server要求您指定将转移到列中的实际值。

我们交叉加入问题并将每个问题引用的结果分别限制为行值和列值的单个问题。然后我们将选项值加入相应的问题参考。如果用户没有回答所有问题,我们会使用LEFT JOIN作为答案。我们通过UserID加入答案,以便我们匹配每个用户的行问题和列问题。选项文本上的MIN是因为我们按OptionID分组和排序以匹配您显示的顺序。

编辑:这是SQLFiddle

对于它的价值,您的查询很复杂,因为您使用的是Entity-Attribute-Value设计模式。相当多的SQL Server专家认为这种模式存在问题,如果可能的话应该避免使用。例如,请参阅https://www.simple-talk.com/sql/t-sql-programming/avoiding-the-eav-of-destruction/

编辑2:既然您接受了我的回答,那么这里是动态SQL数据透视解决方案:) SQLFiddle

DECLARE @SqlCmd NVARCHAR(MAX)

SELECT @SqlCmd = N'SELECT RowOptionText, ' + STUFF(
    (SELECT ', ' + QUOTENAME(o.OptionID) + ' AS ' + QUOTENAME(o.OptionText)
    FROM tblOptions o 
    WHERE o.QuestionID = cq.QuestionID
    FOR XML PATH ('')), 1, 2, '') + ', RowTotal AS [Row Total]
FROM (
    SELECT ro.OptionID RowOptionID, ro.OptionText RowOptionText, co.OptionID ColOptionID,
       ca.UserID, COUNT(ca.UserID) OVER (PARTITION BY ra.OptionID) AS RowTotal
    FROM tblOptions ro
    JOIN tblOptions co ON ro.QuestionID = ' + CAST(rq.QuestionID AS VARCHAR(10)) + 
    ' AND co.QuestionID = ' + CAST(cq.QuestionID AS VARCHAR(10)) + '
    LEFT JOIN tblAnswers ra ON ra.OptionID = ro.OptionID
    LEFT JOIN tblAnswers ca ON ca.OptionID = co.OptionID AND ca.UserID = ra.UserID
    UNION ALL 
    SELECT 999999, ''Column Total'' RowOptionText, co.OptionID ColOptionID,
       ca.UserID, COUNT(ca.UserID) OVER () AS RowTotal
    FROM tblOptions ro
    JOIN tblOptions co ON ro.QuestionID = ' + CAST(rq.QuestionID AS VARCHAR(10)) + 
    ' AND co.QuestionID = ' + CAST(cq.QuestionID AS VARCHAR(10)) + '
    LEFT JOIN tblAnswers ra ON ra.OptionID = ro.OptionID
    LEFT JOIN tblAnswers ca ON ca.OptionID = co.OptionID AND ca.UserID = ra.UserID
) t
PIVOT (COUNT(UserID) FOR ColOptionID IN (' + STUFF(
    (SELECT ', ' + QUOTENAME(o.OptionID) 
    FROM tblOptions o 
    WHERE o.QuestionID = cq.QuestionID
    FOR XML PATH ('')), 1, 2, '') + ')) p
ORDER BY RowOptionID'
FROM tblQuestions rq 
CROSS JOIN tblQuestions cq 
WHERE rq.questionText = 'Gender' 
AND cq.questionText = 'How happy are you?'

EXEC sp_executesql @SqlCmd

答案 2 :(得分:0)

以下一块混乱没有硬编码值,但无法显示计数为0的行。 但是,这可能仍适用于您的报告。

;with stepone as(

SELECT
    RANK() OVER(PARTITION BY a.UserId ORDER BY o.QuestionID) AS [temprank]
,   o.QuestionID AS [QID1]
,   o.OptionID AS [OID1]
,   same.QuestionID
,   same.OptionID
,   a.UserId AS [IDUser]
,   same.UserId
FROM
    tblAnswers a
    INNER JOIN
    tblOptions o
        ON a.OptionID = o.OptionID
    INNER JOIN
    tblQuestions q
        ON o.QuestionID = q.QuestionID
    INNER JOIN
    (
    SELECT
        a.AnswerID
    ,   a.OptionID
    ,   a.UserId
    ,   o.QuestionID    
    FROM
        tblAnswers a
        INNER JOIN
        tblOptions o
            ON a.OptionID = o.OptionID
    ) same
        ON a.UserId = same.UserId AND a.AnswerID <> same.AnswerID

)

, stepthree AS(
SELECT
    t.QID1, t.OID1, t.QuestionID, t.OptionID
,   COUNT(UserId) AS myCount
FROM 
    stepone t
WHERE t.temprank = 1
GROUP BY
    t.QID1, t.OID1, t.QuestionID, t.OptionID
)

SELECT
    o1.OptionText AS [RowTest]
,   o2.OptionText AS [ColumnText]
,   t.myCount AS [Count]

FROM
    stepthree t
    INNER JOIN tblOptions o1
        ON t.OID1 = o1.OptionID
    INNER JOIN tblOptions o2
        ON t.OptionID = o2.OptionID
ORDER BY t.OID1

希望它有所帮助,我很乐意尝试这样做。