SQL:选择动态行数作为列

SQL:选择动态行数作为列,sql,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 其中日期列是未知数

我需要在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

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

缺少外键。我必须假设BlockId应该是表2中的PK

另外,假设这是一个遗留数据库,并且改变结构不是一个选项,我必须问一下哪个平台


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

您需要的是一个pivot查询,以将行数据转换为列数据:

  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
请注意,没有任何东西可以链接表-实际上,表2需要hotelid和blockid属性。按原样,这将返回表1中每个记录的表2的结果

数据库很重要,因为它需要动态SQL来创建MAXCASE。。。声明

在客户端中执行此操作


SQL是一种固定列语言:即使使用PIVOT等工具,列的数量也不能可变。动态SQL不是一个好主意。

我曾经编写过一个存储过程,它就是这样做的。给出了一个用户表,其中包含基本详细信息和用户的可变数量的配置文件属性。配置文件属性的数量因DotNetNuke门户而异

这是直接从DotNetNuke 4.9开始的,从那里检查数据库模式,您将获得详细信息。涉及的表包括用户、UserPortals、UserProfile、ProfilePropertyDefinition

简言之:

我创建了一个临时表,其中所有概要文件属性都是动态列 我用userid作为外键填充temp表,以链接到users表和所有概要文件属性数据 我在users表和temp表上进行连接 这为每个用户提供了一行所有配置文件属性

这里的代码-不完美,但嘿,它是为我的需要量身定做的,但应该很容易重复使用试图避免游标顺便说一句

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

表2是否每行有一个阻止日期?表1和表2之间有什么联系?您可能正在寻找一个轴心。动态SQL通常是不好的。同意。它可能会限制服务器缓存结果集等的能力。但有时,如果使用错误的方式,则速度会更快