Is there a way to determine whether a record was matched or not (whether the record was inserted or updated) after calling MERGE?
Ideally I'd like to output it to a parameter.
Edit:
I've got the merge statement outputting what happened in my management studio using the following statement:
Say I had the following merge statement:
MERGE INTO TestTable as target
USING ( select '00D81CB4EA0842EF9E158BB8FEC48A1E' )
AS source (Guid)
ON ( target.Guid = source.Guid )
WHEN MATCHED THEN
UPDATE SET Test_Column = NULL
WHEN NOT MATCHED THEN
INSERT (Guid, Test_Column) VALUES ('00D81CB4EA0842EF9E158BB8FEC48A1E', NULL)
OUTPUT $action;
I'm trying to use a parameter to get the '$action' output.
What you could do is create a temporary table (or a table variable) and send your output there - add some meaningful fields to your OUTPUT clause to make it clear what row was
affected by what action:
DECLARE #OutputTable TABLE (Guid UNIQUEIDENTIFIER, Action VARCHAR(100))
MERGE INTO TestTable as target
USING ( select '00D81CB4EA0842EF9E158BB8FEC48A1E' )
AS source (Guid)
ON ( target.Guid = source.Guid )
WHEN MATCHED THEN
UPDATE SET Test_Column = NULL
WHEN NOT MATCHED THEN
INSERT (Guid, Test_Column) VALUES ('00D81CB4EA0842EF9E158BB8FEC48A1E', NULL)
OUTPUT INSERTED.Guid, $action INTO #OutputTable
SELECT
Guid, Action
FROM
#OutputTable
UPDATE: ah, okay, so you want to call this from .NET ! Well, in that case, just call it using the .ExecuteReader() method on your SqlCommand object - the stuff you're outputting using OUTPUT... will be returned to the .NET caller as a result set - you can loop through that:
using(SqlCommand cmd = new SqlCommand(mergeStmt, connection))
{
connection.Open();
using(SqlDataReader rdr = cmd.ExecuteReader())
{
while(rdr.Read())
{
var outputAction = rdr.GetValue(0);
}
rdr.Close();
}
connection.Close();
}
You should get back the resulting "$action" from that data reader.
Related
I have a database FOO with several columns, among those I have one column "Url". I need to write a trigger before insert/update that will check the Url columns whether the newer value matches any existing values, i.e. "hello" except some predefined value. That means if "hello" is inserted or updated multiple times no error will happen otherwise it will check for duplicity. And if it finds some aborts the insertion update. This will also return some code so that my script calling for the insertion/update will know a failure has occurred. I know there might be other workarounds but I will need to have it this way. I am pretty new to SQL.
Foo {
Url
}
Here is the algorithm
Before update insert
if new value of Url is not "hello1" o "hello 2"
check if new value of Url already exists in Foo.Url if so abort otherwise allow update/insert
return something if aborted/success
try something like this.. you'll need to index your table..
IF EXISTS(SELECT URL FROM Foo.Url)
BEGIN
SELECT 'URL Exists Already'
END
ELSE
BEGIN
INSERT/UPDATE
END
A unique constraint wouldn't do what you want but you could create an instead of trigger with content something like as:
Create TRIGGER [dbo].[Trig_Insert_XXX]
ON [dbo].[XXX]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO xxx ([url], field1, field2, fieldN)
SELECT [url], field1, field2, fieldN
FROM inserted i
WHERE i.url = 'hello' OR NOT EXISTS (SELECT * FROM xxx t2 WHERE t2.url = i.url);
END;
I suppose you're looking for a UNIQUE constraint & a CHECK constraint as
CREATE TABLE Foo(
Url VARCHAR(250) NOT NULL,
CONSTRAINT UQ_Url UNIQUE(Url),
CONSTRAINT CHK_Url CHECK (Url NOT IN ('hello1', 'hello2'))
);
See how it's working online.
If you are using SQL Server 2008 or newer version you can use MERGE as well, the syntax is like the following :
MERGE [TableName] AS TARGET
USING ( SELECT #UrlName ) AS SOURCE (UrlName) ON SOURCE.UrlName = TARGET.UrlName
WHEN MATCHED THEN
UPDATE SET ...
WHEN NOT MATCHED THEN INSERT ()
VALUES ();
I have a stored procedure and am using a Merge Statement to Insert and Update. This aspect is working as I require.
However, the output when inserting the record is always 1 and I cannot see why? I would be grateful if someone could review this procedure and let me know what I could be doing wrong,.
CREATE PROCEDURE [dbo].[FileAdd]
#FileId int,
#FileData varbinary(max),
#ContentType Varchar(100),
#OperatorId int
AS
BEGIN
--In Memory Table to
DECLARE #MergeOutput TABLE
(
Id INT
);
--Merge needs a table to Merge with so using a CTE
WITH CTE AS (
SELECT #FileId as FileId)
--Merge
MERGE INTO [dbo].[Files] as T
USING CTE AS S
ON T.FileId = S.FileId
WHEN NOT MATCHED THEN
INSERT (
FileData,
ContentType,
OperatorIdCreated,
OperatorIdUpdated
)
VALUES(
#FileData,
#ContentType,
#OperatorId,
#OperatorId
)
WHEN MATCHED THEN
UPDATE SET
FileData = #FileData,
ContentType= #ContentType,
OperatorIdUpdated = #OperatorId,
Updated = GetDate()
OUTPUT
INSERTED.FileId
INTO #MergeOutput;
SELECT * FROM #MergeOutput;
END
GO
The reason you are getting 1 is because that is what is being UPDATED or INSERTED. When it's the UPDATED value, then it is the value are passing into #FileID.
With the OUTPUT clause:
INSERTED Is a column prefix that specifies the value added by the
insert or update operation.
Thus, what ever value is UPDATED (which is #FileID) or INSERTED (which will be whatever your FileID table logic is) this will be returned in your code. If you are always getting 1, then you must me always updating the column for FileID = 1.
Changing your bottom to inserted.* would show you this, as it would OUTPUT the updated row.
Check the demo here.
I have the following trigger:
ALTER TRIGGER [Staging].[tr_UriData_ForInsert]
ON [Staging].[UriData]
FOR INSERT
AS
BEGIN
DECLARE #_Serial NVARCHAR(50)
DECLARE #_Count AS INT
IF ##ROWCOUNT = 0
RETURN
SET NOCOUNT ON;
IF EXISTS(SELECT * FROM inserted)
BEGIN
SELECT #_Count = COUNT(Id) FROM inserted
SELECT #_Serial = SerialNumber FROM inserted
INSERT INTO [Staging].[DataLog]
VALUES (CURRENT_TIMESTAMP, #_Serial + ': Data Insert --> Rows inserted: ' + #_Count, 'New data has been received')
END
END
The table receives multiple rows at once. I want to be able to add one row in the log table to tell me the insert has happened.
It works great with one row being inserted, but with multiple rows, the trigger doesn't fire. I have read other items on here and it is quite clear that you shouldn't use ROW_NUMBER().
In summary: I want to update my log table when a multiple row insert happens in another table called UriData.
The data is inserted from C# using the following:
using (var sqlBulk = new SqlBulkCopy(conn, SqlBulkCopyOptions.Default, transaction))
{
sqlBulk.DestinationTableName = tableName;
try
{
sqlBulk.WriteToServer(dt);
}
catch(SqlException sqlEx)
{
transaction.Rollback();
var msg = sqlEx.Message;
return false;
}
finally {
transaction.Commit();
conn.Close();
}
}
I don't want to know what is being inserted, but when it has happened, so I can run a set of SPROCS to clean and pivot the data.
TIA
The problem is your trigger assumes that only one row will be updated. A scalar variable can only have 1 value. So, for example, the statement SELECT #_Serial = SerialNumber FROM inserted will set #_Serial with the last value returned from the object inserted.
Treat your data as what it is, a dataset. This is untested, however, I suspect this gives you the result you want:
ALTER TRIGGER [Staging].[tr_UriData_ForInsert]
ON [Staging].[UriData]
FOR INSERT
AS
BEGIN
--No need for a ROWCOUNT. If there are no rows, then nothing was inserted, and this trigger won't happen.
INSERT INTO [Staging].[DataLog] ({COLUMNS LIST})
SELECT CURRENT_TIMESTAMP,
SerialNumber + ': Data Insert --> Rows inserted: ' +
CONVERT(varchar(10),COUNT(SerialNumber) OVER (PARTITION BY SerialNumber)), --COUNT returns an INT, so this statement would have failed with a conversion error too
'New data has been received'
FROM inserted;
END
Please note my comments or sections in braces ({}).
Edit: Sean, who has since deleted his answer, used GROUP BY. I copied what exact method you had, however, GROUP BY might well be the clause you want, rather than OVER.
So after a lot of digging and arguing, my hosting company told me that they have disabled bulk inserts of any kind, without bothering to notify their customers.
First the solution is working perfect, and after as per our Project Manager requirement I have added two column in table. After that one insert,update store procedure is not working it show "Invalid column name" (it mention newly inserted two column name). I think some details is stored in temporary but I don't know how to find and solve it.
I tried something like this:
Removed all constrain and tried to run the store procedure, but no use
Just removed the newly added two column, it is working perfect.
Tried to add the column through an Alter query
My stored procedure is
ALTER PROCEDURE [Page].[SP_INSERT_EXPERIENCEDETAILS]
(#EXPERIENCEDETAILS [PAGE].[EXPERIENCEDETAILS] READONLY)
AS --drop PROCEDURE [Page].[SP_INSERT_EXPERIENCEDETAILS]
BEGIN
DECLARE #TEMPTABLE AS TABLE
(
ID INT,
[ACTION] VARCHAR(50)
)
MERGE INTO [PAGE].[EXPERIENCEDETAILS] AS TARGET
USING (SELECT
ID, Description, ISCurrent, COMPANYID,
Designationid, locationid, FROMDAY, FromMonth, FromYear,
TODAY, TOMONTH, Toyear
FROM
#EXPERIENCEDETAILS) AS SOURCE ON TARGET.ID = SOURCE.ID
WHEN MATCHED THEN
UPDATE
SET TARGET.[DESCRIPTION] = SOURCE.[DESCRIPTION],
TARGET.ISCURRENT = SOURCE.ISCURRENT,
TARGET.COMPANYID = SOURCE.COMPANYID,
TARGET.DESIGNATIONID = SOURCE.DESIGNATIONID,
TARGET.LOCATIONID = SOURCE.LOCATIONID,
TARGET.FROMDAY = SOURCE.FROMDAY,
TARGET.FROMMONTH = SOURCE.FROMMONTH,
TARGET.FROMYEAR = SOURCE.FROMYEAR,
TARGET.TODAY = SOURCE.TODAY,
TARGET.TOMONTH = SOURCE.TOMONTH,
TARGET.TOYEAR = SOURCE.TOYEAR
WHEN NOT MATCHED THEN
INSERT
VALUES (SOURCE.MEMBERID, SOURCE.PAGEID, SOURCE.COMPANYID,
SOURCE.DESIGNATIONID, SOURCE.LOCATIONID,
SOURCE.FROMDAY, SOURCE.FROMMONTH, SOURCE.FROMYEAR,
SOURCE.TODAY, SOURCE.TOMONTH, SOURCE.TOYEAR,
SOURCE.[DESCRIPTION], SOURCE.[ISCURRENT],
SOURCE.ENTRYDATE)
OUTPUT INSERTED.ID, $ACTION INTO #TEMPTABLE;
SELECT ID FROM #TEMPTABLE
END
Error shown in the following lines
TARGET.FROMDAY= SOURCE.FROMDAY
TARGET.TODAY=SOURCE.TODAY
SOURCE.FROMDAY
SOURCE.TODAY
You should also add those columns in table type [PAGE].[EXPERIENCEDETAILS] that used in your SP as TVP type.
I want to insert a record to a table called Payment which has column ID as the primary key(Auto Increment) and then I want to get that ID to use in a WHERE clause of another update statement.
var insertSatement = #"BEGIN INSERT INTO Payment (payment_type, reference, payment_date, total_records, total_amount) VALUES(#type, #reference, #date, #totalRecords, #totalAmount ) ";
var updateStatement = #"UPDATE SalaryTrans SET payment_id = (SELECT TOP 1 id FROM Payment ORDER BY Payment.id) WHERE SalaryTrans.id = #paramID ";
These two statements could not be merged as the update is going to update multiple rows. It will update all matching rows of the SalaryTrans table. So I'm using a foreach loop.
//open connection, add parameters
sqlCommand.CommandText = insertStatement;
sqlCommand.ExecuteNonQuery(); // This inserts...
foreach(PaymentInfo p in paymentList)
{
paramID.value = p.id;
sqlCommand.CommandText = updateStatement;
sqlCommand.ExecuteNonQuery();
}
In the loop each time "SELECT TOP 1 id..." is also executed. To avoid it, is there a way to use SCOPE_IDENTITY() to get the last updated ID from Payment table and use it in the loop?
Would there be a difference if I change update statement as follows in this context (performance wise) ?
DECLARE #ID INT = (SELECT TOP 1 id FROM Payment ORDER BY Payment.id)
UPDATE SalaryTrans SET payment_id = #ID WHERE SalaryTrans.id = 1
Or else should I separate this SELECT from the UPDATE to keep it outside the loop?
NOTE : My main concentration here is the performance factor.
What you can also try is, change your statement like below
var insertSatement = #"BEGIN INSERT INTO Payment (payment_type, reference, payment_date, total_records, total_amount) VALUES(#type, #reference, #date, #totalRecords, #totalAmount ); SELECT CAST(scope_identity() AS int) ";
Then in your excecute non query get the return value
sqlCommand.CommandText = insertStatement;
int id = (int) sqlCommand.ExecuteScalar(); // This inserts...
You can use the id in the loop
You can use SCOPE_IDENTITY
It will contain the latest value of the identity column from the newly inserted row
http://msdn.microsoft.com/en-us/library/ms190315.aspx