I have a table A (SQL Server 2008) that contains sets of min and max numbers. In one of my stored procedures I use this table joined with a product table B to find an available product number that's between min and max and then insert a new product with this product number. So I want the next free number that's between min/max.
Between finding out the next available number and inserting the product row I want a lock to prevent anyone of finding the same number (and giving a duplicate).
How should I think in this situation? Should I get an update lock on the A table even though I never modify it? The lock should be released after I do the insert into table B and the transaction finishes? Will this update lock prevent other transactions from reading table A?
Edit: The min/max table is a table for different product series. Depending on which serie you want i want to try and find an available number in this sequence. The productnr is not unique, it would be possible to make it unique in combination with a second column though. Simplified sp:
CREATE PROCEDURE [dbo].[InsertProduct]
(
#Param1 int,
#Param2 bit,
...
#Param20 int) AS
BEGIN
DECLARE #ProductNr int
--Here I do a query to determine which ProductNr I should have. Checking that the number is between max/min in the series and that no other product has this productnr.
--Now insert the row
INSERT INTO Products VALUES (#Param1, #ProductNr, ...., #Param2
END
Your question is kind of obscure, it would help if you included some sample data.
Regardless, a tactic that I have used in the past is to try and wrap everything withing a single statement -- here, it would be an INSERT. Every SQL statement is de-facto wrapped in its own implicit transaction (that's atomicity, what the "A" in the ACID of relational database properties fame stands for). In psuedo-code, it'd look something like:
INSERT MyTable (Id, Plus, Other, Columns)
select CalcForNewId, Plus, Other, Columns
from [what may be a pertty convoluted query to determine the "next" valid Id]
This only works if you can write your business logic as a single reasonable query (where "reasonable" means it doesn't lock, block, or deadlock either the current or any other user for an unreasonable length of time). That can be a pretty tall order, but I'd take that over having to write complex BEGIN TRANSACTION/COMMIT/ROLLBACK code intermixed with TRY...CATCH blocks any day. (That will of course work, and I've upvoted #Scott Bruns accordingly.)
Just insert the next number into table B. If it commits it is yours to use. If you get a Key Violation it means that a different process has just entered the new number. In that case increment the new number and try again.
The database will automatically handle the concerency for you. No manual locking is required.
Related
This question deals with how one should handle conditional insertions into tables.
Suppose we customers and employees.
A customer can only be assigned 4 employees at a time.
We will come back to this in a moment.
On the database level, we have checks and triggers.
In MariaDB, CHECK constrains cannot have subqueries, so we cannot impose constraints
regarding degree of participation in this way. For instance, we cannot say something
like
CHECK ( customer_id IN (SUBQUERY that returns count of Employees with == 4 employees) ).
Triggers may be the solution.
insert-or-update). If a manager attempts to assign a customer to an employee who already has 4 customers, we should not allow that record to be inserted into the table that links customers to their employees. In this case we want a trigger that acts before insertion. We only want the
insertion to occur if that employee is not in the subquery that lists
employees with 4 customers.
We want to stop the insertion, but by law, the trigger will not do this
based on a condition that is merely stated in the trigger. From my understanding, the only way to do this is to send a signal (look at Use a trigger to stop an insert or update
This leads to my next two questions.
Is using a signal 'ideal'? Is it problematic? Is there a better way to insert into a table
based on a condition, instead of merely performing side effects prior to
an insertion or perhaps after an insertion?
It appears that the db would send a signal if the constraint was violated to begin with, so would this ever impact the application built on top of it?
Some flavors of restraint can be dealt with thus:
INSERT INTO TABLE (a,b,c)
SELECT ...
WHERE <-- put the logic here (if possible)
That is, arrange to have the SELECT deliver more or fewer rows based on your business logic.
I was attempting to run a simple Insert into my CustomerOrders table and got the error: #1109 - Unknown table 'ConcertDetails' in field list
I did some searches on this and looked at about 7 different stack overflow posts on it but still not sure what is wrong. I also looked up information on triggers and there seems to be different syntax on different sites. The weird thing is this trigger used to work just fine, not sure what has been altered since 4 days ago.
I tried changing some things, for instance I removed my trigger and it let me insert, but when I put the trigger back I couldn't insert any more, so there must be something wrong with the trigger. This is what I have for the trigger:
DROP TRIGGER IF EXISTS `alterPurchasePrice`;
DELIMITER //
CREATE TRIGGER `alterPurchasePrice` BEFORE INSERT ON `CustomerOrders`
FOR EACH ROW BEGIN
IF new.DiscountCode = 'yes' THEN
SET new.PurchasePrice = ConcertDetails.Cost - 10;
END IF;
END
//
DELIMITER ;
The purpose of the trigger is to lower the price by $10 if the user types 'yes' into the DiscountCode field.
This involves the tables:
CustomerOrders: ConcertID, CustomerName, Discount Code, OrderID,
PurchasePrice ConcertDetails: ConcertDate, ConcertID, Cost
I think you need a SELECT to retrieve values from other tables.
To get the value of "cost" from "ConcertDetails" table, for a specific concert, we could write a query like this:
SELECT ConcertDetails.cost
FROM ConcertDetails
WHERE ConcertDetails.concertid = ?
assuming that "concertid" is the primary key (or unique key) of the ConcertDetails table, we would be guaranteed that the query would return at most one row.
To put that to use in the trigger, we should be able to do something like this
SET NEW.PurchasePrice
= ( SELECT d.cost - 10 AS discount_price
FROM ConcertDetails d
WHERE d.concertid = NEW.concertid
);
The value of the concertid column of the row to be inserted from the CustomerOrders table will be supplied for the query through the reference to NEW.concertid. The return from that query will be assigned to the purchaseprice column.
If the query returns more than one row (which could happen if we don't have any kind of guarantee that "concertid" is unique in the "ConcertDetails" table), the trigger will throw a "too many rows" error.
If there are no rows returned, we'd expect a NULL value to be assigned would be assigned to "purchaseprice". We would also get a NULL returned if the "cost" column is set to NULL.
Is there some sort of guarantee that "cost" will never be less than 10? If the returned Cost is 6, then the value assigned to "purchaseprice" would be -4. If we want the value assigned to "purchaseprice" to never be less than zero, we could do something like this:
SET NEW.purchaseprice
= ( SELECT GREATEST(d.cost-10,0) AS discount_price
FROM ConcertDetails d
WHERE d.concertid = NEW.concertid
);
Other notes:
I'd recommend a different naming convention for your triggers. With multiple tables and multiple triggers, it can get kind of confusing, when looking for triggers on CustomerOrders table, to remember that the name of the BEFORE INSERT trigger is "alterPurchasePrice". Especially if you (or someone else) is coming back to work on the system six months or six years from now.
The convention we follow for trigger names is to use the name of the table, followed by an underscore, followed by one of: bi, bu, bd, ai, au, ad (for Before/After Insert/Update/Delete). (Since MySQL doesn't allow more than one trigger for each of those, we don't get naming collisions. And it makes it easier to check whether a BEFORE INSERT trigger exists on a table, before someone writes a BEFORE INSERT trigger that does something else.)
I also mention, in regards to the use of CamelCase table names... the MySQL Reference Manual says this:
To avoid problems ... it is best to adopt a consistent convention, such as always creating and referring to databases and tables using lowercase names. This convention is recommended for maximum portability and ease of use.
https://dev.mysql.com/doc/refman/5.7/en/identifier-case-sensitivity.html
Then again, these are just conventions. We also follow a convention to name tables by naming what a single row represents. If we had a requirement to create this table, we would assign the name customer_order. And the BEFORE INSERT trigger on the table would be named customer_order_bi
Hi all I having a Identity column and a Computed primary key column in my table I need to get the last inserted record immediately after inserting the record in to database, So I have written the following queries can some one tell which is the best one to choose
SELECT
t.[StudentID]
FROM
[tbl_Student] t
WHERE
t.ID = IDENT_CURRENT('tbl_Student')
The other is using MAX as follows
Select
MAX(StudentID)
from tbl_Student
From the above two queries which is the best one to choose.
MAX and IDENT_CURRENT, according to technet, would behave much the same and both would be equally unreliable.
"IDENT_CURRENT is not limited by scope and session; it is limited to a specified table. IDENT_CURRENT returns the identity value generated for a specific table in any session and any scope. For more information, see IDENT_CURRENT (Transact-SQL)."
Basically, to return the last insert within the current scope, regardless of any potential triggers or inserts / deletes from other sessions, you should use SCOPE_IDENTITY. Of course, that's assuming you're running the query in the same scope as the actual insert in the first place. :)
If you are, you also have the alternative of simply using OUTPUT clause to get the inserted ID values into a table variable / temporary table, and select from there.
The original answer, where my assumptions about IDENT_CURRENTwhere wrong.
Use the first one. IDENT_CURRENT should give you the last item for the current connection. If someone else would insert another student concurrently IDENT_CURRENT will give you the correct value for both clients, while MAX might give you a wrong value.
EDIT:
As it was mentioned in the other answer IDENT_CURRENTand MAXare equally unreliable in case of concurrent usage. I would still go for IDENT_CURRENT but if you want to get the last identity used by the current scope or session you can use the functions ##IDENTITY and SCOPE_IDENTITY. This technet article explains the detailed differences between IDENT_CURRENT, ##IDENTITY and SCOPE_IDENTITY.
We're trying to figure out what the relative costs are between a couple of approaches.
We have a web page where people choose to add/keep/remove rows from a table, by marking them with checkboxes. (People can add new entries to the page as well as see existing ones.)
When posted to the web server the page loops over the entries and calls a stored procedure, passing in the state of the checkbox as one of the parameters.
The stored procedure currently calls a delete statement for each entry, followed by an insert if the checkbox is marked. This has the virtue of simplicity.
We're thinking instead of putting some if exists logic in there, to test whether the row is already in the table.
If so and the checkbox is marked, we'd leave it alone. Otherwise we'd insert it. Conversely, if the row isn't in the table and the checkbox is unmarked, we'd skip the delete and insert statements. This minimizes the number of deletes and such but at a cost of more logic.
In terms of load on the database, is one approach generally preferred to the other?
Is there a cost to calling delete statements that don't, in fact, affect any rows, as would be the case when adding new records? Is this worse than an if exists check?
The table is indexed on all relevant columns. I assume for posting 600,000 entries there would be a big advantage to checking beforehand, but the page in question will have 100 entries at most.
The biggest problem you're going to have with performance here is that you are calling a stored procedure for every entry - it really doesn't matter if inside that stored procedure you use DELETE/INSERT or check first, you're still going to have the overhead of 600K procedure calls, some potentially large portion of 600K logged transactions, etc.
I strongly recommend you look at table-valued parameters. Your C# or whatever can pass a set of 600K entries to a single stored procedure, once, and then you can perform two set-based operations (pseudo-code):
UPDATE src SET val = t.val
FROM dbo.tvp INNER JOIN dbo.source AS src
ON t.key = src.key;
INSERT src SELECT x FROM dbo.tvp AS t
WHERE NOT EXISTS (SELECT 1 FROM src WHERE key = t.key);
My application is generating the ID numbers when registering a new customer then inserting it into the customer table.
The method for generating the ID is by reading the last ID number then incrementing it by one then inserting it into the table.
The application will be used in a network environment with more than 30 users, so there is a possibility (probability?) for at least two users to read the same last ID number at the saving stage, which means both will get the same ID number.
Also I'm using transaction. I need a logical solution that I couldn't find on other sites.
Please reply with a description so I can understand it very well.
use an autoincrement, you can get the last id issued with the mysql_insert_id property.
If for some reason that's not doable, you can craete another table to hold the last id used, then you increment that in a transaction, and then use it as the key for your insert into the table. Got to be two transctions though, otherwise you'll have the same issue you have now. That can get messy and is an extra level of maintenance though. (reset your next id table to zero when ther are still some in teh related table and things go nipples up quick.
Short of putting an exclusive lock on the table during the insert operation (not even slightly recomended), your current solution just can't work.
Okay expanded answer based on leaving schema as it is.
Option 1 in pseudo code
StartTransaction
try
NextId = GetNextId(...)
AddRecord(NextID...)
commit transaction
catch Primary Key Violation
rollback transaction
Do the entire thing again
end
Obviously you could end up in an infinite loop here, unlikely but possible, probably run out of stack space first.
You could some how queue the requests and then attempt to process them, if successful remove from queue.
BUT make customerid an auto inc the entire problem dispappears.
It will still be the primary key, you just don't have to work out what it needs to be any more, in fact you don't supply it in the insert statement, mysql will just take care of it for you.
The only thing you have to remember is if you need the id that has been automatically created is to request it in one transaction.
So your insert query needs to be in the form
Insert SomeTable(SomeColumns) Values(SomeValues)
Select mysql_insert_id
or if multiple statements gets in the way wrap two statements in a start stransaction commit transaction pair.