Does ms sql server not take ReadPast Hint in Functions? - sql

I have a pending order table with a check constraint to prevent people from ordering an item we don't have in stock. This required me to create a counter function to decide if an insert can happen or not. It works until there is 1 item left in inventory then I get a message that we are out of stock of the item. I thought it was a dirty read issue but even after interducing a ReadPast hint I still see this behavior. Is there some other factor causing this problem? Or do I need to setup the isolation level differently?
I have tried calling this function with the sprokID and it returns true which is why I am thinking during insert there is a dirty read taking place.
ALTER TABLE [dbo].[PendingSprokOrders] WITH CHECK ADD CONSTRAINT [CK_SprokInStock] CHECK (([dbo].[SprokInStockCount]([SprokID])=(1)))
FUNCTION [dbo].[SprokInStockCount] ( #SprokId INT )
RETURNS INT
AS
BEGIN
DECLARE #Active INT
SET #Active = ( SELECT COUNT(*)
FROM [PendingSprokOrders] AS uac WITH(READPAST)
WHERE uac.SprokID = #SprokId
)
DECLARE #Total INT
SET #Total = ( SELECT
ISNULL(InStock, 0)
FROM SprokInvetory
WHERE id = #SprokId
)
DECLARE #Result INT
IF #Total - #Active > 0
SET #Result = 1
ELSE
SET #Result = 0
RETURN #Result;
END;

The math is off. Instead of:
IF #Total - #Active > 0
SET #Result = 1
ELSE
SET #Result = 0
it should be:
IF #Total - #Active > -1
SET #Result = 1
ELSE
SET #Result = 0
That's because your constraint function can see the row that you are attempting to add and is counting it.

Yes it does but your set#total statements are contradictory also there are a couple breaks in your code.

Related

SCOPE_IDENTITY returning null values sometimes

