Instructors and training managers want to see who has opened and finished the course content. People ask for content progress as a spreadsheet with one row per learner per course, for the whole class list. This query gives that for every course at once, with topics visited, topics completed and the last content visit.
Where the data comes from: Brightspace Data Sets, the CSV extracts Brightspace publishes in Data Hub. This is not a built-in Brightspace report or screen. Rowseta syncs these data sets for you, or your administrator can download the extracts for your own SQL tool.
Run this query
Choose where to run it
- In Rowseta: open Reports > New report and paste the query into the SQL editor on the Query tab. Sync the data sets listed below first.
- In your own setup: load your Data Hub extracts into your own SQL tool, one table per data set, named without spaces (User Enrollments becomes
UserEnrollments), after applying differentials to the fulls. Another SQL tool may need small changes to these queries.
A free Rowseta Desktop app is coming soon.
Load Content Objects, Content User Progress, Content User Completion, User Enrollments, Users, Organizational Units and Role Details.
Run the content progress query
The query counts people whose role is named Student in Role Details. To count other roles, edit the RoleName IN list near the top, for example ('Student', 'Learner'). Names must match exactly. Joins use RoleId, so two roles with the same name both count, and a renamed role follows its current name.
-- Edit the role names on the RoleName IN line to choose who counts as a learner.
WITH learner_roles AS (
SELECT DISTINCT RoleId
FROM RoleDetails
WHERE RoleName IN ('Student')
), ClassList AS (
SELECT DISTINCT UserEnrollments.UserId, UserEnrollments.OrgUnitId
FROM UserEnrollments
JOIN learner_roles ON learner_roles.RoleId = UserEnrollments.RoleId
JOIN OrganizationalUnits ON OrganizationalUnits.OrgUnitId = UserEnrollments.OrgUnitId
WHERE OrganizationalUnits.Type = 'Course Offering'
), Topics AS (
SELECT ContentObjectId, OrgUnitId
FROM ContentObjects
WHERE ContentObjectType = 'Topic'
AND COALESCE(TRY_CAST(IsDeleted AS BOOLEAN), false) = false
AND COALESCE(TRY_CAST(IsHidden AS BOOLEAN), false) = false
), TopicCounts AS (
SELECT OrgUnitId, COUNT(DISTINCT ContentObjectId) AS Topics
FROM Topics
GROUP BY OrgUnitId
), Visits AS (
SELECT P.UserId, Topics.OrgUnitId,
COUNT(DISTINCT CASE WHEN COALESCE(TRY_CAST(P.IsVisited AS BOOLEAN), false)
THEN P.ContentObjectId END) AS TopicsVisited,
MAX(CASE WHEN COALESCE(TRY_CAST(P.IsVisited AS BOOLEAN), false)
THEN P.LastVisited END) AS LastContentVisit
FROM ContentUserProgress P
JOIN Topics ON Topics.ContentObjectId = P.ContentObjectId
GROUP BY P.UserId, Topics.OrgUnitId
), Completions AS (
SELECT C.UserId, Topics.OrgUnitId, COUNT(DISTINCT C.ContentObjectId) AS TopicsCompleted
FROM ContentUserCompletion C
JOIN Topics ON Topics.ContentObjectId = C.ContentObjectId AND Topics.OrgUnitId = C.OrgUnitId
WHERE C.DateCompleted IS NOT NULL
AND COALESCE(TRY_CAST(C.IsDeleted AS BOOLEAN), false) = false
GROUP BY C.UserId, Topics.OrgUnitId
)
SELECT DISTINCT ClassList.UserId, Users.FirstName, Users.LastName, ClassList.OrgUnitId,
Courses.Name AS CourseName, COALESCE(TopicCounts.Topics, 0) AS Topics,
COALESCE(Visits.TopicsVisited, 0) AS TopicsVisited,
COALESCE(Completions.TopicsCompleted, 0) AS TopicsCompleted,
ROUND(100.0 * COALESCE(Completions.TopicsCompleted, 0)
/ NULLIF(TopicCounts.Topics, 0)) AS PercentCompleted,
Visits.LastContentVisit
FROM ClassList
JOIN Users ON Users.UserId = ClassList.UserId
JOIN OrganizationalUnits Courses ON Courses.OrgUnitId = ClassList.OrgUnitId
LEFT JOIN TopicCounts ON TopicCounts.OrgUnitId = ClassList.OrgUnitId
LEFT JOIN Visits ON Visits.UserId = ClassList.UserId AND Visits.OrgUnitId = ClassList.OrgUnitId
LEFT JOIN Completions ON Completions.UserId = ClassList.UserId AND Completions.OrgUnitId = ClassList.OrgUnitId
ORDER BY ClassList.OrgUnitId, ClassList.UserId;To see one class list, append AND UserEnrollments.OrgUnitId = 12345 to line 11. Replace 12345 with your course's ID.
A topic counts as visited when Content User Progress marks it visited. Completion comes from Content User Completion because the progress data set's completion date is not filled. Hidden topics, deleted topics and modules are left out. A course with no counted topics shows a null percentage. LastContentVisit is in Coordinated Universal Time (UTC). The class list uses current enrollments in the loaded data, so students who left are not shown.
Read the class list progress
Fictional sample data: all 7 rows.
| UserId | FirstName | LastName | OrgUnitId | CourseName | Topics | TopicsVisited | TopicsCompleted | PercentCompleted | LastContentVisit |
|---|---|---|---|---|---|---|---|---|---|
| 1 | Jordan | Lee | 100 | BIO 101 Introduction to Biology | 3 | 1 | 1 | 33 | 2025-10-01T10:00:00Z |
| 2 | Jordan | Lee | 100 | BIO 101 Introduction to Biology | 3 | 2 | 2 | 67 | 2025-10-04T10:00:00Z |
| 3 | Liam | Nguyen | 100 | BIO 101 Introduction to Biology | 3 | 0 | 0 | 0 | null |
| 4 | Sofia | Garcia | 100 | BIO 101 Introduction to Biology | 3 | 0 | 0 | 0 | null |
| 1 | Jordan | Lee | 200 | BIO 101 Introduction to Biology | 3 | 3 | 1 | 33 | 2025-10-07T10:00:00Z |
| 2 | Jordan | Lee | 200 | BIO 101 Introduction to Biology | 3 | 1 | 3 | 100 | 2025-10-08T10:00:00Z |
| 3 | Liam | Nguyen | 300 | CHEM 110 General Chemistry | 0 | 0 | 0 | null | null |
A high visited count with a low completed count can mean completion tracking is off or students only open topics.
When it goes wrong
| What you see | What to do |
|---|---|
| No learners appear | One possible cause is that no role in Role Details matches the role names list. Check the exact role names in Role Details and edit the list at the top of the query. A Rowseta session limited to one course cannot read Role Details, so sign in directly. |
| A topic is not counted | Hidden topics, deleted topics and modules are left out. Only topics in the course itself count. |
| Completed stays at 0 | Content User Completion has no completion date for those topics. Check that the data set is loaded. One possible cause is that completion tracking is off for those topics. |