From d73728da3ad3975c9ccf6376cb8264340e20cf7d Mon Sep 17 00:00:00 2001 From: Tobi Awe Date: Thu, 23 Apr 2026 09:07:11 +0100 Subject: [PATCH 1/3] TD-7078 Creation of a read model for dashboard resources. --- .../Resources/ResourceVersionRepository.cs | 8 +- .../LearningHub.Nhs.Database.sqlproj | 9 + .../Post-Deploy/Script.PostDeployment.sql | 1 + .../Scripts/TD-7078-supportingindexes.sql | 124 +++++++++++ ...tMyRecentCompletedDashboardResourcesRM.sql | 101 +++++++++ .../GetPopularDashboardResourcesRM.sql | 90 ++++++++ .../GetRatedDashboardResourcesRM.sql | 91 ++++++++ .../GetRecentDashboardResourcesRM.sql | 90 ++++++++ .../Resources/RefreshDashboardResources.sql | 176 +++++++++++++++ .../Resources/RefreshUserResourceActivity.sql | 202 ++++++++++++++++++ .../Tables/Report/DashboardResource.sql | 21 ++ .../Tables/Report/UserResourceActivity.sql | 9 + 12 files changed, 918 insertions(+), 4 deletions(-) create mode 100644 WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7078-supportingindexes.sql create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyRecentCompletedDashboardResourcesRM.sql create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetPopularDashboardResourcesRM.sql create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRatedDashboardResourcesRM.sql create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRecentDashboardResourcesRM.sql create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/RefreshDashboardResources.sql create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/RefreshUserResourceActivity.sql create mode 100644 WebAPI/LearningHub.Nhs.Database/Tables/Report/DashboardResource.sql create mode 100644 WebAPI/LearningHub.Nhs.Database/Tables/Report/UserResourceActivity.sql diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRepository.cs index 47d302e0c..8a3d74678 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRepository.cs @@ -732,16 +732,16 @@ public List GetContributions(int userId, ResourceContri switch (dashboardType) { case "my-recent-completed": - dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetMyRecentCompletedDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); + dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetMyRecentCompletedDashboardResourcesRM @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); break; case "recent-resources": - dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetRecentDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); + dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetRecentDashboardResourcesRM @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); break; case "rated-resources": - dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetRatedDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); + dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetRatedDashboardResourcesRM @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); break; case "popular-resources": - dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetPopularDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); + dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetPopularDashboardResourcesRM @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); break; } diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index 5b4022af9..b1892bd14 100644 --- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj +++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj @@ -570,6 +570,15 @@ + + + + + + + + + diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql index 2b2bcda6e..c7a40eb66 100644 --- a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql @@ -88,3 +88,4 @@ UPDATE [resources].[ResourceVersion] SET CertificateEnabled = 0 WHERE VersionSta :r .\Scripts\UpdateFileTypes.sql :r .\Scripts\TD-7116-mib_new_env.sql :r .\Scripts\TD-7106-Resume-Databricks-Ingestion.sql +:r .\Scripts\TD-7078-supportingindexes.sql diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7078-supportingindexes.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7078-supportingindexes.sql new file mode 100644 index 000000000..92c527010 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7078-supportingindexes.sql @@ -0,0 +1,124 @@ + + -- reports.DashboardResources + + +IF OBJECT_ID('reports.DashboardResources', 'U') IS NOT NULL +AND NOT EXISTS +( + SELECT 1 + FROM sys.indexes + WHERE name = 'IX_DashboardResources_ResourceId' + AND object_id = OBJECT_ID('reports.DashboardResources') +) +BEGIN + CREATE INDEX IX_DashboardResources_ResourceId + ON reports.DashboardResources (ResourceId); +END; +GO + +IF OBJECT_ID('reports.DashboardResources', 'U') IS NOT NULL +AND NOT EXISTS +( + SELECT 1 + FROM sys.indexes + WHERE name = 'IX_DashboardResources_Recent' + AND object_id = OBJECT_ID('reports.DashboardResources') +) +BEGIN + CREATE INDEX IX_DashboardResources_Recent + ON reports.DashboardResources (PublishedDate DESC, Title) + INCLUDE + ( + ResourceId, + CatalogueNodeId, + ResourceReferenceId, + ResourceVersionId, + ResourceTypeId, + Description, + DurationInMilliseconds, + CatalogueName, + Url, + BadgeUrl, + RestrictedAccess, + AverageRating, + RatingCount + ); +END; +GO + +IF OBJECT_ID('reports.DashboardResources', 'U') IS NOT NULL +AND NOT EXISTS +( + SELECT 1 + FROM sys.indexes + WHERE name = 'IX_DashboardResources_Popular' + AND object_id = OBJECT_ID('reports.DashboardResources') +) +BEGIN + CREATE INDEX IX_DashboardResources_Popular + ON reports.DashboardResources (ActivityCount DESC, Title) + INCLUDE + ( + ResourceId, + CatalogueNodeId, + ResourceReferenceId, + ResourceVersionId, + ResourceTypeId, + Description, + DurationInMilliseconds, + CatalogueName, + Url, + BadgeUrl, + RestrictedAccess, + AverageRating, + RatingCount + ); +END; +GO + +IF OBJECT_ID('reports.DashboardResources', 'U') IS NOT NULL +AND NOT EXISTS +( + SELECT 1 + FROM sys.indexes + WHERE name = 'IX_DashboardResources_Rated' + AND object_id = OBJECT_ID('reports.DashboardResources') +) +BEGIN + CREATE INDEX IX_DashboardResources_Rated + ON reports.DashboardResources (AverageRating DESC, RatingCount DESC, Title) + INCLUDE + ( + ResourceId, + CatalogueNodeId, + ResourceReferenceId, + ResourceVersionId, + ResourceTypeId, + Description, + DurationInMilliseconds, + CatalogueName, + Url, + BadgeUrl, + RestrictedAccess + ) + WHERE RatingCount > 0; +END; +GO + + --reports.UserResourceActivity + + +IF OBJECT_ID('reports.UserResourceActivity', 'U') IS NOT NULL +AND NOT EXISTS +( + SELECT 1 + FROM sys.indexes + WHERE name = 'IX_UserResourceActivity_UserCompletedLatest' + AND object_id = OBJECT_ID('reports.UserResourceActivity') +) +BEGIN + CREATE INDEX IX_UserResourceActivity_UserCompletedLatest + ON reports.UserResourceActivity (UserId, IsCompleted, LatestActivityId DESC) + INCLUDE (ResourceId, LastAccessedDate); +END; +GO \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyRecentCompletedDashboardResourcesRM.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyRecentCompletedDashboardResourcesRM.sql new file mode 100644 index 000000000..8abb58fa6 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyRecentCompletedDashboardResourcesRM.sql @@ -0,0 +1,101 @@ +CREATE PROCEDURE [resources].[GetMyRecentCompletedDashboardResourcesRM] +( + @UserId INT, + @PageNumber INT = 1, + @TotalRecords INT OUTPUT +) +AS +BEGIN + SET NOCOUNT ON; + + DECLARE @MaxPageNumber INT = 4; + DECLARE @PageSize INT = 3; + DECLARE @MaxRows INT = @MaxPageNumber * @PageSize; + + IF @PageNumber > @MaxPageNumber + SET @PageNumber = @MaxPageNumber; + + DECLARE @Offset INT = (@PageNumber - 1) * @PageSize; + + SELECT DISTINCT + rug.CatalogueNodeId + INTO #Auth + FROM hub.RoleUserGroupView rug + JOIN hub.UserUserGroup uug + ON rug.UserGroupId = uug.UserGroupId + WHERE rug.ScopeTypeId = 1 + AND rug.RoleId IN (1,2,3) + AND uug.Deleted = 0 + AND uug.UserId = @UserId; + + CREATE CLUSTERED INDEX IX_Auth ON #Auth (CatalogueNodeId); + + SELECT TOP (@MaxRows) + ura.ResourceId, + ura.LatestActivityId + INTO #Candidates + FROM reports.UserResourceActivity ura + WHERE ura.UserId = @UserId + AND ura.IsCompleted = 1 + ORDER BY ura.LatestActivityId DESC; + + CREATE CLUSTERED INDEX IX_Candidates ON #Candidates (ResourceId, LatestActivityId); + + SELECT @TotalRecords = COUNT(*) FROM #Candidates; + + SELECT + dr.ResourceId, + dr.ResourceReferenceId AS ResourceReferenceID, + dr.ResourceVersionId, + dr.ResourceTypeId, + dr.Title, + dr.Description, + dr.DurationInMilliseconds, + dr.CatalogueName, + dr.Url, + dr.BadgeUrl, + dr.RestrictedAccess, + CAST( + CASE + WHEN dr.RestrictedAccess = 1 AND auth.CatalogueNodeId IS NULL THEN 0 + ELSE 1 + END AS BIT + ) AS HasAccess, + ub.Id AS BookMarkId, + CAST(ISNULL(ub.Deleted, 1) ^ 1 AS BIT) AS IsBookmarked, + CAST(dr.AverageRating AS DECIMAL(3,2)) AS AverageRating, + dr.RatingCount, + dr.ProvidersJson, + c.LatestActivityId AS SortLatestActivityId + INTO #Base + FROM #Candidates c + JOIN reports.DashboardResources dr + ON dr.ResourceId = c.ResourceId + LEFT JOIN #Auth auth + ON auth.CatalogueNodeId = dr.CatalogueNodeId + LEFT JOIN hub.UserBookmark ub + ON ub.UserId = @UserId + AND ub.ResourceReferenceId = dr.ResourceReferenceId; + + SELECT + ResourceId, + ResourceReferenceID, + ResourceVersionId, + ResourceTypeId, + Title, + Description, + DurationInMilliseconds, + CatalogueName, + Url, + BadgeUrl, + RestrictedAccess, + HasAccess, + BookMarkId, + IsBookmarked, + AverageRating, + RatingCount, + ProvidersJson + FROM #Base + ORDER BY SortLatestActivityId DESC, Title + OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY; +END; \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetPopularDashboardResourcesRM.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetPopularDashboardResourcesRM.sql new file mode 100644 index 000000000..9102a1a30 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetPopularDashboardResourcesRM.sql @@ -0,0 +1,90 @@ +CREATE PROCEDURE [resources].[GetPopularDashboardResourcesRM] +( + @UserId INT, + @PageNumber INT = 1, + @TotalRecords INT OUTPUT +) +AS +BEGIN + SET NOCOUNT ON; + + DECLARE @MaxPageNumber INT = 4; + DECLARE @PageSize INT = 3; + DECLARE @MaxRows INT = @MaxPageNumber * @PageSize; + + IF @PageNumber > @MaxPageNumber + SET @PageNumber = @MaxPageNumber; + + DECLARE @Offset INT = (@PageNumber - 1) * @PageSize; + + SELECT DISTINCT + rug.CatalogueNodeId + INTO #Auth + FROM hub.RoleUserGroupView rug + JOIN hub.UserUserGroup uug + ON rug.UserGroupId = uug.UserGroupId + WHERE rug.ScopeTypeId = 1 + AND rug.RoleId IN (1,2,3) + AND uug.Deleted = 0 + AND uug.UserId = @UserId; + + CREATE CLUSTERED INDEX IX_Auth ON #Auth (CatalogueNodeId); + + SELECT TOP (@MaxRows) + dr.ResourceId, + dr.ResourceReferenceId AS ResourceReferenceID, + dr.ResourceVersionId, + dr.ResourceTypeId, + dr.Title, + dr.Description, + dr.DurationInMilliseconds, + dr.CatalogueName, + dr.Url, + dr.BadgeUrl, + dr.RestrictedAccess, + CAST( + CASE + WHEN dr.RestrictedAccess = 1 AND auth.CatalogueNodeId IS NULL THEN 0 + ELSE 1 + END AS BIT + ) AS HasAccess, + ub.Id AS BookMarkId, + CAST(ISNULL(ub.Deleted, 1) ^ 1 AS BIT) AS IsBookmarked, + CAST(dr.AverageRating AS DECIMAL(3,2)) AS AverageRating, + dr.RatingCount, + dr.ProvidersJson, + dr.ActivityCount AS SortActivityCount + INTO #Base + FROM reports.DashboardResources dr + LEFT JOIN #Auth auth + ON auth.CatalogueNodeId = dr.CatalogueNodeId + LEFT JOIN hub.UserBookmark ub + ON ub.UserId = @UserId + AND ub.ResourceReferenceId = dr.ResourceReferenceId + ORDER BY dr.ActivityCount DESC, dr.Title; + + SELECT @TotalRecords = COUNT(*) FROM #Base; + + SELECT + ResourceId, + ResourceReferenceID, + ResourceVersionId, + ResourceTypeId, + Title, + Description, + DurationInMilliseconds, + CatalogueName, + Url, + BadgeUrl, + RestrictedAccess, + HasAccess, + BookMarkId, + IsBookmarked, + AverageRating, + RatingCount, + ProvidersJson + FROM #Base + ORDER BY SortActivityCount DESC, Title + OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY; +END; +GO \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRatedDashboardResourcesRM.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRatedDashboardResourcesRM.sql new file mode 100644 index 000000000..f89216db4 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRatedDashboardResourcesRM.sql @@ -0,0 +1,91 @@ +CREATE PROCEDURE [resources].[GetRatedDashboardResourcesRM] +( + @UserId INT, + @PageNumber INT = 1, + @TotalRecords INT OUTPUT +) +AS +BEGIN + SET NOCOUNT ON; + + DECLARE @MaxPageNumber INT = 4; + DECLARE @PageSize INT = 3; + DECLARE @MaxRows INT = @MaxPageNumber * @PageSize; + + IF @PageNumber > @MaxPageNumber + SET @PageNumber = @MaxPageNumber; + + DECLARE @Offset INT = (@PageNumber - 1) * @PageSize; + + SELECT DISTINCT + rug.CatalogueNodeId + INTO #Auth + FROM hub.RoleUserGroupView rug + JOIN hub.UserUserGroup uug + ON rug.UserGroupId = uug.UserGroupId + WHERE rug.ScopeTypeId = 1 + AND rug.RoleId IN (1,2,3) + AND uug.Deleted = 0 + AND uug.UserId = @UserId; + + CREATE CLUSTERED INDEX IX_Auth ON #Auth (CatalogueNodeId); + + SELECT TOP (@MaxRows) + dr.ResourceId, + dr.ResourceReferenceId AS ResourceReferenceID, + dr.ResourceVersionId, + dr.ResourceTypeId, + dr.Title, + dr.Description, + dr.DurationInMilliseconds, + dr.CatalogueName, + dr.Url, + dr.BadgeUrl, + dr.RestrictedAccess, + CAST( + CASE + WHEN dr.RestrictedAccess = 1 AND auth.CatalogueNodeId IS NULL THEN 0 + ELSE 1 + END AS BIT + ) AS HasAccess, + ub.Id AS BookMarkId, + CAST(ISNULL(ub.Deleted, 1) ^ 1 AS BIT) AS IsBookmarked, + CAST(dr.AverageRating AS DECIMAL(3,2)) AS AverageRating, + dr.RatingCount, + dr.ProvidersJson, + CAST(dr.AverageRating AS DECIMAL(3,2)) AS SortAverageRating, + dr.RatingCount AS SortRatingCount + INTO #Base + FROM reports.DashboardResources dr + LEFT JOIN #Auth auth + ON auth.CatalogueNodeId = dr.CatalogueNodeId + LEFT JOIN hub.UserBookmark ub + ON ub.UserId = @UserId + AND ub.ResourceReferenceId = dr.ResourceReferenceId + WHERE dr.RatingCount > 0 + ORDER BY dr.AverageRating DESC, dr.RatingCount DESC, dr.Title; + + SELECT @TotalRecords = COUNT(*) FROM #Base; + + SELECT + ResourceId, + ResourceReferenceID, + ResourceVersionId, + ResourceTypeId, + Title, + Description, + DurationInMilliseconds, + CatalogueName, + Url, + BadgeUrl, + RestrictedAccess, + HasAccess, + BookMarkId, + IsBookmarked, + AverageRating, + RatingCount, + ProvidersJson + FROM #Base + ORDER BY SortAverageRating DESC, SortRatingCount DESC, Title + OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY; +END; \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRecentDashboardResourcesRM.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRecentDashboardResourcesRM.sql new file mode 100644 index 000000000..82cdf615c --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRecentDashboardResourcesRM.sql @@ -0,0 +1,90 @@ +CREATE PROCEDURE [resources].[GetRecentDashboardResourcesRM] +( + @UserId INT, + @PageNumber INT = 1, + @TotalRecords INT OUTPUT +) +AS +BEGIN + SET NOCOUNT ON; + + DECLARE @MaxPageNumber INT = 4; + DECLARE @PageSize INT = 3; + DECLARE @MaxRows INT = @MaxPageNumber * @PageSize; + + IF @PageNumber > @MaxPageNumber + SET @PageNumber = @MaxPageNumber; + + DECLARE @Offset INT = (@PageNumber - 1) * @PageSize; + + SELECT DISTINCT + rug.CatalogueNodeId + INTO #Auth + FROM hub.RoleUserGroupView rug + JOIN hub.UserUserGroup uug + ON rug.UserGroupId = uug.UserGroupId + WHERE rug.ScopeTypeId = 1 + AND rug.RoleId IN (1,2,3) + AND uug.Deleted = 0 + AND uug.UserId = @UserId; + + CREATE CLUSTERED INDEX IX_Auth ON #Auth (CatalogueNodeId); + + SELECT TOP (@MaxRows) + dr.ResourceId, + dr.ResourceVersionId, + dr.ResourceTypeId, + dr.Title, + dr.Description, + CAST(dr.AverageRating AS DECIMAL(3,2)) AS AverageRating, + dr.RatingCount, + dr.PublishedDate AS CreateDate, + dr.CatalogueName, + dr.Url, + dr.BadgeUrl, + dr.RestrictedAccess, + dr.ProvidersJson, + dr.DurationInMilliseconds, + dr.ResourceReferenceId, + ub.Id AS BookMarkId, + CAST(ISNULL(ub.Deleted, 1) ^ 1 AS BIT) AS IsBookmarked, + CAST( + CASE + WHEN dr.RestrictedAccess = 1 AND auth.CatalogueNodeId IS NULL THEN 0 + ELSE 1 + END AS BIT + ) AS HasAccess + INTO #Base + FROM reports.DashboardResources dr + LEFT JOIN #Auth auth + ON auth.CatalogueNodeId = dr.CatalogueNodeId + LEFT JOIN hub.UserBookmark ub + ON ub.UserId = @UserId + AND ub.ResourceReferenceId = dr.ResourceReferenceId + ORDER BY dr.PublishedDate DESC, dr.Title; + + SELECT @TotalRecords = COUNT(*) FROM #Base; + + SELECT + ResourceId, + ResourceVersionId, + ResourceTypeId, + Title, + Description, + AverageRating, + RatingCount, + CreateDate, + CatalogueName, + Url, + BadgeUrl, + RestrictedAccess, + ProvidersJson, + DurationInMilliseconds, + ResourceReferenceId, + BookMarkId, + IsBookmarked, + HasAccess + FROM #Base + ORDER BY CreateDate DESC, Title + OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY; +END; \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/RefreshDashboardResources.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/RefreshDashboardResources.sql new file mode 100644 index 000000000..748b6da7f --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/RefreshDashboardResources.sql @@ -0,0 +1,176 @@ +CREATE PROCEDURE [reports].[RefreshDashboardResources] +AS +BEGIN + SET NOCOUNT ON; + SET XACT_ABORT ON; + + BEGIN TRAN; + + BEGIN TRY + IF OBJECT_ID('tempdb..#ActivityCounts') IS NOT NULL DROP TABLE #ActivityCounts; + IF OBJECT_ID('tempdb..#Providers') IS NOT NULL DROP TABLE #Providers; + IF OBJECT_ID('tempdb..#RefMax') IS NOT NULL DROP TABLE #RefMax; + IF OBJECT_ID('tempdb..#Ref') IS NOT NULL DROP TABLE #Ref; + + /* Precompute activity counts */ + SELECT + ra.ResourceId, + COUNT(ra.ResourceVersionId) AS ActivityCount + INTO #ActivityCounts + FROM activity.ResourceActivity ra + GROUP BY ra.ResourceId; + + CREATE CLUSTERED INDEX IX_ActivityCounts + ON #ActivityCounts (ResourceId); + + /* Precompute providers JSON */ + SELECT + rp.ResourceVersionId, + ProvidersJson = ( + SELECT + p.Id, + p.Name, + p.Description, + p.Logo + FROM resources.ResourceVersionProvider rp2 + JOIN hub.Provider p + ON p.Id = rp2.ProviderId + WHERE rp2.ResourceVersionId = rp.ResourceVersionId + AND rp2.Deleted = 0 + AND p.Deleted = 0 + FOR JSON PATH + ) + INTO #Providers + FROM resources.ResourceVersionProvider rp + WHERE rp.Deleted = 0 + GROUP BY rp.ResourceVersionId; + + CREATE CLUSTERED INDEX IX_Providers + ON #Providers (ResourceVersionId); + + /* Latest ResourceReference per (ResourceId, CatalogueNodeId) */ + SELECT + rr.ResourceId, + np.NodeId AS CatalogueNodeId, + MAX(rr.Id) AS MaxResourceReferenceId + INTO #RefMax + FROM resources.ResourceReference rr + JOIN hierarchy.NodePath np + ON np.Id = rr.NodePathId + WHERE rr.Deleted = 0 + AND np.Deleted = 0 + GROUP BY + rr.ResourceId, + np.NodeId; + + CREATE CLUSTERED INDEX IX_RefMax + ON #RefMax (ResourceId, CatalogueNodeId); + + SELECT + rm.ResourceId, + rm.CatalogueNodeId, + rr.OriginalResourceReferenceId + INTO #Ref + FROM #RefMax rm + JOIN resources.ResourceReference rr + ON rr.Id = rm.MaxResourceReferenceId; + + CREATE CLUSTERED INDEX IX_Ref + ON #Ref (ResourceId, CatalogueNodeId); + + + TRUNCATE TABLE reports.DashboardResources; + + INSERT INTO reports.DashboardResources + ( + ResourceId, + ResourceReferenceId, + ResourceVersionId, + ResourceTypeId, + Title, + Description, + DurationInMilliseconds, + CatalogueNodeId, + CatalogueName, + Url, + BadgeUrl, + RestrictedAccess, + PublishedDate, + AverageRating, + RatingCount, + ActivityCount, + ProvidersJson + ) + SELECT + r.Id AS ResourceId, + rf.OriginalResourceReferenceId AS ResourceReferenceId, + rv.Id AS ResourceVersionId, + r.ResourceTypeId, + rv.Title, + rv.Description, + CASE + WHEN r.ResourceTypeId = 7 THEN vrv.DurationInMilliseconds + WHEN r.ResourceTypeId = 2 THEN arv.DurationInMilliseconds + ELSE NULL + END AS DurationInMilliseconds, + n.Id AS CatalogueNodeId, + CASE WHEN n.Id = 1 THEN NULL ELSE cnv.Name END AS CatalogueName, + cnv.Url, + CASE WHEN n.Id = 1 THEN NULL ELSE cnv.BadgeUrl END AS BadgeUrl, + cnv.RestrictedAccess, + pub.CreateDate AS PublishedDate, + CAST(rvrs.AverageRating AS DECIMAL(3,2)) AS AverageRating, + rvrs.RatingCount, + ISNULL(ac.ActivityCount, 0) AS ActivityCount, + prov.ProvidersJson + FROM resources.Resource r + JOIN resources.ResourceVersion rv + ON rv.Id = r.CurrentResourceVersionId + AND rv.Deleted = 0 + AND rv.VersionStatusId = 2 + JOIN hierarchy.Publication pub + ON pub.Id = rv.PublicationId + AND pub.Deleted = 0 + JOIN hierarchy.NodeResource nr + ON nr.ResourceId = r.Id + AND nr.Deleted = 0 + JOIN hierarchy.Node n + ON n.Id = nr.NodeId + AND n.Hidden = 0 + AND n.Deleted = 0 + JOIN hierarchy.NodePath np + ON np.NodeId = n.Id + AND np.Deleted = 0 + AND np.IsActive = 1 + JOIN hierarchy.NodeVersion nv + ON nv.NodeId = np.CatalogueNodeId + AND nv.VersionStatusId = 2 + AND nv.Deleted = 0 + JOIN hierarchy.CatalogueNodeVersion cnv + ON cnv.NodeVersionId = nv.Id + AND cnv.Deleted = 0 + LEFT JOIN #Ref rf + ON rf.ResourceId = r.Id + AND rf.CatalogueNodeId = n.Id + LEFT JOIN #ActivityCounts ac + ON ac.ResourceId = r.Id + LEFT JOIN #Providers prov + ON prov.ResourceVersionId = rv.Id + LEFT JOIN resources.VideoResourceVersion vrv + ON vrv.ResourceVersionId = rv.Id + LEFT JOIN resources.AudioResourceVersion arv + ON arv.ResourceVersionId = rv.Id + LEFT JOIN resources.ResourceVersionRatingSummary rvrs + ON rvrs.ResourceVersionId = rv.Id + AND rvrs.Deleted = 0; + + COMMIT TRAN; + END TRY + BEGIN CATCH + IF @@TRANCOUNT > 0 + ROLLBACK TRAN; + + THROW; + END CATCH +END; +GO \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/RefreshUserResourceActivity.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/RefreshUserResourceActivity.sql new file mode 100644 index 000000000..78ff66a59 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/RefreshUserResourceActivity.sql @@ -0,0 +1,202 @@ +CREATE PROCEDURE [reports].[RefreshUserResourceActivity] +AS +BEGIN + SET NOCOUNT ON; + SET XACT_ABORT ON; + + DECLARE @LastProcessedActivityId INT; + + SELECT @LastProcessedActivityId = ISNULL(MAX(LatestActivityId), 0) + FROM reports.UserResourceActivity; + + IF OBJECT_ID('tempdb..#ChangedPairs') IS NOT NULL DROP TABLE #ChangedPairs; + IF OBJECT_ID('tempdb..#LatestIds') IS NOT NULL DROP TABLE #LatestIds; + IF OBJECT_ID('tempdb..#Latest') IS NOT NULL DROP TABLE #Latest; + IF OBJECT_ID('tempdb..#Ara') IS NOT NULL DROP TABLE #Ara; + IF OBJECT_ID('tempdb..#Mar') IS NOT NULL DROP TABLE #Mar; + IF OBJECT_ID('tempdb..#Sa') IS NOT NULL DROP TABLE #Sa; + IF OBJECT_ID('tempdb..#Completion') IS NOT NULL DROP TABLE #Completion; + + /* Find changed user-resource pairs from newly inserted activities */ + SELECT DISTINCT + a.UserId, + a.ResourceId + INTO #ChangedPairs + FROM activity.ResourceActivity a + WHERE a.Id > @LastProcessedActivityId + AND a.UserId IS NOT NULL + AND a.Deleted = 0; + + IF NOT EXISTS (SELECT 1 FROM #ChangedPairs) + RETURN; + + CREATE CLUSTERED INDEX IX_ChangedPairs + ON #ChangedPairs (UserId, ResourceId); + + /* Latest activity id per changed (UserId, ResourceId)*/ + SELECT + a.UserId, + a.ResourceId, + MAX(a.Id) AS LatestActivityId + INTO #LatestIds + FROM activity.ResourceActivity a + JOIN #ChangedPairs cp + ON cp.UserId = a.UserId + AND cp.ResourceId = a.ResourceId + WHERE a.Deleted = 0 + GROUP BY + a.UserId, + a.ResourceId; + + CREATE CLUSTERED INDEX IX_LatestIds + ON #LatestIds (UserId, ResourceId); + + /* Join back to get the full latest activity row */ + SELECT + a.UserId, + a.ResourceId, + a.Id AS ActivityId, + a.ResourceVersionId, + a.LaunchResourceActivityId, + a.ActivityStatusId, + a.ActivityStart, + a.ActivityEnd, + r.ResourceTypeId + INTO #Latest + FROM #LatestIds li + JOIN activity.ResourceActivity a + ON a.Id = li.LatestActivityId + JOIN resources.Resource r + ON r.Id = a.ResourceId; + + CREATE CLUSTERED INDEX IX_Latest + ON #Latest (UserId, ResourceId); + + + SELECT + ara.ResourceActivityId, + MAX(ara.Score) AS Score + INTO #Ara + FROM activity.AssessmentResourceActivity ara + JOIN #Latest l + ON l.ResourceTypeId = 11 + AND ara.ResourceActivityId = COALESCE(l.LaunchResourceActivityId, l.ActivityId) + WHERE ara.Score IS NOT NULL + GROUP BY ara.ResourceActivityId; + + CREATE CLUSTERED INDEX IX_Ara + ON #Ara (ResourceActivityId); + + SELECT + mar.ResourceActivityId, + MAX(mar.PercentComplete) AS PercentComplete + INTO #Mar + FROM activity.MediaResourceActivity mar + JOIN #Latest l + ON l.ResourceTypeId IN (2,7) + AND mar.ResourceActivityId = COALESCE(l.LaunchResourceActivityId, l.ActivityId) + WHERE mar.PercentComplete IS NOT NULL + GROUP BY mar.ResourceActivityId; + + CREATE CLUSTERED INDEX IX_Mar + ON #Mar (ResourceActivityId); + + SELECT + sa.ResourceActivityId, + MAX(sa.CmiCoreLesson_status) AS CmiCoreLesson_status + INTO #Sa + FROM activity.ScormActivity sa + JOIN #Latest l + ON l.ResourceTypeId = 6 + AND sa.ResourceActivityId = l.ActivityId + WHERE sa.CmiCoreLesson_status IS NOT NULL + GROUP BY sa.ResourceActivityId; + + CREATE CLUSTERED INDEX IX_Sa + ON #Sa (ResourceActivityId); + + /* Recompute completion and latest-access state */ + SELECT + l.UserId, + l.ResourceId, + l.ActivityId AS LatestActivityId, + CAST(COALESCE(l.ActivityStart, l.ActivityEnd) AS DATETIME2) AS LastAccessedDate, + CAST( + CASE + WHEN l.ResourceTypeId IN (2,7) + AND l.ActivityStatusId = 3 + AND ( + (mar.ResourceActivityId IS NOT NULL AND mar.PercentComplete = 100) + OR l.ActivityStart < '2020-09-07' + ) + THEN 1 + + WHEN l.ResourceTypeId = 6 + AND ( + sa.CmiCoreLesson_status IN (3,5) + OR l.ActivityStatusId IN (3,5) + ) + THEN 1 + + WHEN l.ResourceTypeId = 11 + AND ( + ara.Score >= arv.PassMark + OR l.ActivityStatusId IN (3,5) + ) + THEN 1 + + WHEN l.ResourceTypeId IN (1,5,8,9,10,12) + AND l.ActivityStatusId = 3 + THEN 1 + + ELSE 0 + END AS BIT + ) AS IsCompleted + INTO #Completion + FROM #Latest l + LEFT JOIN resources.AssessmentResourceVersion arv + ON arv.ResourceVersionId = l.ResourceVersionId + LEFT JOIN #Ara ara + ON ara.ResourceActivityId = COALESCE(l.LaunchResourceActivityId, l.ActivityId) + LEFT JOIN #Mar mar + ON mar.ResourceActivityId = COALESCE(l.LaunchResourceActivityId, l.ActivityId) + LEFT JOIN #Sa sa + ON sa.ResourceActivityId = l.ActivityId + WHERE COALESCE(l.ActivityStart, l.ActivityEnd) IS NOT NULL; + + CREATE CLUSTERED INDEX IX_Completion + ON #Completion (UserId, ResourceId); + + /* Update existing rows */ + UPDATE ura + SET + ura.LatestActivityId = c.LatestActivityId, + ura.IsCompleted = c.IsCompleted, + ura.LastAccessedDate = c.LastAccessedDate + FROM reports.UserResourceActivity ura + JOIN #Completion c + ON c.UserId = ura.UserId + AND c.ResourceId = ura.ResourceId; + + /* Insert new rows */ + INSERT INTO reports.UserResourceActivity + ( + UserId, + ResourceId, + LatestActivityId, + IsCompleted, + LastAccessedDate + ) + SELECT + c.UserId, + c.ResourceId, + c.LatestActivityId, + c.IsCompleted, + c.LastAccessedDate + FROM #Completion c + LEFT JOIN reports.UserResourceActivity ura + ON ura.UserId = c.UserId + AND ura.ResourceId = c.ResourceId + WHERE ura.UserId IS NULL; +END; +GO \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Tables/Report/DashboardResource.sql b/WebAPI/LearningHub.Nhs.Database/Tables/Report/DashboardResource.sql new file mode 100644 index 000000000..a392e4941 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Tables/Report/DashboardResource.sql @@ -0,0 +1,21 @@ +CREATE TABLE [reports].[DashboardResources] ( + ResourceId INT NOT NULL, + ResourceReferenceId INT NULL, + ResourceVersionId INT NOT NULL, + ResourceTypeId INT NOT NULL, + Title NVARCHAR(500) NOT NULL, + Description NVARCHAR(MAX) NULL, + DurationInMilliseconds INT NULL, + CatalogueNodeId INT NOT NULL, + CatalogueName NVARCHAR(255) NULL, + Url NVARCHAR(500) NULL, + BadgeUrl NVARCHAR(500) NULL, + RestrictedAccess BIT NOT NULL, + PublishedDate DATETIME2 NOT NULL, + AverageRating DECIMAL(3,2) NULL, + RatingCount INT NULL, + ActivityCount INT NOT NULL DEFAULT 0, + ProvidersJson NVARCHAR(MAX) NULL, + CONSTRAINT PK_DashboardResources PRIMARY KEY (ResourceId, CatalogueNodeId) +); +GO \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Tables/Report/UserResourceActivity.sql b/WebAPI/LearningHub.Nhs.Database/Tables/Report/UserResourceActivity.sql new file mode 100644 index 000000000..7f128ae9b --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Tables/Report/UserResourceActivity.sql @@ -0,0 +1,9 @@ +CREATE TABLE [reports].[UserResourceActivity] ( + UserId INT NOT NULL, + ResourceId INT NOT NULL, + LatestActivityId INT NOT NULL, + IsCompleted BIT NOT NULL, + LastAccessedDate DATETIME2 NOT NULL, + CONSTRAINT PK_UserResourceActivity PRIMARY KEY (UserId, ResourceId) +); +GO \ No newline at end of file From 8ff746678391974cc8d5ffb8cc959764f2814d0b Mon Sep 17 00:00:00 2001 From: Tobi Awe Date: Thu, 23 Apr 2026 11:27:14 +0100 Subject: [PATCH 2/3] dashboard optimization update --- .../CatalogueNodeVersionRepository.cs | 2 +- .../LearningHub.Nhs.Database.sqlproj | 10 +- .../Scripts/TD-7078-supportingindexes.sql | 114 ++++++++++ .../Hierarchy/GetDashboardCataloguesRM.sql | 215 ++++++++++++++++++ .../Report/RefreshDashboardCatalogues.sql | 159 +++++++++++++ .../RefreshDashboardResources.sql | 11 +- .../Report/RefreshUserCatalogueActivity.sql | 105 +++++++++ .../RefreshUserResourceActivity.sql | 11 +- ...tMyRecentCompletedDashboardResourcesRM.sql | 11 +- .../GetPopularDashboardResourcesRM.sql | 11 +- .../GetRatedDashboardResourcesRM.sql | 11 +- .../GetRecentDashboardResourcesRM.sql | 11 +- .../Tables/Report/DashboardCatalogue.sql | 19 ++ .../Tables/Report/UserCatalogueActivity.sql | 8 + 14 files changed, 689 insertions(+), 9 deletions(-) create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/GetDashboardCataloguesRM.sql create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshDashboardCatalogues.sql rename WebAPI/LearningHub.Nhs.Database/Stored Procedures/{Resources => Report}/RefreshDashboardResources.sql (93%) create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshUserCatalogueActivity.sql rename WebAPI/LearningHub.Nhs.Database/Stored Procedures/{Resources => Report}/RefreshUserResourceActivity.sql (93%) create mode 100644 WebAPI/LearningHub.Nhs.Database/Tables/Report/DashboardCatalogue.sql create mode 100644 WebAPI/LearningHub.Nhs.Database/Tables/Report/UserCatalogueActivity.sql diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs index 32932e648..76ae6c8e5 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs @@ -364,7 +364,7 @@ public RestrictedCatalogueSummaryViewModel GetRestrictedCatalogueSummary(int cat var param2 = new SqlParameter("@pageNumber", SqlDbType.Int) { Value = pageNumber }; var param3 = new SqlParameter("@TotalRecords", SqlDbType.Int) { Direction = ParameterDirection.Output }; - var dashboardCatalogues = DbContext.DashboardCatalogueDto.FromSqlRaw("[hierarchy].[GetDashboardCatalogues] @DashboardType, @UserId, @pageNumber, @TotalRecords OUTPUT", param0, param1, param2, param3).ToList(); + var dashboardCatalogues = DbContext.DashboardCatalogueDto.FromSqlRaw("[hierarchy].[GetDashboardCataloguesRM] @DashboardType, @UserId, @pageNumber, @TotalRecords OUTPUT", param0, param1, param2, param3).ToList(); return (TotalCount: (int)param3.Value, Catalogues: dashboardCatalogues); } diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index b1892bd14..a74dad0e8 100644 --- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj +++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj @@ -103,6 +103,7 @@ + @@ -572,13 +573,18 @@ - - + + + + + + + diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7078-supportingindexes.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7078-supportingindexes.sql index 92c527010..5fcd750de 100644 --- a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7078-supportingindexes.sql +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7078-supportingindexes.sql @@ -121,4 +121,118 @@ BEGIN ON reports.UserResourceActivity (UserId, IsCompleted, LatestActivityId DESC) INCLUDE (ResourceId, LastAccessedDate); END; +GO + + +IF OBJECT_ID('reports.DashboardCatalogues', 'U') IS NOT NULL +AND NOT EXISTS +( + SELECT 1 + FROM sys.indexes + WHERE name = 'IX_DashboardCatalogues_Name' + AND object_id = OBJECT_ID('reports.DashboardCatalogues') +) +BEGIN + CREATE INDEX IX_DashboardCatalogues_Name + ON reports.DashboardCatalogues (Name); +END; +GO + +IF OBJECT_ID('reports.DashboardCatalogues', 'U') IS NOT NULL +AND NOT EXISTS +( + SELECT 1 + FROM sys.indexes + WHERE name = 'IX_DashboardCatalogues_Popular' + AND object_id = OBJECT_ID('reports.DashboardCatalogues') +) +BEGIN + CREATE INDEX IX_DashboardCatalogues_Popular + ON reports.DashboardCatalogues (CatalogueActivityCount DESC, Name) + INCLUDE + ( + NodeVersionId, + CatalogueNodeVersionId, + Description, + BannerUrl, + BadgeUrl, + CardImageUrl, + Url, + RestrictedAccess, + LastShownDate, + ContributedResourceCount, + SumResourceAverageRating + ); +END; +GO + +IF OBJECT_ID('reports.DashboardCatalogues', 'U') IS NOT NULL +AND NOT EXISTS +( + SELECT 1 + FROM sys.indexes + WHERE name = 'IX_DashboardCatalogues_Recent' + AND object_id = OBJECT_ID('reports.DashboardCatalogues') +) +BEGIN + CREATE INDEX IX_DashboardCatalogues_Recent + ON reports.DashboardCatalogues (LastShownDate DESC, Name) + INCLUDE + ( + NodeVersionId, + CatalogueNodeVersionId, + Description, + BannerUrl, + BadgeUrl, + CardImageUrl, + Url, + RestrictedAccess, + CatalogueActivityCount, + ContributedResourceCount, + SumResourceAverageRating + ) + WHERE LastShownDate IS NOT NULL; +END; +GO + +IF OBJECT_ID('reports.DashboardCatalogues', 'U') IS NOT NULL +AND NOT EXISTS +( + SELECT 1 + FROM sys.indexes + WHERE name = 'IX_DashboardCatalogues_HighlyContributed' + AND object_id = OBJECT_ID('reports.DashboardCatalogues') +) +BEGIN + CREATE INDEX IX_DashboardCatalogues_HighlyContributed + ON reports.DashboardCatalogues (ContributedResourceCount DESC, SumResourceAverageRating DESC, Name) + INCLUDE + ( + NodeVersionId, + CatalogueNodeVersionId, + Description, + BannerUrl, + BadgeUrl, + CardImageUrl, + Url, + RestrictedAccess, + LastShownDate, + CatalogueActivityCount + ); +END; +GO + +IF OBJECT_ID('reports.UserCatalogueActivity', 'U') IS NOT NULL +AND NOT EXISTS +( + SELECT 1 + FROM sys.indexes + WHERE name = 'IX_UserCatalogueActivity_User_Latest' + AND object_id = OBJECT_ID('reports.UserCatalogueActivity') +) +BEGIN + CREATE INDEX IX_UserCatalogueActivity_User_Latest + ON reports.UserCatalogueActivity (UserId, LatestActivityId DESC) + INCLUDE (CatalogueNodeId, LastAccessedDate); +END; GO \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/GetDashboardCataloguesRM.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/GetDashboardCataloguesRM.sql new file mode 100644 index 000000000..bdff1912c --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/GetDashboardCataloguesRM.sql @@ -0,0 +1,215 @@ +------------------------------------------------------------------------------- +-- Author OA +-- Created 23 April 2026 +-- Purpose Gets the catalogues to be displayed on dashboard from read models +-- +-- Modification History +-- +-- 23 April 2026 OA TD-7078 Script Optimization +------------------------------------------------------------------------------- +CREATE PROCEDURE [hierarchy].[GetDashboardCataloguesRM] + @DashboardType NVARCHAR(30), + @UserId INT, + @PageNumber INT = 1, + @TotalRecords INT OUTPUT +AS +BEGIN + SET NOCOUNT ON; + + DECLARE @MaxPageNumber INT = 4; + DECLARE @FetchRows INT = 3; + + IF @DashboardType = 'all-catalogues' + SET @FetchRows = CASE WHEN @PageNumber = -1 THEN 100000 ELSE 9 END; + + IF @PageNumber > @MaxPageNumber AND @DashboardType <> 'all-catalogues' + SET @PageNumber = @MaxPageNumber; + + DECLARE @OffsetRows INT = (@PageNumber - 1) * @FetchRows; + DECLARE @MaxRows INT = @MaxPageNumber * @FetchRows; + + /* Auth stays live */ + SELECT DISTINCT + CatalogueNodeId + INTO #Auth + FROM hub.RoleUserGroupView rug + JOIN hub.UserUserGroup uug + ON rug.UserGroupId = uug.UserGroupId + WHERE rug.ScopeTypeId = 1 + AND rug.RoleId IN (1,2,3) + AND uug.Deleted = 0 + AND uug.UserId = @UserId; + + CREATE CLUSTERED INDEX IX_Auth ON #Auth (CatalogueNodeId); + + IF @DashboardType = 'popular-catalogues' + BEGIN + SELECT @TotalRecords = + CASE WHEN COUNT(*) > 12 THEN @MaxRows ELSE COUNT(*) END + FROM reports.DashboardCatalogues dc + WHERE dc.CatalogueActivityCount > 0; + + SELECT + dc.CatalogueNodeId AS NodeId, + dc.NodeVersionId, + dc.Name, + dc.Description, + dc.BannerUrl, + dc.BadgeUrl, + dc.CardImageUrl, + dc.Url, + dc.RestrictedAccess, + CAST(CASE + WHEN dc.RestrictedAccess = 1 AND a.CatalogueNodeId IS NULL THEN 0 + ELSE 1 + END AS BIT) AS HasAccess, + ub.Id AS BookMarkId, + CAST(ISNULL(ub.Deleted,1) ^ 1 AS BIT) AS IsBookmarked, + dc.ProvidersJson + FROM reports.DashboardCatalogues dc + LEFT JOIN #Auth a + ON a.CatalogueNodeId = dc.CatalogueNodeId + LEFT JOIN hub.UserBookmark ub + ON ub.UserId = @UserId + AND ub.NodeId = dc.CatalogueNodeId + WHERE dc.CatalogueActivityCount > 0 + ORDER BY dc.CatalogueActivityCount DESC, dc.Name + OFFSET @OffsetRows ROWS FETCH NEXT @FetchRows ROWS ONLY; + END + ELSE IF @DashboardType = 'recent-catalogues' + BEGIN + SELECT @TotalRecords = + CASE WHEN COUNT(*) > 12 THEN @MaxRows ELSE COUNT(*) END + FROM reports.DashboardCatalogues dc + WHERE dc.LastShownDate IS NOT NULL; + + SELECT + dc.CatalogueNodeId AS NodeId, + dc.NodeVersionId, + dc.Name, + dc.Description, + dc.BannerUrl, + dc.BadgeUrl, + dc.CardImageUrl, + dc.Url, + dc.RestrictedAccess, + CAST(CASE + WHEN dc.RestrictedAccess = 1 AND a.CatalogueNodeId IS NULL THEN 0 + ELSE 1 + END AS BIT) AS HasAccess, + ub.Id AS BookMarkId, + CAST(ISNULL(ub.Deleted,1) ^ 1 AS BIT) AS IsBookmarked, + dc.ProvidersJson + FROM reports.DashboardCatalogues dc + LEFT JOIN #Auth a + ON a.CatalogueNodeId = dc.CatalogueNodeId + LEFT JOIN hub.UserBookmark ub + ON ub.UserId = @UserId + AND ub.NodeId = dc.CatalogueNodeId + WHERE dc.LastShownDate IS NOT NULL + ORDER BY dc.CatalogueNodeId DESC + OFFSET @OffsetRows ROWS FETCH NEXT @FetchRows ROWS ONLY; + END + ELSE IF @DashboardType = 'highly-contributed-catalogues' + BEGIN + SELECT @TotalRecords = + CASE WHEN COUNT(*) > 12 THEN @MaxRows ELSE COUNT(*) END + FROM reports.DashboardCatalogues dc + WHERE dc.ContributedResourceCount > 0; + + SELECT + dc.CatalogueNodeId AS NodeId, + dc.NodeVersionId, + dc.Name, + dc.Description, + dc.BannerUrl, + dc.BadgeUrl, + dc.CardImageUrl, + dc.Url, + dc.RestrictedAccess, + CAST(CASE + WHEN dc.RestrictedAccess = 1 AND a.CatalogueNodeId IS NULL THEN 0 + ELSE 1 + END AS BIT) AS HasAccess, + dc.SumResourceAverageRating AS AverageRating, + ub.Id AS BookMarkId, + CAST(ISNULL(ub.Deleted,1) ^ 1 AS BIT) AS IsBookmarked, + dc.ProvidersJson + FROM reports.DashboardCatalogues dc + LEFT JOIN #Auth a + ON a.CatalogueNodeId = dc.CatalogueNodeId + LEFT JOIN hub.UserBookmark ub + ON ub.UserId = @UserId + AND ub.NodeId = dc.CatalogueNodeId + WHERE dc.ContributedResourceCount > 0 + ORDER BY dc.ContributedResourceCount DESC, dc.SumResourceAverageRating DESC, dc.Name + OFFSET @OffsetRows ROWS FETCH NEXT @FetchRows ROWS ONLY; + END + ELSE IF @DashboardType = 'my-catalogues' + BEGIN + SELECT @TotalRecords = + CASE WHEN COUNT(*) > 12 THEN @MaxRows ELSE COUNT(*) END + FROM reports.UserCatalogueActivity uca + WHERE uca.UserId = @UserId; + + SELECT + dc.CatalogueNodeId AS NodeId, + dc.NodeVersionId, + dc.Name, + dc.Description, + dc.BannerUrl, + dc.BadgeUrl, + dc.CardImageUrl, + dc.Url, + dc.RestrictedAccess, + CAST(CASE + WHEN dc.RestrictedAccess = 1 AND a.CatalogueNodeId IS NULL THEN 0 + ELSE 1 + END AS BIT) AS HasAccess, + ub.Id AS BookMarkId, + CAST(ISNULL(ub.Deleted,1) ^ 1 AS BIT) AS IsBookmarked, + dc.ProvidersJson + FROM reports.UserCatalogueActivity uca + JOIN reports.DashboardCatalogues dc + ON dc.CatalogueNodeId = uca.CatalogueNodeId + LEFT JOIN #Auth a + ON a.CatalogueNodeId = dc.CatalogueNodeId + LEFT JOIN hub.UserBookmark ub + ON ub.UserId = @UserId + AND ub.NodeId = dc.CatalogueNodeId + WHERE uca.UserId = @UserId + ORDER BY dc.CatalogueNodeId DESC + OFFSET @OffsetRows ROWS FETCH NEXT @FetchRows ROWS ONLY; + END + ELSE + BEGIN + SELECT @TotalRecords = COUNT(*) + FROM reports.DashboardCatalogues; + + SELECT + dc.CatalogueNodeId AS NodeId, + dc.NodeVersionId, + dc.Name, + dc.Description, + dc.BannerUrl, + dc.BadgeUrl, + dc.CardImageUrl, + dc.Url, + dc.RestrictedAccess, + CAST(CASE + WHEN dc.RestrictedAccess = 1 AND a.CatalogueNodeId IS NULL THEN 0 + ELSE 1 + END AS BIT) AS HasAccess, + ub.Id AS BookMarkId, + CAST(ISNULL(ub.Deleted,1) ^ 1 AS BIT) AS IsBookmarked, + dc.ProvidersJson + FROM reports.DashboardCatalogues dc + LEFT JOIN #Auth a + ON a.CatalogueNodeId = dc.CatalogueNodeId + LEFT JOIN hub.UserBookmark ub + ON ub.UserId = @UserId + AND ub.NodeId = dc.CatalogueNodeId + ORDER BY dc.Name + OFFSET @OffsetRows ROWS FETCH NEXT @FetchRows ROWS ONLY; + END +END; \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshDashboardCatalogues.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshDashboardCatalogues.sql new file mode 100644 index 000000000..3aa6d704d --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshDashboardCatalogues.sql @@ -0,0 +1,159 @@ +------------------------------------------------------------------------------- +-- Author OA +-- Created 23 April 2026 +-- Purpose Populate dashbaord catalogues read model +-- +-- Modification History +-- +-- 23 April 2026 OA TD-7078 Script Optimization +------------------------------------------------------------------------------- +CREATE PROCEDURE [reports].[RefreshDashboardCatalogues] +AS +BEGIN + SET NOCOUNT ON; + SET XACT_ABORT ON; + + BEGIN TRAN; + + BEGIN TRY + IF OBJECT_ID('tempdb..#Providers') IS NOT NULL DROP TABLE #Providers; + IF OBJECT_ID('tempdb..#CatalogueActivity') IS NOT NULL DROP TABLE #CatalogueActivity; + IF OBJECT_ID('tempdb..#Contributed') IS NOT NULL DROP TABLE #Contributed; + + /* Precompute provider JSON */ + SELECT + cnp.CatalogueNodeVersionId, + ProvidersJson = ( + SELECT + p.Id, + p.Name, + p.Description, + p.Logo + FROM hierarchy.CatalogueNodeVersionProvider cnp2 + JOIN hub.Provider p + ON p.Id = cnp2.ProviderId + WHERE cnp2.CatalogueNodeVersionId = cnp.CatalogueNodeVersionId + AND cnp2.Deleted = 0 + AND p.Deleted = 0 + FOR JSON PATH + ) + INTO #Providers + FROM hierarchy.CatalogueNodeVersionProvider cnp + WHERE cnp.Deleted = 0 + GROUP BY cnp.CatalogueNodeVersionId; + + CREATE CLUSTERED INDEX IX_Providers + ON #Providers (CatalogueNodeVersionId); + + /* Precompute catalogue activity count */ + SELECT + na.NodeId AS CatalogueNodeId, + COUNT(*) AS CatalogueActivityCount + INTO #CatalogueActivity + FROM activity.NodeActivity na + JOIN hierarchy.Node n + ON n.Id = na.NodeId + WHERE na.CatalogueNodeVersionId = n.CurrentNodeVersionId + AND n.Hidden = 0 + AND n.Deleted = 0 + AND na.NodeId <> 113 + GROUP BY na.NodeId; + + CREATE CLUSTERED INDEX IX_CatalogueActivity + ON #CatalogueActivity (CatalogueNodeId); + + /* Precompute highly contributed metrics */ + SELECT + nr.NodeId AS CatalogueNodeId, + COUNT(*) AS ContributedResourceCount, + CAST(SUM(CAST(ISNULL(rvrs.AverageRating, 0) AS DECIMAL(18,2))) AS DECIMAL(18,2)) AS SumResourceAverageRating + INTO #Contributed + FROM hierarchy.Node n + JOIN hierarchy.NodeResource nr + ON nr.NodeId = n.Id + JOIN resources.Resource r + ON r.Id = nr.ResourceId + JOIN resources.ResourceVersion rv + ON rv.Id = r.CurrentResourceVersionId + AND rv.VersionStatusId = 2 + AND rv.Deleted = 0 + JOIN hierarchy.Publication p + ON p.Id = nr.PublicationId + AND p.Deleted = 0 + LEFT JOIN resources.ResourceVersionRatingSummary rvrs + ON rvrs.ResourceVersionId = rv.Id + AND rvrs.Deleted = 0 + WHERE n.Id <> 1 + AND nr.Deleted = 0 + AND n.Deleted = 0 + AND n.Hidden = 0 + GROUP BY nr.NodeId; + + CREATE CLUSTERED INDEX IX_Contributed + ON #Contributed (CatalogueNodeId); + + TRUNCATE TABLE reports.DashboardCatalogues; + + INSERT INTO reports.DashboardCatalogues + ( + CatalogueNodeId, + NodeVersionId, + CatalogueNodeVersionId, + Name, + Description, + BannerUrl, + BadgeUrl, + CardImageUrl, + Url, + RestrictedAccess, + LastShownDate, + CatalogueActivityCount, + ContributedResourceCount, + SumResourceAverageRating, + ProvidersJson + ) + SELECT + n.Id AS CatalogueNodeId, + cnv.Id AS NodeVersionId, + cnv.Id AS CatalogueNodeVersionId, + cnv.Name, + cnv.Description, + cnv.BannerUrl, + cnv.BadgeUrl, + cnv.CardImageUrl, + cnv.Url, + cnv.RestrictedAccess, + cnv.LastShownDate, + ISNULL(ca.CatalogueActivityCount, 0) AS CatalogueActivityCount, + ISNULL(c.ContributedResourceCount, 0) AS ContributedResourceCount, + c.SumResourceAverageRating, + p.ProvidersJson + FROM hierarchy.Node n + JOIN hierarchy.NodeVersion nv + ON nv.NodeId = n.Id + AND nv.VersionStatusId = 2 + JOIN hierarchy.CatalogueNodeVersion cnv + ON cnv.NodeVersionId = nv.Id + AND cnv.Deleted = 0 + JOIN hub.Scope s + ON s.CatalogueNodeId = n.Id + AND s.Deleted = 0 + LEFT JOIN #Providers p + ON p.CatalogueNodeVersionId = cnv.Id + LEFT JOIN #CatalogueActivity ca + ON ca.CatalogueNodeId = n.Id + LEFT JOIN #Contributed c + ON c.CatalogueNodeId = n.Id + WHERE n.Id <> 1 + AND n.Hidden = 0 + AND n.Deleted = 0; + + COMMIT TRAN; + END TRY + BEGIN CATCH + IF @@TRANCOUNT > 0 + ROLLBACK TRAN; + + THROW; + END CATCH +END; \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/RefreshDashboardResources.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshDashboardResources.sql similarity index 93% rename from WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/RefreshDashboardResources.sql rename to WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshDashboardResources.sql index 748b6da7f..399b498d2 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/RefreshDashboardResources.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshDashboardResources.sql @@ -1,4 +1,13 @@ -CREATE PROCEDURE [reports].[RefreshDashboardResources] +------------------------------------------------------------------------------- +-- Author OA +-- Created 23 April 2026 +-- Purpose Populate dashbaord resources read model +-- +-- Modification History +-- +-- 23 April 2026 OA TD-7078 Script Optimization +------------------------------------------------------------------------------- +CREATE PROCEDURE [reports].[RefreshDashboardResources] AS BEGIN SET NOCOUNT ON; diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshUserCatalogueActivity.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshUserCatalogueActivity.sql new file mode 100644 index 000000000..ada87ee85 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshUserCatalogueActivity.sql @@ -0,0 +1,105 @@ +------------------------------------------------------------------------------- +-- Author OA +-- Created 23 April 2026 +-- Purpose Populate user catalogue activity read model +-- +-- Modification History +-- +-- 23 April 2026 OA TD-7078 Script Optimization +------------------------------------------------------------------------------- +CREATE PROCEDURE [reports].[RefreshUserCatalogueActivity] +AS +BEGIN + SET NOCOUNT ON; + SET XACT_ABORT ON; + + DECLARE @LastProcessedActivityId INT; + + SELECT @LastProcessedActivityId = ISNULL(MAX(LatestActivityId), 0) + FROM reports.UserCatalogueActivity; + + IF OBJECT_ID('tempdb..#ChangedPairs') IS NOT NULL DROP TABLE #ChangedPairs; + IF OBJECT_ID('tempdb..#LatestIds') IS NOT NULL DROP TABLE #LatestIds; + IF OBJECT_ID('tempdb..#Latest') IS NOT NULL DROP TABLE #Latest; + + + SELECT DISTINCT + ra.UserId, + np.CatalogueNodeId + INTO #ChangedPairs + FROM activity.ResourceActivity ra + JOIN hierarchy.NodePath np + ON np.Id = ra.NodePathId + WHERE ra.Id > @LastProcessedActivityId + AND ra.UserId IS NOT NULL + AND ra.Deleted = 0 + AND np.Deleted = 0; + + IF NOT EXISTS (SELECT 1 FROM #ChangedPairs) + RETURN; + + CREATE CLUSTERED INDEX IX_ChangedPairs + ON #ChangedPairs (UserId, CatalogueNodeId); + + /* Latest activity id per changed (UserId, CatalogueNodeId) */ + SELECT + ra.UserId, + np.CatalogueNodeId, + MAX(ra.Id) AS LatestActivityId + INTO #LatestIds + FROM activity.ResourceActivity ra + JOIN hierarchy.NodePath np + ON np.Id = ra.NodePathId + JOIN #ChangedPairs cp + ON cp.UserId = ra.UserId + AND cp.CatalogueNodeId = np.CatalogueNodeId + WHERE ra.Deleted = 0 + AND np.Deleted = 0 + GROUP BY + ra.UserId, + np.CatalogueNodeId; + + CREATE CLUSTERED INDEX IX_LatestIds + ON #LatestIds (UserId, CatalogueNodeId); + + SELECT + li.UserId, + li.CatalogueNodeId, + ra.Id AS LatestActivityId, + CAST(COALESCE(ra.ActivityStart, ra.ActivityEnd) AS DATETIME2) AS LastAccessedDate + INTO #Latest + FROM #LatestIds li + JOIN activity.ResourceActivity ra + ON ra.Id = li.LatestActivityId + WHERE COALESCE(ra.ActivityStart, ra.ActivityEnd) IS NOT NULL; + + CREATE CLUSTERED INDEX IX_Latest + ON #Latest (UserId, CatalogueNodeId); + + UPDATE uca + SET + uca.LatestActivityId = l.LatestActivityId, + uca.LastAccessedDate = l.LastAccessedDate + FROM reports.UserCatalogueActivity uca + JOIN #Latest l + ON l.UserId = uca.UserId + AND l.CatalogueNodeId = uca.CatalogueNodeId; + + INSERT INTO reports.UserCatalogueActivity + ( + UserId, + CatalogueNodeId, + LatestActivityId, + LastAccessedDate + ) + SELECT + l.UserId, + l.CatalogueNodeId, + l.LatestActivityId, + l.LastAccessedDate + FROM #Latest l + LEFT JOIN reports.UserCatalogueActivity uca + ON uca.UserId = l.UserId + AND uca.CatalogueNodeId = l.CatalogueNodeId + WHERE uca.UserId IS NULL; +END; \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/RefreshUserResourceActivity.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshUserResourceActivity.sql similarity index 93% rename from WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/RefreshUserResourceActivity.sql rename to WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshUserResourceActivity.sql index 78ff66a59..041ad6031 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/RefreshUserResourceActivity.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshUserResourceActivity.sql @@ -1,4 +1,13 @@ -CREATE PROCEDURE [reports].[RefreshUserResourceActivity] +------------------------------------------------------------------------------- +-- Author OA +-- Created 23 April 2026 +-- Purpose Populate user resource activity read model +-- +-- Modification History +-- +-- 23 April 2026 OA TD-7078 Script Optimization +------------------------------------------------------------------------------- +CREATE PROCEDURE [reports].[RefreshUserResourceActivity] AS BEGIN SET NOCOUNT ON; diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyRecentCompletedDashboardResourcesRM.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyRecentCompletedDashboardResourcesRM.sql index 8abb58fa6..fa01293cd 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyRecentCompletedDashboardResourcesRM.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyRecentCompletedDashboardResourcesRM.sql @@ -1,4 +1,13 @@ -CREATE PROCEDURE [resources].[GetMyRecentCompletedDashboardResourcesRM] +------------------------------------------------------------------------------- +-- Author OA +-- Created 23 April 2026 +-- Purpose Get user recent completed resource from dashboard read model +-- +-- Modification History +-- +-- 23 April 2026 OA TD-7078 Script Optimization +------------------------------------------------------------------------------- +CREATE PROCEDURE [resources].[GetMyRecentCompletedDashboardResourcesRM] ( @UserId INT, @PageNumber INT = 1, diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetPopularDashboardResourcesRM.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetPopularDashboardResourcesRM.sql index 9102a1a30..98e274b74 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetPopularDashboardResourcesRM.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetPopularDashboardResourcesRM.sql @@ -1,4 +1,13 @@ -CREATE PROCEDURE [resources].[GetPopularDashboardResourcesRM] +------------------------------------------------------------------------------- +-- Author OA +-- Created 23 April 2026 +-- Purpose Get popular resource from dashboard read model +-- +-- Modification History +-- +-- 23 April 2026 OA TD-7078 Script Optimization +------------------------------------------------------------------------------- +CREATE PROCEDURE [resources].[GetPopularDashboardResourcesRM] ( @UserId INT, @PageNumber INT = 1, diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRatedDashboardResourcesRM.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRatedDashboardResourcesRM.sql index f89216db4..b686c8ef6 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRatedDashboardResourcesRM.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRatedDashboardResourcesRM.sql @@ -1,4 +1,13 @@ -CREATE PROCEDURE [resources].[GetRatedDashboardResourcesRM] +------------------------------------------------------------------------------- +-- Author OA +-- Created 23 April 2026 +-- Purpose Get rated resource from dashboard read model +-- +-- Modification History +-- +-- 23 April 2026 OA TD-7078 Script Optimization +------------------------------------------------------------------------------- +CREATE PROCEDURE [resources].[GetRatedDashboardResourcesRM] ( @UserId INT, @PageNumber INT = 1, diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRecentDashboardResourcesRM.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRecentDashboardResourcesRM.sql index 82cdf615c..2416d22ad 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRecentDashboardResourcesRM.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRecentDashboardResourcesRM.sql @@ -1,4 +1,13 @@ -CREATE PROCEDURE [resources].[GetRecentDashboardResourcesRM] +------------------------------------------------------------------------------- +-- Author OA +-- Created 23 April 2026 +-- Purpose Get recent resource from dashboard read model +-- +-- Modification History +-- +-- 23 April 2026 OA TD-7078 Script Optimization +------------------------------------------------------------------------------- +CREATE PROCEDURE [resources].[GetRecentDashboardResourcesRM] ( @UserId INT, @PageNumber INT = 1, diff --git a/WebAPI/LearningHub.Nhs.Database/Tables/Report/DashboardCatalogue.sql b/WebAPI/LearningHub.Nhs.Database/Tables/Report/DashboardCatalogue.sql new file mode 100644 index 000000000..9c407bed0 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Tables/Report/DashboardCatalogue.sql @@ -0,0 +1,19 @@ +CREATE TABLE [reports].[DashboardCatalogues] +( + CatalogueNodeId INT NOT NULL, + NodeVersionId INT NOT NULL, + CatalogueNodeVersionId INT NOT NULL, + Name NVARCHAR(255) NOT NULL, + Description NVARCHAR(MAX) NULL, + BannerUrl NVARCHAR(500) NULL, + BadgeUrl NVARCHAR(500) NULL, + CardImageUrl NVARCHAR(500) NULL, + Url NVARCHAR(500) NULL, + RestrictedAccess BIT NOT NULL, + LastShownDate DATETIME2 NULL, + CatalogueActivityCount INT NOT NULL DEFAULT 0, + ContributedResourceCount INT NOT NULL DEFAULT 0, + SumResourceAverageRating DECIMAL(18,2) NULL, + ProvidersJson NVARCHAR(MAX) NULL, + CONSTRAINT PK_DashboardCatalogues PRIMARY KEY (CatalogueNodeId) +); \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Tables/Report/UserCatalogueActivity.sql b/WebAPI/LearningHub.Nhs.Database/Tables/Report/UserCatalogueActivity.sql new file mode 100644 index 000000000..427135727 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Tables/Report/UserCatalogueActivity.sql @@ -0,0 +1,8 @@ +CREATE TABLE [reports].[UserCatalogueActivity] +( + UserId INT NOT NULL, + CatalogueNodeId INT NOT NULL, + LatestActivityId INT NOT NULL, + LastAccessedDate DATETIME2 NOT NULL, + CONSTRAINT PK_UserCatalogueActivity PRIMARY KEY (UserId, CatalogueNodeId) +); From 58a89c4b26a6c159f8d243a95059d5b430053484 Mon Sep 17 00:00:00 2001 From: Tobi Awe Date: Fri, 8 May 2026 09:53:07 +0100 Subject: [PATCH 3/3] dashboard SP refactor to use 'readmodel' schema, populate dashboard useractivity using SQL trigger in azure function trigger timer execution of dasboard resource and catalogues --- .../CatalogueNodeVersionRepository.cs | 2 +- .../Resources/ResourceVersionRepository.cs | 8 +- .../LearningHub.Nhs.Database.sqlproj | 22 +- .../Schemas/readmodels.sql | 7 + .../Post-Deploy/Script.PostDeployment.sql | 2 + ...opulateReadModelHistoricUserActivities.sql | 221 ++++++++++++++++++ .../Scripts/TD-7078-supportingindexes.sql | 64 ++--- .../Hierarchy/GetDashboardCataloguesRM.sql | 22 +- .../ApplyUserCatalogueActivityChanges.sql} | 44 ++-- .../ApplyUserResourceActivityChanges.sql} | 101 ++++---- .../RefreshDashboardCatalogues.sql | 6 +- .../Readmodel/RefreshDashboardReadModels.sql | 46 ++++ .../RefreshDashboardResources.sql | 6 +- ...tMyRecentCompletedDashboardResourcesRM.sql | 4 +- .../GetPopularDashboardResourcesRM.sql | 2 +- .../GetRatedDashboardResourcesRM.sql | 2 +- .../GetRecentDashboardResourcesRM.sql | 2 +- .../DashboardCatalogue.sql | 2 +- .../DashboardResource.sql | 2 +- .../UserCatalogueActivity.sql | 2 +- .../UserResourceActivity.sql | 2 +- 21 files changed, 438 insertions(+), 131 deletions(-) create mode 100644 WebAPI/LearningHub.Nhs.Database/Schemas/readmodels.sql create mode 100644 WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7078-PopulateReadModelHistoricUserActivities.sql rename WebAPI/LearningHub.Nhs.Database/Stored Procedures/{Report/RefreshUserCatalogueActivity.sql => Readmodel/ApplyUserCatalogueActivityChanges.sql} (70%) rename WebAPI/LearningHub.Nhs.Database/Stored Procedures/{Report/RefreshUserResourceActivity.sql => Readmodel/ApplyUserResourceActivityChanges.sql} (73%) rename WebAPI/LearningHub.Nhs.Database/Stored Procedures/{Report => Readmodel}/RefreshDashboardCatalogues.sql (96%) create mode 100644 WebAPI/LearningHub.Nhs.Database/Stored Procedures/Readmodel/RefreshDashboardReadModels.sql rename WebAPI/LearningHub.Nhs.Database/Stored Procedures/{Report => Readmodel}/RefreshDashboardResources.sql (97%) rename WebAPI/LearningHub.Nhs.Database/Tables/{Report => Readmodel}/DashboardCatalogue.sql (92%) rename WebAPI/LearningHub.Nhs.Database/Tables/{Report => Readmodel}/DashboardResource.sql (92%) rename WebAPI/LearningHub.Nhs.Database/Tables/{Report => Readmodel}/UserCatalogueActivity.sql (80%) rename WebAPI/LearningHub.Nhs.Database/Tables/{Report => Readmodel}/UserResourceActivity.sql (81%) diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs index 76ae6c8e5..32932e648 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Hierarchy/CatalogueNodeVersionRepository.cs @@ -364,7 +364,7 @@ public RestrictedCatalogueSummaryViewModel GetRestrictedCatalogueSummary(int cat var param2 = new SqlParameter("@pageNumber", SqlDbType.Int) { Value = pageNumber }; var param3 = new SqlParameter("@TotalRecords", SqlDbType.Int) { Direction = ParameterDirection.Output }; - var dashboardCatalogues = DbContext.DashboardCatalogueDto.FromSqlRaw("[hierarchy].[GetDashboardCataloguesRM] @DashboardType, @UserId, @pageNumber, @TotalRecords OUTPUT", param0, param1, param2, param3).ToList(); + var dashboardCatalogues = DbContext.DashboardCatalogueDto.FromSqlRaw("[hierarchy].[GetDashboardCatalogues] @DashboardType, @UserId, @pageNumber, @TotalRecords OUTPUT", param0, param1, param2, param3).ToList(); return (TotalCount: (int)param3.Value, Catalogues: dashboardCatalogues); } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRepository.cs b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRepository.cs index 8a3d74678..47d302e0c 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRepository.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Repositories/Repositories/Resources/ResourceVersionRepository.cs @@ -732,16 +732,16 @@ public List GetContributions(int userId, ResourceContri switch (dashboardType) { case "my-recent-completed": - dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetMyRecentCompletedDashboardResourcesRM @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); + dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetMyRecentCompletedDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); break; case "recent-resources": - dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetRecentDashboardResourcesRM @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); + dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetRecentDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); break; case "rated-resources": - dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetRatedDashboardResourcesRM @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); + dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetRatedDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); break; case "popular-resources": - dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetPopularDashboardResourcesRM @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); + dashboardResources = this.DbContext.DashboardResourceDto.FromSqlRaw("resources.GetPopularDashboardResources @userId, @pageNumber, @totalRows output", param0, param1, param2).ToList(); break; } diff --git a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj index a74dad0e8..c51245b15 100644 --- a/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj +++ b/WebAPI/LearningHub.Nhs.Database/LearningHub.Nhs.Database.sqlproj @@ -103,7 +103,8 @@ - + + @@ -571,20 +572,23 @@ - - - - - - - - + + + + + + + + + + + diff --git a/WebAPI/LearningHub.Nhs.Database/Schemas/readmodels.sql b/WebAPI/LearningHub.Nhs.Database/Schemas/readmodels.sql new file mode 100644 index 000000000..9b7d57d38 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Schemas/readmodels.sql @@ -0,0 +1,7 @@ +CREATE SCHEMA [readmodels] + AUTHORIZATION [dbo]; + + + + +GO \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql index c7a40eb66..314c20908 100644 --- a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Script.PostDeployment.sql @@ -89,3 +89,5 @@ UPDATE [resources].[ResourceVersion] SET CertificateEnabled = 0 WHERE VersionSta :r .\Scripts\TD-7116-mib_new_env.sql :r .\Scripts\TD-7106-Resume-Databricks-Ingestion.sql :r .\Scripts\TD-7078-supportingindexes.sql +:r .\Scripts\TD-7078-PopulateReadModelHistoricUserActivities.sql + diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7078-PopulateReadModelHistoricUserActivities.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7078-PopulateReadModelHistoricUserActivities.sql new file mode 100644 index 000000000..a144b1898 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7078-PopulateReadModelHistoricUserActivities.sql @@ -0,0 +1,221 @@ +SET NOCOUNT ON; +SET XACT_ABORT ON; + +--Backfill readmodels.UserResourceActivity + + +IF NOT EXISTS (SELECT 1 FROM readmodels.UserResourceActivity) +BEGIN + IF OBJECT_ID('tempdb..#URA_LatestIds') IS NOT NULL DROP TABLE #URA_LatestIds; + IF OBJECT_ID('tempdb..#URA_Latest') IS NOT NULL DROP TABLE #URA_Latest; + IF OBJECT_ID('tempdb..#URA_Ara') IS NOT NULL DROP TABLE #URA_Ara; + IF OBJECT_ID('tempdb..#URA_Mar') IS NOT NULL DROP TABLE #URA_Mar; + IF OBJECT_ID('tempdb..#URA_Sa') IS NOT NULL DROP TABLE #URA_Sa; + IF OBJECT_ID('tempdb..#URA_Completion') IS NOT NULL DROP TABLE #URA_Completion; + + SELECT + a.UserId, + a.ResourceId, + MAX(a.Id) AS LatestActivityId + INTO #URA_LatestIds + FROM activity.ResourceActivity a + WHERE a.UserId IS NOT NULL + AND a.Deleted = 0 + GROUP BY + a.UserId, + a.ResourceId; + + IF EXISTS (SELECT 1 FROM #URA_LatestIds) + BEGIN + CREATE CLUSTERED INDEX IX_URA_LatestIds + ON #URA_LatestIds (UserId, ResourceId); + + SELECT + a.UserId, + a.ResourceId, + a.Id AS ActivityId, + a.ResourceVersionId, + a.LaunchResourceActivityId, + a.ActivityStatusId, + a.ActivityStart, + a.ActivityEnd, + r.ResourceTypeId + INTO #URA_Latest + FROM #URA_LatestIds li + JOIN activity.ResourceActivity a + ON a.Id = li.LatestActivityId + JOIN resources.Resource r + ON r.Id = a.ResourceId; + + CREATE CLUSTERED INDEX IX_URA_Latest + ON #URA_Latest (UserId, ResourceId); + + SELECT + ara.ResourceActivityId, + MAX(ara.Score) AS Score + INTO #URA_Ara + FROM activity.AssessmentResourceActivity ara + JOIN #URA_Latest l + ON l.ResourceTypeId = 11 + AND ara.ResourceActivityId = COALESCE(l.LaunchResourceActivityId, l.ActivityId) + WHERE ara.Score IS NOT NULL + GROUP BY ara.ResourceActivityId; + + CREATE CLUSTERED INDEX IX_URA_Ara + ON #URA_Ara (ResourceActivityId); + + SELECT + mar.ResourceActivityId, + MAX(mar.PercentComplete) AS PercentComplete + INTO #URA_Mar + FROM activity.MediaResourceActivity mar + JOIN #URA_Latest l + ON l.ResourceTypeId IN (2,7) + AND mar.ResourceActivityId = COALESCE(l.LaunchResourceActivityId, l.ActivityId) + WHERE mar.PercentComplete IS NOT NULL + GROUP BY mar.ResourceActivityId; + + CREATE CLUSTERED INDEX IX_URA_Mar + ON #URA_Mar (ResourceActivityId); + + SELECT + sa.ResourceActivityId, + MAX(sa.CmiCoreLesson_status) AS CmiCoreLesson_status + INTO #URA_Sa + FROM activity.ScormActivity sa + JOIN #URA_Latest l + ON l.ResourceTypeId = 6 + AND sa.ResourceActivityId = l.ActivityId + WHERE sa.CmiCoreLesson_status IS NOT NULL + GROUP BY sa.ResourceActivityId; + + CREATE CLUSTERED INDEX IX_URA_Sa + ON #URA_Sa (ResourceActivityId); + + SELECT + l.UserId, + l.ResourceId, + l.ActivityId AS LatestActivityId, + CAST(COALESCE(l.ActivityStart, l.ActivityEnd) AS DATETIME2) AS LastAccessedDate, + CAST( + CASE + WHEN l.ResourceTypeId IN (2,7) + AND l.ActivityStatusId = 3 + AND ( + (mar.ResourceActivityId IS NOT NULL AND mar.PercentComplete = 100) + OR l.ActivityStart < '2020-09-07' + ) + THEN 1 + + WHEN l.ResourceTypeId = 6 + AND ( + sa.CmiCoreLesson_status IN (3,5) + OR l.ActivityStatusId IN (3,5) + ) + THEN 1 + + WHEN l.ResourceTypeId = 11 + AND ( + ara.Score >= arv.PassMark + OR l.ActivityStatusId IN (3,5) + ) + THEN 1 + + WHEN l.ResourceTypeId IN (1,5,8,9,10,12) + AND l.ActivityStatusId = 3 + THEN 1 + + ELSE 0 + END AS BIT + ) AS IsCompleted + INTO #URA_Completion + FROM #URA_Latest l + LEFT JOIN resources.AssessmentResourceVersion arv + ON arv.ResourceVersionId = l.ResourceVersionId + LEFT JOIN #URA_Ara ara + ON ara.ResourceActivityId = COALESCE(l.LaunchResourceActivityId, l.ActivityId) + LEFT JOIN #URA_Mar mar + ON mar.ResourceActivityId = COALESCE(l.LaunchResourceActivityId, l.ActivityId) + LEFT JOIN #URA_Sa sa + ON sa.ResourceActivityId = l.ActivityId + WHERE COALESCE(l.ActivityStart, l.ActivityEnd) IS NOT NULL; + + CREATE CLUSTERED INDEX IX_URA_Completion + ON #URA_Completion (UserId, ResourceId); + + INSERT INTO readmodels.UserResourceActivity + ( + UserId, + ResourceId, + LatestActivityId, + IsCompleted, + LastAccessedDate + ) + SELECT + c.UserId, + c.ResourceId, + c.LatestActivityId, + c.IsCompleted, + c.LastAccessedDate + FROM #URA_Completion c; + END +END; + + +--Backfill readmodels.UserCatalogueActivity + + +IF NOT EXISTS (SELECT 1 FROM readmodels.UserCatalogueActivity) +BEGIN + IF OBJECT_ID('tempdb..#UCA_LatestIds') IS NOT NULL DROP TABLE #UCA_LatestIds; + IF OBJECT_ID('tempdb..#UCA_Latest') IS NOT NULL DROP TABLE #UCA_Latest; + + SELECT + ra.UserId, + np.CatalogueNodeId, + MAX(ra.Id) AS LatestActivityId + INTO #UCA_LatestIds + FROM activity.ResourceActivity ra + JOIN hierarchy.NodePath np + ON np.Id = ra.NodePathId + WHERE ra.UserId IS NOT NULL + AND ra.Deleted = 0 + AND np.Deleted = 0 + GROUP BY + ra.UserId, + np.CatalogueNodeId; + + IF EXISTS (SELECT 1 FROM #UCA_LatestIds) + BEGIN + CREATE CLUSTERED INDEX IX_UCA_LatestIds + ON #UCA_LatestIds (UserId, CatalogueNodeId); + + SELECT + li.UserId, + li.CatalogueNodeId, + ra.Id AS LatestActivityId, + CAST(COALESCE(ra.ActivityStart, ra.ActivityEnd) AS DATETIME2) AS LastAccessedDate + INTO #UCA_Latest + FROM #UCA_LatestIds li + JOIN activity.ResourceActivity ra + ON ra.Id = li.LatestActivityId + WHERE COALESCE(ra.ActivityStart, ra.ActivityEnd) IS NOT NULL; + + CREATE CLUSTERED INDEX IX_UCA_Latest + ON #UCA_Latest (UserId, CatalogueNodeId); + + INSERT INTO readmodels.UserCatalogueActivity + ( + UserId, + CatalogueNodeId, + LatestActivityId, + LastAccessedDate + ) + SELECT + l.UserId, + l.CatalogueNodeId, + l.LatestActivityId, + l.LastAccessedDate + FROM #UCA_Latest l; + END +END; \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7078-supportingindexes.sql b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7078-supportingindexes.sql index 5fcd750de..02bbf362e 100644 --- a/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7078-supportingindexes.sql +++ b/WebAPI/LearningHub.Nhs.Database/Scripts/Post-Deploy/Scripts/TD-7078-supportingindexes.sql @@ -1,32 +1,32 @@  - -- reports.DashboardResources + -- readmodels.DashboardResources -IF OBJECT_ID('reports.DashboardResources', 'U') IS NOT NULL +IF OBJECT_ID('readmodels.DashboardResources', 'U') IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM sys.indexes WHERE name = 'IX_DashboardResources_ResourceId' - AND object_id = OBJECT_ID('reports.DashboardResources') + AND object_id = OBJECT_ID('readmodels.DashboardResources') ) BEGIN CREATE INDEX IX_DashboardResources_ResourceId - ON reports.DashboardResources (ResourceId); + ON readmodels.DashboardResources (ResourceId); END; GO -IF OBJECT_ID('reports.DashboardResources', 'U') IS NOT NULL +IF OBJECT_ID('readmodels.DashboardResources', 'U') IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM sys.indexes WHERE name = 'IX_DashboardResources_Recent' - AND object_id = OBJECT_ID('reports.DashboardResources') + AND object_id = OBJECT_ID('readmodels.DashboardResources') ) BEGIN CREATE INDEX IX_DashboardResources_Recent - ON reports.DashboardResources (PublishedDate DESC, Title) + ON readmodels.DashboardResources (PublishedDate DESC, Title) INCLUDE ( ResourceId, @@ -46,17 +46,17 @@ BEGIN END; GO -IF OBJECT_ID('reports.DashboardResources', 'U') IS NOT NULL +IF OBJECT_ID('readmodels.DashboardResources', 'U') IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM sys.indexes WHERE name = 'IX_DashboardResources_Popular' - AND object_id = OBJECT_ID('reports.DashboardResources') + AND object_id = OBJECT_ID('readmodels.DashboardResources') ) BEGIN CREATE INDEX IX_DashboardResources_Popular - ON reports.DashboardResources (ActivityCount DESC, Title) + ON readmodels.DashboardResources (ActivityCount DESC, Title) INCLUDE ( ResourceId, @@ -76,17 +76,17 @@ BEGIN END; GO -IF OBJECT_ID('reports.DashboardResources', 'U') IS NOT NULL +IF OBJECT_ID('readmodels.DashboardResources', 'U') IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM sys.indexes WHERE name = 'IX_DashboardResources_Rated' - AND object_id = OBJECT_ID('reports.DashboardResources') + AND object_id = OBJECT_ID('readmodels.DashboardResources') ) BEGIN CREATE INDEX IX_DashboardResources_Rated - ON reports.DashboardResources (AverageRating DESC, RatingCount DESC, Title) + ON readmodels.DashboardResources (AverageRating DESC, RatingCount DESC, Title) INCLUDE ( ResourceId, @@ -105,50 +105,50 @@ BEGIN END; GO - --reports.UserResourceActivity + --readmodels.UserResourceActivity -IF OBJECT_ID('reports.UserResourceActivity', 'U') IS NOT NULL +IF OBJECT_ID('readmodels.UserResourceActivity', 'U') IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM sys.indexes WHERE name = 'IX_UserResourceActivity_UserCompletedLatest' - AND object_id = OBJECT_ID('reports.UserResourceActivity') + AND object_id = OBJECT_ID('readmodels.UserResourceActivity') ) BEGIN CREATE INDEX IX_UserResourceActivity_UserCompletedLatest - ON reports.UserResourceActivity (UserId, IsCompleted, LatestActivityId DESC) + ON readmodels.UserResourceActivity (UserId, IsCompleted, LatestActivityId DESC) INCLUDE (ResourceId, LastAccessedDate); END; GO -IF OBJECT_ID('reports.DashboardCatalogues', 'U') IS NOT NULL +IF OBJECT_ID('readmodels.DashboardCatalogues', 'U') IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM sys.indexes WHERE name = 'IX_DashboardCatalogues_Name' - AND object_id = OBJECT_ID('reports.DashboardCatalogues') + AND object_id = OBJECT_ID('readmodels.DashboardCatalogues') ) BEGIN CREATE INDEX IX_DashboardCatalogues_Name - ON reports.DashboardCatalogues (Name); + ON readmodels.DashboardCatalogues (Name); END; GO -IF OBJECT_ID('reports.DashboardCatalogues', 'U') IS NOT NULL +IF OBJECT_ID('readmodels.DashboardCatalogues', 'U') IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM sys.indexes WHERE name = 'IX_DashboardCatalogues_Popular' - AND object_id = OBJECT_ID('reports.DashboardCatalogues') + AND object_id = OBJECT_ID('readmodels.DashboardCatalogues') ) BEGIN CREATE INDEX IX_DashboardCatalogues_Popular - ON reports.DashboardCatalogues (CatalogueActivityCount DESC, Name) + ON readmodels.DashboardCatalogues (CatalogueActivityCount DESC, Name) INCLUDE ( NodeVersionId, @@ -166,17 +166,17 @@ BEGIN END; GO -IF OBJECT_ID('reports.DashboardCatalogues', 'U') IS NOT NULL +IF OBJECT_ID('readmodels.DashboardCatalogues', 'U') IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM sys.indexes WHERE name = 'IX_DashboardCatalogues_Recent' - AND object_id = OBJECT_ID('reports.DashboardCatalogues') + AND object_id = OBJECT_ID('readmodels.DashboardCatalogues') ) BEGIN CREATE INDEX IX_DashboardCatalogues_Recent - ON reports.DashboardCatalogues (LastShownDate DESC, Name) + ON readmodels.DashboardCatalogues (LastShownDate DESC, Name) INCLUDE ( NodeVersionId, @@ -195,17 +195,17 @@ BEGIN END; GO -IF OBJECT_ID('reports.DashboardCatalogues', 'U') IS NOT NULL +IF OBJECT_ID('readmodels.DashboardCatalogues', 'U') IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM sys.indexes WHERE name = 'IX_DashboardCatalogues_HighlyContributed' - AND object_id = OBJECT_ID('reports.DashboardCatalogues') + AND object_id = OBJECT_ID('readmodels.DashboardCatalogues') ) BEGIN CREATE INDEX IX_DashboardCatalogues_HighlyContributed - ON reports.DashboardCatalogues (ContributedResourceCount DESC, SumResourceAverageRating DESC, Name) + ON readmodels.DashboardCatalogues (ContributedResourceCount DESC, SumResourceAverageRating DESC, Name) INCLUDE ( NodeVersionId, @@ -222,17 +222,17 @@ BEGIN END; GO -IF OBJECT_ID('reports.UserCatalogueActivity', 'U') IS NOT NULL +IF OBJECT_ID('readmodels.UserCatalogueActivity', 'U') IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM sys.indexes WHERE name = 'IX_UserCatalogueActivity_User_Latest' - AND object_id = OBJECT_ID('reports.UserCatalogueActivity') + AND object_id = OBJECT_ID('readmodels.UserCatalogueActivity') ) BEGIN CREATE INDEX IX_UserCatalogueActivity_User_Latest - ON reports.UserCatalogueActivity (UserId, LatestActivityId DESC) + ON readmodels.UserCatalogueActivity (UserId, LatestActivityId DESC) INCLUDE (CatalogueNodeId, LastAccessedDate); END; GO \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/GetDashboardCataloguesRM.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/GetDashboardCataloguesRM.sql index bdff1912c..85855d892 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/GetDashboardCataloguesRM.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Hierarchy/GetDashboardCataloguesRM.sql @@ -46,7 +46,7 @@ BEGIN BEGIN SELECT @TotalRecords = CASE WHEN COUNT(*) > 12 THEN @MaxRows ELSE COUNT(*) END - FROM reports.DashboardCatalogues dc + FROM readmodels.DashboardCatalogues dc WHERE dc.CatalogueActivityCount > 0; SELECT @@ -66,7 +66,7 @@ BEGIN ub.Id AS BookMarkId, CAST(ISNULL(ub.Deleted,1) ^ 1 AS BIT) AS IsBookmarked, dc.ProvidersJson - FROM reports.DashboardCatalogues dc + FROM readmodels.DashboardCatalogues dc LEFT JOIN #Auth a ON a.CatalogueNodeId = dc.CatalogueNodeId LEFT JOIN hub.UserBookmark ub @@ -80,7 +80,7 @@ BEGIN BEGIN SELECT @TotalRecords = CASE WHEN COUNT(*) > 12 THEN @MaxRows ELSE COUNT(*) END - FROM reports.DashboardCatalogues dc + FROM readmodels.DashboardCatalogues dc WHERE dc.LastShownDate IS NOT NULL; SELECT @@ -100,7 +100,7 @@ BEGIN ub.Id AS BookMarkId, CAST(ISNULL(ub.Deleted,1) ^ 1 AS BIT) AS IsBookmarked, dc.ProvidersJson - FROM reports.DashboardCatalogues dc + FROM readmodels.DashboardCatalogues dc LEFT JOIN #Auth a ON a.CatalogueNodeId = dc.CatalogueNodeId LEFT JOIN hub.UserBookmark ub @@ -114,7 +114,7 @@ BEGIN BEGIN SELECT @TotalRecords = CASE WHEN COUNT(*) > 12 THEN @MaxRows ELSE COUNT(*) END - FROM reports.DashboardCatalogues dc + FROM readmodels.DashboardCatalogues dc WHERE dc.ContributedResourceCount > 0; SELECT @@ -135,7 +135,7 @@ BEGIN ub.Id AS BookMarkId, CAST(ISNULL(ub.Deleted,1) ^ 1 AS BIT) AS IsBookmarked, dc.ProvidersJson - FROM reports.DashboardCatalogues dc + FROM readmodels.DashboardCatalogues dc LEFT JOIN #Auth a ON a.CatalogueNodeId = dc.CatalogueNodeId LEFT JOIN hub.UserBookmark ub @@ -149,7 +149,7 @@ BEGIN BEGIN SELECT @TotalRecords = CASE WHEN COUNT(*) > 12 THEN @MaxRows ELSE COUNT(*) END - FROM reports.UserCatalogueActivity uca + FROM readmodels.UserCatalogueActivity uca WHERE uca.UserId = @UserId; SELECT @@ -169,8 +169,8 @@ BEGIN ub.Id AS BookMarkId, CAST(ISNULL(ub.Deleted,1) ^ 1 AS BIT) AS IsBookmarked, dc.ProvidersJson - FROM reports.UserCatalogueActivity uca - JOIN reports.DashboardCatalogues dc + FROM readmodels.UserCatalogueActivity uca + JOIN readmodels.DashboardCatalogues dc ON dc.CatalogueNodeId = uca.CatalogueNodeId LEFT JOIN #Auth a ON a.CatalogueNodeId = dc.CatalogueNodeId @@ -184,7 +184,7 @@ BEGIN ELSE BEGIN SELECT @TotalRecords = COUNT(*) - FROM reports.DashboardCatalogues; + FROM readmodels.DashboardCatalogues; SELECT dc.CatalogueNodeId AS NodeId, @@ -203,7 +203,7 @@ BEGIN ub.Id AS BookMarkId, CAST(ISNULL(ub.Deleted,1) ^ 1 AS BIT) AS IsBookmarked, dc.ProvidersJson - FROM reports.DashboardCatalogues dc + FROM readmodels.DashboardCatalogues dc LEFT JOIN #Auth a ON a.CatalogueNodeId = dc.CatalogueNodeId LEFT JOIN hub.UserBookmark ub diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshUserCatalogueActivity.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Readmodel/ApplyUserCatalogueActivityChanges.sql similarity index 70% rename from WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshUserCatalogueActivity.sql rename to WebAPI/LearningHub.Nhs.Database/Stored Procedures/Readmodel/ApplyUserCatalogueActivityChanges.sql index ada87ee85..4ae4b4be4 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshUserCatalogueActivity.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Readmodel/ApplyUserCatalogueActivityChanges.sql @@ -7,31 +7,46 @@ -- -- 23 April 2026 OA TD-7078 Script Optimization ------------------------------------------------------------------------------- -CREATE PROCEDURE [reports].[RefreshUserCatalogueActivity] +CREATE PROCEDURE [readmodels].[ApplyUserCatalogueActivityChanges] +( + @ChangedActivityIdsJson NVARCHAR(MAX) +) AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; - DECLARE @LastProcessedActivityId INT; - - SELECT @LastProcessedActivityId = ISNULL(MAX(LatestActivityId), 0) - FROM reports.UserCatalogueActivity; + IF @ChangedActivityIdsJson IS NULL OR ISJSON(@ChangedActivityIdsJson) <> 1 + RETURN; + IF OBJECT_ID('tempdb..#ChangedActivityIds') IS NOT NULL DROP TABLE #ChangedActivityIds; IF OBJECT_ID('tempdb..#ChangedPairs') IS NOT NULL DROP TABLE #ChangedPairs; IF OBJECT_ID('tempdb..#LatestIds') IS NOT NULL DROP TABLE #LatestIds; IF OBJECT_ID('tempdb..#Latest') IS NOT NULL DROP TABLE #Latest; - + SELECT DISTINCT + TRY_CAST([value] AS INT) AS ActivityId + INTO #ChangedActivityIds + FROM OPENJSON(@ChangedActivityIdsJson) + WHERE TRY_CAST([value] AS INT) IS NOT NULL; + + IF NOT EXISTS (SELECT 1 FROM #ChangedActivityIds) + RETURN; + + CREATE CLUSTERED INDEX IX_ChangedActivityIds + ON #ChangedActivityIds (ActivityId); + + /* Derive affected user-catalogue pairs */ SELECT DISTINCT ra.UserId, np.CatalogueNodeId INTO #ChangedPairs - FROM activity.ResourceActivity ra + FROM #ChangedActivityIds c + JOIN activity.ResourceActivity ra + ON ra.Id = c.ActivityId JOIN hierarchy.NodePath np ON np.Id = ra.NodePathId - WHERE ra.Id > @LastProcessedActivityId - AND ra.UserId IS NOT NULL + WHERE ra.UserId IS NOT NULL AND ra.Deleted = 0 AND np.Deleted = 0; @@ -41,7 +56,7 @@ BEGIN CREATE CLUSTERED INDEX IX_ChangedPairs ON #ChangedPairs (UserId, CatalogueNodeId); - /* Latest activity id per changed (UserId, CatalogueNodeId) */ + /* Latest activity id per affected (UserId, CatalogueNodeId) */ SELECT ra.UserId, np.CatalogueNodeId, @@ -80,12 +95,12 @@ BEGIN SET uca.LatestActivityId = l.LatestActivityId, uca.LastAccessedDate = l.LastAccessedDate - FROM reports.UserCatalogueActivity uca + FROM readmodels.UserCatalogueActivity uca JOIN #Latest l ON l.UserId = uca.UserId AND l.CatalogueNodeId = uca.CatalogueNodeId; - INSERT INTO reports.UserCatalogueActivity + INSERT INTO readmodels.UserCatalogueActivity ( UserId, CatalogueNodeId, @@ -98,8 +113,9 @@ BEGIN l.LatestActivityId, l.LastAccessedDate FROM #Latest l - LEFT JOIN reports.UserCatalogueActivity uca + LEFT JOIN readmodels.UserCatalogueActivity uca ON uca.UserId = l.UserId AND uca.CatalogueNodeId = l.CatalogueNodeId WHERE uca.UserId IS NULL; -END; \ No newline at end of file +END; +GO \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshUserResourceActivity.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Readmodel/ApplyUserResourceActivityChanges.sql similarity index 73% rename from WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshUserResourceActivity.sql rename to WebAPI/LearningHub.Nhs.Database/Stored Procedures/Readmodel/ApplyUserResourceActivityChanges.sql index 041ad6031..59efcd264 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshUserResourceActivity.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Readmodel/ApplyUserResourceActivityChanges.sql @@ -7,17 +7,19 @@ -- -- 23 April 2026 OA TD-7078 Script Optimization ------------------------------------------------------------------------------- -CREATE PROCEDURE [reports].[RefreshUserResourceActivity] +CREATE PROCEDURE [readmodels].[ApplyUserResourceActivityChanges] +( + @ChangedActivityIdsJson NVARCHAR(MAX) +) AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; - DECLARE @LastProcessedActivityId INT; - - SELECT @LastProcessedActivityId = ISNULL(MAX(LatestActivityId), 0) - FROM reports.UserResourceActivity; + IF @ChangedActivityIdsJson IS NULL OR ISJSON(@ChangedActivityIdsJson) <> 1 + RETURN; + IF OBJECT_ID('tempdb..#ChangedActivityIds') IS NOT NULL DROP TABLE #ChangedActivityIds; IF OBJECT_ID('tempdb..#ChangedPairs') IS NOT NULL DROP TABLE #ChangedPairs; IF OBJECT_ID('tempdb..#LatestIds') IS NOT NULL DROP TABLE #LatestIds; IF OBJECT_ID('tempdb..#Latest') IS NOT NULL DROP TABLE #Latest; @@ -26,15 +28,28 @@ BEGIN IF OBJECT_ID('tempdb..#Sa') IS NOT NULL DROP TABLE #Sa; IF OBJECT_ID('tempdb..#Completion') IS NOT NULL DROP TABLE #Completion; - /* Find changed user-resource pairs from newly inserted activities */ SELECT DISTINCT - a.UserId, - a.ResourceId + TRY_CAST([value] AS INT) AS ActivityId + INTO #ChangedActivityIds + FROM OPENJSON(@ChangedActivityIdsJson) + WHERE TRY_CAST([value] AS INT) IS NOT NULL; + + IF NOT EXISTS (SELECT 1 FROM #ChangedActivityIds) + RETURN; + + CREATE CLUSTERED INDEX IX_ChangedActivityIds + ON #ChangedActivityIds (ActivityId); + + /* Derive affected user-resource pairs */ + SELECT DISTINCT + ra.UserId, + ra.ResourceId INTO #ChangedPairs - FROM activity.ResourceActivity a - WHERE a.Id > @LastProcessedActivityId - AND a.UserId IS NOT NULL - AND a.Deleted = 0; + FROM #ChangedActivityIds c + JOIN activity.ResourceActivity ra + ON ra.Id = c.ActivityId + WHERE ra.UserId IS NOT NULL + AND ra.Deleted = 0; IF NOT EXISTS (SELECT 1 FROM #ChangedPairs) RETURN; @@ -42,46 +57,46 @@ BEGIN CREATE CLUSTERED INDEX IX_ChangedPairs ON #ChangedPairs (UserId, ResourceId); - /* Latest activity id per changed (UserId, ResourceId)*/ + /* Latest activity id per affected (UserId, ResourceId) */ SELECT - a.UserId, - a.ResourceId, - MAX(a.Id) AS LatestActivityId + ra.UserId, + ra.ResourceId, + MAX(ra.Id) AS LatestActivityId INTO #LatestIds - FROM activity.ResourceActivity a + FROM activity.ResourceActivity ra JOIN #ChangedPairs cp - ON cp.UserId = a.UserId - AND cp.ResourceId = a.ResourceId - WHERE a.Deleted = 0 + ON cp.UserId = ra.UserId + AND cp.ResourceId = ra.ResourceId + WHERE ra.Deleted = 0 GROUP BY - a.UserId, - a.ResourceId; + ra.UserId, + ra.ResourceId; CREATE CLUSTERED INDEX IX_LatestIds ON #LatestIds (UserId, ResourceId); - /* Join back to get the full latest activity row */ + /* Latest full row */ SELECT - a.UserId, - a.ResourceId, - a.Id AS ActivityId, - a.ResourceVersionId, - a.LaunchResourceActivityId, - a.ActivityStatusId, - a.ActivityStart, - a.ActivityEnd, + ra.UserId, + ra.ResourceId, + ra.Id AS ActivityId, + ra.ResourceVersionId, + ra.LaunchResourceActivityId, + ra.ActivityStatusId, + ra.ActivityStart, + ra.ActivityEnd, r.ResourceTypeId INTO #Latest FROM #LatestIds li - JOIN activity.ResourceActivity a - ON a.Id = li.LatestActivityId + JOIN activity.ResourceActivity ra + ON ra.Id = li.LatestActivityId JOIN resources.Resource r - ON r.Id = a.ResourceId; + ON r.Id = ra.ResourceId; CREATE CLUSTERED INDEX IX_Latest ON #Latest (UserId, ResourceId); - + /* Collapse child tables */ SELECT ara.ResourceActivityId, MAX(ara.Score) AS Score @@ -124,7 +139,7 @@ BEGIN CREATE CLUSTERED INDEX IX_Sa ON #Sa (ResourceActivityId); - /* Recompute completion and latest-access state */ + /* Recompute completion */ SELECT l.UserId, l.ResourceId, @@ -139,25 +154,21 @@ BEGIN OR l.ActivityStart < '2020-09-07' ) THEN 1 - WHEN l.ResourceTypeId = 6 AND ( sa.CmiCoreLesson_status IN (3,5) OR l.ActivityStatusId IN (3,5) ) THEN 1 - WHEN l.ResourceTypeId = 11 AND ( ara.Score >= arv.PassMark OR l.ActivityStatusId IN (3,5) ) THEN 1 - WHEN l.ResourceTypeId IN (1,5,8,9,10,12) AND l.ActivityStatusId = 3 THEN 1 - ELSE 0 END AS BIT ) AS IsCompleted @@ -176,19 +187,19 @@ BEGIN CREATE CLUSTERED INDEX IX_Completion ON #Completion (UserId, ResourceId); - /* Update existing rows */ + /* Update existing */ UPDATE ura SET ura.LatestActivityId = c.LatestActivityId, ura.IsCompleted = c.IsCompleted, ura.LastAccessedDate = c.LastAccessedDate - FROM reports.UserResourceActivity ura + FROM readmodels.UserResourceActivity ura JOIN #Completion c ON c.UserId = ura.UserId AND c.ResourceId = ura.ResourceId; - /* Insert new rows */ - INSERT INTO reports.UserResourceActivity + /* Insert new */ + INSERT INTO readmodels.UserResourceActivity ( UserId, ResourceId, @@ -203,7 +214,7 @@ BEGIN c.IsCompleted, c.LastAccessedDate FROM #Completion c - LEFT JOIN reports.UserResourceActivity ura + LEFT JOIN readmodels.UserResourceActivity ura ON ura.UserId = c.UserId AND ura.ResourceId = c.ResourceId WHERE ura.UserId IS NULL; diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshDashboardCatalogues.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Readmodel/RefreshDashboardCatalogues.sql similarity index 96% rename from WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshDashboardCatalogues.sql rename to WebAPI/LearningHub.Nhs.Database/Stored Procedures/Readmodel/RefreshDashboardCatalogues.sql index 3aa6d704d..dae33125e 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshDashboardCatalogues.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Readmodel/RefreshDashboardCatalogues.sql @@ -7,7 +7,7 @@ -- -- 23 April 2026 OA TD-7078 Script Optimization ------------------------------------------------------------------------------- -CREATE PROCEDURE [reports].[RefreshDashboardCatalogues] +CREATE PROCEDURE [readmodels].[RefreshDashboardCatalogues] AS BEGIN SET NOCOUNT ON; @@ -92,9 +92,9 @@ BEGIN CREATE CLUSTERED INDEX IX_Contributed ON #Contributed (CatalogueNodeId); - TRUNCATE TABLE reports.DashboardCatalogues; + TRUNCATE TABLE readmodels.DashboardCatalogues; - INSERT INTO reports.DashboardCatalogues + INSERT INTO readmodels.DashboardCatalogues ( CatalogueNodeId, NodeVersionId, diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Readmodel/RefreshDashboardReadModels.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Readmodel/RefreshDashboardReadModels.sql new file mode 100644 index 000000000..6a4f413a8 --- /dev/null +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Readmodel/RefreshDashboardReadModels.sql @@ -0,0 +1,46 @@ +------------------------------------------------------------------------------- +-- Author OA +-- Created 23 April 2026 +-- Purpose Refresh resource and calaogue read models +-- +-- Modification History +-- +-- 23 April 2026 OA TD-7078 Script Optimization +------------------------------------------------------------------------------- +CREATE PROCEDURE [readmodels].[RefreshDashboardReadModels] +AS +BEGIN + SET NOCOUNT ON; + SET XACT_ABORT ON; + + DECLARE @LockResult INT; + + BEGIN TRAN; + + EXEC @LockResult = sys.sp_getapplock + @Resource = 'readmodels.RefreshDashboardReadModels', + @LockMode = 'Exclusive', + @LockOwner = 'Transaction', + @LockTimeout = 0; + + IF @LockResult < 0 + BEGIN + -- If already running,exit gracefully. + ROLLBACK TRAN; + RETURN; + END; + + BEGIN TRY + EXEC [readmodels].[RefreshDashboardResources]; + EXEC [readmodels].[RefreshDashboardCatalogues]; + + COMMIT TRAN; + END TRY + BEGIN CATCH + IF @@TRANCOUNT > 0 + ROLLBACK TRAN; + + THROW; + END CATCH +END; +GO \ No newline at end of file diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshDashboardResources.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Readmodel/RefreshDashboardResources.sql similarity index 97% rename from WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshDashboardResources.sql rename to WebAPI/LearningHub.Nhs.Database/Stored Procedures/Readmodel/RefreshDashboardResources.sql index 399b498d2..cdfceb529 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Report/RefreshDashboardResources.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Readmodel/RefreshDashboardResources.sql @@ -7,7 +7,7 @@ -- -- 23 April 2026 OA TD-7078 Script Optimization ------------------------------------------------------------------------------- -CREATE PROCEDURE [reports].[RefreshDashboardResources] +CREATE PROCEDURE [readmodels].[RefreshDashboardResources] AS BEGIN SET NOCOUNT ON; @@ -88,9 +88,9 @@ BEGIN ON #Ref (ResourceId, CatalogueNodeId); - TRUNCATE TABLE reports.DashboardResources; + TRUNCATE TABLE readmodels.DashboardResources; - INSERT INTO reports.DashboardResources + INSERT INTO readmodels.DashboardResources ( ResourceId, ResourceReferenceId, diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyRecentCompletedDashboardResourcesRM.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyRecentCompletedDashboardResourcesRM.sql index fa01293cd..66c340d77 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyRecentCompletedDashboardResourcesRM.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetMyRecentCompletedDashboardResourcesRM.sql @@ -43,7 +43,7 @@ BEGIN ura.ResourceId, ura.LatestActivityId INTO #Candidates - FROM reports.UserResourceActivity ura + FROM readmodels.UserResourceActivity ura WHERE ura.UserId = @UserId AND ura.IsCompleted = 1 ORDER BY ura.LatestActivityId DESC; @@ -78,7 +78,7 @@ BEGIN c.LatestActivityId AS SortLatestActivityId INTO #Base FROM #Candidates c - JOIN reports.DashboardResources dr + JOIN readmodels.DashboardResources dr ON dr.ResourceId = c.ResourceId LEFT JOIN #Auth auth ON auth.CatalogueNodeId = dr.CatalogueNodeId diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetPopularDashboardResourcesRM.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetPopularDashboardResourcesRM.sql index 98e274b74..e9ff9c00c 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetPopularDashboardResourcesRM.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetPopularDashboardResourcesRM.sql @@ -64,7 +64,7 @@ BEGIN dr.ProvidersJson, dr.ActivityCount AS SortActivityCount INTO #Base - FROM reports.DashboardResources dr + FROM readmodels.DashboardResources dr LEFT JOIN #Auth auth ON auth.CatalogueNodeId = dr.CatalogueNodeId LEFT JOIN hub.UserBookmark ub diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRatedDashboardResourcesRM.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRatedDashboardResourcesRM.sql index b686c8ef6..5ee409e06 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRatedDashboardResourcesRM.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRatedDashboardResourcesRM.sql @@ -65,7 +65,7 @@ BEGIN CAST(dr.AverageRating AS DECIMAL(3,2)) AS SortAverageRating, dr.RatingCount AS SortRatingCount INTO #Base - FROM reports.DashboardResources dr + FROM readmodels.DashboardResources dr LEFT JOIN #Auth auth ON auth.CatalogueNodeId = dr.CatalogueNodeId LEFT JOIN hub.UserBookmark ub diff --git a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRecentDashboardResourcesRM.sql b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRecentDashboardResourcesRM.sql index 2416d22ad..1e0839aa8 100644 --- a/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRecentDashboardResourcesRM.sql +++ b/WebAPI/LearningHub.Nhs.Database/Stored Procedures/Resources/GetRecentDashboardResourcesRM.sql @@ -64,7 +64,7 @@ BEGIN END AS BIT ) AS HasAccess INTO #Base - FROM reports.DashboardResources dr + FROM readmodels.DashboardResources dr LEFT JOIN #Auth auth ON auth.CatalogueNodeId = dr.CatalogueNodeId LEFT JOIN hub.UserBookmark ub diff --git a/WebAPI/LearningHub.Nhs.Database/Tables/Report/DashboardCatalogue.sql b/WebAPI/LearningHub.Nhs.Database/Tables/Readmodel/DashboardCatalogue.sql similarity index 92% rename from WebAPI/LearningHub.Nhs.Database/Tables/Report/DashboardCatalogue.sql rename to WebAPI/LearningHub.Nhs.Database/Tables/Readmodel/DashboardCatalogue.sql index 9c407bed0..16da68107 100644 --- a/WebAPI/LearningHub.Nhs.Database/Tables/Report/DashboardCatalogue.sql +++ b/WebAPI/LearningHub.Nhs.Database/Tables/Readmodel/DashboardCatalogue.sql @@ -1,4 +1,4 @@ -CREATE TABLE [reports].[DashboardCatalogues] +CREATE TABLE [readmodels].[DashboardCatalogues] ( CatalogueNodeId INT NOT NULL, NodeVersionId INT NOT NULL, diff --git a/WebAPI/LearningHub.Nhs.Database/Tables/Report/DashboardResource.sql b/WebAPI/LearningHub.Nhs.Database/Tables/Readmodel/DashboardResource.sql similarity index 92% rename from WebAPI/LearningHub.Nhs.Database/Tables/Report/DashboardResource.sql rename to WebAPI/LearningHub.Nhs.Database/Tables/Readmodel/DashboardResource.sql index a392e4941..654a9d052 100644 --- a/WebAPI/LearningHub.Nhs.Database/Tables/Report/DashboardResource.sql +++ b/WebAPI/LearningHub.Nhs.Database/Tables/Readmodel/DashboardResource.sql @@ -1,4 +1,4 @@ -CREATE TABLE [reports].[DashboardResources] ( +CREATE TABLE [readmodels].[DashboardResources] ( ResourceId INT NOT NULL, ResourceReferenceId INT NULL, ResourceVersionId INT NOT NULL, diff --git a/WebAPI/LearningHub.Nhs.Database/Tables/Report/UserCatalogueActivity.sql b/WebAPI/LearningHub.Nhs.Database/Tables/Readmodel/UserCatalogueActivity.sql similarity index 80% rename from WebAPI/LearningHub.Nhs.Database/Tables/Report/UserCatalogueActivity.sql rename to WebAPI/LearningHub.Nhs.Database/Tables/Readmodel/UserCatalogueActivity.sql index 427135727..2f2986cb5 100644 --- a/WebAPI/LearningHub.Nhs.Database/Tables/Report/UserCatalogueActivity.sql +++ b/WebAPI/LearningHub.Nhs.Database/Tables/Readmodel/UserCatalogueActivity.sql @@ -1,4 +1,4 @@ -CREATE TABLE [reports].[UserCatalogueActivity] +CREATE TABLE [readmodels].[UserCatalogueActivity] ( UserId INT NOT NULL, CatalogueNodeId INT NOT NULL, diff --git a/WebAPI/LearningHub.Nhs.Database/Tables/Report/UserResourceActivity.sql b/WebAPI/LearningHub.Nhs.Database/Tables/Readmodel/UserResourceActivity.sql similarity index 81% rename from WebAPI/LearningHub.Nhs.Database/Tables/Report/UserResourceActivity.sql rename to WebAPI/LearningHub.Nhs.Database/Tables/Readmodel/UserResourceActivity.sql index 7f128ae9b..566b82b8b 100644 --- a/WebAPI/LearningHub.Nhs.Database/Tables/Report/UserResourceActivity.sql +++ b/WebAPI/LearningHub.Nhs.Database/Tables/Readmodel/UserResourceActivity.sql @@ -1,4 +1,4 @@ -CREATE TABLE [reports].[UserResourceActivity] ( +CREATE TABLE [readmodels].[UserResourceActivity] ( UserId INT NOT NULL, ResourceId INT NOT NULL, LatestActivityId INT NOT NULL,