210 lines
15 KiB
Transact-SQL
210 lines
15 KiB
Transact-SQL
-- Merge duplicate locations and story assets without deleting their archived source records.
|
|
-- Keep the selected target details; move canonical references and preserve known names.
|
|
|
|
CREATE OR ALTER PROCEDURE dbo.Location_Merge
|
|
@SourceID int,
|
|
@TargetID int,
|
|
@UserID int
|
|
AS
|
|
BEGIN
|
|
SET NOCOUNT ON;
|
|
SET XACT_ABORT ON;
|
|
IF @SourceID IS NULL OR @TargetID IS NULL OR @SourceID = @TargetID
|
|
THROW 51000, 'Choose two different locations to merge.', 1;
|
|
|
|
BEGIN TRY
|
|
BEGIN TRANSACTION;
|
|
DECLARE @ProjectID int, @SourceName nvarchar(200), @TargetName nvarchar(200), @Now datetime2 = SYSUTCDATETIME();
|
|
SELECT @ProjectID = ProjectID FROM dbo.Locations WHERE LocationID = @SourceID;
|
|
-- Serialize merges in a project before locking individual entities.
|
|
DECLARE @LockedProjectID int;
|
|
SELECT @LockedProjectID = ProjectID FROM dbo.Projects WITH (UPDLOCK, HOLDLOCK) WHERE ProjectID = @ProjectID;
|
|
IF @UserID IS NULL OR NOT EXISTS
|
|
(SELECT 1 FROM dbo.ProjectUserAccess WHERE ProjectID = @ProjectID AND UserID = @UserID AND IsActive = 1)
|
|
THROW 51000, 'You do not have access to this project.', 1;
|
|
SELECT @SourceName = LocationName FROM dbo.Locations WITH (UPDLOCK, HOLDLOCK)
|
|
WHERE LocationID = @SourceID AND IsArchived = 0;
|
|
SELECT @TargetName = LocationName FROM dbo.Locations WITH (UPDLOCK, HOLDLOCK)
|
|
WHERE LocationID = @TargetID AND ProjectID = @ProjectID AND IsArchived = 0;
|
|
IF @SourceName IS NULL OR @TargetName IS NULL
|
|
THROW 51000, 'Both locations must be active and belong to the same project.', 1;
|
|
|
|
-- Merging an ancestor into its descendant would create a hierarchy cycle.
|
|
DECLARE @AncestorID int = @TargetID;
|
|
DECLARE @Visited TABLE (LocationID int PRIMARY KEY);
|
|
WHILE @AncestorID IS NOT NULL
|
|
BEGIN
|
|
IF @AncestorID = @SourceID OR EXISTS (SELECT 1 FROM @Visited WHERE LocationID = @AncestorID)
|
|
THROW 51000, 'Cannot merge a location into one of its descendants. Choose another location or change its parent first.', 1;
|
|
INSERT @Visited VALUES (@AncestorID);
|
|
SELECT @AncestorID = ParentLocationID FROM dbo.Locations WITH (UPDLOCK, HOLDLOCK) WHERE LocationID = @AncestorID;
|
|
END;
|
|
|
|
INSERT dbo.LocationAliases (LocationID, Alias, SortOrder)
|
|
SELECT @TargetID, AliasValue, ISNULL((SELECT MAX(SortOrder) FROM dbo.LocationAliases WHERE LocationID = @TargetID), 0)
|
|
+ ROW_NUMBER() OVER (ORDER BY AliasValue) * 10
|
|
FROM (SELECT LTRIM(RTRIM(@SourceName)) AS AliasValue
|
|
UNION SELECT LTRIM(RTRIM(Alias)) FROM dbo.LocationAliases WHERE LocationID = @SourceID) names
|
|
WHERE NULLIF(AliasValue, N'') IS NOT NULL AND AliasValue <> @TargetName
|
|
AND NOT EXISTS (SELECT 1 FROM dbo.LocationAliases existing WHERE existing.LocationID = @TargetID
|
|
AND UPPER(LTRIM(RTRIM(existing.Alias))) = UPPER(AliasValue));
|
|
DELETE sourceRows FROM dbo.WordCompanionSceneLocations sourceRows
|
|
WHERE sourceRows.LocationID = @SourceID
|
|
AND EXISTS (SELECT 1 FROM dbo.WordCompanionSceneLocations targetRows WHERE targetRows.LocationID = @TargetID
|
|
AND targetRows.SceneID = sourceRows.SceneID);
|
|
DELETE sourceRows FROM dbo.SceneLocationSuggestions sourceRows
|
|
WHERE sourceRows.LocationID = @SourceID AND sourceRows.Status = N'Pending'
|
|
AND EXISTS (SELECT 1 FROM dbo.SceneLocationSuggestions targetRows WHERE targetRows.LocationID = @TargetID
|
|
AND targetRows.SceneID = sourceRows.SceneID AND targetRows.Status = sourceRows.Status);
|
|
UPDATE dbo.Locations SET ParentLocationID = @TargetID WHERE ParentLocationID = @SourceID;
|
|
UPDATE dbo.Scenes SET PrimaryLocationID = @TargetID WHERE PrimaryLocationID = @SourceID;
|
|
UPDATE dbo.SceneCharacters SET LocationID = @TargetID WHERE LocationID = @SourceID;
|
|
UPDATE dbo.SceneCharacters SET EntryLocationID = @TargetID WHERE EntryLocationID = @SourceID;
|
|
UPDATE dbo.SceneCharacters SET ExitLocationID = @TargetID WHERE ExitLocationID = @SourceID;
|
|
UPDATE dbo.StoryAssets SET CurrentLocationID = @TargetID WHERE CurrentLocationID = @SourceID;
|
|
UPDATE dbo.SceneAssetLocations SET LocationID = @TargetID WHERE LocationID = @SourceID;
|
|
UPDATE dbo.SceneFloorPlanOccupancy SET LocationID = @TargetID WHERE LocationID = @SourceID;
|
|
UPDATE dbo.WordCompanionSceneLocations SET LocationID = @TargetID WHERE LocationID = @SourceID;
|
|
UPDATE dbo.SceneLocationSuggestions SET LocationID = @TargetID WHERE LocationID = @SourceID;
|
|
UPDATE dbo.FloorPlans SET LocationID = @TargetID WHERE LocationID = @SourceID;
|
|
UPDATE dbo.FloorPlanFloors SET LocationID = @TargetID WHERE LocationID = @SourceID;
|
|
UPDATE dbo.FloorPlanBlocks SET LocationID = @TargetID WHERE LocationID = @SourceID;
|
|
UPDATE dbo.FloorPlanTransitions SET FromLocationID = @TargetID WHERE FromLocationID = @SourceID;
|
|
UPDATE dbo.FloorPlanTransitions SET ToLocationID = @TargetID WHERE ToLocationID = @SourceID;
|
|
UPDATE dbo.CoreImportBasicLocationCandidates SET ExistingLocationID = @TargetID WHERE ExistingLocationID = @SourceID;
|
|
UPDATE dbo.CoreImportBasicLocationCandidates SET ParentLocationID = @TargetID WHERE ParentLocationID = @SourceID;
|
|
UPDATE dbo.CoreImportBasicLocationCandidates SET ResultLocationID = @TargetID WHERE ResultLocationID = @SourceID;
|
|
UPDATE dbo.CoreImportBasicLocationCandidates SET SameAsLocationID = @TargetID WHERE SameAsLocationID = @SourceID;
|
|
|
|
-- Keep self-links archived with their original endpoints, as in character merging.
|
|
UPDATE dbo.LocationRelationships SET IsArchived = 1, UpdatedDate = @Now
|
|
WHERE (FromLocationID = @SourceID AND ToLocationID = @TargetID) OR (FromLocationID = @TargetID AND ToLocationID = @SourceID);
|
|
UPDATE dbo.LocationRelationships SET FromLocationID = CASE WHEN FromLocationID = @SourceID THEN @TargetID ELSE FromLocationID END,
|
|
ToLocationID = CASE WHEN ToLocationID = @SourceID THEN @TargetID ELSE ToLocationID END, UpdatedDate = @Now
|
|
WHERE IsArchived = 0 AND (FromLocationID = @SourceID OR ToLocationID = @SourceID);
|
|
-- Collapse identical active links while retaining links with different evidence/settings.
|
|
UPDATE duplicate SET IsArchived = 1, UpdatedDate = @Now
|
|
FROM dbo.LocationRelationships duplicate
|
|
WHERE duplicate.IsArchived = 0 AND (duplicate.FromLocationID = @TargetID OR duplicate.ToLocationID = @TargetID)
|
|
AND EXISTS (SELECT 1 FROM dbo.LocationRelationships kept WHERE kept.IsArchived = 0
|
|
AND kept.LocationRelationshipID < duplicate.LocationRelationshipID
|
|
AND kept.FromLocationID = duplicate.FromLocationID AND kept.ToLocationID = duplicate.ToLocationID
|
|
AND (kept.LocationRelationshipTypeID = duplicate.LocationRelationshipTypeID OR (kept.LocationRelationshipTypeID IS NULL AND duplicate.LocationRelationshipTypeID IS NULL))
|
|
AND (kept.IsBidirectional = duplicate.IsBidirectional OR (kept.IsBidirectional IS NULL AND duplicate.IsBidirectional IS NULL))
|
|
AND (kept.Strength = duplicate.Strength OR (kept.Strength IS NULL AND duplicate.Strength IS NULL))
|
|
AND (kept.CanHearNormalSpeech = duplicate.CanHearNormalSpeech OR (kept.CanHearNormalSpeech IS NULL AND duplicate.CanHearNormalSpeech IS NULL))
|
|
AND (kept.CanHearLoudNoise = duplicate.CanHearLoudNoise OR (kept.CanHearLoudNoise IS NULL AND duplicate.CanHearLoudNoise IS NULL))
|
|
AND (kept.CanSeeMovement = duplicate.CanSeeMovement OR (kept.CanSeeMovement IS NULL AND duplicate.CanSeeMovement IS NULL))
|
|
AND (kept.CanSeeClearly = duplicate.CanSeeClearly OR (kept.CanSeeClearly IS NULL AND duplicate.CanSeeClearly IS NULL))
|
|
AND (kept.CanTravelDirectly = duplicate.CanTravelDirectly OR (kept.CanTravelDirectly IS NULL AND duplicate.CanTravelDirectly IS NULL))
|
|
AND (kept.Notes = duplicate.Notes OR (kept.Notes IS NULL AND duplicate.Notes IS NULL)));
|
|
|
|
UPDATE dbo.Locations
|
|
SET IsArchived = 1, ArchivedDate = @Now,
|
|
ArchivedReason = CONCAT(N'Merged into ', @TargetName, N' (LocationID ', @TargetID, N').'),
|
|
UpdatedDate = @Now, ModifiedByUserID = @UserID, ModifiedDateUTC = @Now
|
|
WHERE LocationID = @SourceID;
|
|
COMMIT TRANSACTION;
|
|
SELECT @ProjectID AS ProjectID, @TargetID AS TargetID, @SourceName AS SourceName, @TargetName AS TargetName;
|
|
END TRY
|
|
BEGIN CATCH
|
|
IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
|
|
THROW;
|
|
END CATCH;
|
|
END;
|
|
GO
|
|
|
|
CREATE OR ALTER PROCEDURE dbo.StoryAsset_Merge
|
|
@SourceID int,
|
|
@TargetID int,
|
|
@UserID int
|
|
AS
|
|
BEGIN
|
|
SET NOCOUNT ON;
|
|
SET XACT_ABORT ON;
|
|
IF @SourceID IS NULL OR @TargetID IS NULL OR @SourceID = @TargetID
|
|
THROW 51000, 'Choose two different assets to merge.', 1;
|
|
|
|
BEGIN TRY
|
|
BEGIN TRANSACTION;
|
|
DECLARE @ProjectID int, @SourceName nvarchar(200), @TargetName nvarchar(200), @Now datetime2 = SYSUTCDATETIME();
|
|
SELECT @ProjectID = ProjectID FROM dbo.StoryAssets WHERE StoryAssetID = @SourceID;
|
|
-- Serialize merges in a project before locking individual entities.
|
|
DECLARE @LockedProjectID int;
|
|
SELECT @LockedProjectID = ProjectID FROM dbo.Projects WITH (UPDLOCK, HOLDLOCK) WHERE ProjectID = @ProjectID;
|
|
IF @UserID IS NULL OR NOT EXISTS
|
|
(SELECT 1 FROM dbo.ProjectUserAccess WHERE ProjectID = @ProjectID AND UserID = @UserID AND IsActive = 1)
|
|
THROW 51000, 'You do not have access to this project.', 1;
|
|
SELECT @SourceName = AssetName FROM dbo.StoryAssets WITH (UPDLOCK, HOLDLOCK)
|
|
WHERE StoryAssetID = @SourceID AND IsArchived = 0;
|
|
SELECT @TargetName = AssetName FROM dbo.StoryAssets WITH (UPDLOCK, HOLDLOCK)
|
|
WHERE StoryAssetID = @TargetID AND ProjectID = @ProjectID AND IsArchived = 0;
|
|
IF @SourceName IS NULL OR @TargetName IS NULL
|
|
THROW 51000, 'Both assets must be active and belong to the same project.', 1;
|
|
|
|
INSERT dbo.AssetAliases (StoryAssetID, Alias, SortOrder)
|
|
SELECT @TargetID, AliasValue, ISNULL((SELECT MAX(SortOrder) FROM dbo.AssetAliases WHERE StoryAssetID = @TargetID), 0)
|
|
+ ROW_NUMBER() OVER (ORDER BY AliasValue) * 10
|
|
FROM (SELECT LTRIM(RTRIM(@SourceName)) AS AliasValue
|
|
UNION SELECT LTRIM(RTRIM(Alias)) FROM dbo.AssetAliases WHERE StoryAssetID = @SourceID) names
|
|
WHERE NULLIF(AliasValue, N'') IS NOT NULL AND AliasValue <> @TargetName
|
|
AND NOT EXISTS (SELECT 1 FROM dbo.AssetAliases existing WHERE existing.StoryAssetID = @TargetID
|
|
AND UPPER(LTRIM(RTRIM(existing.Alias))) = UPPER(AliasValue));
|
|
DELETE sourceRows FROM dbo.WordCompanionSceneAssets sourceRows
|
|
WHERE sourceRows.StoryAssetID = @SourceID
|
|
AND EXISTS (SELECT 1 FROM dbo.WordCompanionSceneAssets targetRows WHERE targetRows.StoryAssetID = @TargetID
|
|
AND targetRows.SceneID = sourceRows.SceneID);
|
|
DELETE sourceRows FROM dbo.SceneAssetSuggestions sourceRows
|
|
WHERE sourceRows.AssetID = @SourceID AND sourceRows.Status = N'Pending'
|
|
AND EXISTS (SELECT 1 FROM dbo.SceneAssetSuggestions targetRows WHERE targetRows.AssetID = @TargetID
|
|
AND targetRows.SceneID = sourceRows.SceneID AND targetRows.Status = sourceRows.Status);
|
|
DELETE sourceRows FROM dbo.SceneFloorPlanOccupancy sourceRows
|
|
WHERE sourceRows.StoryAssetID = @SourceID
|
|
AND EXISTS (SELECT 1 FROM dbo.SceneFloorPlanOccupancy targetRows WHERE targetRows.StoryAssetID = @TargetID
|
|
AND targetRows.SceneID = sourceRows.SceneID);
|
|
DELETE sourceRows FROM dbo.ContinuityWarningAcknowledgements sourceRows
|
|
WHERE sourceRows.AssetID = @SourceID
|
|
AND EXISTS (SELECT 1 FROM dbo.ContinuityWarningAcknowledgements targetRows WHERE targetRows.AssetID = @TargetID
|
|
AND targetRows.ProjectID = sourceRows.ProjectID AND targetRows.WarningType = sourceRows.WarningType AND targetRows.SceneIDKey = sourceRows.SceneIDKey AND targetRows.CharacterIDKey = sourceRows.CharacterIDKey);
|
|
UPDATE dbo.AssetEvents SET StoryAssetID = @TargetID WHERE StoryAssetID = @SourceID;
|
|
UPDATE dbo.AssetCustodyEvents SET StoryAssetID = @TargetID WHERE StoryAssetID = @SourceID;
|
|
UPDATE dbo.CharacterKnowledge SET StoryAssetID = @TargetID WHERE StoryAssetID = @SourceID;
|
|
UPDATE dbo.SceneAssetLocations SET StoryAssetID = @TargetID WHERE StoryAssetID = @SourceID;
|
|
UPDATE dbo.SceneFloorPlanOccupancy SET StoryAssetID = @TargetID WHERE StoryAssetID = @SourceID;
|
|
UPDATE dbo.WordCompanionSceneAssets SET StoryAssetID = @TargetID WHERE StoryAssetID = @SourceID;
|
|
UPDATE dbo.SceneAssetSuggestions SET AssetID = @TargetID WHERE AssetID = @SourceID;
|
|
UPDATE dbo.ContinuityWarningAcknowledgements SET AssetID = @TargetID WHERE AssetID = @SourceID;
|
|
|
|
-- Keep self-links archived with their original endpoints, as in character merging.
|
|
UPDATE dbo.AssetDependencies SET IsArchived = 1, UpdatedDate = @Now
|
|
WHERE (SourceAssetID = @SourceID AND TargetAssetID = @TargetID) OR (SourceAssetID = @TargetID AND TargetAssetID = @SourceID);
|
|
UPDATE dbo.AssetDependencies SET SourceAssetID = CASE WHEN SourceAssetID = @SourceID THEN @TargetID ELSE SourceAssetID END,
|
|
TargetAssetID = CASE WHEN TargetAssetID = @SourceID THEN @TargetID ELSE TargetAssetID END, UpdatedDate = @Now
|
|
WHERE IsArchived = 0 AND (SourceAssetID = @SourceID OR TargetAssetID = @SourceID);
|
|
-- Collapse identical active links while retaining links with different evidence/settings.
|
|
UPDATE duplicate SET IsArchived = 1, UpdatedDate = @Now
|
|
FROM dbo.AssetDependencies duplicate
|
|
WHERE duplicate.IsArchived = 0 AND (duplicate.SourceAssetID = @TargetID OR duplicate.TargetAssetID = @TargetID)
|
|
AND EXISTS (SELECT 1 FROM dbo.AssetDependencies kept WHERE kept.IsArchived = 0
|
|
AND kept.AssetDependencyID < duplicate.AssetDependencyID
|
|
AND kept.SourceAssetID = duplicate.SourceAssetID AND kept.TargetAssetID = duplicate.TargetAssetID
|
|
AND (kept.AssetDependencyTypeID = duplicate.AssetDependencyTypeID OR (kept.AssetDependencyTypeID IS NULL AND duplicate.AssetDependencyTypeID IS NULL))
|
|
AND (kept.SourceRequiredStateID = duplicate.SourceRequiredStateID OR (kept.SourceRequiredStateID IS NULL AND duplicate.SourceRequiredStateID IS NULL))
|
|
AND (kept.TargetBlockedStateID = duplicate.TargetBlockedStateID OR (kept.TargetBlockedStateID IS NULL AND duplicate.TargetBlockedStateID IS NULL))
|
|
AND (kept.Description = duplicate.Description OR (kept.Description IS NULL AND duplicate.Description IS NULL)));
|
|
|
|
UPDATE dbo.StoryAssets
|
|
SET IsArchived = 1, ArchivedDate = @Now,
|
|
ArchivedReason = CONCAT(N'Merged into ', @TargetName, N' (StoryAssetID ', @TargetID, N').'),
|
|
UpdatedDate = @Now, ModifiedByUserID = @UserID, ModifiedDateUTC = @Now
|
|
WHERE StoryAssetID = @SourceID;
|
|
COMMIT TRANSACTION;
|
|
SELECT @ProjectID AS ProjectID, @TargetID AS TargetID, @SourceName AS SourceName, @TargetName AS TargetName;
|
|
END TRY
|
|
BEGIN CATCH
|
|
IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;
|
|
THROW;
|
|
END CATCH;
|
|
END;
|
|
GO
|