Skip to main content
added 1 character in body
Source Link
Taryn
  • 9.7k
  • 4
  • 46
  • 74
DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT ',' + QUOTENAME(col + '_' + cast(seq as varchar(2))) 
                    from
                    (
                      select seq = row_number() over(partition by h.HotelName, r.FloorNo
                                                      order by r.RoomNo) 
                      from dbo.Hotels h
                      inner join dbo.HotelRooms r
                        on h.seqid = r.hotelseqid
                    ) d
                    cross apply
                    (
                      select 'RoomNo', 1 union all
                      select 'Beds', 2
                    ) c (col, so)
                    group by col, so, seq
                    order by seq, so
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')


set @query = N'SELECT HotelName, FloorNo'FloorNo,' + @cols + N' 
            from 
            (
              select hr.HotelName, 
                hr.FloorNo, 
                col = c.col + ''_'' + cast(seq as varchar(2)),
                c.val
              from
              (
                select h.HotelName, 
                  r.FloorNo,
                  r.RoomNo,
                  r.Beds,
                  seq = row_number() over(partition by h.HotelName, r.FloorNo
                                          order by r.RoomNo) 
                from dbo.Hotels h
                inner join dbo.HotelRooms r
                  on h.seqid = r.hotelseqid
              ) hr
              cross apply
              (
                select ''RoomNo'', RoomNo union all
                select ''Beds'', Beds
              ) c (col, val)
            ) x
            pivot 
            (
                max(val)
                for col in (' + @cols + N')
            ) p 
            order by HotelName, FloorNo'

exec sp_executesql @query
|                 HOTELNAME | FLOORNO  |  ROOMNO_1 | BEDS_1 | ROOMNO_2 | BEDS_2 | ROOMNO_3 | BEDS_3 | ROOMNO_4 | BEDS_4 | ROOMNO_5 | BEDS_5 | ROOMNO_6 | BEDS_6 |
|---------------------------|----------|-----------|--------|----------|--------|----------|--------|----------|--------|----------|--------|----------|--------|
| Hotel 1                   |        1 |       101 |     1  |      102 |      2 |      103 |      1 |      104 |      2 |   (null) | (null) |   (null) | (null) |
| Hotel 1                   |        2 |       201 |     1  |      202 |      2 |      203 |      1 |      204 |      2 |      205 |      1 |      206 |      2 |
| Hotel 2                   |        1 |      2 101 |     4  |      102 |      6 |   (null) | (null) |   (null) | (null) |   (null) | (null) |   (null) | (null) |
| Hotel 2                   |        2 |      3 201 |     2  |      202 |      7 |   (null) | (null) |   (null) | (null) |   (null) | (null) |   (null) | (null) |
DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT ',' + QUOTENAME(col + '_' + cast(seq as varchar(2))) 
                    from
                    (
                      select seq = row_number() over(partition by h.HotelName, r.FloorNo
                                                      order by r.RoomNo) 
                      from dbo.Hotels h
                      inner join dbo.HotelRooms r
                        on h.seqid = r.hotelseqid
                    ) d
                    cross apply
                    (
                      select 'RoomNo', 1 union all
                      select 'Beds', 2
                    ) c (col, so)
                    group by col, so, seq
                    order by seq, so
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')


set @query = N'SELECT HotelName, FloorNo' + @cols + N' 
            from 
            (
              select hr.HotelName, 
                hr.FloorNo, 
                col = c.col + ''_'' + cast(seq as varchar(2)),
                c.val
              from
              (
                select h.HotelName, 
                  r.FloorNo,
                  r.RoomNo,
                  r.Beds,
                  seq = row_number() over(partition by h.HotelName, r.FloorNo
                                          order by r.RoomNo) 
                from dbo.Hotels h
                inner join dbo.HotelRooms r
                  on h.seqid = r.hotelseqid
              ) hr
              cross apply
              (
                select ''RoomNo'', RoomNo union all
                select ''Beds'', Beds
              ) c (col, val)
            ) x
            pivot 
            (
                max(val)
                for col in (' + @cols + N')
            ) p 
            order by HotelName, FloorNo'

