I have a database with an "ID" column. Whenever there is a new entry for the database, I fetch the last ID from the database, increment the value, and then use it in the Insert statement.
EDIT : I need the ID to use in multiple Insert statements. I will fetch this ID from the primary table and use this ID to insert values into related tables.
NextID = Select Max(ID) + 1 From Table
INSERT INTO Table1(ID, Col1, Col2...) Values(NextId, Value1, Value2...)
INSERT INTO Table2 (ID,col1,col2....) Values (NextID, Value1, Value2...)
I dont know if this is a good way because I know there will be concurrency issues.
When my application tries to read the NextID, there is a chance that another instance of the application is also trying to read the same value and thus concurrency issues may arise.
Is there a proper way to deal with this situation? I mean there are ways to set the database isolation level. Which would be a proper Isolation level for this situation.
Also if anybody could suggest me with an alternate way to maintain and increment manually the ID in the database, I'm also open to that.
If this information is not enough, please let me know what you require.
I am working with ASP.Net with VB and MS Sql Server 2008. I do not want to use the built-in "Identity" of SQL Server.
The only way to get the next ID is to actually insert the row, and use identity. Everything else will fail. So you must start by inserting into the parent table:
begin transaction;
insert into Table (col1, col2, col3) values (value1, value2, value3);
set #Id = scope_identity();
insert into Table1(ID, col1, col2) values (#Id, ...);
insert into Table3(ID, col1, col2) values (#Id, ...);
commit;
This is atomic and concurrency safe.
I do not want to use the built-in "Identity" of SQL Server.
tl;dr. What you 'want' matter little unless you can make a clear justification why. You can do it correctly, or you can spend the time 'ill oblivion reinventing the wheel.
Esentially you have a batch of three SQL statements - one select and two inserts. The database engine can execute another statement from a different session anywhere between them, thus breaking your data consistency - some other session can get the same MAX() value that you've got and use it for other insert statements. The only way to prevent DB engine from doing it is to use transactions. Wrap your batch with BEGIN TRANSACTION ... COMMIT and you are done.
Your way of doing this fine, what you would need is transaction handling..
BEGIN TRANSACTION
begin try
NextID = Select Max(ID) + 1 From Table
INSERT INTO Table1(ID, Col1, Col2...) Values(NextId, Value1, Value2...)
INSERT INTO Table2 (ID,col1,col2....) Values (NextID, Value1, Value2...)
COMMIT TRANSACTION
end try
begin catch
ROLLBACK TRANSACTION
--exception logging goes here
end catch
Related
I'm inserting data to a database where each "batch" must have a new unique id for the batch itself. I could add a batch table and use it's AUTOINCREMENT id, but I don't really need it for anything else, so it seems excessive.
I'm currently doing a SELECT MAX(batchid) + 1 FROM items and then using it for inserts, this is of course prone to race-conditions (2 new batches simultaneously can get conflicting ids)
Using an IMMEDIATE transaction is impractical. Is it possible to force an upgrade of a DEFERRED transaction to EXCLUSIVE before doing the select?
Some ideas;
Can I do some No-op cheap update?
Some explicit instruction to now go exclusive?
INSERT INTO items (batchid, value) VALUES ((SELECT MAX(batchid)+1 FROM items), "monkey"), ((SELECT MAX(batchid)+1 FROM items), "banana"), the idea being that the select is now explicitly part of the update?
It would indeed be possible to put the batch ID lookup into a subquery, but that would be a lot of duplication.
The easiest way is to do something to write to the database, such as PRAGMA user_version = x.
I have an ASP.NET web application that populates the SQL Server 2008 database table like this:
INSERT INTO tblName1 (col1, col2, col3)
VALUES(1, 2, 3)
I also have a separate service application that processes the contents of that table (on the background) by first renaming that table, and then by creating an empty table as such:
SET XACT_ABORT ON
BEGIN TRANSACTION
--Rename table
EXEC sp_rename 'tblName1', 'temp_tblName1'
--Create new table
CREATE TABLE tblName1(
id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
col1 INT,
col2 INT,
col3 INT
)
COMMIT
SET XACT_ABORT OFF
--Begin working with the 'temp_tblName1' table
What I am not sure is which SQL lock do I need to use in this situation on the tblName1 table?
PS. To give you a frequency with which these two code samples run: first may run several times a second (although most times, less frequently), and the second one -- twice a day.
As some of the comments have suggested, consider doing this differently. You may benefit from using the snapshot isolation level. Using snapshot isolation requires ALLOW_SNAPSHOT_ISOLATION to be set to ON on the database. This setting is off by default, so you'll want to check whether you can turn it on.
Once you are able to use snapshot isolation, you would not need to change your INSERT statement, but your other process could change to something like:
SET XACT_ABORT ON
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
BEGIN TRANSACTION
-- Do whatever this process does, but don't rename the table.
-- If you want to get rid of the old records:
DELETE [tblName1] WHERE 1 = 1
-- Then
COMMIT TRANSACTION
In case you really do need to create a new non-temporary table for some reason, you may need to do so before entering the transaction, as there are some limits on what you are allowed to do during snapshot isolation.
I'm trying to add an auto-calculated field in SQL Server 2012 Express, that stores the % of project completion, by calculating the date difference by using:
ALTER TABLE dbo.projects
ADD PercentageCompleted AS (select COUNT(*) FROM projects WHERE project_finish > project_start) * 100 / COUNT(*)
But I am getting this error:
Msg 1046, Level 15, State 1, Line 2
Subqueries are not allowed in this context. Only scalar expressions are allowed.
What am I doing wrong?
Even if it would be possible (it isn't), it is anyway not something you would want to have as a caculated column:
it will be the same value in each row
the entire table would need to be updated after every insert/update
You should consider doing this in a stored procedure or a user defined function instead.Or even better in the business logic of your application,
I don't think you can do that. You could write a trigger to figure it out or do it as part of an update statement.
Are you storing "percentageCompleted" as a duplicated column value in the same table as your project data?
If this is the case, I would not recommend this, because it would duplicate the data.
If you don't care about duplicate data, try something separating the steps out like this:
ALTER TABLE dbo.projects
ADD PercentageCompleted decimal(2,2) --You could also store it as a varchar or char
declare #percentageVariable decimal(2,2)
select #percentageVariable = (select count(*) from projects where Project_finish > project_start) / (select count(*) from projects) -- need to get ratio by completed/total
update projects
set PercentageCompleted = #percentageVariable
this will give you a decimal value in that table, then you can format it on select if you desire to % + PercentageCompleted * 100
I have a web form that inserts a row into an SQL Server 2008 table. Table has a field which must be unique, but for some reasons we are not allowed to use autoincrement utility. Instead of this I select the maximum of this field and increment it from code and insert the row with that id.
The problem is, if more than one person uses this form simultaneously, there occurs a concurrency problem and same id will be inserted to the table. I do not know how to solve this issue in a asp.net web project because all users have their own session and threads.
Any idea on how to manage concurrency problems in asp.net project will be very useful.
Thanks.
If you perform the insert into a stored procedure, or in a transaction in the code, I don't see a reason to have problems with concurrency. Something like:
BEGIN TRAN
DECLARE #MAX int
SELECT #MAX = MAX(ID) FROM MyTable
INSERT INTO MyTable (#MAX, VAlue, Value2..)
COMMIT TRAN
or
SqlCommand command = connection.CreateCommand();
SqlTransaction transaction;
transaction = connection.BeginTransaction("InsertRow");
... perform insert using command object ...
transaction.Commit();
I'm building a website using ASP.NET and SQL Server, and I use
SELECT PK FROM Table WHERE PK = ##identity
My question is which is better and more reliable to retrieve the last inserted PK for multiuser website, using ##identity or using this:
SELECT MAX(PK) FROM Table WHERE PK = Session ("UserID")
I'm not sure exactly what you want to achieve, but the recommended way to retrieve the primary key value of the last statement on a connection is to use SCOPE_IDENTITY()
##Identity is particularly risky where you are using triggers, since it returns the last generated identity value, including those generated by triggers flowing on from a statement.
MSDN has the following to say:
SCOPE_IDENTITY and ##IDENTITY return
the last identity values that are
generated in any table in the current
session. However, SCOPE_IDENTITY
returns values inserted only within
the current scope; ##IDENTITY is not
limited to a specific scope.
You should certainly use SCOPE_IDENTITY() in favour of the MAX(PK) approach - any number of possible future changes could invalidate this method.
For SQL Server 2005 and above...
You can do the INSERT and SELECT in one call using the OUTPUT clause...
INSERT MyTable (col1, col2, ..., coln)
OUTPUT INSERTED.keycol, INSERTED.col1, INSERTED.col2, ..., INSERTED.coln
VALUES (val1, val2, ..., valn)
Otherwise, you only use SCOPE_IDENTITY()
As mentioned by #David Hall the ##IDENTITY keyword returns the most recently created identity for your current connection, not always the identity for the recently added record in your query and may return an incorrect value. Using MAX(PK) there is a higher chance for an incorrect value and I'd strongly recommend against using it. To avoid the any race conditions I'd suggest that you use SCOPE_IDENTITY() to return the identity of the recently added record in your INSERT SQL Statement or Stored Procedure.
Depends on what you're trying to accomplish. If you want to return the just-generated ID to the ASP.NET code (a typical scenario), then ##identity is your friend. In a high-concurrency situation, mak(PK) is not even guaranteed to be the PK you're after.