我应该如何“旋转”或“压扁”这些研究数据? PIVOT,自我加入还是其他什么?

时间:2010-11-16 17:29:42

标签: c# linq sql-server-2005 tsql

我很难找到任何与我正在做的事情相近的例子,或者我只是不理解我找到的例子。

我有一个研究数据库,其中包含一个人在不同时间点对多个问题的回答。下面的“Admin#”表示数据所代表的测试的“管理”。或者您可以将其视为包含给出测试的“时间”,例如,time1,time2,time3

RespondentID# Admin# Question1 Question2 Question3 Question4 Question5
            1      1     A         B        C          D         E
            1      2     E         D        C          B         A
            1      3     Q         W        E          R         T
            2      1     Z         X        C          V         B
            2      2     P         O        I          U         Y
            2      3     Y         H        N          U         J

我现在需要做的是安排这些数据,以便特定受访者的每组响应都在同一行。因此,我们将5个问题字段转换为15个问题字段,

RespondentID# Admin1Question1 Admin1Question2 Admin1Question3 Admin1Question4 Admin1Question5 Admin2Question1 Admin2Question2 Admin2Question3 Admin2Question4 Admin2Question5 Admin3Question1 Admin3Question2 Admin3Question3 Admin3Question4 Admin3Question5 

如您所见,以Admin1开头的每个字段都对应于上面示例中Admin#值为1的行。

如果我没有正确解释,请原谅我。

为了使问题更加复杂,将来“管理”或“时间”的最大数量会增加。目前它是3,但未来可能会进行相同的测试4次,5次或更多次。用于此问题的任何解决方案都可以是静态的,然后手动更新以考虑将来的额外“时间”,但如果解决方案动态地计算了未指定数量的“时间”,那将是非常棒的。

这些数据存储在MS SQL 2005数据库中,所以tsql显然是一个选项,但如果C#或LINQ中存在更好的解决方案(整个项目是一个asp.net应用程序),我对此持开放态度。好。无论你认为什么效果最好! :)

非常感谢您阅读我的问题!

3 个答案:

答案 0 :(得分:2)

我遵循的基本方法是使用以下代码手动转移:

select RespondentID,
    min(case when Admin=1 then Question1 else null end) Admin1_Question1,
    min(case when Admin=2 then Question1 else null end) Admin2_Question1,
    min(case when Admin=3 then Question1 else null end) Admin3_Question1
from tests
group by RespondentID

因此,使用动态t-sql语句,我们按如下方式构建并执行查询:

declare @select varchar(max)

select @select = coalesce(@select+',','')+
    'min(case when Admin='+a+' then '+q+' else null end) as [Admin'+a+'_'+q+']'
from (select distinct cast(Adminas varchar(10)) a from tests) p1
    cross join (
    select 'Question1' q union
    select 'Question2' union
    select 'Question3' union
    select 'Question4' union
    select 'Question5'
    ) p2
order by a, q


declare @sql varchar(max)
set @sql = 'select RespondentID, '+@select+' from tests group by RespondentID'

execute(@sql)

这不是最动态的t-sql解决方案,但它应该可以工作!

答案 1 :(得分:2)

我的想法是记录5个问题的每个记录,并将答辩人,管理员和问题归一化为个人答案列表,然后由答辩人分组。

var myResultsList = GetResultsFromDatabase();

var normalizedResults = myResultsList
   .SelectMany(r=>new[]{
      new{Respondent = r.RespondentId, Admin = r.AdminId, Question = 1, Answer= r.Question1},
      new{Respondent = r.RespondentId, Admin = r.AdminId, Question = 2, Answer = r.Question2},
      new{Respondent = r.RespondentId, Admin = r.AdminId, Question = 3, Answer = r.Question3},
      new{Respondent = r.RespondentId, Admin = r.AdminId, Question = 4, Answer = r.Question4},
      new{Respondent = r.RespondentId, Admin = r.AdminId, Question = 5, Answer = r.Question5},
   };

//finding a single answer, by respondent, admin and question:
normalizedList.FirstOrDefault(x=>x.Respondent == 1 && x.Admin == 2 && x.Question == 1);

现在您有一个匿名类型列表,其中包含Respondent,Admin,Question和Answer字段。您现在可以通过Respondent对这些元素进行分组,并生成一个由响应者ID键入的Lookup(基本上是一个列表字典):

var groupedResults = normalizedResults.GroupBy(r=>r.RespondentID);

//Get all records for Respondent # 1, ordered by Admin and Question:
var oneRespondentsResults = normalizedResults[1].OrderBy(x=>x.Admin).ThenBy(x=>x.Question);

如果您真的想要花哨,可以在嵌套的词典结构中设置它们,并通过关键字段的独特组合来引用答案:

var nestedDictionary = normalizedResults
    .ToDictionary(x=>x.Respondent,
        x=>nestedDictionary.Where(x2=>x2.Respondent == x.Respondent)
            .ToDictionary(x2=>x2.Admin,
                x2=>nestedDictionary.Where(x3=>x3.Respondent == x2.Respondent && x3.Admin == x2.Admin)
                    .ToDictionary(x3=>x3.Question, x3=>x3.Answer)));

//All that mess makes getting to a single value pretty easy:
var answer = nestedDictionary[1][2][1]; //Respondent 1, Admin 2, Question 1

如果需要在创建它们的函数之外使用这些结果,请设置一个结构或简单类来代替匿名类型(您仍然可以使用推断的数组初始值设定项),或使用嵌套字典(将在匿名类型中包含的原始类型中键入或赋值。)

答案 2 :(得分:2)

在阅读@ KeithS的回答后,我想到了使用PIVOT和UNPIVOT的以下方法:

使用UNPIVOT将原始数据标准化为RepsondentID, FullQuestionID, Answer

select RespondentID,
       [FullQuestionID] = 'Admin'+cast(admin as varchar)+'_'+question,
       Answer
from (
       select RespondentID, Admin, Question1, Question2, Question3, Question4, Question5
       from tests
     ) t UNPIVOT (
       answer for question in (Question1, Question2, Question3, Question4, Question5)
     ) up

然后使用PIVOT将数据取消标准化为您想要的RespondentID列表:

;with data as (
  --unpivot code
)
select RespondentID, [Admin1_Question1], [Admin2_Question1], [Admin3_Question1]
from data
  PIVOT (min(Answer) for FullQuestionID in
     ([Admin1_Question1], [Admin2_Question1], [Admin3_Question1])
  ) p

最后,您可以使用动态t-sql来构建所有管理/问题组合的列表。将所有内容组合在一起,如下所示:

declare @list varchar(max)
select @list = coalesce(@list+',','')+'[Admin'+a+'_'+q+']'
from (select distinct cast(admin as varchar) a from tests) p1
    cross join (
        select 'Question1' q union
        select 'Question2' union
        select 'Question3' union
        select 'Question4' union
        select 'Question5'
    ) p2
order by a, q

declare @sql varchar(max)
set @sql =
';with data as (
    select RespondentID, [FullQuestionID]=''Admin''+cast(Admin as varchar)+''_''+question, Answer
    from (
        select respondentID, Admin, Question1, Question2, Question3, Question4, Question5
        from tests
        ) p
    UNPIVOT
        (answer for question in
            (Question1, Question2, Question3, Question4, Question5)
        ) as unPvt
)
select respondentID, '+@list+'
from data d
    PIVOT (min(answer) for FullQuestionID in
        ('+@list+')
    ) p'

exec(@sql)