PlotDirector/PlotLine/Sql/024_CharacterTimelinePerformance.sql
2026-06-02 21:43:31 +01:00

152 lines
6.8 KiB
Transact-SQL

SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
/*
Focused optimisation for dbo.CharacterTimeline_GetByProject.
Diagnosis:
- The timeline timing showed this procedure as the clear bottleneck.
- The original appearance query joined SceneCharacters to Characters, Scenes,
Chapters, Books, and the recursive LocationPaths view three times.
- Filtering the project/book scene set once gives SQL Server a small driving
set before joining appearances.
- Materialising project location paths once avoids repeated recursive view
expansion for location, entry location, and exit location labels.
*/
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Characters_ProjectID_Importance_Name' AND object_id = OBJECT_ID(N'dbo.Characters'))
BEGIN
-- Supports CharacterTimeline_GetByProject character list ordering.
CREATE INDEX IX_Characters_ProjectID_Importance_Name
ON dbo.Characters(ProjectID, IsArchived, CharacterImportance DESC, CharacterName);
END;
GO
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_SceneCharacters_CharacterID_SceneID' AND object_id = OBJECT_ID(N'dbo.SceneCharacters'))
BEGIN
-- Supports appearance lookups when the optimizer starts from project characters.
CREATE INDEX IX_SceneCharacters_CharacterID_SceneID
ON dbo.SceneCharacters(CharacterID, SceneID, RoleInSceneTypeID);
END;
GO
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Locations_Project_Parent_Name' AND object_id = OBJECT_ID(N'dbo.Locations'))
BEGIN
-- Supports project-scoped LocationPaths materialisation.
CREATE INDEX IX_Locations_Project_Parent_Name
ON dbo.Locations(ProjectID, IsArchived, ParentLocationID, LocationName);
END;
GO
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_Locations_ParentLocationID' AND object_id = OBJECT_ID(N'dbo.Locations'))
BEGIN
-- Supports recursive LocationPaths expansion from parent to child locations.
CREATE INDEX IX_Locations_ParentLocationID
ON dbo.Locations(ParentLocationID, IsArchived, LocationName);
END;
GO
CREATE OR ALTER PROCEDURE dbo.CharacterTimeline_GetByProject
@ProjectID int,
@BookID int = NULL
AS
BEGIN
SET NOCOUNT ON;
CREATE TABLE #TimelineScenes
(
SceneID int NOT NULL PRIMARY KEY,
SceneNumber decimal(9,2) NOT NULL,
SceneTitle nvarchar(200) NOT NULL,
ChapterNumber decimal(9,2) NOT NULL,
BookTitle nvarchar(200) NOT NULL,
BookSortOrder int NOT NULL,
ChapterSortOrder int NOT NULL,
SceneSortOrder int NOT NULL
);
INSERT #TimelineScenes (SceneID, SceneNumber, SceneTitle, ChapterNumber, BookTitle, BookSortOrder, ChapterSortOrder, SceneSortOrder)
SELECT s.SceneID, s.SceneNumber, s.SceneTitle, ch.ChapterNumber, b.BookTitle, b.SortOrder, ch.SortOrder, s.SortOrder
FROM dbo.Books b
INNER JOIN dbo.Chapters ch ON ch.BookID = b.BookID
INNER JOIN dbo.Scenes s ON s.ChapterID = ch.ChapterID
WHERE b.ProjectID = @ProjectID
AND b.IsArchived = 0
AND ch.IsArchived = 0
AND s.IsArchived = 0
AND (@BookID IS NULL OR b.BookID = @BookID);
CREATE TABLE #TimelineCharacters
(
CharacterID int NOT NULL PRIMARY KEY,
ProjectID int NOT NULL,
CharacterName nvarchar(200) NOT NULL,
ShortName nvarchar(100) NULL,
Sex nvarchar(50) NULL,
BirthDate date NULL,
AgeAtSeriesStart int NULL,
AgeReferenceSceneID int NULL,
Height nvarchar(50) NULL,
EyeColour nvarchar(50) NULL,
CharacterImportance int NULL,
DefaultDescription nvarchar(max) NULL,
CreatedDate datetime2 NOT NULL,
UpdatedDate datetime2 NOT NULL,
IsArchived bit NOT NULL
);
INSERT #TimelineCharacters (CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID, Height, EyeColour, CharacterImportance, DefaultDescription, CreatedDate, UpdatedDate, IsArchived)
SELECT CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID,
Height, EyeColour, CharacterImportance, DefaultDescription, CreatedDate, UpdatedDate, IsArchived
FROM dbo.Characters
WHERE ProjectID = @ProjectID AND IsArchived = 0;
CREATE TABLE #ProjectLocationPaths
(
LocationID int NOT NULL PRIMARY KEY,
LocationName nvarchar(200) NOT NULL,
LocationPath nvarchar(max) NOT NULL
);
;WITH ProjectLocationTree AS
(
SELECT LocationID, LocationName, CAST(LocationName AS nvarchar(max)) AS LocationPath
FROM dbo.Locations
WHERE ProjectID = @ProjectID AND ParentLocationID IS NULL AND IsArchived = 0
UNION ALL
SELECT child.LocationID, child.LocationName, CAST(parent.LocationPath + N' > ' + child.LocationName AS nvarchar(max)) AS LocationPath
FROM dbo.Locations child
INNER JOIN ProjectLocationTree parent ON parent.LocationID = child.ParentLocationID
WHERE child.ProjectID = @ProjectID AND child.IsArchived = 0
)
INSERT #ProjectLocationPaths (LocationID, LocationName, LocationPath)
SELECT LocationID, LocationName, LocationPath
FROM ProjectLocationTree;
SELECT CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart, AgeReferenceSceneID,
Height, EyeColour, CharacterImportance, DefaultDescription, CreatedDate, UpdatedDate, IsArchived
FROM #TimelineCharacters
ORDER BY ISNULL(CharacterImportance, 0) DESC, CharacterName;
SELECT sc.SceneCharacterID, sc.SceneID, sc.CharacterID, c.ProjectID, c.CharacterName, c.ShortName, c.BirthDate, c.AgeAtSeriesStart,
sc.RoleInSceneTypeID, role.TypeName AS RoleInSceneTypeName, sc.PresenceTypeID, presence.TypeName AS PresenceTypeName,
sc.LocationID, location.LocationName, location.LocationPath, sc.EntryLocationID, entry.LocationName AS EntryLocationName,
sc.ExitLocationID, exitLocation.LocationName AS ExitLocationName, sc.AppearanceNotes, sc.OutfitDescription,
sc.PhysicalCondition, sc.EmotionalState, sc.KnowledgeNotes, s.SceneNumber, s.SceneTitle, s.ChapterNumber, s.BookTitle,
sc.CreatedDate, sc.UpdatedDate
FROM #TimelineScenes s
INNER JOIN dbo.SceneCharacters sc ON sc.SceneID = s.SceneID
INNER JOIN #TimelineCharacters c ON c.CharacterID = sc.CharacterID
LEFT JOIN dbo.CharacterRoleInSceneTypes role ON role.CharacterRoleInSceneTypeID = sc.RoleInSceneTypeID
LEFT JOIN dbo.PresenceTypes presence ON presence.PresenceTypeID = sc.PresenceTypeID
LEFT JOIN #ProjectLocationPaths location ON location.LocationID = sc.LocationID
LEFT JOIN #ProjectLocationPaths entry ON entry.LocationID = sc.EntryLocationID
LEFT JOIN #ProjectLocationPaths exitLocation ON exitLocation.LocationID = sc.ExitLocationID
ORDER BY s.BookSortOrder, s.ChapterSortOrder, s.SceneSortOrder, c.CharacterName;
END;
GO