I've got a SQL query where sometimes the IDENTITY value from the INSERT [Form] in the first statement comes back as NULL and causes the rest of the statements in the batch to fail.
I included the second block of the procedure to indicate where it fails due to a conflict with the FK constraint on the table Form
Previously, it had been using SCOPE_IDENTITY, but I had changed it to use SQL Server's temp tables and an OUTPUT clause to see if it would alleviate the issue and it hasn't.
This procedure is being called from an ASP.NET webforms application, and the call is initiated by a Web API call running in that application.
Previously before this was ever made into a Web API call, it was initiated by a click event in the webforms application. Back then I would see this error occur every now and again.
With more and more use of the application and heavier loads, this seems to occur more frequently.
I checked and there are no triggers on the table that are firing off. I can't think of any way to replicate or track down the problem.
In most cases the procedure works fine, but every now and again it doesn't, and I'm unsure why. Sometimes I'll see a log of the error occurring multiple times in a row as the user attempts to save what they are working on. If they try enough times, it seems to work.
I've looked into using other forms of identity retrieval like ##IDENTITY and those won't work for what I need.
Is there something I'm missing?
ALTER PROCEDURE [dbo].[IC_Note_UpdateForm]
#FormID int = -1 OUTPUT, #ConsultFormID int = -1 OUTPUT, #PatientSignature bit, #DSPSignature bit, #Editable bit, #Narrative nvarchar(MAX) = NULL, #SignificantIssues nvarchar(MAX), #UserID int, #DSPID int, #FormTypeID int, #ServiceID int, #ApptID int OUTPUT, #LocationID int, #LoggedInUser int, #PortalId int,#ClientNotes nvarchar(MAX)
WITH EXEC AS CALLER
AS
SET NOCOUNT ON;
--This is needed for whatever reason in order to get the correct date submitted. Using the function call inline causes weird stuff to happen
DECLARE #DateSubmitted DATETIME = dbo.GetLocalTime(default)
DECLARE #count int
--See if a record exists for the Form
SELECT #count = COUNT(FormId) FROM Form WHERE (formID = #FormID OR (apptID = #ApptID AND apptID >= 1))
if #count > 0 BEGIN
UPDATE dbo.Form SET
FormTypeID = #FormTypeID,
patientSignature = #PatientSignature,
dspSignature = #DSPSignature,
editable = #Editable,
dateSubmitted = #DateSubmitted
WHERE
formID = #FormID
IF #Editable = 0 BEGIN
exec IC_NoteAudit_Insert #FormId, #DSPID, 'SUBMITTED'
END ELSE BEGIN
exec IC_NoteAudit_Insert #FormID, #DSPID, 'UPDATED'
END
END ELSE BEGIN
DECLARE #tempForm TABLE (FormId int)
INSERT dbo.Form (
PortalId
,userID
,dspID
,dateSubmitted
,patientSignature
,dspSignature
,editable
,approved
,dateApproved
,rejected
,formTypeID
,paid
,billed
,serviceID
,apptID
) OUTPUT inserted.formId INTO #tempForm
VALUES (
#PortalId
,#UserID -- userID - int
,#DSPID -- dspID - int
,#DateSubmitted -- dateSubmitted - datetime
,#PatientSignature -- patientSignature - bit
,#DSPSignature -- dspSignature - bit
,#Editable -- editable - bit
,null -- approved - bit
,null -- dateApproved - datetime
,null -- rejected - bit
,#FormTypeID -- formTypeID - int
,0 -- paid - bit
,0 -- billed - bit
,#ServiceID -- serviceID - int
,#ApptID -- apptID - int
)
--This was SET #FormId = SCOPE_IDENTITY() before and had the same NULL FK constraint occur
SET #FormID = (SELECT TOP 1 FormId FROM #tempForm)
END
--Move these out of scope of the IDENTITY retrieval
IF #count = 0 BEGIN
exec IC_NoteAudit_Insert #formID, #DSPID, 'CREATED'
IF #Editable = 0 BEGIN
exec IC_NoteAudit_Insert #formID, #DSPID, 'SUBMITTED'
END
END
SELECT #count = COUNT(FormId) FROM ConsultForm WHERE formId = #FormID
IF #count > 0 BEGIN
--See if a row exists for the ConsultForm
UPDATE dbo.ConsultForm SET
narrative = #Narrative,
significantIssues = #SignificantIssues
WHERE
consultFormID = #ConsultFormID
AND formID = #FormID
END ELSE BEGIN
DECLARE #tempConsultForm TABLE (ConsultFormId int)
INSERT dbo.ConsultForm (
PortalId
,formID
,dateOfService
,timeIn
,timeOut
,narrative
,significantIssues
,locationOfService
) OUTPUT inserted.ConsultFormID INTO #tempConsultForm
VALUES (
#PortalId,
#FormID -- formID - int
,null -- dateOfService - datetime
,null -- timeIn - datetime
,null -- timeOut - datetime
,#Narrative -- narrative - nvarchar(MAX)
,#SignificantIssues -- significantIssues - nvarchar(MAX)
,null -- locationOfService - nvarchar(MAX)
)
/*** Failure with FK constraint happens here, #FormId is NULL ***/
SET #ConsultFormID = (SELECT TOP 1 ConsultFormId FROM #tempConsultForm)
END ````
Which Version of SQL, seems like a bug in SQL. Can You use RAISERROR inside the SQL Code to make sure whether it is an issue in SQL itself.

WAITFOR DELAY doesn't act separately within each WHILE loop

