2014-08-29 143 views
2

需要帮助了解如何在一个查询中执行交叉制表报表。有3-4个表涉及,但用户表可能不需要包含在查询中,因为我们只需要一个计数。SQL交叉表查询

我已经把表的架构和数据作为一个例子可以看到下面的截图:

我需要什么返回是一个查询结果,看起来像:

所以我可以做一个报告,看起来像:

我试图做光标循环,因为它是我可以用我的基本知识,做到这一点的唯一方法,但它的方式过于缓慢。

我试图生成的一个特定报告包含32行和64列,其中包含约70,000个答案,因此它的全部内容都是关于尽可能快地将其分解为一个查询的性能。

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

谢谢!

+0

您是否阅读过“SQL PIVOT”? – 2014-08-29 14:43:45

+0

您可以使用数据透视查询,或者如果您在所需输出中有一组固定的列,则可以使用case语句。或者,更好的是,您可以使用报告工具来处理您的演示文稿。 – Andrew 2014-08-29 14:45:03

+0

我已经查看了SQL PIVOT,但无法弄清楚如何将它用于我的场景。不幸的是,使用第三方报告工具不是一种选择。 – Aki 2014-08-29 14:47:44

回答

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

对于它的价值,你的查询是因为你使用的是实体 - 属性 - 值设计模式复杂。不少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 
+0

我打算查看数据库设计,看看我能用它做什么。该数据库已超过10年,拥有1亿条记录,因此改变设计有点棘手。 无论如何,你的答案完全符合我的需要。将其放入实时数据库中,可在3秒内为30行,60列报告返回结果。 :) 谢谢! – Aki 2014-08-29 16:47:57

+0

sqlFiddle不包含列值?你将colOption替换为rowOption,所以它只返回第一列 – Beth 2014-08-29 17:02:09

+0

好的抓住@你绝对是对的。现在解决这个问题。 – 2014-08-30 01:34:51

1

我想我看到了问题。我知道你不能修改模式,但是你需要一个交叉表信息的概念表,比如哪个questionID是rowHeader,哪个是colHeader。您可以在外部数据源中创建它,并与现有源码合并,或者简单地在sql中对表值进行硬编码。

您需要有2个问题/选项/回答关系实例,每个交叉表的每个rowHeader和colHeader都有一个实例。这两个关系由用户标识符连接。

这个版本贵外连接: sqlFiddle

这个版本不具备交叉表,只是行和col questionIDs硬编码: sqlFiddleNoTbl

+0

感谢您的努力@Beth,迈克的答案直接适用于我的代码,但总体上几乎相同的答案。 hanks! :) – Aki 2014-08-29 16:49:45

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 

希望它有帮助,我喜欢尝试这样做。

+0

我会在实时代码中尝试一下,看看它与其他代码相比如何。谢谢! – Aki 2014-08-29 16:53:00

+1

@MikeStankavich版本可以更好地满足您的外观需求。 (绝对作弊将问题文本在SQL中);) – 2014-08-29 16:57:56