How to copy a foreign key constrained table to another database? - sql-server

I'm trying to replace gibberish data on my development server (recovering it from the production server).
I first tried using SSMS's "import data" task, but encountered an error saying: "Cannot truncate a table with a foreign key constraint" therefore I resolved to do the following:
Create a new temp copy of the table on the development server (to avoid accessing it from the production database).
Write an update script.
This is my script:
DECLARE #IdToCopy INT;
DECLARE #cnt INT;
SET #IdToCopy = 1
WHILE #IdToCopy <= 55
BEGIN
UPDATE DocumentTypes
SET Name = DocumentTypesTemp.Name
FROM DocumentTypesTemp
WHERE DocumentTypesTemp.DocumentTypeId = #IdToCopy;
SET #IdToCopy += 1;
END;
I expected it to update the table to have the same values, but the only value was that of the last row from the copying table.

I expected it to update the table to have the same values, but the
only value was that of the last row from the copying table.
In each iteration of while loop, your entire DocumentTypes table is getting updated. This is the reason, you see only last record name appearing in the entire table, you have to join the source and destination table based on the id.
UPDATE dest
SET dest.NAME = src.NAME
FROM documenttypes dest
INNER JOIN documenttypestemp src
ON src.documenttypeid = dest.documenttypeid
Above query will update the name from destination table to source table with matching documenttypeid in a single query.
Note: You don't need a while loop to update the name from source to destination.

I fixed my script using the following:
DECLARE #IdToCopy INT;
DECLARE #cnt INT;
DECLARE #NameToCopy varchar(256);
SET #IdToCopy=1
SELECT #cnt=COUNT(*) FROM DocumentTypesTemp;
WHILE #IdToCopy <= #cnt
BEGIN
SELECT #NameToCopy=Name
FROM DocumentTypesTemp
WHERE DocumentTypesTemp.DocumentTypeId=#IdToCopy;
UPDATE DocumentTypes
SET Name=#NameToCopy
WHERE DocumentTypes.DocumentTypeId=#IdToCopy;
SET #IdToCopy += 1;
END;

Related

SQL Loop through table of tables names and insert