I've been teaching myself to use WHILE loops and decided to try making a fun Russian Roulette simulation. That is, a query that will randomly SELECT (or PRINT) up to 6 statements (one for each of the chambers in a revolver), the last of which reads "you die!" and any prior to this reading "you survive."
I did this by first creating a table #Nums which contains the numbers 1-6 in random order. I then have a WHILE loop as follows, with a BREAK if the chamber containing the "bullet" (1) is selected (I know there are simpler ways of selecting a random number, but this is adapted from something else I was playing with before and I had no interest in changing it):
SET NOCOUNT ON
CREATE TABLE #Nums ([Num] INT)
DECLARE #Count INT = 1
DECLARE #Limit INT = 6
DECLARE #Number INT
WHILE #Count <= #Limit
BEGIN
SET #Number = ROUND(RAND(CONVERT(varbinary,NEWID()))*#Limit,0,1)+1
IF NOT EXISTS (SELECT [Num] FROM #Nums WHERE [Num] = #Number)
BEGIN
INSERT INTO #Nums VALUES(#Number)
SET #Count += 1
END
END
DECLARE #Chamber INT
WHILE 1=1
BEGIN
SET #Chamber = (SELECT TOP 1 [Num] FROM #Nums)
IF #Chamber = 1
BEGIN
SELECT 'you die!' [Unlucky...]
BREAK
END
SELECT
'you survive.' [Phew...]
DELETE FROM #Nums WHERE [Num] = #Chamber
END
DROP TABLE #Nums
This works fine, but the results all appear instantaneously, and I want to add a delay between each one to add a bit of tension.
I tried using WAITFOR DELAY as follows:
WHILE 1=1
BEGIN
WAITFOR DELAY '00:00:03'
SET #Chamber = (SELECT TOP 1 [Num] FROM #Nums)
IF #Chamber = 1
BEGIN
SELECT 'you die!' [Unlucky...]
BREAK
END
SELECT
'you survive.' [Phew...]
DELETE FROM #Nums WHERE [Num] = #Chamber
END
I would expect the WAITFOR DELAY to initially cause a 3 second delay, then for the first SELECT statement to be executed and for the text to appear in the results grid, and then, assuming the live chamber was not selected, for there to be another 3 second delay and so on, until the live chamber is selected.
However, before anything appears in my results grid, there is a delay of 3 seconds per number of SELECT statements that are executed, after which all results appear at the same time.
I tried using PRINT instead of SELECT but encounter the same issue.
Clearly there's something I'm missing here - can anyone shed some light on this?
It's called buffering. The server doesn't want to return an only partially full response because most of the time, there's all of the networking overheads to account for. Lots of very small packets is more expensive than a few larger packets1.
If you use RAISERROR (don't worry about the name here where we're using 10) you can specify NOWAIT to say "send this immediately". There's no equivalent with PRINT or returning result sets:
SET NOCOUNT ON
CREATE TABLE #Nums ([Num] INT)
DECLARE #Count INT = 1
DECLARE #Limit INT = 6
DECLARE #Number INT
WHILE #Count <= #Limit
BEGIN
SET #Number = ROUND(RAND(CONVERT(varbinary,NEWID()))*#Limit,0,1)+1
IF NOT EXISTS (SELECT [Num] FROM #Nums WHERE [Num] = #Number)
BEGIN
INSERT INTO #Nums VALUES(#Number)
SET #Count += 1
END
END
DECLARE #Chamber INT
WHILE 1=1
BEGIN
WAITFOR DELAY '00:00:03'
SET #Chamber = (SELECT TOP 1 [Num] FROM #Nums)
IF #Chamber = 1
BEGIN
RAISERROR('you die!, Unlucky',10,1) WITH NOWAIT
BREAK
END
RAISERROR('you survive., Phew...',10,1) WITH NOWAIT
DELETE FROM #Nums WHERE [Num] = #Chamber
END
DROP TABLE #Nums
As Larnu already aluded to in comments, this isn't a good use of T-SQL.
SQL is a set-oriented language. We try not to write procedural code (do this, then do that, then run this block of code multiple times). We try to give the server as much as possible in a single query and let it work out how to process it. Whilst T-SQL does have language support for loops, we try to avoid them if possible.
1I'm using packets very loosely here. Note that it applies the same optimizations no matter what networking (or no-networking-local-memory) option is actually being used to carry the connection between client and server.

Speed up simple update statement in postgres for 1 million rows

I have a very simple sql update statement in postgres.
UPDATE p2sa.observation SET file_path = replace(file_path, 'path/sps', 'newpath/p2s')
The observation table has 1513128 rows. The query so far has been running for around 18 hours with no end in sight.
The file_path column is not indexed so I guess it is doing a top to bottom scan but it seems a bit excessive the time. Probably replace is also a slow operation.
Is there some alternative or better approach for doing this one off kind of update which affects all rows. It is essentially updating an old file path to a new location. It only needs to be updated once or maybe again in the future.
Thanks.
In SQL you could do a while loop to update in batches.
Try this to see how it performs.
Declare #counter int
Declare #RowsEffected int
Declare #RowsCnt int
Declare #CodeId int
Declare #Err int
DECLARE #MaxNumber int = (select COUNT(*) from p2sa.observation)
SELECT #COUNTER = 1
SELECT #RowsEffected = 0
WHILE ( #RowsEffected < #MaxNumber)
BEGIN
SET ROWCOUNT 10000
UPDATE p2sa.observation
SET file_path = replace(file_path, 'path/sps', 'newpath/p2s')
where file_path != 'newpath/p2s'
SELECT #RowsCnt = ##ROWCOUNT ,#Err = ##error
IF #Err <> 0
BEGIN
Print 'Problem Updating the records'
BREAK
END
ELSE
SELECT #RowsEffected = #RowsEffected + #RowsCnt
PRINT 'The total number of rows effected :'+convert(varchar,#RowsEffected)
/*delaying the Loop for 10 secs , so that Update is completed*/
WAITFOR DELAY '00:00:10'
END
SET ROWCOUNT 0

SQL Server Function Efficiency If Exists

I have the following function which returns a bit:
Declare #Ret bit
SET #Ret = 0
IF EXISTS ( Select * from tblExclusion where StatusID = 1 and AccountID = #AccountID )
Begin
SET #Ret = 1
End
Return #Ret
Now there can be multiple entries for the same AccountID in the table or none at all but only one entry will ever have a "1" status if it exists.
I have to be honest I'm not very knowledgeable when it comes to SQL but when called the function seems to take a long time to return. I'm wondering if there is a more efficient way of writing the above.
Thanks in advance.
An index may be necessary, reviewing a sample execution plan will reveal what index would improve.
If you were to modify your query to:
Declare #Ret bit
SET #Ret = 0
IF EXISTS ( Select 1 from tblExclusion where StatusID = 1 and AccountID = #AccountID )
Begin
SET #Ret = 1
End
Return #Ret
An NONCLUSTERED INDEX would be of the format:
USE [DatabaseName]
GO
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[tblExclusion] ([StatusID],[AccountID])
<optional, INCLUDE ([columns within the select,]) >
GO
Types of indexes and how to create them: Create Index
If it takes a long time to run, then I would suspect that there is no index on the column "AccountID". Adding an index on that column will probably significantly improve performance. However, without knowing how tblExclusion is defined, there is no way to be certain of this answer. Also, adding an index to StatusID will help as well, assuming there are a large number of entries for different StatusIDs.
Also, since you only need to test the existence of the record, you don't need to select every column in tblExclusion. You could change "*" to "1" or something, though this will not improve performance significantly.
Try this form
Declare #Ret bitSET #Ret = 0
IF EXISTS ( Select top 1 * from tblExclusion(nolock) where StatusID = 1 and AccountID = #AccountID )
Begin
SET #Ret = 1
End
Return #Ret
Remember the index's and maintenance can make this work slow.
I suggest using select top 1 1 from instead of select * from as in:
Declare #Ret bit
SET #Ret = 0
IF EXISTS (Select top 1 1 from tblExclusion where StatusID = 1 and AccountID = #AccountID)
SET #Ret = 1
Return #Ret
This way you avoid getting unneeded and probably large data.

I'm trying to make a stored procedure to verify a input integer

I'm trying to make a stored procedure to verify a input INT have maxim 15 digits and the first digit is not 0.
And now I have this:
CREATE PROCEDURE usp_verify_phone_numbers (#phone_number INT)
AS
IF #phone_number > 15
BEGIN
PRINT 'Not ok'
END
ELSE PRINT 'OK'
DECLARE #fld_id INT
SET #fld_id = (SELECT LEFT(#phone_number, 1)
DECLARE #result CHAR(25)
SET #result = CASE
WHEN #fld_id > 0
THEN 'OK'
ELSE 'Not OK'
END
PRINT #result
How can I put together to verify this conditions?
Use an OUTPUT parameter to get pass or Fail status for passed values, Also use a BIT value like 1 for Pass and 0 for fail.
Also you are using Int data type for storing numbers and INT data type cannot accommodate a 15 integer long value.
CREATE PROCEDURE usp_verify_phone_numbers
#phone_number BIGINT --<-- Int cannot hold 15 interger values
,#Result BIT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
IF (LEN(#phone_number) <= 15)
BEGIN
SET #Result = 1; --<-- OK
END
ELSE
BEGIN
SET #Result = 0; --<-- Not OK
END
END
Calling Procedure
DECLARE #Result_Rnt BIT
EXECUTE usp_verify_phone_numbers #phone_number = 1234567812
,#Result = #Result_Rnt OUTPUT
SELECT #Result_Rnt
since your input parameter is int database will eliminate a leading 0, so your check of #fld_id will always return OK.
CREATE PROCEDURE usp_verify_phone_numbers (#phone_number nvarchar(100))
AS
DECLARE #longerthanfifteen int = 0, #startswithzero int = 0
IF LEN(#phone_number) > 15
BEGIN
SET #longerthanfifteen=1
END
IF LEFT(#phone_number,1)='0'
BEGIN
SET #startswithzero=1
END
SELECT
CASE WHEN #longerthanfifteen + #startswithzero > 0
then 'Not OK' ELSE 'OK' END as Result
END
But I agree with the comment above that this probably doesn't belong on the SQL server, rather as validation in your input form