TSQL infinite loop in cursor when joining temp table only - sql-server

I have a SP that updates prices in a table using a CURSOR and FETCH NEXT.
The cursor's query is a simple "select', and instead of a WHERE - I'm joining a function who brings all the related User IDs.
Now, due to the fact the my SP updates a lot of different tables (attached is only part of it), and in every update I use the JOIN of the users function - I wanted to save some time, and bring the Users list only once, at the beginning of the SP.
If I join my temp table (#t, which holds the User IDs) I get an infinite loop.
At the end the prices will be infinite.
BUT - If I join the function itself, instead of the temp table that should have exactly the same values in it, everything is fine.
I commented the problematic JOIN.
-- This is my main object, who "holds" a lot of users
DECLARE #DistID INT = 123
DECLARE #t TABLE
(MainDistID int,
DistID int,
UserID int)
INSERT INTO #t
SELECT MainDistID,
DistID,
UserID
FROM [MyScheme].[FnGetAllDistAndUsers] (#DistID)
DECLARE #Bid INT -- Will contain the ID we need to update
DECLARE c CURSOR LOCAL FOR
SELECT ID
FROM BillingDetails AS bd
-- BOTH JOINS SHOULD BE THE SAME:
--JOIN #t AS GUsers -- Infinite loop...
-- ON bd.UserID = GUsers.UserID
JOIN [MyScheme].[FnGetAllDistAndUsers] (#DistID) AS GUsers -- NO infinite loop
ON bd.UserID = GUsers.UserID
OPEN c
FETCH NEXT FROM c
INTO #Bid
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE MyTable
SET Price = Price * 2
WHERE MyTableID = #Bid
FETCH NEXT FROM c
INTO #Bid
END
Thanks!

I think Mitch is right you shouldn't have to use a cursor. When doing updates it is fastest if you join on the primary key, so you could do something like this.
DECLARE #t TABLE (Bid int)
INSERT INTO #t
SELECT bd.ID
FROM [MyScheme].[FnGetAllDistAndUsers] u (#DistID)
INNER JOIN BillingDetails bd
ON u.UserID = bd.UserID
UPDATE mt
SET Price = Price * 2
FROM MyTable mt
INNER JOIN #t t
ON mt.MyTableID = t.Bid

Related

Retrieve values for multiple ids given in the order

I am having a stored procedure which uses CSV to accept multiple IDs. I want to retrieve the values from the table in the order of the Ids given. The stored procedure I am currently using is
CREATE procedure [dbo].[Proc1]
#Userid VARCHAR(MAX)=NULL
as
begin
set nocount on;
DECLARE #Useridord TABLE (Userid VARCHAR(MAX),Position int identity(1,1));
INSERT INTO #Useridord
SELECT item
FROM [dbo].[Split] (#Userid, ',')
select * from User where User.UserId IN (select TOP 100 Percent Userid from #Useridord ORDER BY Position)
end
GO
What I am trying to do is from csv I am inserting the values into temporary table adding the order value as position. As I use Orderby Position, it is only implemented in the inner select. But the Output is given as the order in the table. I know to use an orderby in the outer select statement but I don't know the correct syntax to execute it. Can anyone help me?
How about using INNER JOIN?
SELECT
u.*
FROM User u
INNER JOIN #Useridord uo
ON uo.Userid = u.UserId
ORDER BY uo.Position
I believe that you should guaranty position inside function like:
create function fnSplit(#users varchar(100), #del char(10))
returns #t table(userid int, pos int) as
begin
insert into #t values(3,1),(2,2),(1,3)
return
end
Then you can join directly on function result like:
Select u.* from users u
join fnSplit('','') s on u.id = s.userid
order by s.pos
Here is fiddle http://sqlfiddle.com/#!6/a8acc/4

Is it possible to use a stored procedure to update three different tables?

I have an proc doing a select and update statements as follows. I need to incorporate the update statements and select statement which put the data into a temp table into a single sql select statement.
Is this possible?
'
Yes:
CREATE TABLE TableA
(
valueA int
)
INSERT INTO
TableA
VALUES
(1),
(2),
(3)
GO
CREATE PROCEDURE test_procedure (#in_value int)
AS
BEGIN
--insert into temp table
SELECT
#in_value [out_value]
INTO
#TestTable
-- update with join on temp table
UPDATE
T
SET
T.[out_value] = 2
FROM
#TestTable T
INNER JOIN TableA A on A.valueA = T.out_value
WHERE
A.valueA = 1
-- update with join on temp table again
UPDATE
T
SET
[out_value] = 3
FROM
#TestTable T
INNER JOIN TableA A on A.valueA = T.out_value
WHERE
A.valueA = 2
--select results including the original "in_value"
SELECT
*,
#in_value [in_value]
FROM
TableA A
LEFT JOIN #TestTable T on T.out_value = A.valueA
END;
GO
--execute stored procedure
EXEC test_procedure 1
I have found one way out by using union.
this is bit wierd but since i am not able to find anything else, settled with this method

Inserting random number of rows in SQL Server via join to integer list is inconsistent

I am creating a database with sample data. Each time I run the stored procedure to generate some new data for my sample database, I would like to clear out and repopulate table B ("Item") based on all the rows in table A ("Product").
If table A contained the rows with primary key values 1, 2, 3, 4, and 5, I would want table B to have a foreign key for table A and insert a random number of rows into table B for each table A row. (We are essentially stocking the shelves with a random number of "item" for any given "product.")
I am using code from this answer to generate a list of numbers. I join to the results of this function to create the rows to insert:
WITH cte AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY (select 0)) AS i
FROM
sys.columns c1 CROSS JOIN sys.columns c2 CROSS JOIN sys.columns c3
)
SELECT i
FROM cte
WHERE
i BETWEEN #p_Min AND #p_Max AND
i % #p_Increment = 0
Random numbers are generated in a view (to get around the limitations of functions) as follows:
-- Mock.NewGuid view
SELECT id = ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)))
And a function that returns the random numbers:
-- Mock.GetRandomInt(min, max) function definition
DECLARE #random int;
SELECT #random = Id % (#MaxValue - #MinValue + 1) FROM Mock.NewGuid;
RETURN #random + #MinValue;
However, when you look at this code and execute it...
WITH Products AS
(
SELECT ProductId, ItemCount = Mock.GetRandomInt(1,5)
FROM Product.Product
)
SELECT A = Products.ProductId, B = i
FROM Products
JOIN (SELECT i FROM Mock.GetIntList(1,5,1)) Temp ON
i < Products.ItemCount
ORDER BY ProductId, i
... this returns some inconsistent results!
A,B
1,1
1,2
1,3
2,1
2,2
3,2 <-- where is 1?
3,3
4,1
5,3 <-- where is 1, 2?
6,1
I would expect that, for every product id, the JOIN results in 1-5 rows. However, it seems like values get skipped! This is even more apparent with larger data sets. I was originally trying to generate 20-50 rows in Item for each Product row, but this resulted in only 30-40 rows for each product.
The question: Any idea why this is happening? Each product should have a random number of rows (between 1 and 5) inserted for it and the B value should be sequential! Instead, some numbers are missing!
This issue also happens if I store numbers in a table I created and then join to that, or if I use a recursive CTE.
I am using SQL Server 2008R2, but I believe I see the same issue on my 2012 database as well. Compatibility levels are 2008 and 2012 respectively.
This is a fun problem. I've dealt with this in a round about way a number of times. I am sure there is a way to not use a cursor. But why not. This is a cheap problem memory wise so long as the #RandomMaxRecords doesn't get huge or you have a significant amount of product records. If the data in the Items table is meaningless then I would suggest truncating any in memory table where I define the hash table for #Item. And obviously you will pull from your Product table not the hash I have created for testing.
This is a fantastic article and describes in detail how I arrive at my solution. Less Than Dot Blog
CODE
--This is your product table with 5 random products
IF OBJECT_ID('tempdb..#Product') IS NOT NULL DROP TABLE #Product
CREATE TABLE #Product
(
ProductID INT PRIMARY KEY IDENTITY(1,1),
ProductName VARCHAR(25),
ProductDescription VARCHAR(max)
)
INSERT INTO #Product (ProductName,ProductDescription) VALUES ('Product Name 1','Product Description 1'),
('Product Name 2','Product Description 2'),
('Product Name 3','Product Description 3'),
('Product Name 4','Product Description 4'),
('Product Name 5','Product Description 5')
--This is your item table. This would probably just be a truncate statement so that your table is reset for the new values to go in
IF OBJECT_ID ('tempdb..#Item') IS NOT NULL DROP TABLE #Item
CREATE TABLE #Item
(
ItemID INT PRIMARY KEY IDENTITY(1,1),
FK_ProductID INT NOT NULL,
ItemName VARCHAR(25),
ItemDescription VARCHAR(max)
)
--Declare a bunch of variables for the cursor and insert into the item table process
DECLARE #ProductID INT
DECLARE #ProductName VARCHAR(25)
DECLARE #ProductDescription VARCHAR(max)
DECLARE #RandomItemCount INT
DECLARE #RowEnumerator INT
DECLARE #RandomMaxRecords INT = 10
--We declare a cursor to iterate over the records in product and generate random amounts of items
DECLARE ItemCursor CURSOR
FOR SELECT * FROM #Product
OPEN ItemCursor
FETCH NEXT FROM ItemCursor INTO #ProductID, #ProductName, #ProductDescription
WHILE (##FETCH_STATUS <> -1)
BEGIN
--Get the Random Number into the variable. And we only want 1 or more records. Mod division will produce a 0.
SELECT #RandomItemCount = ABS(CHECKSUM(NewID())) % #RandomMaxRecords
SELECT #RandomItemCount = CASE #RandomItemCount WHEN 0 THEN 1 ELSE #RandomItemCount END
--Iterate on the RowEnumerator to the RandomItemCount and insert item rows
SET #RowEnumerator = 1
WHILE (#RowEnumerator <= #RandomItemCount)
BEGIN
INSERT INTO #Item (FK_ProductID,ItemName,ItemDescription)
SELECT #ProductID, REPLACE(#ProductName,'Product','Item'),REPLACE(#ProductDescription,'Product','Item')
SELECT #RowEnumerator = #RowEnumerator + 1
END
FETCH NEXT FROM ItemCursor INTO #ProductID, #ProductName, #ProductDescription
END
CLOSE ItemCursor
DEALLOCATE ItemCursor
GO
--Look at the result
SELECT
*
FROM
#Product AS P
RIGHT JOIN #Item AS I ON (P.ProductID = I.FK_ProductID)
--Cleanup
DROP TABLE #Product
DROP TABLE #Item
It looks like a LEFT OUTER JOIN to GetIntList (as opposed to INNER JOIN) fixes the problem I am having.

Merge statement on a single record table

I need to write a single statement to insert or update a record in a single record table
the merge statement allows me to write this:
create table t1 (n int)
-- insert into t1 (n) Values (1); -- uncomment to test the matched branch
MERGE t1 AS P
USING (SELECT 3 AS n) AS S
ON 1 = 1
WHEN MATCHED THEN
UPDATE SET n = S.n
WHEN NOT MATCHED THEN
INSERT (n)
VALUES (S.n);
select * from t1
this work, but I think that the 1=1 condition purpose is not very easy to understand.
Is there a different syntax to insert a record when the table is empty or update the record when it does already exist?
The other option would be to do it the old fashioned way.
if exists (select null from t1)
update t1 set n = 3
else
insert into t1 (n) values (3)
Replace
ON 1 = 1
with
ON S.n = P.n
Example of recent procedure I wrote to either update an existing row or insert a new row.
Table has the same structure as MembershipEmailFormat the table variable.
Found it easiest to create a table variable to be the source in the Using clause. I realize that the main purpose of Merge statements really are merging muliple rows between two tables. My use case is that I need to insert a new email address for a user or modify and existing email address.
CREATE PROCEDURE [dbo].[usp_user_merge_emailformat]
#UserID UNIQUEIDENTIFIER,
#Email varchar(256),
#UseHTML bit
AS
BEGIN
--SELECT #UserID='04EFF187-AEAC-408E-9FA8-284B31890FBD',
-- #Email='person#xxxx.com',
-- #UseHTML=0
DECLARE #temp TABLE
(
UserID UNIQUEIDENTIFIER,
Email varchar(256),
HtmlFormat bit
)
INSERT INTO #temp(UserID,Email, HtmlFormat)
Values(#UserID,#Email,#UseHTML)
SELECT * FROM #temp
MERGE dbo.MembershipEmailFormat as t
USING #temp AS s
ON (t.UserID = s.UserID and t.Email = s.Email)
WHEN MATCHED THEN UPDATE SET t.HtmlFormat = s.HtmlFormat
WHEN NOT MATCHED THEN INSERT VALUES(s.UserID,s.Email,s.HtmlFormat);
END

Table variable in SQL Server

I am using SQL Server 2005. I have heard that we can use a table variable to use instead of LEFT OUTER JOIN.
What I understand is that, we have to put all the values from the left table to the table variable, first. Then we have to UPDATE the table variable with the right table values. Then select from the table variable.
Has anyone come across this kind of approach? Could you please suggest a real time example (with query)?
I have not written any query for this. My question is - if someone has used a similar approach, I would like to know the scenario and how it is handled. I understand that in some cases it may be slower than the LEFT OUTER JOIN.
Please assume that we are dealing with tables that have less than 5000 records.
Thanks
It can be done, but I have no idea why you would ever want to do it.
This realy does seem like it is being done backwards. But if you are trying this for your own learning only, here goes:
DECLARE #MainTable TABLE(
ID INT,
Val FLOAT
)
INSERT INTO #MainTable SELECT 1, 1
INSERT INTO #MainTable SELECT 2, 2
INSERT INTO #MainTable SELECT 3, 3
INSERT INTO #MainTable SELECT 4, 4
DECLARE #LeftTable TABLE(
ID INT,
MainID INT,
Val FLOAT
)
INSERT INTO #LeftTable SELECT 1, 1, 11
INSERT INTO #LeftTable SELECT 3, 3, 33
SELECT *,
mt.Val + ISNULL(lt.Val, 0)
FROM #MainTable mt LEFT JOIN
#LeftTable lt ON mt.ID = lt.MainID
DECLARE #Table TABLE(
ID INT,
Val FLOAT
)
INSERT INTO #Table
SELECT ID,
Val
FROM #MainTable
UPDATE #Table
SET Val = t.Val + lt.Val
FROM #Table t INNER JOIN
#LeftTable lt ON t.ID = lt.ID
SELECT *
FROM #Table
I don't think it's very clear from your question what you want to achieve? (What your tables look like, and what result you want). But you can certainly select data into a variable of a table datatype, and tamper with it. It's quite convenient:
DECLARE #tbl TABLE (id INT IDENTITY(1,1), userId int, foreignId int)
INSERT INTO #tbl (userId)
SELECT id FROM users
WHERE name LIKE 'a%'
UPDATE #tbl t
SET
foreignId = (SELECT id FROM foreignTable f WHERE f.userId = t.userId)
In that example I gave the table variable an identity column of its own, distinct from the one in the source table. I often find that useful. Adjust as you like... Again, it's not very clear what the question is, but I hope this might guide you in the right direction...?
Every scenario is different, and without full details on a specific case it's difficult to say whether it would be a good approach for you.
Having said that, I would not be looking to use the table variable approach unless I had a specific functional reason to - if the query can be fulfilled with a standard SELECT query using an OUTER JOIN, then I'd use that as I'd expect that to be most efficient.
The times where you may want to use a temp table/table variable instead, are more when you want to get an intermediary resultset and then do some processing on it before then returning it out - i.e. the kind of processing that cannot be done with a straight forward query.
Note the table variables are very handy, but take into account that they are not guaranteed to reside in-memory - they can get persisted to tempdb like standard temp tables.
Thank you, astander.
I tried with an example given below. Both of the approaches took 19 seconds. However, I guess some tuning will help the Table varaible update approach to become faster than LEFT JOIN.
AS I am not a master in tuning I request your help. Any SQL expert ready to prove it?
---- PLease replace "" with '' below. I am not familiar with how to put code in this forum... It causes some troubles....
CREATE TABLE #MainTable (
CustomerID INT PRIMARY KEY,
FirstName VARCHAR(100)
)
DECLARE #Count INT
SET #Count = 0
DECLARE #Iterator INT
SET #Iterator = 0
WHILE #Count <8000
BEGIN
INSERT INTO #MainTable SELECT #Count, "Cust"+CONVERT(VARCHAR(10),#Count)
SET #Count = #Count+1
END
CREATE TABLE #RightTable
(
OrderID INT PRIMARY KEY,
CustomerID INT,
Product VARCHAR(100)
)
CREATE INDEX [IDX_CustomerID] ON #RightTable (CustomerID)
WHILE #Iterator <400000
BEGIN
IF #Iterator % 2 = 0
BEGIN
INSERT INTO #RightTable SELECT #Iterator,2, "Prod"+CONVERT(VARCHAR(10),#Iterator)
END
ELSE
BEGIN
INSERT INTO #RightTable SELECT #Iterator,1, "Prod"+CONVERT(VARCHAR(10),#Iterator)
END
SET #Iterator = #Iterator+1
END
-- Using LEFT JOIN
SELECT mt.CustomerID,mt.FirstName,COUNT(rt.Product) [CountResult]
FROM #MainTable mt
LEFT JOIN #RightTable rt ON mt.CustomerID = rt.CustomerID
GROUP BY mt.CustomerID,mt.FirstName
---------------------------
-- Using Table variable Update
DECLARE #WorkingTableVariable TABLE
(
CustomerID INT,
FirstName VARCHAR(100),
ProductCount INT
)
INSERT
INTO #WorkingTableVariable (CustomerID,FirstName)
SELECT CustomerID, FirstName FROM #MainTable
UPDATE #WorkingTableVariable
SET ProductCount = [Count]
FROM #WorkingTableVariable wt
INNER JOIN
(SELECT CustomerID,COUNT(rt.Product) AS [Count]
FROM #RightTable rt
GROUP BY CustomerID) IV ON wt.CustomerID = IV.CustomerID
SELECT CustomerID,FirstName, ISNULL(ProductCount,0) [CountResult] FROM #WorkingTableVariable
ORDER BY CustomerID
--------
DROP TABLE #MainTable
DROP TABLE #RightTable
Thanks
Lijo
In my opinion there is one reason to do this:
If you have a complicated query with lots of inner joins and one left join you sometimes get in trouble because this query is hundreds of times less fast than using the same query without the left join.
If you query lots of records with a result of very few records to be joined to the left join you could get faster results if you materialize the intermediate result into a table variable or temp table.
But usually there is no need to really update the data in the table variable - you could query the table variable using the left join to return the result.
... just my two cents.

Resources