Reputation: 23
I have one table of activities, one table of users, and a third table linking users to activities using foreign keys.
What I'm trying to do is create a results table that will have the activities as columns and the users as rows with the cells being the number of activities of that type the user participated in.
For example, the columns would be
User | Activity A | Activity B | Activity C
And a user who had done each activity three times would result in a row of
John Doe | 3 | 3 | 3
Now, I can do this easily if I manually add a count() call for each activity in the database like:
select
u.name,
(select count(*)
from userActivity ua
where ua.userID = user.userID and ua.activityID = 1),
(select count(*)
from userActivity ua
where ua.userID = user.userID and ua.activityID = 2),
(select count(*)
from userActivity ua
where ua.userID = user.userID and ua.activityID = 3)
from
user u
But this doesn't help me if someone enters an Activity D into the system tomorrow. The report wouldn't show it. How can I use the Activity table's rows as columns?
Upvotes: 2
Views: 4239
Reputation: 2348
I did a quick query that might help. This uses the Pivot function, which was mentioned before.
You can run the whole thing, or just skip to the bottom!
-- Temp tables
IF OBJECT_ID('tempdb.dbo.#_tmp') IS NOT NULL DROP TABLE #_tmp
IF OBJECT_ID('tempdb.dbo.#_user') IS NOT NULL DROP TABLE #_user
IF OBJECT_ID('tempdb.dbo.#_activity') IS NOT NULL DROP TABLE #_activity
IF OBJECT_ID('tempdb.dbo.#_useractivity') IS NOT NULL DROP TABLE #_useractivity
-- User table
CREATE TABLE #_user (
[USER_ID] INT IDENTITY(1,1) NOT NULL,
[FIRST_NAME] NVARCHAR(50)
)
INSERT INTO #_user ([FIRST_NAME])
VALUES ('John'), ('Peter'), ('Paul')
-- Activity table
CREATE TABLE #_activity (
[ACTIVITY_ID] INT IDENTITY(1,1) NOT NULL,
[ACTIVITY_NAME] NVARCHAR(255)
)
INSERT INTO #_activity ([ACTIVITY_NAME])
VALUES ('Sailing'), ('Bowling'), ('Hiking')
-- Composite table
CREATE TABLE #_useractivity (
[LOG_ID] INT IDENTITY(1,1) NOT NULL,
[USER_ID] INT,
[ACTIVITY_ID] INT
)
INSERT INTO #_useractivity ([USER_ID], [ACTIVITY_ID])
VALUES (1,1),(1,2),(1,3),(1,3),(2,2),(2,3),(3,1), (3,2),(1,2),(2,1)
-- Main data table.
SELECT USR.FIRST_NAME
, A.ACTIVITY_NAME
INTO #_tmp
FROM #_useractivity AS UA
INNER JOIN #_user AS USR ON USR.USER_ID = UA.USER_ID
INNER JOIN #_activity AS A ON A.ACTIVITY_ID = UA.ACTIVITY_ID
SELECT * FROM #_tmp
-- Use pivot function to get desired results.
DECLARE @_cols AS NVARCHAR(MAX)
DECLARE @_sql AS NVARCHAR(MAX)
SET @_cols = STUFF((SELECT ',' + QUOTENAME(T.ACTIVITY_NAME)
FROM #_tmp AS T
GROUP BY T.ACTIVITY_NAME
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
-- Trick is to add 1 "counter" before pivoting.
set @_sql = '
SELECT Name, ' + @_cols + '
FROM (
SELECT FIRST_NAME AS Name, ACTIVITY_NAME, 1 AS COUNT
FROM #_tmp
) AS SRC
PIVOT (
SUM(COUNT) FOR ACTIVITY_NAME IN (' + @_cols + ')
) p'
EXEC(@_sql)
Main data table:
FIRST_NAME ACTIVITY_NAME
John Sailing
John Bowling
John Hiking
John Hiking
Peter Bowling
Peter Hiking
Paul Sailing
Paul Bowling
John Bowling
Peter Sailing
Output:
Name Bowling Hiking Sailing
John 2 2 1
Paul 1 NULL 1
Peter 1 1 1
Upvotes: 2
Reputation: 1269563
You seem to want conditional aggregation:
select u.name,
sum(case when ua.activityID = 1 then 1 else 0 end) as cnt_1,
sum(case when ua.activityID = 2 then 1 else 0 end) as cnt_2,
sum(case when ua.activityID = 3 then 1 else 0 end) as cnt_3
from user u left join
userActivity ua
on ua.userID = u.userID
group by u.name;
Upvotes: 0