I have this store procedure
CREATE PROCEDURE [dbo].[upsertGrps] #Groups GrpType READONLY AS
BEGIN
MERGE ExpandedGroupInformation AS Target
USING #Groups AS Source
ON (Source.ObjectGuid = Target.ObjectGuid or Source.Name = Target.Name)
WHEN MATCHED THEN
UPDATE SET Target.Description = Source.Description,
-- update data
WHEN NOT MATCHED BY TARGET
THEN INSERT
GO
This is used to update or insert data using the merge technique.
ObjectGuid in both Target and Source is uniqueidentifier.
I have noticed that it fails on matching them. Is it case sensitive? Do I have to apply some sort of collation on this?
I can do ToLower on both but I have data around 100,000 and then the query gets very slow.
Any suggestions on what I might be missing here?
Related
I have a FileTable FT and another table AT. In AT, I have extended metadata properties of the file in FT.
I have tried to create a trigger ON FT FOR UPDATE that will update the file path that is in AT.
Here is what I've tried:
ALTER TRIGGER [dbo].[u_filepath]
ON [FileDB].[dbo].[FT]
FOR UPDATE
AS
Declare #newpath nvarchar(1000)
Declare #oldpath nvarchar(1000)
select #newpath = file_stream.pathname()
from inserted
select #oldpath = file_stream.pathname()
from deleted
update AT
set [Path] = #newpath
where [Path] = #oldpath
GO
When I execute the query, it spins. I'm planning on leaving it running overnight just in case it decides to do something.
I want the Path column in AT to update to the updated file_stream.PathName() from FT.
Is the trigger logical?
Should I store the file_stream BLOB in my AT Path column instead of the actual path?
Your trigger has MAJOR flaw in that you seem to assume it'll be called once per row - that is not the case.
The trigger will fire once per statement, so if your UPDATE statement that causes this trigger to fire inserts 25 rows, you'll get the trigger fired once, but then the Deleted and Inserted pseudo tables will each contain 25 rows.
Which of those 25 rows will your code select here?
select #newpath = file_stream.pathname()
from inserted
It's non-deterministic, you'll get one arbitrary row and you will be ignoring all other rows.
You need to rewrite your trigger to take this into account - use a set-based approach to updating your data - not a row-based one - something like this:
ALTER TRIGGER [dbo].[u_filepath]
ON [FileDB].[dbo].[FT]
FOR UPDATE
AS
-- create a CTE to get the primary key, old and new path names
WITH UpdatedPaths AS
(
SELECT
i.PrimaryKey,
OldPathName = d.file_stream.pathname(),
NewPathName = i.file_stream.pathname()
FROM
Inserted i
INNER JOIN
Deleted d ON i.PrimaryKey = d.PrimaryKey
)
-- update the "AT" table where the old and new path names don't match
UPDATE dbo.AT
SET [Path] = up.NewPathName
FROM UpdatedPaths up
WHERE
up.OldPathName <> up.NewPathName
dbo.AT.PrimaryKey = up.PrimaryKey
GO
I am using the MERGE INTO statement to work with some tables, and I've a simple question. How can I make a Matched/Not Matched Statement do 2 things? For example, I have this and it works:
MERGE INTO VMFG.dbo._AR_Mill1ActiveSchedule t --target
USING #temp --source
On t.Base_ID = #temp.WORKORDER_BASE_ID AND t.Lot_ID = #temp.WORKORDER_Lot_ID
WHEN MATCHED AND #temp.rollstatus = 'R' THEN
UPDATE
SET t.sawStatus = #temp.sawstatus, t.rollStatus = #temp.rollstatus
WHEN NOT MATCHED BY TARGET THEN
INSERT (BASE_ID, LOT_ID, sawStatus, rollstatus, preheatcheck) VALUES (#temp.WorkOrder_Base_ID, #temp.WorkOrder_Lot_ID, #temp.sawStatus, #temp.rollStatus, 'False')
WHEN NOT MATCHED BY SOURCE AND t.SawStatus = 'C' THEN
Delete ;
Drop Table #temp
Now, I want to accomplish something like this:
MERGE INTO VMFG.dbo._AR_Mill1ActiveSchedule t --target
USING #temp --source
On t.Base_ID = #temp.WORKORDER_BASE_ID AND t.Lot_ID = #temp.WORKORDER_Lot_ID
WHEN MATCHED AND #temp.rollstatus = 'R' THEN
UPDATE
SET t.sawStatus = #temp.sawstatus, t.rollStatus = #temp.rollstatus
WHEN NOT MATCHED BY TARGET THEN
INSERT (BASE_ID, LOT_ID, sawStatus, rollstatus, preheatcheck) VALUES (#temp.WorkOrder_Base_ID, #temp.WorkOrder_Lot_ID, #temp.sawStatus, #temp.rollStatus, 'False')
WHEN NOT MATCHED BY SOURCE AND t.SawStatus = 'C' THEN
INSERT INTO _AR_Mill1RemovedWO (BASE_ID, LOT_ID) VALUES (#temp.WorkOrder_Base_ID, #temp.WorkOrder_Lot_ID)
Delete ;
Drop Table #temp
However, when I run this I get the error "Incorrect syntax near the keyword 'INTO'".
I've tried using the OUTPUT clause but I couldn't get it to work correctly, and would rather use an insert statement anyways if possible.
Thank you :)
EDIT:
The Output clause would be awesome to use, but I'll explain why it hasn't worked. When I used it, i.e.:
....
WHEN NOT MATCHED BY SOURCE AND t.SawStatus = 'C' THEN
--INSERT INTO _AR_Mill1RemovedWO (BASE_ID, LOT_ID) VALUES (#temp.WorkOrder_Base_ID, #temp.WorkOrder_Lot_ID)
Delete
OUTPUT deleted.Base_ID, deleted.Lot_ID INTO VMFG.dbo._AR_Mill1RemovedWO
;
Drop Table #temp
I run in to a problem. It adds rows to my table that weren't actually deleted. It's like it's just adding everything, regardless of being deleted. Is there a reason for this?
This is impossible to do with a single MERGE statement. Use the OUTPUT clause to direct information about the writes executed into a table variable. Execute a second statement such as INSERT to write to the second table.
I have a database that needs from time to time an update.
It may also happens that there are new data while the update runs.
In MySQL there is a option
INSERT INTO IGNORE
I can't find something like this in T-SQL.
No Problem to update ID 1-4 but then there is a new record for ID 5.
The UPDATE query don't work here.
And when I try to INSERT all data again I get a DUPLICATE KEY error.
Additional Infos:
I've forgotten to say that my data come from external sources. I call an API to get data from it. From there I have to insert these data into my database.
I have to admit that I don't understand MERGE. So my solution for now is to use TRUNCATE first and then insert all data again.
Not the best solution but MERGE works, so far I understand it, with two tables. But I have only one table. And to create a table temporarly to use MERGE and later drop that table is in my eyes a bit to much for my little table with 200 records in it.
You can use MERGE keyword. Basically, you need to specify the column(s) on which to join the source of data with target table, and depending on whether it is matching (existing record) or not matching (new record), you run an UPDATE or INSERT.
Reference: http://msdn.microsoft.com/en-us/library/bb510625.aspx
Is a stored procedure an option?
CREATE PROCEDURE dbo.Testing (#ID int, #Field1 varchar(20))
AS
BEGIN
UPDATE tblTesting
SET Field1 = #Field1
WHERE ID = #ID
IF ##ROWCOUNT = 0
INSERT INTO tblTesting (ID, Field1) SELECT #ID, #Field1
END
I have a sequence I need to use to recalculate both fields in the primary key if an update match isn't found. Is it possible to still use the MERGE statement here? I tried WHEN MATCHED THEN BEGIN, but BEGIN isn't valid here.
Specifically, I have a pair of numbers that make up the primary key. The first is a grouping, and the second is a sequence of items within the group. If something goes wrong, the group comes in as 99990, and I need to combine it with the sequence and use a sequence to increment, then split it back apart. So, when the group comes in with 99990, my calculated groups can range from 99990 through 99999, and the sequence number will then range from 00 through 99.
I can't think of a way to do this as part of the INSERT assignment, and I can't figure out how to make the MERGE do multiple steps, so I'm guessing I'm back to UPDATE, IF ##ROWCOUNT = 0 BEGIN. Anyone have a faster way to do this?
This works,
Declare a flag variable (bit) called #isinsert
Initialize it with the value of 1.
On the update clause set it to 0.
Like this: (assuming a table called table1 with a numeric 'id' field and an nvarchar 'field1' field).
declare #id numeric(18,0) -- That's the he lookup key
set #id=999 -- We use id as the search key
-- You can use any other field
declare #field nvarchar(50)
set #field = 'insert or update value(s)' -- This is the new value
declare #isinsert bit -- This is a flag that will
set #isinsert=1 -- indicate whether an insert or
-- an update were performed
MERGE table1 AS target
USING (SELECT #field) AS source (field1)
ON (target.id = #id)
WHEN MATCHED THEN
UPDATE SET field1 = source.field1
,#isinsert = 0 -- Set #isinsert to 0 on updates
WHEN NOT MATCHED THEN
INSERT (field1)
VALUES (source.field1);
if (#isinsert=1) print concat('inserted record at id: ',##IDENTITY)
else print concat('updated record at id: ',#id)
Unfortunately, it isn't possible to do multiple steps in a MERGE. Sometimes the new Common Table Expression syntax can be used, but I resorted to an UPDATE, IF ##ROWcOUNT = 0, INSERT so I could do multiple steps on the insert.
I'm not sure if this is something I should do in T-SQL or not, and I'm pretty sure using the word 'iterate' was wrong in this context, since you should never iterate anything in sql. It should be a set based operation, correct? Anyway, here's the scenario:
I have a stored proc that returns many uniqueidentifiers (single column results). These ids are the primary keys of records in a another table. I need to set a flag on all the corresponding records in that table.
How do I do this without the use of cursors? Should be an easy one for you sql gurus!
This may not be the most efficient, but I would create a temp table to hold the results of the stored proc and then use that in a join against the target table. For example:
CREATE TABLE #t (uniqueid int)
INSERT INTO #t EXEC p_YourStoredProc
UPDATE TargetTable
SET a.FlagColumn = 1
FROM TargetTable a JOIN #t b
ON a.uniqueid = b.uniqueid
DROP TABLE #t
You could also change your stored proc to a user-defined function that returns a table with your uniqueidentifiers. You can joing directly to the UDF and treat it like a table which avoids having to create the extra temp table explicitly. Also, you can pass parameters into the function as you're calling it, making this a very flexible solution.
CREATE FUNCTION dbo.udfGetUniqueIDs
()
RETURNS TABLE
AS
RETURN
(
SELECT uniqueid FROM dbo.SomeWhere
)
GO
UPDATE dbo.TargetTable
SET a.FlagColumn = 1
FROM dbo.TargetTable a INNER JOIN dbo.udfGetUniqueIDs() b
ON a.uniqueid = b.uniqueid
Edit:
This will work on SQL Server 2000 and up...
Insert the results of the stored proc into a temporary table and join this to the table you want to update:
INSERT INTO #WorkTable
EXEC usp_WorkResults
UPDATE DataTable
SET Flag = Whatever
FROM DataTable
INNER JOIN #WorkTable
ON DataTable.Ket = #WorkTable.Key
If you upgrade to SQL 2008 then you can pass table parameters I believe. Otherwise, you're stuck with a global temporary table or creating a permanent table that includes a column for some sort of process ID to identify which call to the stored procedure is relevant.
How much room do you have in changing the stored procedure that generates the IDs? You could add code in there to handle it or have a parameter that lets you optionally flag the rows when it is called.
Use temporary tables or a table variable (you are using SS2005).
Although, that's not nest-able - if a stored proc uses that method then you can't dumpt that output into a temp table.
An ugly solution would be to have your procedure return the "next" id each time it is called by using the other table (or some flag on the existing table) to filter out the rows that it has already returned
You can use a temp table or table variable with an additional column:
DECLARE #MyTable TABLE (
Column1 uniqueidentifer,
...,
Checked bit
)
INSERT INTO #MyTable
SELECT [...], 0 FROM MyTable WHERE [...]
DECLARE #Continue bit
SET #Continue = 1
WHILE (#Continue)
BEGIN
SELECT #var1 = Column1,
#var2 = Column2,
...
FROM #MyTable
WHERE Checked = 1
IF #var1 IS NULL
SET #Continue = 0
ELSE
BEGIN
...
UPDATE #MyTable SET Checked = 1 WHERE Column1 = #var1
END
END
Edit: Actually, in your situation a join will be better; the code above is a cursorless iteration, which is overkill for your situation.