I have run into an interesting scenario. Using the context.database.sqlquery available in the Entity Framework I am calling a stored procedure to update a field.
When calling the procedure directly in management studio no deadlocks occur when editing two or more items.
When calling the same stored procedure using context.database.sqlquery a dealock is encountered, when editing two or more items.
I have found a way to correct for this, but I do not see why calling the procedure from the framework would cause a deadlock. So I thought I would ask.
--Create a temp table to hold items based on userid
CREATE TABLE #tableA
(
[Id] int identity (1,1),
[Item_Id] int
)
INSERT INTO #tableA ([Item_Id]) SELECT [Id] FROM [Items] WHERE [UserId] = #UserId
--All of the other processing is omitted for brevity
--Based on final processing above update the IsActive flag on the Items table
UPDATE i
SET [IsActive] = 0
FROM #tableA ta INNER JOIN [Items] i ON ta.[Item_Id] = i.[Item_Id]
WHERE [IsActive] = 1
Again, I have a solution that worked, just trying to understand why a deadlock would occur in EF and not when calling the procedure directly.
BTW, the solution was to add the IsActive bit to the temp table and populating it initially and then on the join in the update statement I used temp table isactive place in the where clause.
--Create a temp table to hold items based on userid
CREATE TABLE #tableA
(
[Id] int identity (1,1),
[Item_Id] int,
[IsActive]
)
INSERT INTO #tableA ([Item_Id], [IsActive]) SELECT [Id], [IsActive] FROM [Items] WHERE [UserId] = #UserId
--All of the other processing is omitted for brevity
--Based on final processing above update the IsActive flag on the Items table
UPDATE i
SET [IsActive] = 0
FROM #tableA ta INNER JOIN [Items] i ON ta.[Item_Id] = i.[Item_Id]
WHERE ta.[IsActive] = 1
Related
I have a very simple update statement within one job step:
UPDATE [Table]
SET
[Flag] = 1
WHERE [ID] = (SELECT MAX([ID]) FROM [Table] WHERE [Name] = 'DEV')
Normally there are no issues with this code, but sometimes it ends up with the deadlock.
Is it in general possible, that such stand-alone piece of code leads to a deadlock?
Table schema:
CREATE TABLE [Table]
(
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[Name] [varchar](100) NOT NULL,
[Flag] [bit] NULL,
CONSTRAINT [Table_ID] PRIMARY KEY CLUSTERED
)
The deadlock cause is quite obvious: there is no index on Name, so it's going to scan the whole table for the subquery. There is also no UPDLOCK hint on it, so that is also going to make deadlocks more likely.
Create an index on Name
CREATE NONCLUSTERED INDEX IX_Name ON [Table] (Name) INCLUDE (ID);
And make sure you use UPDLOCK on the subquery
UPDATE [Table]
SET Flag = 1
WHERE ID = (
SELECT MAX(ID)
FROM [Table] t2 WITH (UPDLOCK)
WHERE t2.Name = 'DEV')
This query is much more efficiently written without a self-join, like this:
UPDATE t
SET Flag = 1
FROM (
SELECT TOP (1)
*
FROM [Table] t
WHERE t.Name = 'DEV'
ORDER BY ID DESC
) t;
Even though the optimizer can often transform into this version, it's better to just write it like this anyway.
This version does not need a UPDLOCK, it will be added automatically. You still need the above index though.
db<>fiddle
I'm writing an import process which will import data from one (somewhat legacy) database to another. The import process takes one flat table with the source data. I have this populating a temp table (#SourcePersonAccount) at the start. The goal is to distribute this data into three destination tables (dbo.Person, dbo.Account & dbo.PersonAccount). This runs within a trigger on a table use SQL Server Replication, so needs to run quickly.
#SourcePersonAccount([AccountNumber], [CompanyId], [TargetPersonId], [TargetAccountId]);
dbo.Person ([Id] pk identity(1,1), [CompanyId], ...);
dbo.Account ([Id] pk identity(1,1), [AccountNumber], ...);
dbo.PersonAccount ([Id], [PersonId] fk_Person_Id, [AccountId] fk_Account_Id);
In my code, I have the TargetPersonId already populated in the #SourcePersonAccount temp table. All that's left is to 1) insert into dbo.Account, 2) update #SourcePersonAccount with the inserted dbo.Account.Id value, 3) insert into dbo.PersonAccount.
One of the challenges is that the AccountNumber and CompanyId make up a composite primary key of the source table, so both are needed to join properly on the #SourcePersonAccount temp table.
I have seen threads addressing similar issues to a certain extent here and here which did not solve my particular problem, mostly due to performance issues.
As stated in this post, the OUTPUT clause cannot output columns that were not included in the insert, so that is not an option here.
One solution I saw that technically can give the desired output (I can't find the link to where I found the suggestion) while using the OUTPUT clause is to actually add and drop a column within the query.
DECLARE #PersonAccountTbl TABLE ([AccountId] INT, [AccountNumber] INT, [CompanyId] INT);
ALTER TABLE [dbo].[Account]
ADD [CompanyId] INT NULL;
INSERT INTO [dbo].[Account]
([AccountNumber], [CompanyId])
OUTPUT INSERTED.[Id], INSERTED.[AccountNumber], INSERTED.[CompanyId]
INTO #PersonAccountTbl
SELECT
[AccountNumber], [CompanyId]
FROM #SourcePersonAccount
WHERE
[TargetAccountId] IS NULL;
ALTER TABLE [dbo].[Account]
DROP COLUMN [CompanyId];
This is not a viable option for my situation.
I tried using MERGE as every thread I've found on this issue recommends using it. I do not like MERGE for a few reasons. I tried it anyways; the below code gives the desired output, but ended up being much too slow for my purposes.
DECLARE #PersonAccountTbl TABLE ([AccountId] INT, [AccountNumber] INT, [CompanyId] INT);
MERGE INTO [dbo].[Account] a
USING #SourcePersonAccount spa
ON spa.[TargetAccountId] IS NULL
WHEN NOT MATCHED THEN
INSERT
([AccountNumber])
VALUES
(spa.[AccountNumber])
OUTPUT INSERTED.[Id], INSERTED.[AccountNumber], spa.[CompanyId]
INTO #PersonAccountTbl ([AccountId], [AccountNumber], [CompanyId]);
UPDATE spa
SET spa.[TargetAccountId] = pat.[AccountId]
FROM #SourcePersonAccount spa
JOIN #PersonAccountTbl pat
ON pat.[AccountNumber] = spa.[AccountNumber]
AND pat.[CompanyId] = spa.[CompanyId];
INSERT INTO [dbo].[PersonAccount]
([PersonId], [AccountId])
SELECT
spa.[TargetPersonId], spa.[TargetAccountId]
FROM #SourcePersonAccount spa
LEFT JOIN [dbo].[PersonAccount] pa
ON pa.[PersonId] = spa.[TargetPersonId]
AND pa.[AccountId] = spa.[TargetAccountId]
WHERE
pa.[Id] IS NULL;
Is there a way other than MERGE or adding/dropping a column to accomplish this?
You can use a SEQUENCE instead of an IDENTITY column. Then you can assign the IDs to a temp table or table variable before you INSERT the data.
This question already has an answer here:
View or function '' is not updatable because the modification affects multiple base tables
(1 answer)
Closed 5 years ago.
I'm trying to implement a basic queue using tables, in SQL Server. I have "tasks" that I want to add to my "queue". Here is my Task table, followed by my TaskQueue table ...
CREATE TABLE Task (
Id INT PRIMARY KEY IDENTITY(1, 1),
JobId INT NOT NULL REFERENCES Job (Id) ON DELETE CASCADE,
Payload NVARCHAR(MAX) NOT NULL,
PayloadType NVARCHAR(MAX) NOT NULL,
DateCreated DATETIME2 NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE TaskQueue (
TaskId INT UNIQUE NOT NULL REFERENCES Task (Id) ON DELETE CASCADE,
DateCreated DATETIME2 NOT NULL DEFAULT CURRENT_TIMESTAMP
);
As you can see, the queue itself is nothing but a table, as described here by Remus Rusanu.
I'm not concerned with order of items, so the enqueue procedure is just as Remus recommends. Below is my dequeue procedure ...
CREATE TYPE DequeueTaskPayloadType AS TABLE (
PayloadType NVARCHAR(MAX) NOT NULL
);
CREATE PROCEDURE DequeueTask
#payloadTypes DequeueTaskPayloadType READONLY
AS
SET NOCOUNT ON;
WITH NextTask AS (
SELECT TOP 1 q.*
FROM TaskQueue AS q WITH (ROWLOCK, READPAST)
JOIN Task AS t ON t.Id = q.TaskId AND t.PayloadType IN (
SELECT PayloadType
FROM #payloadTypes))
DELETE FROM NextTask
OUTPUT deleted.*;
My dequeue method differs from Remus' in that I need to include a JOIN so that I can dequeue records that have an appropriate filter - the filter can be seen in my query, but basically the Task has a PayloadType and I want the stored procedure to require an argument through which I can dequeue tasks with a specific payload type.
My problem is that when I execute my DequeueTask stored procedure, I get the following error ...
View or function 'NextTask' is not updatable because the modification
affects multiple base tables.
I understand that my NextTask CTE is basically a view here but I do not understand why this affects multiple base tables. I do think it's obvious that it has to do with me using a JOIN, but I guess my question is ...
How can I DELETE from a CTE in which a JOIN exists?
JOIN won't work in CTE/DELETE, try to restructure CTE query to use WHERE EXISTS () instead of JOIN like here:
http://stevestedman.com/2015/06/deleting-from-a-cte-with-an-exists-statement/
Delete from CTE with join
Following is new data type that I created.
CREATE TYPE [dbo].[UpdateHotelTableType] AS TABLE(
[ID] [int] NULL,
[HotelID] [int] NULL,
[FromDate] [datetime] NULL,
)
Following is my stored procedure that I used the above datatype.
ALTER PROCEDURE [dbo].[SP_Hotel_Info_Update]
-- Add the parameters for the stored procedure here
#XHotelInfoDetails UpdateHotelTableType READONLY,
AS
BEGIN
Update dbo.HotelInfo
SET
FromDate = r.FromDate,
from #XHotelInfoDetails r
Where HotelInfo.ID = r.ID
END
This is working fine for update results in database. But I want to check whether the id is exists and if the id is not exists insert the row in to the table. otherwise update current record. In here I am sending the list of data for update.
Can any one help me to recreate the stored procedure to insert the data too by checking the existence of ID.
Use MERGE:
Performs insert, update, or delete operations on a target table based on the results of a join with a source table. For example, you can synchronize two tables by inserting, updating, or deleting rows in one table based on differences found in the other table.
ALTER PROCEDURE [dbo].[SP_Hotel_Info_Update]
-- Add the parameters for the stored procedure here
#XHotelInfoDetails UpdateHotelTableType READONLY,
AS
BEGIN
MERGE dbo.HotelInfo AS trg
USING #XHotelInfoDetails AS src
ON src.ID = trg.ID
WHEN MATCHED THEN
UPDATE SET FromDate = src.FromDate
WHEN NOT MATCHED BY TARGET THEN
INSERT (col1, col2, ...)
VALUES (src.col1, src.col2, ...);
END
EDIT:
In my datatable, there can be newly added rows as well as deleted rows. So how can I compare the id and delete rows from hotelinfo table?
You could add new clause:
WHEN NOT MATCHED BY SOURCE [ AND <clause_search_condition> ]
THEN DELETE;
with specific condition to delete data from target.
You can Simply do 2 Queries:
1. "Update" command
2. "Insert" command.
ALTER PROCEDURE [dbo].[SP_Hotel_Info_Update]
-- Add the parameters for the stored procedure here
#XHotelInfoDetails UpdateHotelTableType READONLY,
AS
BEGIN
UPDATE dbo.HotelInfo
SET FromDate = r.FromDate,
FROM dbo.HotelInfo
JOIN #XHotelInfoDetails X ON X.Id = HotelInfo.Id
INSERT INTO dbo.HotelInfo (Col1,Col2)
SELECT X.Col1,
X.Col2
FROM #XHotelInfoDetails X
WHERE NOT EXISTS
(
SELECT 1
FROM dbo.HotelInfo InnerTable
WHERE X.Id = InnerTable.Id
)
END
I am looking for suggestions on how to handle incoming data from a C# client app that sends via System.Data.SqlClient.SqlBulkCopy. The original intent was to maintain all data sent via the app into our sql server where the data would be 'massaged'. To keep things secure, data is sent to write only tables where an AFTER INSERT trigger will copy to another table and delete incoming.
Simple Example:
ALTER TRIGGER [dbo].[FromWeb_To_MyTable] ON [dbo].[_FromWeb_MyTable]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [dbo].[MyTable]([MyTableID],[MemberID],[LocationID],[ODBCID],[InsertDateTime])
SELECT i.[MyTableID],i.[MemberID],i.[LocationID],i.[ODBCID],i.[InsertDateTime]
FROM Inserted i;
DELETE FROM _FromWeb_MyTable
WHERE [MyTableID] IN (SELECT i.[MyTableID] FROM Inserted i);
END
Now I am going to be using a bit differently and need to delete everything in the 'go to' table. My largest table will be around 350,000 records. I intend to DROP and re-CREATE said table like so:
(this method IS working fine see my questions below)
ALTER TRIGGER [dbo].[FromWeb_To_MyTable] ON [dbo].[_FromWeb_MyTable]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS(SELECT [name] FROM sys.TABLES WHERE [name] = 'MyTable') DROP TABLE [dbo].[MyTable];
CREATE TABLE [dbo].[MyTable] (
[MyTableID] [int] NOT NULL,
[MemberID] [varchar](6) NOT NULL,
[LocationID] [varchar](50) NOT NULL,
[ODBCID] [varchar](50) NOT NULL,
[InsertDateTime] [datetime] NOT NULL
) on [PRIMARY];
INSERT INTO [dbo].[MyTable] ( [MyTableID], [MemberID],[LocationID],[ODBCID],[InsertDateTime] )
SELECT i.[MyTableID],i.[MemberID],i.[LocationID],i.[ODBCID],i.[InsertDateTime]
FROM Inserted i;
DELETE FROM _FromWeb_MyTable
WHERE [MyTableID] IN (SELECT [MyTableID] FROM Inserted);
END
Does anyone see any problems with this method? Any different suggestions? Can someone explain when and how to index the newly recreated table?
Since you reuse your table instead of dropping and re-creating it just use TRUNCATE.
ALTER TRIGGER dbo.FromWeb_To_MyTable ON dbo._FromWeb_MyTable
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
TRUNCATE TABLE dbo.MyTable;
INSERT INTO dbo.MyTable (MyTableID, MemberID,LocationID,ODBCID,InsertDateTime)
SELECT i.MyTableID,i.MemberID,i.LocationID,i.ODBCID,i.InsertDateTime
FROM Inserted i;
DELETE FROM _FromWeb_MyTable
WHERE MyTableID IN (SELECT MyTableID FROM Inserted);
END