Simple While Loop Example - Without a Cursor


The first thing I will do is create a System-Versioned table in SQL Server 2016.  Before creating the table, I will determine if the object already exists and if true I will drop the table. If you have an interest in system-versioned (temporal tables) note the alter table statement before I drop the object.


IF OBJECT_ID('WHILELOOPTEST') is not null
BEGIN
ALTER TABLE  [dbo].[WHILELOOPTEST] SET ( SYSTEM_VERSIONING = OFF  )
DROP TABLE   [dbo].WHILELOOPTEST
END

Next, I create the system versioned table so we can see the number changes caused by the following WHILE loop.

Create TABLE WHILELOOPTEST
(
ProductPK int identity(1,1) primary key,
Product_Cost money,
Start_Date datetime2 generated always as row start hidden NOT NULL ,
End_Date   datetime2 generated always as row end hidden NOT NULL,
PERIOD FOR SYSTEM_TIME(Start_Date, End_Date)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.WHILELOOPTESTHISTORY));

/** Insert Test Values  **/
INSERT INTO WHILELOOPTEST(Product_Cost)
values (2.50), (3.5), (9.0), (10.0)

/** View data before loop **/
SELECT *
FROM WHILELOOPTEST;


For those not familiar with while loops in SQL Server, it is important to know the WHILE loop is used to do an action until a condition is met.  That can be a very obtuse explanation which is why an example is helpful.  The first thing that happens in a WHILE loop is a Boolean (true or false) statement is evaluated.  In the example case the average of the product cost compared to the number 5000.  If the average is less than 5000, the statement is true and the logic which follows is executed. In the example the Product Cost is multiplied by 2 and updated.


WHILE (SELECT avg(Product_Cost) from WHILELOOPTEST) < 5000
BEGIN
update WHILELOOPTEST
set Product_Cost = Product_Cost * 2;
END

One of the most important things to remember when using a WHILE loop in any language is to ensure the condition will eventually evaluate to false. Otherwise you will end up in an unending, infinite loop.

/**Show Table Data **/
SELECT *
FROM WHILELOOPTEST;

It is possible to test within the logic of the WHILE loop for conditions as well and if the condition is met it is possible to break out of the loop.  In the second example the loop is broken if greatest Product Cost is great than 5000.  The WHILE loop actually ends up creating values greater than 5000 because the condition is evaluated before the update logic, so testing if the greatest value is greater than 5000 ensures the values do not go too high.  Of course this example is arbitrary, but in our day to day business processes this option to break could be really useful.

/** WHILE WITH CONDITIONAL BREAK **/
/** DROP TABLE IF EXISTS **/
IF OBJECT_ID('WHILELOOPTEST2') is not null
BEGIN
ALTER TABLE  [dbo].[WHILELOOPTEST2] SET ( SYSTEM_VERSIONING = OFF  )
DROP TABLE   [dbo].WHILELOOPTEST2
END

/**Create Table **/
Create TABLE WHILELOOPTEST2
(
ProductPK int identity(1,1) primary key,
Product_Cost money,
Start_Date datetime2 generated always as row start hidden,
End_Date   datetime2 generated always as row end hidden,
PERIOD FOR SYSTEM_TIME(Start_Date, End_Date)
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.WHILELOOPTESTHISTORY2));

/** Insert values into table **/
INSERT INTO WHILELOOPTEST2(Product_Cost)
values (2.50), (3.5), (9.0), (10.0)

/** observe values**/
SELECT *
FROM WHILELOOPTEST2;

/** WHILE LOOP WITH BREAK CONDITION **/
WHILE (SELECT AVG(Product_Cost) from WHILELOOPTEST2) < 5000
BEGIN
if(select TOP(1) Product_Cost from WHILELOOPTEST2 order by product_cost desc) > 5000
Break
update WHILELOOPTEST2
set Product_Cost = Product_Cost * 2

END;


Finally, because we used system versioned tables, we can actually look at the changes made by the WHILE loop.  Notice there are many updates per record which exceed the number of updates which might be expected.  The point to be taken away is that a WHILE loop can be much more expensive than it appears on the surface especially since SQL is optimized for set based operations.  The WHILE loop is included and has its uses but use the WHILE loop cautiously.

/**Compare data sets with and without break **/
SELECT *
FROM WHILELOOPTEST
FOR SYSTEM_TIME ALL
order by ProductPK, Product_Cost;

SELECT *
FROM WHILELOOPTEST2
FOR SYSTEM_TIME ALL
order by ProductPK, Product_Cost;

Popular posts from this blog

Power BI Report Server: "An Error Has Occurred" or Power BI Report Server: "My Power BI Report is Coming up Blank" or Power BI Report Server: "401 and 403” Errors From Nowhere

SQL SERVER 2017: STRING_AGG

Dynamic Label Positions With SSRS