As part of a SSIS package I have a SQL table containing the staging table name and corresponding 'real' table name. The staging table names will change based on the date but there is a previous step that works out what the Real_Table is.
How do I loop through each one in SQL and insert all the data from the staging tables (columns are identical in both) into the real table and update the flag from 0 to 1 to mark it was done. This is my table:
Staging_Table Real_Table UpdateFlag
Customers_01012018 Customers 0
Order_01012018 Order 0
Suppliers_02022018 Suppliers 0
You can use while loop to load the data into Real tables,
DECLARE #total INT
DECLARE #start INT
DECLARE #query NVARCHAR(MAX)
DECLARE #staging_table NVARCHAR(MAX)
DECLARE #real_table NVARCHAR(MAX)
SET #start = 1
SET #total = (SELECT COUNT(*) FROM Stg_tables)
WHILE(#start <= #total)
BEGIN
SELECT TOP 1 #staging_table = Staging_Table, #real_table = Real_Table FROM Stg_tables WHERE UpdateFlag = 0
SET #query = 'INSERT INTO ' + #real_table + ' SELECT * FROM ' + #staging_table
EXEC(#query)
UPDATE Stg_tables SET UpdateFlag = 1 WHERE Staging_Table = #staging_table AND Real_Table = #real_table
SET #start = #start + 1
END
An overview of how to go about this is below. Of course make sure to match the correct configurations to your environment and set the metadata appropriately where applicable.
Create an Execute SQL Task that selects the staging and real table
names. Choose the "Full result set" ResultSet. On the Result Set
page, add an object variable and set the Result Name to 0 to use the
immediate results.
Add a Foreach Loop that is of the Foreach ADO Enumerator type. Use the object variable from the Execute SQL Task as the ADO Object Source Variable. On the Variable Mappings page, add a string variable at index 1 and 0. One of these will hold the staging table name and the other will hold the real table name. These will align with the order they were selected in the Execute SQL Task, so if you selected the staging table first there use a the variable that will hold this name at index 0.
Create another string variable that contains an expression selecting the necessary columns that will be loaded from the staging table with the variable holding this table name concatenated within in. An Example of this follows with the #[User::StagingTableVariable] variable representing the variable holding the staging table name.
Within the Foreach Loop, add Data Flow Task then add an OLE DB Source inside this. This will use the variable with the SQL selecting from the staging table, but to correctly set the metadata choose the SQL Command option and use a SQL statement that selects the same columns from an existing staging table name. Once this is set, change the ValidateExternalMetadata property to false, choose "SQL command from variable" for the Data Access Mode, and pick the variable holding the SQL statement that uses the staging table name.
Add an OLE DB Destination and connect the source to this. Like before, use an existing real table and map the columns. After this, again set ValidateExternalMetadata to false, change Data Access Mode to the use a table or view name from variable (I would recommend the fast load option), and add the variable holding the real table name.
After the Data Flow Task add another Execute SQL Task that's linked to the DFT. Create a string variable with an update statement for the mapping table where the table names originate from. Set the SQLSourceType value to variable and select this variable for the SourceVariable property. If you're using a text column in the WHERE clause of the update statement, make sure that the expression contains single quotes (') as a typical SQL update statement would.
Example OLE DB Source Variable Expression:
"SELECT ColA, ColB, ColC from YourSchema." + #[User::StagingTableVariable]
Example Variable Expression Update Command:
"UPDATE YourSchema.MappingTable SET UpdateFlag = 1 where Real_Table = '" + #[User::RealTableVariable] + "'"

Inserting/Updating a SQL Server table using stored procedure in BizTalk

I am currently working on getting a set of records from a view in the Oracle database and trying to insert/update them in to the table in the SQL Server table depending on a column using BizTalk.
For this I created a stored procedure:
Create PROCEDURE [dbo].[uspInsertorUpdateDepartment]
#dept_name varchar(64),
#jax_dept_id char(32)
AS
BEGIN
SET NOCOUNT ON;
IF (SELECT TOP (1) 1 FROM afm.[jax_dept]
WHERE jax_dept_id = #jax_dept_id) IS NULL
INSERT INTO afm.[jax_dept](dept_name, jax_dept_id)
VALUES (#dept_name,#jax_dept_id)
ELSE
UPDATE afm.[jax_dept]
SET dept_name = #dept_name
WHERE jax_dept_id = #jax_dept_id
END
I created the schema for the stored procedure using consume adapter service. Used them in the mapping and the orchestration. Though I was not able to use the lopping functoid in the mapping
So removed the lopping and deployed the application. And tried to run and it ran without any error but just insert the first record from the oracle view in to the SQL Server database leaving all the other records. How can this be approached so the entire set of records from the oracle is inserted/updated in to SQL Server database.
Here I converted the separate update and insert into one merge statement:
Create PROCEDURE [dbo].[uspInsertorUpdateDepartment]
#dept_name varchar(64),
#jax_dept_id char(32)
AS
BEGIN
SET NOCOUNT ON;
merge afm.[jax_dept] as target
using (select #dept_name as dept_name, #jax_dept_id as jax_dept_id) as source
on source.jax_dept_id = target.jax_dept_id
when matched then
update target
SET dept_name = #dept_name
when not matched then
insert (dept_name, jax_dept_id)
values (#dept_name,#jax_dept_id)
;
END
Use table type as a parameter for the SP, instead of passing individually. We can
use looping functoid if we use User Defined Table value as a parameter.
CREATE TYPE dbo.SampleType AS TABLE
(
dept_name varchar(64) not null,
jax_dept_id char(32) not null
)
---
Create PROCEDURE [dbo].[uspInsertorUpdateDepartment]
#TVP dbo.SampleType READONLY
AS
BEGIN
SET NOCOUNT ON;
--your insert or update query
For more infor on how to use table value parameter check out this link:-
https://learn.microsoft.com/en-us/sql/relational-databases/tables/use-table-valued-parameters-database-engine

How to Update or Insert a record using SQL Merge

I have a windows forms application that needs to edit an existing record if it already exists and create it if it does not. I'm using SQL Server 2008 R2. My application reads data from various tables which includes an ID field for the output table if a record already exists.
The ID field is blank if a new record is being created. The ID field is the primary key and an Identity (auto increment) field for the destination table.
I have created a stored procedure using MERGE that I hope will create a new record or update the existing one. The update part is working but I can't figure out what to do with the ID field when creating.
When doing an update I pass in an ID Parameter and the existing record is located. Obviously if it is a new record I won't have an ID yet but I can't then leave that Parameter out or I get an unassigned variable error as you would expect.
Here is my stored procedure. Am I just barking up the wrong tree here
somewhere?
Should I just create two stored procedures and call Update if I have and ID and Call Create if I don't have and ID?
Thanks for any assistance.
USE [Insurance]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[CreateModifyValuation]
-- Add the parameters for the stored procedure here
#ValuationID int,
#OwnersCorporationID int,
#ValDate datetime,
#ValuerID int,
#Amount money,
#Printed bit,
#Approved bit,
#Notes varchar(max),
#MultiplierValue money,
#MultiplierClass char(10),
#Adjustment money,
#SubmittedDate datetime
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
Merge Valuation as Target
USING (Select
#ValuationID,
#OwnersCorporationID,
#ValDate,
#ValuerID,
#Amount,
#Printed,
#Approved,
#Notes,
#MultiplierValue,
#MultiplierClass,
#Adjustment,
#SubmittedDate
)
As Source(
ValuationID,
OwnersCorporationID,
ValDate,
ValuerID,
Amount,
Printed,
Approved,
Notes,
MultiplierValue,
MultiplierClass,
Adjustment,
SubmittedDate
)
ON Source.ValuationID = Target.ValuationID
WHEN MATCHED THEN
UPDATE SET
Target.OwnersCorporationID = Source.OwnersCorporationID,
Target.ValDate = Source.ValDate,
Target.ValuerID = Source.ValuerID,
Target.Amount = Source.Amount,
Target.Printed = Source.Printed,
Target.Approved = Source.Approved,
Target.Notes = Source.Notes,
Target.MultiplierValue = Source.MultiplierValue,
Target.MultiplierClass = Source.MultiplierClass,
Target.Adjustment = Source.Adjustment,
Target.SubmittedDate = Source.SubmittedDate
WHEN NOT MATCHED BY Target THEN
INSERT (
OwnerscorporationID,
ValDate,
ValuerID,
Amount,
Printed,
Approved,
Notes,
MultiplierValue,
MultiplierClass,
Adjustment,
SubmittedDate
)
VALUES (
Source.OwnersCorporationID,
Source.ValDate,
Source.ValuerID,
Source.Amount,
Source.Printed,
Source.Approved,
Source.Notes,
Source.MultiplierValue,
Source.MultiplierClass,
Source.Adjustment,
Source.SubmittedDate
);
END
I feel like I cheated but it's only one line so how bad can it be :)
In My SQL I added this line before the "SET NOCOUNT ON;"
(Funny if I put it after SET NOCOUNT ON I get a syntax error)
if (#ValuationID = 0) set #ValuationID = null
Then in my C# code I set the ID to 0 for a new record and it seems to work after a couple of tests. There may be a better way to do this but like most things in life you stop looking once it works.
Thanks again to those who commented.
David

There already exists an object in the database SQL Server

I have created a stored procedure that returns the id of last inserted row of a table based on one condition.
Condition is such that if the row being inserted already exists then it takes identity column of the row otherwise it inserts a new row into the table.
To do this, I have written the following code in a stored procedure
ALTER PROCEDURE [dbo].[Test_Procedure]
#description nvarchar(max)
AS
BEGIN
DECLARE #tempId int;
SELECT CommentId
INTO tempId
FROM TestTable
WHERE description = #description;
IF #tempId IS NULL
BEGIN
INSERT INTO TestTable
VALUES (#description);
SELECT scope_identity();
END
ELSE
BEGIN
SELECT #tempId FROM dual;
END
DROP TABLE tempId;
END
When I run the above stored procedure, first time it ran successfully and then on wards it started throwing the following error message
Msg 2714, Level 16, State 6, Procedure Test_Procedure, Line 15
There is already an object named 'tempId' in the database.
The bit I'm not understanding is tempId is used as a variable not as a table. I have seen people with the similar problem but in their case they used temporary tables
I really appreciate your help in resolving the above issue.
Try this syntax for setting your variable.
SELECT #tempId = CommentId from TestTable where description = #description;
Currently your 'select into' is creating a table 'tempId' on the database.

Locally scoped begin-end declares (altering multiple triggers in a single transaction)

Goal
I need to alter a number of almost identical triggers on a number of tables (and a number of databases).
Therefore I wan't to make one big script, and perform all the changes in one succeed-or-fail transaction.
My first attempt (that doesn't work)
---First alter trigger
ALTER TRIGGER [dbo].[trg_UserGarbleValue] ON [dbo].[users]
FOR INSERT
AS
Begin
DECLARE #GarbleValue NVARCHAR(200)
DECLARE #NewID NVARCHAR(20)
SET #NewID = (SELECT TOP 1 usr_id FROM users ORDER BY usr_id DESC)
SET #GarbleValue = dbo.fn_GetRandomString(4) + #NewID + dbo.fn_GetRandomString(4)
UPDATE users SET usr_garble_value = #GarbleValue WHERE usr_id = #NewID
End
Go
--Subsequent alter trigger (there would be many more in the real-world usage)
ALTER TRIGGER [dbo].[trg_SegmentGarbleValue] ON [dbo].[segment]
FOR INSERT
AS
Begin
DECLARE #GarbleValue NVARCHAR(200)
DECLARE #NewID NVARCHAR(20)
SET #NewID = (SELECT TOP 1 seg_id FROM segment ORDER BY seg_id DESC)
SET #GarbleValue = dbo.fn_GetRandomString(4) + #NewID + dbo.fn_GetRandomString(4)
UPDATE segment SET seg_garble_value = #GarbleValue WHERE seg_id = #NewID
End
Go
Running each of the alter trigger statements by themselves works fine. But when both of them are run in the same transaction, the declares crash in the second alter because the variables name already exists.
How do I accomplish this? Is there any way to declare a variable locally within a begin-end scope, or do I need to rethink it completely?
(I'm aware that the "top 1" for fetching new records is probably not very clever, but that is another matter)
I think you've confused GO (the batch separator) and transactions. It shouldn't complain about the variable names being redeclared, provided the batch separators are still present:
BEGIN TRANSACTION
GO
ALTER TRIGGER [dbo].[trg_UserGarbleValue] ON [dbo].[users]
FOR INSERT
AS
---Etc
Go
ALTER TRIGGER [dbo].[trg_SegmentGarbleValue] ON [dbo].[segment]
FOR INSERT
AS
---Etc
Go
COMMIT
As to your note about TOP 1, it's worse than you think - a trigger runs once per statement, not once per row - it could be running in response to multiple rows having been inserted. And, happily, there is a pseudo-table available (called inserted) that contains exactly those rows which caused the trigger to fire - there's no need for you to go searching for those row(s) in the base table.

Resources