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.

sql
-- 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 seeWhat to do
No learners appearOne 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 countedHidden topics, deleted topics and modules are left out. Only topics in the course itself count.
Completed stays at 0Content 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.

Next guides

At-risk students with Brightspace Data Sets: who has not opened a course in 5 daysHow to join Brightspace Data Sets: keys and a worked query