Question

I have an Items table with about 30000 records in it, a Groupings table into which my clients are grouped, a Categories table into which my Products are sorted, and a Product_Categories table that stores which Items go into which Categories for which Groupings (as each Grouping gets its own printed catalogue where Items are in different categories depending on the Grouping).

+----------+   +---------------+   +---------------+   +-------------------+
| Items    |   | Groupings     |   | Categories    |   | Item_Categories   |
+----------+   +---------------+   +---------------+   +-------------------+
| Item_ID  |   | Grouping_ID   |   | Category_ID   |   | Item_ID           |
| Item_Name|   | Grouping_Name |   | Category_Name |   | Grouping_ID       |
+----------+   +---------------+   +---------------+   | Category_ID       |
                                                       +-------------------+

For example, my product 'Red Paint' is in the 'Art Supplies' category for my clients in the 'Artists' grouping (which I abbreviate 'AR'), but it appears in the 'Paint - Acrylic' category for my clients in the 'Painters' (PN) grouping, and in the 'Back to School' category for my clients in the 'Schools' grouping.

So the relationships are stored in the Item_Categories table like this (showing names instead of IDs for clarity):

+------------+--------------+-----------------+
| Item_ID    | Grouping_ID  | Category_ID     |
+------------+--------------+-----------------+
| Red Paint  | Artists      | Art Supplies    |
| Red Paint  | Painters     | Paint - Acrylic |
| Red Paint  | Schools      | Back to School  |
+------------+--------------+-----------------+

Now what I need is a SQL Server 2005 query that provides the Item and all the Groupings in the header row, and the actual relationships in grid format, like this:

+--------------+-----------------+-----------------+----------------+
| Item_Name    | Artists         | Painters        | Schools        |
+--------------+-----------------+-----------------+----------------+
| Red Paint    | Art Supplies    | Paint - Acrylic | Back to School |
| Blue Paint   | Art Supplies    | Paint - Acrylic | Back to School |
| 1" Brush     | Art Supplies    | Brushes - Small | Back to School |
+--------------+-----------------+-----------------+----------------+

I hope what I'm asking makes sense. My gut tells me that this can be achieved with a single query if I use a 'crosstab' type approach, but the (apparent) complexity of the output format I need keeps throwing my head into a tizzy.

Thank you for taking the time to read my question; I hope you might be able to offer some guidance.

Was it helpful?

Solution

You type of process to transform rows on data into columns is known as a PIVOT. There are a few ways that you can pivot the data in sql server.

You can use an aggregate function with a CASE expression:

select i.item_name,
  max(case when g.grouping_name = 'Artists' then c.category_name end) Artists,
  max(case when g.grouping_name = 'Painters' then c.category_name end) Painters,
  max(case when g.grouping_name = 'Schools' then c.category_name end) Schools
from items i
left join item_categories ic
  on i.item_id = ic.item_id
left join groupings g
  on ic.grouping_id = g.grouping_id
left join categories c
  on ic.category_id = c.category_id
group by i.item_name;

See SQL Fiddle with Demo

If you have known values, then you can hard-code the query using PIVOT:

select item_name,
 Artists, Painters, Schools 
from
(
  select i.item_name,
    g.grouping_name,
    c.category_name
  from items i
  left join item_categories ic
    on i.item_id = ic.item_id
  left join groupings g
    on ic.grouping_id = g.grouping_id
  left join categories c
    on ic.category_id = c.category_id
) d
pivot
(
  max(category_name)
  for grouping_name in (Artists, Painters, Schools)
) piv;

See SQL Fiddle with Demo.

But it seems like you might have an unknown number of values that you want to convert into columns, if that is the case, then you will need to use dynamic SQL to get the solution:

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

select @cols = STUFF((SELECT distinct ',' + QUOTENAME(Grouping_Name) 
                    from Groupings
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT item_name, ' + @cols + ' 
             from 
             (
                select i.item_name,
                  g.grouping_name,
                  c.category_name
                from items i
                left join item_categories ic
                  on i.item_id = ic.item_id
                left join groupings g
                  on ic.grouping_id = g.grouping_id
                left join categories c
                  on ic.category_id = c.category_id
            ) x
            pivot 
            (
                max(category_name)
                for grouping_name in (' + @cols + ')
            ) p '

execute(@query);

See SQL Fiddle with Demo. All versions will give a result:

|  ITEM_NAME |      ARTISTS |        PAINTERS |        SCHOOLS |
----------------------------------------------------------------
|   1" Brush | Art Supplies |          (null) | Back to School |
| Blue Paint | Art Supplies | Paint - Acrylic | Back to School |
|  Red Paint | Art Supplies | Paint - Acrylic | Back to School |
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top