I have an application that is not working as intended.
We have found a system error, but the vendor claims that this issue cannot be fixed.
The issue is related to data getting overwritten in the database.
The system collects data from an external datasource on a daily basis.
Data that are collected contains data records for the last 3 days.
A SQL Insert do overwrite existing data already inserted in the SQL database, but can this really be true that a stored procedure cannot prevent data been overwriten?
the table dbo.PointValue contains the following:
PointID DataTime DataValue DataValueType DataValueStatus
32 2015-08-14 23:00:00.000 8,07 NULL NULL
If point ID and DataTime is present, then data should not be inserted.
I believe that this part of the stored procedure that is likely to cause this issue
UPDATE PointValue
SET
DataValue = #data_val,
DataValueType = #data_value_type,
DataValueStatus = #data_value_status
WHERE (PointID = #point_id) AND (DataTime = #data_time)
IF ##ROWCOUNT = 0
but I'm no expert in SQL and stored procedures.
Please, any input on how to prevent data getting overwritten is more than welcome.
Complet store procedure below:
USE [i96X]
GO
/****** Object: StoredProcedure [dbo].[usp_insertLogDataXML] Script Date: 23-11-2015 10:33:34 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
ALTER PROCEDURE [dbo].[usp_insertLogDataXML]
#xml VARCHAR(MAX)
AS
DECLARE #iDoc INT -- A handle to the prepared XML document
-- Prepare the XML document in memory
SET NOCOUNT ON
EXECUTE sp_xml_preparedocument #iDoc OUTPUT, #xml
-- 18/02/2005 : We may find a condition where the XML document contains 2 duplicate times for
-- the same point id, this is likely to happen at the summer time -> standard time change. We
-- cannot violate the primary key constraint, so make sure we check whether a value already
-- exists for this point id and time before inserting.
--
-- 07/07/2006
-- DataValue type changed from float to varchar(10)
-- and converted back to float after replacing comma with decimal point
--
DECLARE log_data_cursor CURSOR FOR
SELECT theXML.PointID, theXML.DataTime, theXML.DataValue, theXML.DataValueType, theXML.DataValueStatus
FROM OpenXML(#iDoc, '/root/P',1) WITH
(
PointID int '#i',
DataTime DATETIME '#t',
DataValue varchar(10) '#v',
DataValueType int '#y',
DataValueStatus int '#s'
) theXML
DECLARE #point_id int
DECLARE #data_time DATETIME
DECLARE #data_val varchar(10)
DECLARE #data_value float
DECLARE #data_value_type int
DECLARE #data_value_status int
OPEN log_data_cursor
FETCH NEXT FROM log_data_cursor
INTO #point_id, #data_time, #data_val, #data_value_type, #data_value_status
WHILE ##FETCH_STATUS = 0
BEGIN
-- replace the , with . in #data_val
SET #data_val = REPLACE(#data_val,N',',N'.')
-- change the #data_val here to float
SET #data_value = CAST(#data_val AS float)
-- if data type and status is equal to -1, then set them to NULL
IF #data_value_type = -1
SET #data_value_type = NULL
IF #data_value_status = -1
SET #data_value_status = NULL
UPDATE PointValue
SET
DataValue = #data_val,
DataValueType = #data_value_type,
DataValueStatus = #data_value_status
WHERE (PointID = #point_id) AND (DataTime = #data_time)
IF ##ROWCOUNT = 0
BEGIN
-- Nothing already there for this point / time so we are
-- safe to do an insert.
INSERT INTO PointValue (PointID, DataTime, DataValue, DataValueType, DataValueStatus)
VALUES (#point_id, #data_time, #data_val, #data_value_type, #data_value_status)
END
FETCH NEXT FROM log_data_cursor
INTO #point_id, #data_time, #data_val, #data_value_type, #data_value_status
END
CLOSE log_data_cursor
DEALLOCATE log_data_cursor
-- Remove the XML document
EXECUTE sp_xml_removedocument #iDoc
The UPDATE block may fails due to the AND (DataTime = #data_time) in the WHERE clause. Because the DataTime contains the timestamp and #data_time may not contain the timestamp and its possible to fails.
The example will help you to understand:
-- Create the temporary table
CREATE TABLE #DateTimeTest(DataTime DATETIME)
-- Inserting few entries for testing
INSERT INTO #DateTimeTest (DataTime)
VALUES ('2015-11-23 04:55:00'), ('2015-11-23 05:00:00'), ('2016-11-24 06:00:00')
-- Declare the datetime variable
DECLARE #TestDataTime AS DATETIME = '2015-11-23';
-- Select the records for the given datetime variable
SELECT * FROM #DateTimeTest WHERE DataTime = #TestDataTime
-- Drop the temp table
DROP TABLE #DateTimeTest
Here the SELECT * FROM #DateTimeTest WHERE DataTime = #TestDataTime doesn't return data, even if I have the records for 2015-11-23.
But if you add the time period the below query will return the data:
SELECT * FROM #DateTimeTest
WHERE DataTime BETWEEN #TestDataTime + ' 00:00:00' AND #TestDataTime + ' 23:59:59'
So in your code if you change the UPDATE block as below it will work:
UPDATE PointValue
SET
DataValue = #data_val,
DataValueType = #data_value_type,
DataValueStatus = #data_value_status
WHERE (PointID = #point_id)
AND (DataTime BETWEEN #data_time + ' 00:00:00' AND #data_time + ' 23:59:59')
Related
create procedure test
(#TABLE_NAME varchar(20))
as
declare #lastval varchar(10)
set #lastval = right('000000000' + convert(varchar(10),
(select IsNull(max(Serialno), 0) + 1
from #TABLE_NAME)), 10)
return #lastval
end
Now tell me how I could compose or form dynamic SQL with above SQL where I will pass table name to store procedure when call that stored procedure?
How to return #lastval value to its calling environment?
How to call stored procedure test from another stored procedure where I will store the return value ?
Guide me with sample code.
Genearlly, it's best to use an IDENTITY or a SEQUENCE to assign serial numbers. A zero-padded string or other formatting requirements could be added to the table as a computed column based on the underlying serial integer or formatted in the app code. However, both IDENTITY and SEQUENCE may have gaps, such as due to rollbacks or SQL Server service restart.
In cases where an unbroken sequence of serial values is required by business, one can maintan the last assigned values in a table and assign values transactionally. Below is an example that returns the value using an OUTPUT parameter. Although the proc in your question uses the stored proc RETURN value for this purpose, that should only be used to indicate success or failure, not return data.
CREATE TABLE dbo.TableSerialNumber(
TableName sysname NOT NULL
CONSTRAINT PK_SerialNumber PRIMARY KEY
, SerialNumber int NOT NULL
, FormatString nvarchar(20) NULL
);
GO
INSERT INTO dbo.TableSerialNumber VALUES('Invoice', 0, '0000000000');
INSERT INTO dbo.TableSerialNumber VALUES('PurchaseOrder', 0, '0000000000');
GO
CREATE PROC dbo.GetNextSerialNumberForTable
#TableName sysname
, #FormattedSerialNumber varchar(10) OUTPUT
AS
SET NOCOUNT ON;
DECLARE
#SerialNumber int
, #FormatString nvarchar(20);
UPDATE dbo.TableSerialNumber
SET
#SerialNumber = SerialNumber += 1
, #FormatString = FormatString
WHERE TableName = #TableName;
IF ##ROWCOUNT = 0
RAISERROR('Table %s does not exist in dbo.TableSerialNumber', 16, 1, #TableName);
SET #FormattedSerialNumber = CAST(FORMAT(#SerialNumber, #FormatString) AS varchar(10));
GO
--example usage
CREATE PROC dbo.InsertInvoice
#InvoiceData int
AS
SET XACT_ABORT ON;
DECLARE #InvoiceNumber varchar(10);
BEGIN TRY
BEGIN TRAN;
EXECUTE dbo.GetNextSerialNumberForTable
#TableName = N'Invoice'
, #FormattedSerialNumber = #InvoiceNumber OUTPUT;
INSERT INTO dbo.Invoice (InvoiceID, InvoiceData)
VALUES(#InvoiceNumber, #InvoiceData);
SELECT #InvoiceNumber AS InvoiceNumber;
COMMIT;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK;
THROW;
END CATCH;
GO
I am just new in SQL language and still studying it. I'm having hard time looking for answer on how can I use the stored procedure and insert value into a table.
I have this stored procedure:
CREATE PROCEDURE TestID
AS
SET NOCOUNT ON;
BEGIN
DECLARE #NewID VARCHAR(30),
#GenID INT,
#BrgyCode VARCHAR(5) = '23548'
SET #GenID = (SELECT TOP (1) NextID
FROM dbo.RandomIDs
WHERE IsUsed = 0
ORDER BY RowNumber)
SET #NewID = #BrgyCode + '-' + CAST(#GenID AS VARCHAR (30))
UPDATE dbo.RandomIDs
SET dbo.RandomIDs.IsUsed = 1
WHERE dbo.RandomIDs.NextID = #GenID
SELECT #NewID
END;
and what I'm trying to do is this:
INSERT INTO dbo.Residents([ResidentID], NewResidentID, [ResLogdate],
...
SELECT
[ResidentID],
EXEC TestID ,
[ResLogdate],
....
FROM
source.dbo.Resident;
There is a table dbo.RandomIDs containing random 6 digit non repeating numbers where I'm pulling out the value via the stored procedure and updating the IsUsed column of the table to 1. I'm transferring data from one database to another database and doing some processing on the data while transferring. Part of the processing is generating a new ID with the required format.
But I can't get it to work Sad I've been searching the net for hours now but I'm not getting the information that I need and that the reason for my writing. I hope someone could help me with this.
Thanks,
Darren
your question is little bit confusing, because you have not explained what you want to do. As i got your question, you want to fetch random id from randomids table and after performed some processing on nextid you want to insert it into resident table [newresidentid] and end of the procedure you fetch data from resident table. if i get anything wrong feel free to ask me.
your procedure solution is following.
CREATE PROCEDURE [TestId]
AS
SET NOCOUNT ON;
BEGIN
DECLARE #NEWID NVARCHAR(30)
DECLARE #GENID BIGINT
DECLARE #BRGYCODE VARCHAR(5) = '23548'
DECLARE #COUNT INTEGER
DECLARE #ERR NVARCHAR(20) = 'NO IDS IN RANDOM ID'
SET #COUNT = (SELECT COUNT(NEXTID) FROM RandomIds WHERE [IsUsed] = 0)
SET #GENID = (SELECT TOP(1) [NEXTID] FROM RandomIds WHERE [IsUsed] = 0 ORDER BY [ID] ASC)
--SELECT #GENID AS ID
IF #COUNT = 0
BEGIN
SELECT #ERR AS ERROR
END
ELSE
BEGIN
SET #NEWID = #BRGYCODE + '-' + CAST(#GENID AS varchar(30))
UPDATE RandomIds SET [IsUsed] = 1 WHERE [NextId] = #GENID
INSERT INTO Residents ([NewResidentId] , [ResLogDate] ) VALUES (#NEWID , GETDATE())
SELECT * FROM Residents
END
END
this procedure will fetch data from your randomids table and perform some processing on nextid than after it directs insert it into resident table and if you want to insert some data through user you can use parameter after declaring procedure name
E.G
CREATE PROCEDURE [TESTID]
#PARAM1 DATATYPE,
#PARAM2 DATATYPE
AS
BEGIN
END
I'm not convinced that your requirement is a good one but here is a way to do it.
Bear in mind that concurrent sessions will not be able to read your update until it is committed so you have to kind of "lock" the update so you will get a block until you're going to commit or rollback. This is rubbish for concurrency, but that's a side effect of this requirement.
declare #cap table ( capturedValue int);
declare #GENID int;
update top (1) RandomIds set IsUsed=1
output inserted.NextID into #cap
where IsUsed=0;
set #GENID =(select max( capturedValue) from #cap )
A better way would be to use an IDENTITY or SEQUENCE to solve your problem. This would leave gaps but help concurrency.
I'm working on a stored procedure that is supposed to update a table Order_TruckDelivery with info from another table Basket_TruckDelivery if the second table has any data. There are two columns in each of the tables: an int id and a datetime column called TruckDeliveryDate. If Basket_TruckDelivery has a date stored for the current basket id, then insert that date into the Order_TruckDelivery table.
Right now, the INSERT will execute regardless if there is anything in the Basket_TruckDelivery table, and this results in a NULL value for the TruckDelveryDate column in the Order_TruckDelivery column. I want to prevent this from happening but am not entirely sure how. Basically, I only want to perform and INSERT into the Order_TruckDelivery table IF the value of TruckDeliveryDate in Basket_TruckDelivery is NOT empty or null.
This is what I have so far...I have not done much work with stored procedures, so I am not sure what I've missed....
ALTER PROCEDURE [dbo].[SaveTruckIntoOrder]
#BasketID INT,
#OrderID INT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE
#TruckDeliveryDate DATETIME
IF(EXISTS(SELECT uidBasket FROM [Basket_TruckDelivery] WHERE [uidBasket] = #BasketID))
BEGIN
SELECT
#TruckDeliveryDate = [TruckDeliveryDate]
FROM
[Basket_TruckDelivery]
WHERE
[uidBasket] = #BasketID
END
BEGIN
INSERT INTO [Order_TruckDelivery] ([uidOrder], [TruckDeliveryDate])
VALUES (#OrderID, #TruckDeliveryDate)
END
END
ALTER PROCEDURE [dbo].[SaveTruckIntoOrder] #BasketID INT
,#OrderID INT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #TruckDeliveryDate DATETIME
IF (
EXISTS (
SELECT uidBasket
FROM [Basket_TruckDelivery]
WHERE [uidBasket] = #BasketID
)
)
BEGIN
SELECT #TruckDeliveryDate = [TruckDeliveryDate]
FROM [Basket_TruckDelivery]
WHERE [uidBasket] = #BasketID
END
IF (
#TruckDeliveryDate IS NOT NULL
AND #TruckDeliveryDate != ''
)
BEGIN
INSERT INTO [Order_TruckDelivery] (
[uidOrder]
,[TruckDeliveryDate]
)
VALUES (
#OrderID
,#TruckDeliveryDate
)
END
END
Be gentle, I'm a SQL newbie. I have a table named autonumber_settings like this:
Prefix | AutoNumber
SO | 112320
CA | 3542
A whenever a new sales line is created, a stored procedure is called that reads the current autonumber value from the 'SO' row, then increments the number, updates that same row, and return the number back from the stored procedure. The stored procedure is below:
ALTER PROCEDURE [dbo].[GetAutoNumber]
(
#type nvarchar(50) ,
#out nvarchar(50) = '' OUTPUT
)
as
set nocount on
declare #currentvalue nvarchar(50)
declare #prefix nvarchar(10)
if exists (select * from autonumber_settings where lower(autonumber_type) = lower(#type))
begin
select #prefix = isnull(autonumber_prefix,''),#currentvalue=autonumber_currentvalue
from autonumber_settings
where lower(autonumber_type) = lower(#type)
set #currentvalue = #currentvalue + 1
update dbo.autonumber_settings set autonumber_currentvalue = #currentvalue where lower(autonumber_type) = lower(#type)
set #out = cast(#prefix as nvarchar(10)) + cast(#currentvalue as nvarchar(50))
select #out as value
end
else
select '' as value
Now, there is another procedure that accesses the same table that duplicates orders, copying both the header and the lines. On occasion, the duplication results in duplicate line numbers. Here is a piece of that procedure:
BEGIN TRAN
IF exists
(
SELECT *
FROM autonumber_settings
WHERE autonumber_type = 'SalesOrderDetail'
)
BEGIN
SELECT
#prefix = ISNULL(autonumber_prefix,'')
,#current_value=CAST (autonumber_currentvalue AS INTEGER)
FROM autonumber_settings
WHERE autonumber_type = 'SalesOrderDetail'
SET #new_auto_number = #current_value + #number_of_lines
UPDATE dbo.autonumber_settings
SET autonumber_currentvalue = #new_auto_number
WHERE autonumber_type = 'SalesOrderDetail'
END
COMMIT TRAN
Any ideas on why the two procedures don't seem to play well together, occasionally giving the same line numbers created from scratch as lines created by duplication.
This is a race condition or your autonumber assignment. Two executions have the potential to read out the same value before a new one is written back to the database.
The best way to fix this is to use an identity column and let SQL server handle the autonumber assignments.
Barring that you could use sp_getapplock to serialize your access to autonumber_settings.
You could use repeatable read on the selects. That will lock the row and block the other procedure's select until you update the value and commit.
Insert WITH (REPEATABLEREAD,ROWLOCK) after the from clause for each select.
What I need is to search for a string in a specific column (datatype: text) of a table and replace it with another text.
For example
Id | Text
-----------------------------
1 this is test
2 that is testosterone
If I chose to replace test with quiz, results should be
this is quiz
that is quizosterone
What I've tried so far?
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROC [dbo].[SearchAndReplace]
(
#FindString NVARCHAR(100)
,#ReplaceString NVARCHAR(100)
)
AS
BEGIN
SET NOCOUNT ON
SELECT CONTENT_ID as id, CONTENT_TEXT, textptr(CONTENT_TEXT) as ptr, datalength(CONTENT_TEXT) as lng
INTO #newtable6 FROM HTML_CONTENTS
DECLARE #COUNTER INT = 0
DECLARE #TextPointer VARBINARY(16)
DECLARE #DeleteLength INT
DECLARE #OffSet INT
SELECT #TextPointer = TEXTPTR(CONTENT_TEXT)
FROM #newtable6
SET #DeleteLength = LEN(#FindString)
SET #OffSet = 0
SET #FindString = '%' + #FindString + '%'
WHILE (SELECT COUNT(*)
FROM #newtable6
WHERE PATINDEX(#FindString, CONTENT_TEXT) <> 0) > 0
BEGIN
SELECT #OffSet = PATINDEX(#FindString, CONTENT_TEXT) - 1
FROM #newtable6
WHERE PATINDEX(#FindString, CONTENT_TEXT) <> 0
UPDATETEXT #newtable6.CONTENT_TEXT
#TextPointer
#OffSet
#DeleteLength
#ReplaceString
SET #COUNTER = #COUNTER + 1
END
select #COUNTER,* from #newtable6
drop table #newtable6
SET NOCOUNT OFF
I get the error:
Msg 7116, Level 16, State 4, Procedure SearchAndReplace, Line 31
Offset 1900 is not in the range of available LOB data.
The statement has been terminated.
Thank you
If you can't change your column types permanently, you can cast them on the fly:
ALTER PROC [dbo].[SearchAndReplace]
(#FindString VARCHAR(100),
#ReplaceString VARCHAR(100) )
AS
BEGIN
UPDATE dbo.HTML_CONTENTS
SET CONTENT_TEXT = cast (REPLACE(cast (CONTEXT_TEXT as varchar(max)), #FindString, #ReplaceString) as TEXT)
END
The datatype TEXT is deprecated and should not be used anymore - exactly because it's clunky and doesn't support all the usual string manipulation methods.
From the MSDN docs on text, ntext, image:
ntext, text, and image data types will
be removed in a future version of
MicrosoftSQL Server. Avoid using these
data types in new development work,
and plan to modify applications that
currently use them. Use nvarchar(max),
varchar(max), and varbinary(max)
instead.
My recommendation: convert that column to VARCHAR(MAX) and you should be fine after that!
ALTER TABLE dbo.HTML_CONTENTS
ALTER COLUMN CONTEXT_TEXT VARCHAR(MAX)
That should do it.
When your column is VARCHAR(MAX), then your stored procedures becomes totally simple:
ALTER PROC [dbo].[SearchAndReplace]
(#FindString VARCHAR(100),
#ReplaceString VARCHAR(100) )
AS
BEGIN
UPDATE dbo.HTML_CONTENTS
SET CONTENT_TEXT = REPLACE(CONTEXT_TEXT, #FindString, #ReplaceString)
END
Two observations on the side:
it would be helpful to have a WHERE clause in your stored proc, in order not to update the whole table (unless that's what you really need to do)
you're using TEXT in your table, yet your stored procedure parameters are of type NVARCHAR - try to stick to one set - either TEXT/VARCHAR(MAX) and regular VARCHAR(100) parameters, or then use all Unicode strings: NTEXT/NVARCHAR(MAX) and NVARCHAR(100). Constantly mixing those non-Unicode and Unicode strings is a mess and causes lots of conversions and unnecessary overhead