SET ANSI_NULLS ON; GO SET QUOTED_IDENTIFIER ON; GO CREATE OR ALTER PROCEDURE dbo.GivenName_Search @UserID int, @ProjectID int, @BookID int = NULL, @Search nvarchar(120) = NULL, @LocaleCode nvarchar(16) = NULL, @BirthYear int = NULL, @GenderPreference nvarchar(12) = NULL, @GenderThreshold decimal(9,4) = NULL, @NameType nvarchar(40) = NULL, @MinimumUsage decimal(9,4) = NULL, @UsedScope nvarchar(20) = N'Project', @UsedOnly bit = NULL, @Page int = 1, @PageSize int = 25 AS BEGIN SET NOCOUNT ON; IF NOT EXISTS (SELECT 1 FROM dbo.ProjectUserAccess WHERE ProjectID = @ProjectID AND UserID = @UserID AND IsActive = 1) BEGIN SELECT TOP (0) CAST(NULL AS int) AS GivenNameID; RETURN; END; IF @Page < 1 SET @Page = 1; IF @PageSize < 1 SET @PageSize = 25; IF @PageSize > 100 SET @PageSize = 100; DECLARE @NameLocaleID int = NULL; IF @LocaleCode IS NOT NULL BEGIN SELECT TOP (1) @NameLocaleID = NameLocaleID FROM dbo.NameLocales WHERE LocaleCode = @LocaleCode AND IsActive = 1; END; ;WITH UsageChoice AS ( SELECT u.*, l.LocaleCode, l.DisplayName AS LocaleDisplayName, ROW_NUMBER() OVER (PARTITION BY u.GivenNameID ORDER BY CASE WHEN @BirthYear IS NOT NULL AND @BirthYear BETWEEN u.BirthYearFrom AND u.BirthYearTo THEN 0 ELSE 1 END, CASE WHEN @BirthYear IS NULL THEN ABS(1960 - ((u.BirthYearFrom + u.BirthYearTo) / 2)) ELSE ABS(@BirthYear - ((u.BirthYearFrom + u.BirthYearTo) / 2)) END, u.UsageWeight DESC) AS rn FROM dbo.NameUsages u INNER JOIN dbo.NameLocales l ON l.NameLocaleID = u.NameLocaleID WHERE u.IsActive = 1 AND (@NameLocaleID IS NULL OR u.NameLocaleID = @NameLocaleID) ), Used AS ( SELECT g.GivenNameID, COUNT(DISTINCT c.CharacterID) AS UsedCount, STRING_AGG(CONVERT(nvarchar(max), c.CharacterName + N' (' + p.ProjectName + COALESCE(N' / ' + b.BookTitle, N'') + N')'), N'; ') AS UsedSummary FROM dbo.GivenNames g INNER JOIN ( SELECT c.CharacterID, c.ProjectID, c.CharacterName, c.CharacterName AS MatchedName FROM dbo.Characters c WHERE c.IsArchived = 0 UNION ALL SELECT c.CharacterID, c.ProjectID, c.CharacterName, ca.Alias AS MatchedName FROM dbo.CharacterAliases ca INNER JOIN dbo.Characters c ON c.CharacterID = ca.CharacterID WHERE c.IsArchived = 0 ) c ON UPPER(LTRIM(RTRIM(c.MatchedName))) = g.NormalisedName COLLATE Latin1_General_100_CI_AI_SC INNER JOIN dbo.Projects p ON p.ProjectID = c.ProjectID INNER JOIN dbo.ProjectUserAccess pua ON pua.ProjectID = c.ProjectID AND pua.UserID = @UserID AND pua.IsActive = 1 LEFT JOIN dbo.Books b ON b.ProjectID = c.ProjectID AND b.IsArchived = 0 WHERE (@UsedScope = N'Library' OR c.ProjectID = @ProjectID) AND (@BookID IS NULL OR @UsedScope <> N'Book' OR b.BookID = @BookID) GROUP BY g.GivenNameID ), Related AS ( SELECT x.GivenNameID, STRING_AGG(CONVERT(nvarchar(max), x.DisplayName), N', ') AS RelatedForms FROM ( SELECT r.FromGivenNameID AS GivenNameID, t.DisplayName FROM dbo.NameRelationships r INNER JOIN dbo.GivenNames t ON t.GivenNameID = r.ToGivenNameID WHERE r.IsActive = 1 UNION SELECT r.ToGivenNameID AS GivenNameID, f.DisplayName FROM dbo.NameRelationships r INNER JOIN dbo.GivenNames f ON f.GivenNameID = r.FromGivenNameID WHERE r.IsActive = 1 AND r.IsBidirectional = 1 ) x GROUP BY x.GivenNameID ), Filtered AS ( SELECT g.GivenNameID, g.GivenNameKey, g.DisplayName, g.NormalisedName, g.NameType, g.IsWordAmbiguous, u.MaleWeight, u.FemaleWeight, u.UsageWeight, u.Confidence, u.BirthYearFrom, u.BirthYearTo, u.LocaleCode, u.LocaleDisplayName, u.SourceType, rel.RelatedForms, COALESCE(used.UsedSummary, N'') AS UsedSummary, COALESCE(used.UsedCount, 0) AS UsedCount, COUNT(1) OVER() AS TotalRows FROM dbo.GivenNames g LEFT JOIN UsageChoice u ON u.GivenNameID = g.GivenNameID AND u.rn = 1 LEFT JOIN Used used ON used.GivenNameID = g.GivenNameID LEFT JOIN Related rel ON rel.GivenNameID = g.GivenNameID WHERE g.IsActive = 1 AND (@Search IS NULL OR g.NormalisedName LIKE UPPER(LTRIM(RTRIM(@Search))) + N'%' COLLATE Latin1_General_100_CI_AI_SC OR g.DisplayName LIKE @Search + N'%') AND (@NameType IS NULL OR g.NameType = @NameType) AND (@MinimumUsage IS NULL OR u.UsageWeight >= @MinimumUsage) AND (@UsedOnly IS NULL OR (@UsedOnly = 1 AND COALESCE(used.UsedCount, 0) > 0) OR (@UsedOnly = 0 AND COALESCE(used.UsedCount, 0) = 0)) AND ( @GenderPreference IS NULL OR @GenderThreshold IS NULL OR (@GenderPreference = N'Female' AND u.FemaleWeight >= @GenderThreshold) OR (@GenderPreference = N'Male' AND u.MaleWeight >= @GenderThreshold) OR (@GenderPreference = N'Mixed' AND u.MaleWeight >= (1 - @GenderThreshold) AND u.FemaleWeight >= (1 - @GenderThreshold)) ) ) SELECT * FROM Filtered ORDER BY CASE WHEN @Search IS NOT NULL AND NormalisedName = UPPER(LTRIM(RTRIM(@Search))) COLLATE Latin1_General_100_CI_AI_SC THEN 0 ELSE 1 END, UsageWeight DESC, DisplayName OFFSET (@Page - 1) * @PageSize ROWS FETCH NEXT @PageSize ROWS ONLY; END; GO