exec sp_executesql @query
|                 HOTELNAME | ROOMNO_1 | BEDS_1 | ROOMNO_2 | BEDS_2 | ROOMNO_3 | BEDS_3 | ROOMNO_4 | BEDS_4 | ROOMNO_5 | BEDS_5 | ROOMNO_6 | BEDS_6 |
|---------------------------|----------|--------|----------|--------|----------|--------|----------|--------|----------|--------|----------|--------|
| Hotel 1                   |        1 |      1 |      102 |      2 |      103 |      1 |      104 |      2 |   (null) | (null) |   (null) | (null) |
| Hotel 1                   |        2 |      1 |      202 |      2 |      203 |      1 |      204 |      2 |      205 |      1 |      206 |      2 |
| Hotel 2                   |        1 |      2 |      102 |      6 |   (null) | (null) |   (null) | (null) |   (null) | (null) |   (null) | (null) |
| Hotel 2                   |        2 |      3 |      202 |      7 |   (null) | (null) |   (null) | (null) |   (null) | (null) |   (null) | (null) |
DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT ',' + QUOTENAME(col + '_' + cast(seq as varchar(2))) 
                    from
                    (
                      select seq = row_number() over(partition by h.HotelName, r.FloorNo
                                                      order by r.RoomNo) 
                      from dbo.Hotels h
                      inner join dbo.HotelRooms r
                        on h.seqid = r.hotelseqid
                    ) d
                    cross apply
                    (
                      select 'RoomNo', 1 union all
                      select 'Beds', 2
                    ) c (col, so)
                    group by col, so, seq
                    order by seq, so
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')


set @query = N'SELECT HotelName, FloorNo,' + @cols + N' 
            from 
            (
              select hr.HotelName, 
                hr.FloorNo, 
                col = c.col + ''_'' + cast(seq as varchar(2)),
                c.val
              from
              (
                select h.HotelName, 
                  r.FloorNo,
                  r.RoomNo,
                  r.Beds,
                  seq = row_number() over(partition by h.HotelName, r.FloorNo
                                          order by r.RoomNo) 
                from dbo.Hotels h
                inner join dbo.HotelRooms r
                  on h.seqid = r.hotelseqid
              ) hr
              cross apply
              (
                select ''RoomNo'', RoomNo union all
                select ''Beds'', Beds
              ) c (col, val)
            ) x
            pivot 
            (
                max(val)
                for col in (' + @cols + N')
            ) p 
            order by HotelName, FloorNo'

exec sp_executesql @query
|                 HOTELNAME | FLOORNO  |  ROOMNO_1 | BEDS_1 | ROOMNO_2 | BEDS_2 | ROOMNO_3 | BEDS_3 | ROOMNO_4 | BEDS_4 | ROOMNO_5 | BEDS_5 | ROOMNO_6 | BEDS_6 |
|---------------------------|----------|-----------|--------|----------|--------|----------|--------|----------|--------|----------|--------|----------|--------|
| Hotel 1                   |        1 |       101 |     1  |      102 |      2 |      103 |      1 |      104 |      2 |   (null) | (null) |   (null) | (null) |
| Hotel 1                   |        2 |       201 |     1  |      202 |      2 |      203 |      1 |      204 |      2 |      205 |      1 |      206 |      2 |
| Hotel 2                   |        1 |       101 |     4  |      102 |      6 |   (null) | (null) |   (null) | (null) |   (null) | (null) |   (null) | (null) |
| Hotel 2                   |        2 |       201 |     2  |      202 |      7 |   (null) | (null) |   (null) | (null) |   (null) | (null) |   (null) | (null) |
Source Link
Taryn
  • 9.7k
  • 4
  • 46
  • 74

Since you want to use PIVOT to get the result and you need to to this dynamically, I would always suggest writing a static PIVOT query first, this allows you to get the syntax correct before trying to convert it to dynamic SQL.

The desired output shows that you want to PIVOT on two columns, RoomNo and Beds - as a result you'll need to unpivot these columns first, then apply the pivot.

Your current query is on the right track, you do need to use row_number() so you can get the number of rooms/beds on each floor - but your unpivot is using Beds and HotelSeqID. You don't want to unpivot HotelSeqID because that doesn't have a value that you eventually want as a new column.

I'd start a static version the following way - first the subquery to get the data from your tables vertically with the row_number() included:

select h.HotelName, 
  r.FloorNo,
  r.RoomNo,
  r.Beds,
  seq = row_number() over(partition by h.HotelName, r.FloorNo
                          order by r.RoomNo) 
from dbo.Hotels h
inner join dbo.HotelRooms r
  on h.seqid = r.hotelseqid

See SQL Fiddle with Demo. Your data will look like this with a new column that contains the sequence number based on the HotelName and FloorNo:

|                 HOTELNAME | FLOORNO | ROOMNO | BEDS | SEQ |
|---------------------------|---------|--------|------|-----|
| Hotel 1                   |       1 |    101 |    1 |   1 |
| Hotel 1                   |       1 |    102 |    2 |   2 |
| Hotel 1                   |       1 |    103 |    1 |   3 |
| Hotel 1                   |       1 |    104 |    2 |   4 |
| Hotel 1                   |       2 |    201 |    1 |   1 |
| Hotel 1                   |       2 |    202 |    2 |   2 |

Now, you can unpivot the RoomNo and Beds columns into multiple rows. Since you are using SQL Server 2008 you can use CROSS APPLY to get the result. The query will be:

select hr.HotelName, 
  hr.FloorNo, 
  col = c.col + '_' + cast(seq as varchar(2)),
  c.val
