SQL:选择动态行数作为列

时间:2010-02-05 15:19:24

标签: sql

我需要选择静态列+动态行数作为SQL

中的列
TABLE 1
-------
HotelID
BlockID
BlockName

TABLE 2
-------
BlockDate (unknown number of these)
NumberOfRooms

Desired Result Row
------------------
HotelID | BlockID | BlockName | 02/10/10 | 02/11/10 | 02/12/10 | ...N

其中日期列是未知数量的封锁行。

4 个答案:

答案 0 :(得分:2)

在客户端执行此操作。

SQL是一种固定的列语言:您不能拥有可变数量的列(即使使用PIVOT等)。动态SQL不是一个好主意。

答案 1 :(得分:1)

您需要的是一个数据透视查询,用于将行数据转换为柱状数据:

  SELECT t.hotelid,
         t.blockid,
         t.blockname,
         MAX(CASE WHEN t2.blockdate = '02-10-10' THEN t.numberofrooms ELSE NULL END) AS 02_10_10,
         MAX(CASE WHEN t2.blockdate = '02-11-10' THEN t.numberofrooms ELSE NULL END) AS 02_11_10,
         MAX(CASE WHEN t2.blockdate = '02-12-10' THEN t.numberofrooms ELSE NULL END) AS 02_12_10,
         ...
    FROM TABLE_1 t
    JOIN TABLE_2 t2
GROUP BY t.hotelid, t.blockid, t.blockname
  1. 请注意,没有任何内容可以链接表格 - 实际上TABLE_2需要hotelidblockid属性。按原样,这将为TABLE_2中的每条记录返回TABLE_1的结果...

  2. 数据库很重要,因为它需要动态SQL来创建MAX(CASE...语句

答案 2 :(得分:0)

你错过了一个外键。我必须假设BlockId应该是表2中的PK?

另外,假设这是遗留数据库并且不能改变结构,我不得不问哪个平台?

如果使用ms sql,可以使用动态sql语句轻松实现。

答案 3 :(得分:0)

我曾经写过一个存储过程就是这样做的。给定的是具有基本详细信息和用户可变数量的配置文件属性的用户表。 (配置文件属性的数量因DotNetNuke Portal而异)

这是直接来自DotNetNuke 4.9,从那里检查数据库架构,你会得到详细信息。涉及的表是Users,UserPortals,UserProfile,ProfilePropertyDefinition

简而言之:

  1. 我创建了一个临时表,其中所有配置文件属性都是列(动态)
  2. 我使用userid填充临时表(作为链接到users表的外键和所有配置文件属性数据
  3. 我在用户表和临时表上进行联接
  4. 这为每个用户提供了一行所有配置文件属性。

    这里的代码 - 不完美,但是它根据我的需求量身定做,但应该很容易重复使用(试图避免使用游标)

    set ANSI_NULLS ON
    set QUOTED_IDENTIFIER ON
    go
    
    ALTER PROCEDURE [dbo].[rows2cols] @portalId INT
    AS BEGIN
    print 'PortalID=' + convert(varchar,@portalId)
        --SET NOCOUNT ON;
        declare @idx int    
        declare @rowcount int
        declare @tmpStr nvarchar(max)
        declare @ctype nvarchar(max)
        declare @cname nvarchar(max)
        declare @clen int
        declare @createStr nvarchar(max)    
    ---------------------------------------------------------------------------
    -- create tmp table --
    ---------------------------------------------------------------------------   
    IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[xxxx]') AND type in (N'U'))
    DROP TABLE [dbo].[xxxx]
    
    print 'Building Temp Table Cols for profile properties...'  
    set @rowcount = (select count(*) from ProfilePropertyDefinition where PortalID=0 and deleted=0)
    set @idx = 1
    set @tmpStr = ''
    while (@idx <= @rowcount)
    begin
        -- dynamically generate rownumbers to be able to do loop over them (avoid cursors)
        select @cname = t1.PropertyName from
        ( select ROW_NUMBER() OVER (ORDER BY ViewOrder) as Idx, PropertyName from ProfilePropertyDefinition 
          where PortalID=0 and deleted=0
        ) as t1 where t1.Idx = @idx
    
        if (@cname = 'Email' or @cname = 'FirstName' or @cname = 'LastName') begin
            set @clen = 1 
        end else begin 
            set @tmpStr = @tmpStr + '[' + @cname + '] [nvarchar](500), '
        end
        set @idx = @idx + 1
    end
    
    set @tmpStr = @tmpStr + '[userid] [int] '
    set @createStr = 'create table xxxx ( ' + @tmpStr + ' )'
    
    Print @createStr
    Exec (@createStr)
    
    ---------------------------------------------------------------------------
    -- fill tmp table --
    ---------------------------------------------------------------------------    
    declare @propName nvarchar(max)
    declare @propVal nvarchar(max)
    declare @userId int
    declare @idx2 int
    declare @rowcount2 int
    declare @inscol nvarchar(max)
    declare @insval nvarchar(max)
    
    set @rowcount = (select count(*) FROM Users LEFT OUTER JOIN UserPortals ON Users.UserID = UserPortals.UserId WHERE UserPortals.PortalId = @portalId)
    set @idx = 1
        while (@idx <= @rowcount)
        begin
            -- get userId
            select @userId = t1.UserID from (select u.UserID, ROW_NUMBER() OVER (ORDER BY u.UserID) as Idx
            from Users as u LEFT OUTER JOIN UserPortals as up ON u.UserID = up.UserId where up.PortalId = @portalId) as t1 
            where t1.Idx = @idx 
    
            set @idx2 = 1
            set @rowcount2 = (select count(*) from UserProfile where UserID = @userId)
            set @inscol = ''
            set @insval = ''
    
            while (@idx2 < @rowcount2)
            begin
                -- build insert for a specific user
                select @propName = t1.PropertyName , @propVal=t1.PropertyValue from
                ( select ROW_NUMBER() OVER (ORDER BY ProfileID) as Idx, up.PropertyDefinitionID,ppd.PropertyName, up.PropertyValue 
                  from UserProfile as up 
                   inner join ProfilePropertyDefinition as ppd on up.PropertyDefinitionID = ppd.PropertyDefinitionID 
                  where UserID = @userId
                ) as t1 where t1.Idx = @idx2
    
                if (@propName != 'Firstname' and @propName != 'LastName' and @propName != 'Email')
                begin
                    set @inscol = @inscol + @propName + ', '
                    set @insval = @insval + 'N''' + replace(@propVal,'''','''''') + ''', '
                end
    
                set @idx2 = @idx2 + 1
            end
    
            set @inscol = @inscol + 'userid'
            set @insval = @insval + convert(nvarchar,@userId) 
    
            set @tmpStr = 'insert into xxxx (' + @inscol + ') values (' + @insval + ')'
            --print @tmpStr
            Exec(@tmpStr)
            set @idx = @idx + 1
        end
    
    -- -------------------------------------------------------------------------
    -- return tmp table & dump  --
    -- -------------------------------------------------------------------------    
    SELECT Users.*, xxxx.* FROM xxxx INNER JOIN Users ON xxxx.userid = Users.UserID
    
    IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[xxxx]') AND type in (N'U'))
    DROP TABLE [dbo].[xxxx]
    END