I have a portion of a SQL Server database with the...
作成日: 2025年12月6日
使用モデル GPT-5.1 Thinking by Chat01
作成日: 2025年12月6日
使用モデル GPT-5.1 Thinking by Chat01
I have a portion of a SQL Server database with the structure described in the attached image. I am trying to clone a ProductPricingPlan. During the cloning procedure the following should happen:
You basically need a “deep-clone with id-mapping”:
Below is a stored procedure that shows the pattern.
You’ll probably need to tweak a few column names (e.g. ProductPricingPlanPaymentScheduleId) to match your schema exactly, but the idea is complete.
sqlCREATE PROCEDURE Billing.CloneProductPricingPlan ( @SourcePlanId INT -- existing ProductPricingPlan.Id , @NewPlanName NVARCHAR(200) = NULL , @NewPlanId INT OUTPUT -- new ProductPricingPlan.Id ) AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; DECLARE @OldSmallLogoId INT, @OldLargeLogoId INT, @OldVersionId INT, @NewSmallLogoId INT, @NewLargeLogoId INT, @NewVersionId INT; -- Get values from source plan SELECT @OldSmallLogoId = SmallLogoImageId , @OldLargeLogoId = LargeLogoImageId , @OldVersionId = VersionId FROM ProductPricingPlan WHERE Id = @SourcePlanId; BEGIN TRY BEGIN TRAN; /* 1. Clone logo files (Files table) */ IF @OldSmallLogoId IS NOT NULL BEGIN INSERT INTO Files (QueuedEmailId, FileName, [Content], MimeType) SELECT QueuedEmailId, FileName, [Content], MimeType FROM Files WHERE Id = @OldSmallLogoId; SET @NewSmallLogoId = SCOPE_IDENTITY(); END IF @OldLargeLogoId IS NOT NULL BEGIN INSERT INTO Files (QueuedEmailId, FileName, [Content], MimeType) SELECT QueuedEmailId, FileName, [Content], MimeType FROM Files WHERE Id = @OldLargeLogoId; SET @NewLargeLogoId = SCOPE_IDENTITY(); END /* 2. Reset versioning (new ProductPricingPlanVersion row) */ IF @OldVersionId IS NOT NULL BEGIN INSERT INTO ProductPricingPlanVersion (CreatedOn, UpdatedOn, Name) SELECT GETUTCDATE(), GETUTCDATE(), Name FROM ProductPricingPlanVersion WHERE Id = @OldVersionId; SET @NewVersionId = SCOPE_IDENTITY(); END /* 3. Clone ProductPricingPlan itself */ INSERT INTO ProductPricingPlan ( ProductId , CurrencyCode , Name , UpfrontCharge , TrialPeriodCharge , TrialPeriodDuration , Alias , ProductPricingPlanId , LicenseNote , OEMNote , EditionType , IsLicenseNeeded , TaxCategoryId -- same tax category , TaxMode , LicenseFilename , DisplayOrder , VersionId -- new version chain , ExpiryNote , LicenseInstructions , UseApiKeys , SmallLogoImageId -- cloned files , LargeLogoImageId , RequiresShipping , PhysicalWeight , PhysicalLength , PhysicalHeight , PhysicalWidth , HasFreeShipping , IsShippedIndividually , PlanType ) SELECT ProductId , CurrencyCode , ISNULL(@NewPlanName, Name) , UpfrontCharge , TrialPeriodCharge , TrialPeriodDuration , Alias , ProductPricingPlanId , LicenseNote , OEMNote , EditionType , IsLicenseNeeded , TaxCategoryId -- **same TaxCategory as requested** , TaxMode , LicenseFilename , DisplayOrder , @NewVersionId , ExpiryNote , LicenseInstructions , UseApiKeys , @NewSmallLogoId , @NewLargeLogoId , RequiresShipping , PhysicalWeight , PhysicalLength , PhysicalHeight , PhysicalWidth , HasFreeShipping , IsShippedIndividually , PlanType FROM ProductPricingPlan WHERE Id = @SourcePlanId; SET @NewPlanId = SCOPE_IDENTITY(); /* 4. Clone payment schedules, keep old→new map */ DECLARE @ScheduleMap TABLE (OldId INT PRIMARY KEY, NewId INT); INSERT INTO ProductPricingPlanPaymentSchedule ( ProductPricingPlanId , Name , FrequencyType , FrequencyInterval , FrequencyRelativeInterval , FrequencyRecurrenceFactor , StartDate , EndDate , SubscriptionPeriodCharge , Priority , IsMasterSchedule ) OUTPUT src.Id, inserted.Id INTO @ScheduleMap(OldId, NewId) SELECT @NewPlanId , src.Name , src.FrequencyType , src.FrequencyInterval , src.FrequencyRelativeInterval , src.FrequencyRecurrenceFactor , src.StartDate , src.EndDate , src.SubscriptionPeriodCharge , src.Priority , src.IsMasterSchedule FROM ProductPricingPlanPaymentSchedule AS src WHERE src.ProductPricingPlanId = @SourcePlanId; /* 5. Clone ProductItem rows, map old→new IDs */ DECLARE @ItemMap TABLE (OldId INT PRIMARY KEY, NewId INT); INSERT INTO ProductItem ( Name , Description , ProductPricingPlanId , ProductItemId -- whatever this points to, usually stays same , ChargeModel , ProductItemType , UnitName , IsVisibleOnHostedPage , ProductPricingPlanPaymentScheduleId , MinQuantity , MaxQuantity , DisplayOrder ) OUTPUT src.Id, inserted.Id INTO @ItemMap(OldId, NewId) SELECT src.Name , src.Description , @NewPlanId , src.ProductItemId , src.ChargeModel , src.ProductItemType , src.UnitName , src.IsVisibleOnHostedPage , sm.NewId -- re-point to cloned schedule , src.MinQuantity , src.MaxQuantity , src.DisplayOrder FROM ProductItem AS src LEFT JOIN @ScheduleMap AS sm ON sm.OldId = src.ProductPricingPlanPaymentScheduleId WHERE src.ProductPricingPlanId = @SourcePlanId; /* 6. Clone ProductMeteredPrice rows for each cloned ProductItem */ INSERT INTO ProductMeteredPrice ( ProductItemId , StartQuantity , EndQuantity , UnitPrice , Description ) SELECT im.NewId , src.StartQuantity , src.EndQuantity , src.UnitPrice , src.Description FROM ProductMeteredPrice AS src JOIN @ItemMap AS im ON im.OldId = src.ProductItemId; COMMIT TRAN; END TRY BEGIN CATCH IF @@TRANCOUNT > 0 ROLLBACK TRAN; -- surface the error THROW; END CATCH END GO
What this gives you:
TaxCategoryId is copied as-is from the original plan.SmallLogoImageId / LargeLogoImageId rows are copied in Files, and the new IDs are used in the new plan.ProductPricingPlanVersion row is created and linked via VersionId.ProductItem rows for the old plan are inserted again, now pointing to the new ProductPricingPlanId (and cloned payment schedules), and their metered prices are copied as well.If you want the new plan’s name, alias, etc. to differ, just pass them in as parameters and use them instead of copying the original values in the INSERT ... SELECT for ProductPricingPlan.
it throws error:
The multi-part identifier "src.Id" could not be bound.
Right – that error is from the OUTPUT src.Id bit.
In an INSERT ... SELECT statement, the OUTPUT clause cannot see the source-table alias (src), so src.Id “cannot be bound”. That’s why you get:
The multi-part identifier "src.Id" could not be bound.
You used src.Id in two places:
ProductPricingPlanPaymentScheduleProductItemBelow is one clean way to fix it: use MERGE for those two steps, because MERGE’s OUTPUT can see both the SOURCE (our src) and INSERTED.
Replace your whole ProductPricingPlanPaymentSchedule insert block with this:
sqlDECLARE @ScheduleMap TABLE (OldId INT PRIMARY KEY, NewId INT); MERGE ProductPricingPlanPaymentSchedule AS tgt USING ( SELECT Id AS OldId , Name , FrequencyType , FrequencyInterval , FrequencyRelativeInterval , FrequencyRecurrenceFactor , StartDate , EndDate , SubscriptionPeriodCharge , Priority , IsMasterSchedule FROM ProductPricingPlanPaymentSchedule WHERE ProductPricingPlanId = @SourcePlanId ) AS src ON 1 = 0 -- force INSERT only WHEN NOT MATCHED THEN INSERT ( ProductPricingPlanId , Name , FrequencyType , FrequencyInterval , FrequencyRelativeInterval , FrequencyRecurrenceFactor , StartDate , EndDate , SubscriptionPeriodCharge , Priority , IsMasterSchedule ) VALUES ( @NewPlanId , src.Name , src.FrequencyType , src.FrequencyInterval , src.FrequencyRelativeInterval , src.FrequencyRecurrenceFactor , src.StartDate , src.EndDate , src.SubscriptionPeriodCharge , src.Priority , src.IsMasterSchedule ) OUTPUT src.OldId , inserted.Id INTO @ScheduleMap (OldId, NewId);
Now we have a valid @ScheduleMap with OldId -> NewId.
Replace your ProductItem INSERT ... OUTPUT src.Id block with this:
sqlDECLARE @ItemMap TABLE (OldId INT PRIMARY KEY, NewId INT); MERGE ProductItem AS tgt USING ( SELECT pi.Id AS OldId , pi.Name , pi.Description , pi.ProductItemId , pi.ChargeModel , pi.ProductItemType , pi.UnitName , pi.IsVisibleOnHostedPage , pi.MinQuantity , pi.MaxQuantity , pi.DisplayOrder , sm.NewId AS NewScheduleId FROM ProductItem AS pi LEFT JOIN @ScheduleMap AS sm ON sm.OldId = pi.ProductPricingPlanPaymentScheduleId WHERE pi.ProductPricingPlanId = @SourcePlanId ) AS src ON 1 = 0 -- force INSERT only WHEN NOT MATCHED THEN INSERT ( Name , Description , ProductPricingPlanId , ProductItemId , ChargeModel , ProductItemType , UnitName , IsVisibleOnHostedPage , ProductPricingPlanPaymentScheduleId , MinQuantity , MaxQuantity , DisplayOrder ) VALUES ( src.Name , src.Description , @NewPlanId , src.ProductItemId , src.ChargeModel , src.ProductItemType , src.UnitName , src.IsVisibleOnHostedPage , src.NewScheduleId , src.MinQuantity , src.MaxQuantity , src.DisplayOrder ) OUTPUT src.OldId , inserted.Id INTO @ItemMap (OldId, NewId);
Your final ProductMeteredPrice insert that uses @ItemMap can stay as-is:
sqlINSERT INTO ProductMeteredPrice ( ProductItemId , StartQuantity , EndQuantity , UnitPrice , Description ) SELECT im.NewId , src.StartQuantity , src.EndQuantity , src.UnitPrice , src.Description FROM ProductMeteredPrice AS src JOIN @ItemMap AS im ON im.OldId = src.ProductItemId;
So the key fix: don’t try to reference src.Id in an INSERT ... OUTPUT statement; use MERGE (or another pattern) where OUTPUT can see both src and inserted. With these two blocks replaced, the cloning procedure should run without that “multi-part identifier” error and still give you the old→new ID mappings you need.