from
(
  select h.HotelName, 
    r.FloorNo,
    r.RoomNo,
    r.Beds,
    seq = row_number() over(partition by h.HotelName, r.FloorNo
                            order by r.RoomNo) 
  from dbo.Hotels h
  inner join dbo.HotelRooms r
    on h.seqid = r.hotelseqid
) hr
cross apply
(
  select 'RoomNo', RoomNo union all
  select 'Beds', Beds
) c (col, val);

See SQL Fiddle with Demo. Your data has now been transformed into multiple columns:

|                 HOTELNAME | FLOORNO |      COL | VAL |
|---------------------------|---------|----------|-----|
| Hotel 1                   |       1 | RoomNo_1 | 101 |
| Hotel 1                   |       1 |   Beds_1 |   1 |
| Hotel 1                   |       1 | RoomNo_2 | 102 |
| Hotel 1                   |       1 |   Beds_2 |   2 |
| Hotel 1                   |       1 | RoomNo_3 | 103 |
| Hotel 1                   |       1 |   Beds_3 |   1 |
| Hotel 1                   |       1 | RoomNo_4 | 104 |
| Hotel 1                   |       1 |   Beds_4 |   2 |
| Hotel 1                   |       2 | RoomNo_1 | 201 |

Finally, you can pivot to get the final result.

select HotelName, FloorNo,
  RoomNo_1, Beds_1, RoomNo_2, Beds_2,
  RoomNo_3, Beds_3, RoomNo_4, Beds_4
from
(
  select hr.HotelName, 
    hr.FloorNo, 
    col = c.col + '_' + cast(seq as varchar(2)),
    c.val
  from
  (
    select h.HotelName, 
      r.FloorNo,
      r.RoomNo,
      r.Beds,
      seq = row_number() over(partition by h.HotelName, r.FloorNo
                              order by r.RoomNo) 
    from dbo.Hotels h
    inner join dbo.HotelRooms r
      on h.seqid = r.hotelseqid
  ) hr
  cross apply
  (
    select 'RoomNo', RoomNo union all
    select 'Beds', Beds
  ) c (col, val)
) d
pivot
(
  max(val)
  for col in (RoomNo_1, Beds_1, RoomNo_2, Beds_2,
              RoomNo_3, Beds_3, RoomNo_4, Beds_4)
) piv
order by HotelName, FloorNo;

See SQL Fiddle with Demo. Once you've tested a static version to make sure it gets your the desired result, you can easily convert this into dynamic SQL.

DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT ',' + QUOTENAME(col + '_' + cast(seq as varchar(2))) 
                    from
                    (
                      select seq = row_number() over(partition by h.HotelName, r.FloorNo
                                                      order by r.RoomNo) 
                      from dbo.Hotels h
                      inner join dbo.HotelRooms r
                        on h.seqid = r.hotelseqid
                    ) d
                    cross apply
                    (
                      select 'RoomNo', 1 union all
                      select 'Beds', 2
                    ) c (col, so)
                    group by col, so, seq
                    order by seq, so
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')


set @query = N'SELECT HotelName, FloorNo' + @cols + N' 
            from 
            (
              select hr.HotelName, 
                hr.FloorNo, 
                col = c.col + ''_'' + cast(seq as varchar(2)),
                c.val
              from
              (
                select h.HotelName, 
                  r.FloorNo,
                  r.RoomNo,
                  r.Beds,
                  seq = row_number() over(partition by h.HotelName, r.FloorNo
                                          order by r.RoomNo) 
                from dbo.Hotels h
                inner join dbo.HotelRooms r
                  on h.seqid = r.hotelseqid
              ) hr
              cross apply
              (
                select ''RoomNo'', RoomNo union all
                select ''Beds'', Beds
              ) c (col, val)
            ) x
            pivot 
            (
                max(val)
                for col in (' + @cols + N')
            ) p 
            order by HotelName, FloorNo'

exec sp_executesql @query

See SQL Fiddle with Demo. This query will give you the result:

|                 HOTELNAME | ROOMNO_1 | BEDS_1 | ROOMNO_2 | BEDS_2 | ROOMNO_3 | BEDS_3 | ROOMNO_4 | BEDS_4 | ROOMNO_5 | BEDS_5 | ROOMNO_6 | BEDS_6 |
|---------------------------|----------|--------|----------|--------|----------|--------|----------|--------|----------|--------|----------|--------|
| Hotel 1                   |        1 |      1 |      102 |      2 |      103 |      1 |      104 |      2 |   (null) | (null) |   (null) | (null) |
| Hotel 1                   |        2 |      1 |      202 |      2 |      203 |      1 |      204 |      2 |      205 |      1 |      206 |      2 |
| Hotel 2                   |        1 |      2 |      102 |      6 |   (null) | (null) |   (null) | (null) |   (null) | (null) |   (null) | (null) |
| Hotel 2                   |        2 |      3 |      202 |      7 |   (null) | (null) |   (null) | (null) |   (null) | (null) |   (null) | (null) |