I have a stored procedure that mimics the MYSQL 'UPSERT' command. ie. insert if new / update existing if record exists.
I wish to keep the number of calls to SQL Server to an absolute minimum ie. 1
So can I pass an param string to a stored procedure (SP_MAIN) and in this stored procedure then call my 'UPSERT' stored procedure for every unique table row that is passed as a param to SP_MAIN...?
If so, can anyone illustrate with a simple example please..?
Thank you in advance.
You can use the merge statements. See a sample below: The table to be updated is dbo.Table. We use Table Valued Parameter to update/insert the data. The merge statement is within a stored procedure
CREATE TABLE dbo.[Table]
(
PrimaryKey INT IDENTITY (1, 1) NOT NULL
,Column1 INT NOT NULL
,Column2 INT NOT NULL
)
GO
CREATE TYPE dbo.[TableTVP] AS TABLE (
PrimaryKey INT NULL
,Column1 INT NULL
,Column2 INT NULL
)
GO
CREATE PROCEDURE dbo.CRUD_Table
#TableTVP dbo.TableTVP READONLY
AS
SET NOCOUNT ON
DECLARE #OutPut TABLE (Action VARCHAR(10) NULL,EntityKey INT NULL)
MERGE dbo.[Table] AS TARGET
USING (SELECT
PrimaryKey
,Column1
,Column2
,BINARY_CHECKSUM (Column1, Column2) as DataCheckSum
FROM
#TableTVP) AS SOURCE ON SOURCE.PrimaryKey = TARGET.PrimaryKey
WHEN MATCHED AND SOURCE.DataCheckSum <> BINARY_CHECKSUM (TARGET.Column1, TARGET.Column2) THEN
UPDATE SET
Column1 = SOURCE.Column1
,Column2 = SOURCE.Column2
WHEN NOT MATCHED THEN
INSERT (
Column1
,Column2
)
VALUES (
SOURCE.Column1
,SOURCE.Column2
)
OUTPUT $action as [Action]
,CASE WHEN $action IN ('INSERT', 'UPDATE') THEN Inserted.PrimaryKey ELSE Deleted.PrimaryKey END as [EntityKey] INTO #OutPut;
SELECT Action,EntityKey FROM #OutPut
GO
Related
I have to insert one record at a time into a table and then execute a stored procedure by passing output variables.
The insert part actually does do the this and I see two different records into the destination table. But the stored procedure seems to use the same out parameters that were passed for the very first record inserted into destination table. So basically when a stored procedures is being called in loops over the same output parameters over and over again for each distinct ID that is being inserted into the destination table.
In my pseudocode below it prints 3 times the following 5, 10, 15 . Which is correct since it takes each new ID in the dbo.Table_Test. But in my actual code actually it does take only only very first ID that repeats looping over the same ID three times.
-------- CREATING STORED PROCEDURE --------
USE MyDB;
GO
DROP PROCEDURE IF EXISTS dbo.sp_Testing
USE MyDB;
GO
CREATE PROCEDURE dbo.sp_Testing
#QueueId INT,
-- response
#MainId INT OUT, -- this allows null
#MessageTx VARCHAR(500) OUT,
#SuccessIn BIT OUT
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT * FROM MyDB.sys.tables WHERE name = 'Table_Test') --print 1
CREATE TABLE dbo.Table_Test
(
ID INT NOT NULL PRIMARY KEY IDENTITY(5,5),
name VARCHAR(10) NULL,
Phone INT NULL,
category VARCHAR(10) NULL
)
INSERT INTO dbo.Table_Test (name)
VALUES ('Andrew')
SET #MainId = SCOPE_IDENTITY()
PRINT #MainId
END
-------- END OR STORED PROCEDURE --------
GO
-------- INSERT STATEMENTS ---------
USE MyDB;
IF OBJECT_ID('tempdb..#MainTable') IS NOT NULL
DROP TABLE #MainTable
IF OBJECT_ID('tempdb..#Queue') IS NOT NULL
DROP TABLE #Queue
DECLARE #MessageTx VARCHAR(30)
DECLARE #SuccessIn BIT
DECLARE #QueueId INT
DECLARE #MainId INT
DECLARE #ParentId INT
SET #MainId = NULL
SET #SuccessIn = 1
CREATE TABLE #MainTable
(
ID INT NOT NULL PRIMARY KEY IDENTITY(1,1),
name VARCHAR(10) NULL,
Phone INT NOT NULL,
category VARCHAR(10) NULL
)
INSERT INTO #MainTable (name, Phone, category)
VALUES ('Adam', 123433, 'new'),
('John', 222222, 'new'),
('Samuel', 123123313, 'new')
-- SELECT * FROM #MainTable
-- SELECT * FROM #Queue
-- SELECT * FROM #Test
DECLARE Cursor_test CURSOR LOCAL FOR
SELECT id
FROM #MainTable
-- get relationships for next level
OPEN Cursor_test
FETCH NEXT FROM Cursor_test INTO #ParentId
WHILE ##FETCH_STATUS = 0
BEGIN
IF OBJECT_ID('tempdb..#Queue') IS NOT NULL
DROP TABLE #Queue
CREATE TABLE #Queue
(
PK INT NOT NULL PRIMARY KEY identity (3,2),
ID INT NOT NULL
)
INSERT INTO #Queue (id)
SELECT id
FROM #MainTable
SET #QueueId = SCOPE_IDENTITY()
-- real-time creation
EXEC dbo.sp_Testing #QueueId, #MainId, #MessageTx OUT, #SuccessIn OUT
FETCH NEXT FROM Cursor_test INTO #ParentId
END
CLOSE Cursor_test
DEALLOCATE Cursor_test
This is a bit too long to be in the comment.
Firstly you must understand that the temp table #test only exists within the stored procedure. It is created in your stored procedure and dropped once the stored procedure exits.
So every time you execute the stored procedure, it creates the temp table, when you insert the row, it return the same identity seed value which is 5.
I have a stored procedure that executes two stored procedures and gets the correct data, but now I need to add a column into the temp table with default values.
e.g I want to add a Location type column to the temp table when the first stored procedure executes i want to add 1 to each record and when the second stored procedure is added to the temp table I want to add 2 to this column for each record. Can this be achieved?
Nothing much more
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[uspStockAdjustmentGetStorageLocationTESTDELETEAFTER]
#CategoryId INT,
#ReasonId INT
AS
SET NOCOUNT ON
DECLARE #LocationType INT
SELECT #LocationType = LocationType
FROM [dbo].[StockAdjustmentReasonGLAccount]
WHERE CategoryId = #CategoryId AND ReasonId = #ReasonId
IF(#LocationType = 1)
BEGIN
CREATE TABLE #tmp
(
ID INT IDENTITY(1, 1),
CODE NVARCHAR(50),
Description NVARCHAR(50)
)
INSERT INTO #tmp
EXECUTE dbo.uspStockAdjustmentWorkCentreSelectAll
INSERT INTO #tmp
EXECUTE dbo.uspStockAdjustmentGetSAPStorageType
SELECT
Code, MIN(id) AS Id
FROM
#tmp
GROUP BY
CODE
ORDER BY
Id
DROP TABLE #tmp
END
I am not sure on how to add this column with the default values
create the temp table with the new column with default value as 2
CREATE TABLE #tmp
(
ID INT IDENTITY(1,1) ,
CODE nvarchar(50),
Description nvarchar(50),
LocationType INT default 2
)
specify the column explicitly when insert into the temp table.
INSERT INTO #tmp (CODE, Description)
EXECUTE dbo.uspStockAdjustmentWorkCentreSelectAll
change the LocationType to 1 after first stored procedure executes
UPDATE #tmp
SET LocationType = 1
INSERT INTO #tmp (CODE, Description)
EXECUTE dbo.uspStockAdjustmentGetSAPStorageType
result from second stored procedure will have the value 2
I have table holding items for a given list id in my Ms Sql server database (2008R2).
I would like to add constraints so that no two list ids have same item list. Below illustrate my schema.
ListID , ItemID
1 a
1 b
2 a
3 a
3 b
In above example ListID 3 should fail. I guess you can't put constarint/check within the database itself (Triggers,check) and the logic constaint can only be done from the frontend?
Thanks in advance for any help.
Create a function that performs the logic you want and then create a check constraint or index that leverages that function.
Here is a functional example, the final insert fails. The function is evaluated row by row, so if you need to insert as a set and evaluate after, you'd need to do an "instead of" trigger:
CREATE TABLE dbo.Test(ListID INT, ItemID CHAR(1))
GO
CREATE FUNCTION dbo.TestConstraintPassed(#ListID INT, #ItemID CHAR(1))
RETURNS TINYINT
AS
BEGIN
DECLARE #retVal TINYINT = 0;
DECLARE #data TABLE (ListID INT, ItemID CHAR(1),[Match] INT)
INSERT INTO #data(ListID,ItemID,[Match]) SELECT ListID,ItemID,-1 AS [Match] FROM dbo.Test
UPDATE #data
SET [Match]=1
WHERE ItemID IN (SELECT ItemID FROM #data WHERE ListID=#ListID)
DECLARE #MatchCount INT
SELECT #MatchCount=SUM([Match]) FROM #data WHERE ListID=#ListID
IF NOT EXISTS(
SELECT *
FROM (
SELECT ListID,SUM([Match]) AS [MatchCount]
FROM #data
WHERE ListID<>#ListID
GROUP BY ListID
) dat
WHERE #MatchCount=[MatchCount]
)
BEGIN
SET #retVal=1;
END
RETURN #retVal;
END
GO
ALTER TABLE dbo.Test
ADD CONSTRAINT chkTest
CHECK (dbo.TestConstraintPassed(ListID, ItemID) = 1);
GO
INSERT INTO dbo.Test(ListID,ItemID) SELECT 1,'a'
INSERT INTO dbo.Test(ListID,ItemID) SELECT 1,'b'
INSERT INTO dbo.Test(ListID,ItemID) SELECT 2,'a'
INSERT INTO dbo.Test(ListID,ItemID) SELECT 2,'b'
Related
I have two tables: TableA and TableB
CREATE TABLE TableA (
[TableAId] [int] IDENTITY(1,1) NOT NULL...
CREATE TABLE TableB (
[TableBId] [int] IDENTITY(1,1) NOT NULL,
[TableAID] [int] NOT NULL... -- this is the FK
Question: (as a C# developer)
I am writing a function that INSERTs into TableA. I need to grab the newly created primary key in TableA and insert it into the FK of TableB along with other data.
I've done this before, but I didn't like what I did which is lookup the TableA PK value immediately after the insert, store it as a variable and then insert it into TableB.
Can someone show me a more efficient way of doing this? Maybe using scope_identity() in a stored proc? A trigger won't work because I need the new PK back in my C# so I can add additional data before I insert into TableB. Plus, I want to lock both tables while this runs.
Thank you,
Robert
Declare #TableAId int
Insert TableA ( ... )
Values( ... )
Set #TableAId = Scope_Identity();
Insert TableB( TableAId, ... )
Values( #TableAId, ... )
It should be noted that it is possible to send this to SQL Server in a single batch. I.e., you can send this entire command text to SQL Server and execute all at once. Of course, you'll need to use parameters for all non-identity columns of table A and table B. For example:
const string sql = #"Declare #TableAId int
Insert TableA ( ... )
Values( ... )
Set #TableAId = Scope_Identity();
Insert TableB( TableAId, ... )
Values( #TableAId, ... )";
using ( var conn = new SqlConnection( ... ) )
{
using ( var cmd = new SqlCommand( sql, conn ) )
{
cmd.Parameters.AddWithValue( #TableACol, ... );
cmd.Parameters.AddWithValue( #TableACol2, ... );
...
cmd.Parameters.AddWithValue( #TableBCol, ... );
cmd.ExecuteNonQuery();
}
}
Another choice if you are using SQL Server 2005 or later might be to use the OUTPUT clause:
Insert TableA( ... )
Output Inserted.TableAId, #TableBCol1, #TableBCol2, ...
Into TableB
Values( ... )
If you have one stored procedure are inserting both sets of data at the same time then use this code.
DECLARE #id INT
INSERT INTO [dbo].[TableA] VALUES (...)
SET #id = SCOPE_IDENTITY()
INSERT INTO [dbo].[TableB] VALUES (#id ...)
If you are using raw ADO, then set #id as an OUTPUT parameter and use the return value in your second DB call. Refer to here for an example of how to use an output parameter.
As for locking both tables, you can either wrap the call in a transaction (TSQL or ADO.NET) or set the SET TRANSACTION ISOLATION LEVEL SERIALIZABLE for the stored procedure.
This worked...
USE [TestDB01]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[sp_INSERT_INTO_Cars_And_Owners]
-- Add the parameters for the stored procedure here
#PartnerId INT
, #Title NVARCHAR(100)
, #Description TEXT
, #IPAddress NVARCHAR(16)
, #IsCarOwner BIT
, #OwnerTypeTypeID INT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #CarId INT
INSERT INTO [dbo].[Cars] (
PartnerId
, Title
, Description
, IPAddress
, IsCarOwner
)
VALUES (
#PartnerId
, #Title
, #Description
, #IPAddress
, #IsCarOwner
)
SET #CarId = SCOPE_IDENTITY()
INSERT INTO [dbo].[Owners] (
OwnerTypeTypeID
, CarId
)
VALUES (
#OwnerTypeTypeID
, #CarId
)
END
I have a requirement to insert multiple rows into table1 and at the same time insert a row into table2 with a pkID from table1 and a value that comes from a SP parameter.
I created a stored procedure that performs a batch insert with a table valued parameter which contains the rows to be inserted into table1. But I have a problem with inserting the row into table2 with the corresponding Id (identity) from table1, along with parameter value that I have passed.
Is there anyone who implemented this, or what is the good solution for this?
CREATE PROCEDURE [dbo].[oSP_TV_Insert]
#uID int
,#IsActive int
,#Type int -- i need to insert this in table 2
,#dTableGroup table1 READONLY -- this one is a table valued
AS
DECLARE #SQL varchar(2000)
DECLARE #table1Id int
BEGIN
INSERT INTO dbo.table1
(uID
,Name
,Contact
,Address
,City
,State
,Zip
,Phone
,Active)
SELECT
#uID
,Name
,Contact
,Address
,City
,State
,Zip
,Phone
,Active
,#G_Active
FROM #dTableGroup
--the above query will perform batch insert using the records from dTableGroup which is table valued
SET #table1ID = SCOPE_IDENTITY()
-- this below will perform inserting records to table2 with every Id inserted in table1.
Insert into table2(#table1ID , #type)
You need to temporarily store the inserted identity values and then create a second INSERT statement - using the OUTPUT clause.
Something like:
-- declare table variable to hold the ID's that are being inserted
DECLARE #InsertedIDs TABLE (ID INT)
-- insert values into table1 - output the inserted ID's into #InsertedIDs
INSERT INTO dbo.table1(ID, Name, Contact, Address, City, State, Zip, Phone, Active)
OUTPUT INSERTED.ID INTO #InsertedIDs
SELECT
#ID, Name, Contact, Address, City, State, Zip, Phone, Active, #G_Active
FROM #dTableGroup
and then you can have your second INSERT statement:
INSERT INTO dbo.table2(Table1ID, Type)
SELECT ID, #type FROM #InsertedIDs
See the MSDN docs on the OUTPUT clause for more details on what you can do with the OUTPUT clause - one of the most underused and most "unknown" features of SQL Server these days!
Another approach using OUTPUT clause and only one statement for inserting data in both destination tables:
--Parameters
DECLARE #TableGroup TABLE
(
Name NVARCHAR(100) NOT NULL
,Phone VARCHAR(10) NOT NULL
);
DECLARE #Type INT;
--End Of parameters
--Destination tables
DECLARE #FirstDestinationTable TABLE
(
FirstDestinationTableID INT IDENTITY(1,1) PRIMARY KEY
,Name NVARCHAR(100) NOT NULL
,Phone VARCHAR(10) NOT NULL
);
DECLARE #SecondDestinationTable TABLE
(
SecondDestinationTable INT IDENTITY(2,2) PRIMARY KEY
,FirstDestinationTableID INT NOT NULL
,[Type] INT NOT NULL
,CHECK([Type] > 0)
);
--End of destination tables
--Test1
--initialization
INSERT #TableGroup
VALUES ('Bogdan SAHLEAN', '0721200300')
,('Ion Ionescu', '0211002003')
,('Vasile Vasilescu', '0745600800');
SET #Type = 9;
--execution
INSERT #SecondDestinationTable (FirstDestinationTableID, [Type])
SELECT FirstINS.FirstDestinationTableID, #Type
FROM
(
INSERT #FirstDestinationTable (Name, Phone)
OUTPUT inserted.FirstDestinationTableID
SELECT tg.Name, tg.Phone
FROM #TableGroup tg
) FirstINS
--check records
SELECT *
FROM #FirstDestinationTable;
SELECT *
FROM #SecondDestinationTable;
--End of test1
--Test2
--initialization
DELETE #TableGroup;
DELETE #FirstDestinationTable;
DELETE #SecondDestinationTable;
INSERT #TableGroup
VALUES ('Ion Ionescu', '0210000000')
,('Vasile Vasilescu', '0745000000');
SET #Type = 0; --Wrong value
--execution
INSERT #SecondDestinationTable (FirstDestinationTableID, [Type])
SELECT FirstINS.FirstDestinationTableID, #Type
FROM
(
INSERT #FirstDestinationTable (Name, Phone)
OUTPUT inserted.FirstDestinationTableID
SELECT tg.Name, tg.Phone
FROM #TableGroup tg
) FirstINS
--check records
DECLARE #rc1 INT, #rc2 INT;
SELECT *
FROM #FirstDestinationTable;
SET #rc1 = ##ROWCOUNT;
SELECT *
FROM #SecondDestinationTable;
SET #rc2 = ##ROWCOUNT;
RAISERROR('[Test2 results] #FirstDestinationTable: %d rows; ##SecondDestinationTable: %d rows;',1,1,#rc1,#rc2);
--End of test1
Since you need all inserted identity values, look at the output clause of the insert statement: http://msdn.microsoft.com/en-us/library/ms177564.aspx