如何做列数不同的交叉表? (ADO SQL 服务器)

How to do a cross tab where the number of columns varies? (ADO SQL Server)

我正在尝试编写一个具有可变列数的查询,具体取决于数据,但我从未做过这种事情。

我们是 运行 ADO,希望只有一个查询(可能带有子查询)但没有其他编码或 GO 语句、存储过程等。

我们计划在 editable 网格中使用此查询的结果。

以下是我们的数据示例。我们有一个员工列表和一个项目列表。请注意,这不是 "summed" 交叉表。每个单元格只有一个源编号。

我们希望查询结果中每个项目都有一列。此列中的单元格将包含该员工在该项目中的工时。

如果我们添加一个项目,我们希望在查询结果中出现另一列。

编辑:由于我们在代码中编写查询并提交它,我们可以动态生成查询。我们不需要动态生成代码。例如,在我们下面的数据中,我们将能够(用我们的母语)阅读项目 table 并知道我们有 3 个项目以及它们的名称。我们可以在阅读中看到的 Pivot 中使用它们,但不确定如何...

这样的事情怎么样:http://sqlfiddle.com/#!6/2ded1/6

declare @pivotquery as nvarchar(max)
declare @columnname as nvarchar(max)
select @columnname= isnull(@columnname + ',','') 
      + quotename(name)
from (select name from project) as t2
set @pivotquery = 
     N'with t1 as (
         select ph.employee, e.name as empname, p.name, ph.hours
         from project_hours ph
         inner join project p
         on p.id = ph.project
         inner join employees e
         on e.id = ph.employee
       )
       select *
       from t1
       pivot(sum(hours) for name in (' + @columnname + ')) as pivot_table'
exec sp_executesql @pivotquery

这里有重要的帮助和进一步的解释:http://sqlhints.com/2014/03/18/dynamic-pivot-in-sql-server/

编辑:当我再次阅读你的问题时,我注意到你正在用代码构建查询,在这种情况下你可能不需要上面的实用程序,而是一个简单的枢轴,你可以在其中构建 for name in 子句,像这样:http://sqlfiddle.com/#!6/2ded1/12

with t1 as (
  select ph.employee, e.name as empname, p.name, ph.hours
  from project_hours ph
  inner join project p
  on p.id = ph.project
  inner join employees e
  on e.id = ph.employee
)
select *
from t1
pivot(sum(hours)
      for name in ([First Floor], [Basement], [Parking Lot A]))
      as hours_summary

对于未来的读者,这里是一个通用的 ANSI 语法 SQL 查询,使用两个嵌套派生的 table 子查询。

此查询应符合大多数 RDMS(SQL 服务器、MySQL、SQLite、Oracle、PostgreSQL、DB2),因为它不使用 CTE Window 函数 (WITH) 或特定于数据库的函数,例如 SQL 服务器的 Pivot():

SELECT [Key], [Employee Name], [Age],
       Max(FF) As [First Floor],
       Max(BSMT) As [Basement],
       Max(PRKLotA) As [Parking Lot A]    
FROM (
   SELECT dT.Key,
          dT.[Employee Name],
          dT.[Age],
          CASE WHEN dT.Project = 'First Floor' THEN dT.Hours END As FF,
          CASE WHEN dT.Project = 'Basement' THEN dT.Hours END As BSMT,
          CASE WHEN dT.Project = 'Parking Lot A' THEN dT.Hours END As PRKLotA
   FROM (
          SELECT [Employees].Key, 
                 [Employees.[Employee Name], 
                 Employees.Age, Projects.Project, 
                 [Project Hours].Hours
          FROM [Project Hours] 
          INNER JOIN Employees ON [Project Hours].[Employees FK] = Employees.Key 
          INNER JOIN Projects ON [Project Hours].[Projects FK] = Projects.Key
   ) AS dT
) As dT2    
GROUP BY [Key], [Employee Name], [Age]

输出:

Key Employee Name    Age    First Floor     Basement    Parking Lot A
1            Tim     40           1000          3000    
2           John      5           2000          4000             5000