Question

I have 3 tables; category, location and business.

The category and location tables simply have an id, and a name.

Each business record has a categoryID, and a locationID, and a name field.

I'd like to construct a table that shows as a matrix, the number of businesses in each location and category combination. So having the categories as columns and locations as rows, with the counts in as the cell data.

Having a totals column and row would also be amazing.

I know I should be able to do this with pivot tables but I'm unable to get my head around the syntax for the pivots.

Any help would be much appreciated.

Thanks,

Nick

Edit: Here is a JS fiddle of my tables; http://sqlfiddle.com/#!2/4d6d2/1

Desired output:

            | Activities  | Bars     | Sweet shops   | Total
Chester     | 1           | 0        | 0             | 1
Frodsham    | 0           | 2        | 0             | 2
Stockport   | 1           | 0        | 1             | 2
Total       | 2           | 2        | 1             | 5
Was it helpful?

Solution

To get the final result that you want you can use the PIVOT function. I would first start with a subquery that returns all of your data plus gives you a total of each activity per location:

select l.name location,
  c.name category,
  count(b.locationid) over(partition by b.locationid) total
from location l
left join business b
  on l.id = b.locationid
left join category c
  on b.categoryid = c.id;

See SQL Fiddle with Demo. Using the windowing function count() over() creates the total number of activities for each location. Once you have this, then you can pivot the data to convert your categories to columns:

select 
  isnull(location, 'Total') Location, 
  sum([Activities]) Activities, 
  sum([Bars]) bars, 
  sum([Sweet Shops]) SweetShops,
  sum(tot) total
from
(
  select l.name location,
    c.name category,
    count(b.locationid) over(partition by b.locationid) tot
  from location l
  left join business b
    on l.id = b.locationid
  left join category c
    on b.categoryid = c.id
) d
pivot
(
  count(category)
  for category in ([Activities], [Bars], [Sweet Shops])
) piv
group by grouping sets(location, ());

See SQL Fiddle with Demo. I also implemented GROUPING SETS() to create the final row with the totals for each activity.

The above works great if you have a limited number of activities but if your activities will be unknown, then you will want to use dynamic SQL:

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

select @cols = STUFF((SELECT ',' + QUOTENAME(name) 
                    from dbo.category
                    group by id, name
                    order by id
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

select @colsgroup = STUFF((SELECT ', sum(' + QUOTENAME(name)+ ') as '+ QUOTENAME(name)
                    from dbo.category
                    group by id, name
                    order by id
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')



set @query = N'SELECT 
                Isnull(location, ''Total'') Location, '+ @colsgroup + ', sum(Total) as Total
            from 
            (
              select l.name location,
                c.name category,
                count(b.locationid) over(partition by b.locationid) total
              from location l
              left join business b
                on l.id = b.locationid
              left join category c
                on b.categoryid = c.id
            ) x
            pivot 
            (
                count(category)
                for category in ('+@cols+')
            ) p 
            group by grouping sets(location, ());'

exec sp_executesql @query;

See SQL Fiddle with Demo. Both versions give the result:

|  LOCATION | ACTIVITIES | BARS | SWEET SHOPS | TOTAL |
|-----------|------------|------|-------------|-------|
|   Chester |          1 |    0 |           0 |     1 |
|  Frodsham |          0 |    1 |           0 |     1 |
| Stockport |          1 |    0 |           1 |     2 |
|     Total |          2 |    1 |           1 |     4 |

OTHER TIPS

`SELECT b.businessName, count(l.locationId),count(b.categoryId)
FROM businesses b JOIN locations l ON b.locationId=l.locationId 
JOIN categories c ON b.categoryId=c.categoryId GROUP BY b.businessName;`
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top