You need to do this in transaction to ensure two simultaneous clients won't insert same fieldValue twice:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLEBEGIN TRANSACTION DECLARE @id AS INT SELECT @id = tableId FROM table WHERE fieldValue=@newValue IF @id IS NULL BEGIN INSERT INTO table (fieldValue) VALUES (@newValue) SELECT @id = SCOPE_IDENTITY() END SELECT @idCOMMIT TRANSACTION
you can also use Double-checked locking to reduce locking overhead
DECLARE @id AS INTSELECT @id = tableID FROM table (NOLOCK) WHERE fieldValue=@newValueIF @id IS NULLBEGIN SET TRANSACTION ISOLATION LEVEL SERIALIZABLE BEGIN TRANSACTION SELECT @id = tableID FROM table WHERE fieldValue=@newValue IF @id IS NULL BEGIN INSERT INTO table (fieldValue) VALUES (@newValue) SELECT @id = SCOPE_IDENTITY() END COMMIT TRANSACTIONENDSELECT @id
As for why ISOLATION LEVEL SERIALIZABLE is necessary, when you are inside a serializable transaction, the first SELECT that hits the table creates a range lock covering the place where the record should be, so nobody else can insert the same record until this transaction ends.
Without ISOLATION LEVEL SERIALIZABLE, the default isolation level (READ COMMITTED) would not lock the table at read time, so between SELECT and UPDATE, somebody would still be able to insert. Transactions with READ COMMITTED isolation level do not cause SELECT to lock. Transactions with REPEATABLE READS lock the record (if found) but not the gap.