SQL : Select a dynamic number of rows as columns

前端 未结 4 1244
春和景丽
春和景丽 2021-01-22 23:21

I need to select static colums + a dynamic number of rows as columns in SQL

TABLE 1
-------
HotelID
BlockID
BlockName

TABLE 2
-------
BlockDate (unknown number          


        
4条回答
  •  醉话见心
    2021-01-22 23:55

    I once wrote a stored procedure that did just something like this. Given are a users table with basic details and a variable number of profile properties for users. (The number of profile properties varies per DotNetNuke Portal)

    This is straight from DotNetNuke 4.9, check the database schema from there and you'll get the details. Tables involved are Users, UserPortals, UserProfile, ProfilePropertyDefinition

    In short words :

    1. I create a temp table with all the profile properties as columns (dynamically)
    2. I fill the temp table with userid (as foreign key to link to users table and all the profile properties data
    3. I do a join on users table and temp table

    This gives me one row per user with all profile properties.

    Here the code - not perfect but hey its tailored for my needs but should be easy enough to re-use (tried to avoid cursors btw)

    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
    

提交回复
热议问题