TSQL - While in While code - sql

Can someone please tell me why this wont work. I want to loop inside a loop .....
BEGIN
SET NOCOUNT ON;
Declare #TempLocations Table (PK int Identity(1,1) not null Primary key, LocationID Int)
Declare #TempItems Table (PK1 int Identity(1,1) not null Primary key, ItemID int)
Declare #TempTable Table (ID Int Identity(1,1), LocationID int, ItemID int)
Declare #MaxLocationID int,
#MaxItemID Int,
#LocationID int,
#ItemID int
-- Load "Can be sold from" Locations into Temp Table
Insert Into #TempLocations (LocationID)
Select LocationID from WMS.Locations
Where CanBeSoldFrom = 'Checked'
Set #MaxItemID = (Select MAX(PK1) From #TempItems)
Set #LocationID = 1
-- Load "IsActive" Items into Temp Table
Insert Into #TempItems (ItemID)
Select ItemID from IMS.ItemDetails
Where IsActive = 'Checked'
Set #MaxLocationID = (Select MAX(PK) From #TempLocations)
Set #ItemID = 1
--Main Code
While #LocationID <= #MaxLocationID
Begin
While #ItemID <= #MaxItemID
Begin
Insert into #TempTable (LocationID, ItemID)
Values (#LocationID, #ItemID)
Set #ItemID = #ItemID + 1
end
Set #LocationID = #LocationID + 1
End
Select * from #TempTable
END
The result I am Tryinig to get is this
#tempTable =
LocationID = 1
ItemID = 1
ItemID = 2
ItemID = 3
ItemID = 4
LocationID = 2
ItemID = 1
ItemID = 2
ItemID = 3
ItemID = 4
and so on ......

This shouldn't be done in a procedural code at all. Use pure SQL and let the DB engine do it's job, it will perform much better, and less code = less bugs. I'm not sure I completely understand what results you want, but I think this does it:
select
LocationID,
ItemID
from
(
Select LocationID from WMS.Locations
Where CanBeSoldFrom = 'Checked'
)
cross join
(
Select ItemID from IMS.ItemDetails
Where IsActive = 'Checked'
)
order by
LocationID,
ItemID

Your query selects the #MaxItemID before anything is filled into #TempItems. Therefor #MaxItemID is null. You have to switch the Statements Set #MaxLocationID = (Select MAX(PK) From #TempLocations) and Set #MaxItemID = (Select MAX(PK1) From #TempItems).
I agree with Jeremy though, it would be better to do that with set-based-programming.

Related

How to get records that between m records back and n records forward from a reference row - Non-sequential data

My scenario is as follows:
I have a reference record, say, ProductId = 1
The records each have a non-unique ItemTypeId
I would like to fetch records that exists between the following points
START POINT being 2 records BACKWARDS of type ItemTypeId = 1, from record of ProductId =1
END POINT being 3 records FORWARDS of type ItemTypeId = 1, from record of ProductId = 1
The query should get ALL data between the two points, inclusively
Here's a picture that illustrates this better than my words:
How would I structure my query to do this?
Any better way to do it without temp tables?
Thank-you!
Note that for this to work at all, you need that record ID to be an actual column in the table. Rows have no inherent order in a table.
With that in place, you can use LAG and LEAD to get what you want:
CREATE TABLE #t
(
RecordId INT IDENTITY(1,1),
ProductId INT,
ItemType INT
);
INSERT INTO #t(ProductId, ItemType)
VALUES
(5,1),(3,1),(7,3),(6,1),(2,7),
(1,1),(7,3),(8,1),(10,3),(9,5),
(11,1),(19,1),(17,4),(13,3);
WITH c1 AS
(
SELECT ProductId,
RecordId,
LAG(RecordId,2) OVER (ORDER BY RecordId) AS Back2,
LEAD(RecordId,3) OVER (ORDER BY RecordId) AS Forward3
FROM #t
WHERE ItemType = (SELECT ItemType FROM #t WHERE ProductId = 1)
),c2 AS
(
SELECT c1.Back2, c1.Forward3 FROM c1
WHERE c1.ProductId = 1
)
SELECT #t.*
FROM #t
INNER JOIN c2 ON #t.RecordId BETWEEN c2.Back2 AND c2.Forward3;
If you wanna do without using temp tables as you ask, the following solution work.
But it is not very nice i agree.
Well this is what i done :
CREATE DATABASE TEST;
USE TEST
CREATE TABLE PRODUCT
(
ProductId INT,
ItemType INT
)
INSERT INTO PRODUCT
VALUES
(5,1),
(3,1),
(7,3),
(6,1),
(2,7),
(1,1),
(7,3),
(8,1),
(10,3),
(9,5),
(11,1),
(19,1),
(17,4),
(13,3)
DECLARE product_cursor CURSOR FOR
SELECT * FROM PRODUCT;
OPEN product_cursor
DECLARE
#ProductId INT,
#ItemId INT,
#END_FETCH INT,
#countFrom INT,
#countTo INT
DECLARE #TableResult TABLE
(
RProductId INT,
RItemId INT
)
FETCH NEXT FROM product_cursor
INTO #ProductId, #ItemId
SET #END_FETCH = 0
SET #countFrom = 0
SET #countTo = 0
WHILE ##FETCH_STATUS = 0 AND #END_FETCH = 0
BEGIN
IF #ItemId = 1 AND (#countFrom = 0 AND #countTo = 0)
BEGIN
SET #countFrom = 3
SET #countTo = 3
END
ELSE
BEGIN
IF #countFrom > 0
BEGIN
--SELECT 'INSERTION : ' ,#ProductId,#ItemId
INSERT INTO #TableResult VALUES(#ProductId, #ItemId)
IF #ItemId = 1
BEGIN
SET #countFrom -= 1
--SELECT 'CountFrom : ', #countFrom
END
END
ELSE
BEGIN
IF #countTo > 0
BEGIN
--SELECT 'INSERTION : ' ,#ProductId,#ItemId
INSERT INTO #TableResult VALUES(#ProductId, #ItemId)
IF #ItemId = 1
BEGIN
SET #countTo -= 1
--SELECT 'CountTO : ', #countTo
END
END
ELSE
BEGIN
SET #END_FETCH = 1
END
END
END
FETCH NEXT FROM product_cursor
INTO #ProductId, #ItemId
END
CLOSE product_cursor
DEALLOCATE product_cursor
SELECT * FROM #TableResult
And this is the result i got :
RProductId RItemId
3 1
7 3
6 1
2 7
1 1
7 3
8 1
10 3
9 5
11 1
19 1
But i prefer the solution of #James Casey.
By the way, why won't you use temp table ?

SQL Server generate script for views and how to decide order?

I am generating the script for views using SQL Server built-in feature (Task -> Generate script). I am creating separate file for each object (of view). I have say around 400 files (containing SQL script of all views) to be executed on another database and to do that automatically I have created BAT file which takes care of that.
There are views which are dependent on other views and due to that many views failed to execute. Is there any way by which we can set order of execution and get rid off the failure ?
Any pointers would be a great help.
Please let me know if you need more details.
Thanks
Jony
Could you try this query? You can execute the create scripts in order to "gen" (generation).
DECLARE #cnt int = 0, #index int;
DECLARE #viewNames table (number int, name varchar(max))
DECLARE #viewGen table (id uniqueidentifier, gen int, name varchar(max), parentId uniqueidentifier)
INSERT INTO #viewNames
SELECT ROW_NUMBER() OVER(ORDER BY object_Id), name FROM sys.views
SELECT #cnt = COUNT(*) FROM #viewNames
SET #index = #cnt;
WHILE ((SELECT COUNT(*) FROM #viewGen) < #cnt)
BEGIN
DECLARE #viewName varchar(200)
SELECT #viewName = name FROM #viewNames WHERE number = #index;
DECLARE #depCnt int = 0;
SELECT #depCnt = COUNT(*) FROM sys.dm_sql_referencing_entities ('dbo.' + #viewName, 'OBJECT')
IF (#depCnt = 0)
BEGIN
INSERT INTO #viewGen SELECT NEWID(), 0, name, null FROM #viewNames WHERE number = #index;
END
ELSE
BEGIN
IF EXISTS(SELECT * FROM sys.dm_sql_referencing_entities ('dbo.' + #viewName, 'OBJECT') AS r INNER JOIN #viewGen AS v ON r.referencing_entity_name = v.name)
BEGIN
DECLARE #parentId uniqueidentifier = NEWID();
INSERT INTO #viewGen SELECT #parentId, 0, name, null FROM #viewNames WHERE number = #index;
UPDATE v
SET v.gen = (v.gen + 1), parentId = #parentId
FROM #viewGen AS v
INNER JOIN sys.dm_sql_referencing_entities('dbo.' + #viewName, 'OBJECT') AS r ON r.referencing_entity_name = v.name
UPDATE #viewGen
SET gen = gen + 1
WHERE Id = parentId OR parentId IN (SELECT Id FROM #viewGen WHERE parentId = parentId)
END
END
SET #index = #index - 1
IF (#index < 0) BEGIN SET #index = #cnt; END
END
SELECT gen as [order], name FROM #viewGen ORDER BY gen
Expecting result:
order name
0 vw_Ancient
1 vw_Child1
1 vw_Child2
2 vw_GrandChild

Replace Cursor Next number assignment Operation with Set based equivalent

Good day all,
I have the following cursor query and would like to replace it with a set based query to address performance issues.
DECLARE #EmpIDM CHAR(21);
DECLARE #EmpName CHAR(21);
DECLARE #EMPCatID INT;
DECLARE Assign_Emp SCROLL CURSOR
FOR
SELECT DISTINCT EMP
, EMPNAME
FROM HR_EMPLOYEES
SET NOCOUNT ON
OPEN Assign_Emp;
FETCH NEXT
FROM Assign_Emp
INTO #EmpIDM
, #EmpName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #EMPCatID = (
SELECT TOP 1 CategoryID
FROM Categories
)
UPDATE Categories
SET CategoryID = (CategoryID + 1) /*Increment Category ID for next Insert*/
INSERT INTO Table1 (
EmpNumber
, EmployeeName
, EmployeeCategoryID
)
VALUES (
#EmpIDM
, #EmpName
, #EMPCatID
)
FETCH NEXT
FROM Assign_Emp
INTO #EmpIDM
, #EmpName
END
CLOSE Assign_Emp;
CLOSE Assign_Emp;
SET NOCOUNT OFF
My challenge is adapting the following code segment into a set based operation
SET #EMPCatID = (
SELECT TOP 1 CategoryID
FROM Categories
)
UPDATE Categories
SET CategoryID = (CategoryID + 1) /*Increment Category ID for next Insert*/
I humbly appreciate any insight on how I can achieve this.
Many Thanks,
Re-write using temp. table with identity column:
declare #offset int
select #offset = isnull(max(CategoryID),0) from Categories
create table #data (
EmpNumber CHAR(21),
EmployeeName CHAR(21),
EmployeeCategoryID int identity
)
INSERT INTO #data (
EmpNumber
, EmployeeName)
SELECT DISTINCT EmpIDM
, EmpName
FROM HR_EMPLOYEES
insert into Table1 (
EmpNumber
, EmployeeName
, EmployeeCategoryID
) select
EmpNumber
, EmployeeName
, EmployeeCategoryID + #offset
from #data
update Categories
set CategoryID = (select max(EmployeeCategoryID) from #data) + #offset

Loop through a recordset and use the result to do another SQL select and return the results

I am completely new to stored procedure. This time, I need to create a stored procedure in MS SQL.
Let's say I have the following table.
Table name: ListOfProducts
--------------------------
SomeID, ProductID
34, 4
35, 8
35, 11
How do I pass in a SomeID. Use this SomeID to select a recordset from table, ListOfProducts. Then loop through this record set.
Let's say I pass in SomeID = 35.
So, the record set will return 2 records with SomeID 35. In the loop, I will get ProductID 8 and 11, which will be used to do another select from another table.
The stored procedure should return the results from the 2nd select.
How can I do this in MS SQL stored procedure?
Sorry, for this newbie question. Thanks for any help.
If you want looping through the records. You can do like:
--Container to Insert Id which are to be iterated
Declare #temp1 Table
(
tempId int
)
--Container to Insert records in the inner select for final output
Declare #FinalTable Table
(
Id int,
ProductId int
)
Insert into #temp1
Select Distinct SomeId From YourTable
-- Keep track of #temp1 record processing
Declare #Id int
While((Select Count(*) From #temp1)>0)
Begin
Set #Id=(Select Top 1 tempId From #temp1)
Insert Into #FinalTable
Select SomeId,ProductId From ListOfProducts Where Id=#Id
Delete #temp1 Where tempId=#Id
End
Select * From #FinalTable
There is probably no point in writing an explicit loop if you don't need to preform some action on the products that can't be done on the whole set. SQL Server can handle stuff like this much better on its own. I don't know what your tables look like, but you should try something that looks more like this.
CREATE PROC dbo.yourProcName
#SomeID int
AS
BEGIN
SELECT
P.ProductId,
P.ProductName
FROM
Product P
JOIN
ListOfProducts LOP
ON LOP.ProductId = P.ProductId
WHERE
LOP.SomeId = #SomeID
END
I had to do something similar in order to extract hours from a select resultset start/end times and then create a new table iterating each hour.
DECLARE #tCalendar TABLE
(
RequestedFor VARCHAR(50),
MeetingType VARCHAR(50),
RoomName VARCHAR(MAX),
StartTime DATETIME,
EndTime DATETIME
)
INSERT INTO #tCalendar(RequestedFor,MeetingType,RoomName,StartTime,EndTime)
SELECT req as requestedfor
,meet as meetingtype
,room as rooms
,start as starttime
,end as endtime
--,u.datetime2 as endtime
FROM mytable
DECLARE #tCalendarHours TABLE
(
RequestedFor VARCHAR(50),
MeetingType VARCHAR(50),
RoomName VARCHAR(50),
Hour INT
)
DECLARE #StartHour INT,#EndHour INT, #StartTime DATETIME, #EndTime DATETIME
WHILE ((SELECT COUNT(*) FROM #tCalendar) > 0)
BEGIN
SET #StartTime = (SELECT TOP 1 StartTime FROM #tCalendar)
SET #EndTime = (SELECT TOP 1 EndTime FROM #tCalendar)
SET #StartHour = (SELECT TOP 1 DATEPART(HOUR,DATEADD(HOUR,0,StartTime)) FROM #tCalendar)
SET #EndHour = (SELECT TOP 1 DATEPART(HOUR,DATEADD(HOUR,0,EndTime)) FROM #tCalendar)
WHILE #StartHour <= #EndHour
BEGIN
INSERT INTO #tCalendarHours
SELECT RequestedFor,MeetingType,RoomName,#StartHour FROM #tCalendar WHERE StartTime = #StartTime AND EndTime = #EndTime
SET #StartHour = #StartHour + 1
END
DELETE #tCalendar WHERE StartTime = #StartTime AND EndTime = #EndTime
END
Do something like this:
Declare #ID int
SET #ID = 35
SELECT
p.SomeID
,p.ProductID
FROM ListOfProducts p
WHERE p.SomeID = #ID
-----------------------
--Or if you have to join to get it
Declare #ID int
SET #ID = 35
SELECT
c.SomeID
,p.ProductID
,p.ProductName
FROM ListOfProducts p
INNER JOIN categories c on p.ProductID = c.SomeID
WHERE p.SomeID = #ID
You can use option with WHILE loop and BREAK/CONTINUE keywords
CREATE PROC dbo.yourProcName
#SomeID int
AS
BEGIN
IF OBJECT_ID('tempdb.dbo.#resultTable') IS NOT NULL DROP TABLE dbo.#resultTable
CREATE TABLE dbo.#resultTable
(Col1 int, Col2 int)
DECLARE #ProductID int = 0
WHILE(1=1)
BEGIN
SELECT #ProductID = MIN(ProductID)
FROM ListOfProducts
WHERE SomeID = #SomeID AND ProductID > #ProductID
IF #ProductID IS NULL
BREAK
ELSE
INSERT dbo.#resultTable
SELECT Col1, Col2
FROM dbo.yourSearchTable
WHERE ProductID = #ProductID
CONTINUE
END
SELECT *
FROM dbo.#resultTable
END
Demo on SQLFiddle

SQL: create new table with distinct value and max/min values for other columns w/in an existing table

Example:
Suppose the original table contains the following values
AcctNbr StatusDate
------------------
123 01/01/2012
123 01/01/2013
123 12/11/2011
987 01/01/2009
The SQL would create a new table containing
AcctNbr EarliestStatusDate LatestStatusDate
-------------------------------------------
123 12/11/2011 01/01/2013
987 01/01/2009 01/01/2009
I'm looking for an efficient way to do this. I have a method that works, but it takes an unacceptably long time. Does anyone have any optimization tips. Any help would be greatly appreciated.
SET NOCOUNT ON
DROP TABLE loopTemp
DROP TABLE reportTemp
CREATE TABLE loopTemp
( ID int IDENTITY(1, 1) NOT NULL,
AcctNbr varchar(50),
)
CREATE TABLE reportTemp
(
AcctNbr varchar(50),
EarliestStatus Date,
LatestStatus Date
)
INSERT INTO loopTemp
SELECT DISTINCT AcctNbr
FROM AutoStatusHistory
DECLARE #COUNTER AS INT
SET #COUNTER = 1
DECLARE #MAX AS INT
SET #MAX = (SELECT MAX(ID) FROM loopTemp)
WHILE #COUNTER < #MAX BEGIN
DECLARE #ACCOUNT_NUMBER AS varchar(50)
SET #ACCOUNT_NUMBER =
(SELECT AcctNbr FROM loopTemp WHERE ID = #COUNTER)
DECLARE #EARLIESTSTATUSDATE AS DATE
SET #EARLIESTSTATUSDATE = (SELECT MIN(NewStatusDate)
FROM AutoStatusHistory
WHERE AcctNbr = #ACCOUNT_NUMBER)
DECLARE #LATESTSTATUSDATE AS DATE
SET #LATESTSTATUSDATE = (SELECT MAX(NewStatusDate)
FROM AutoStatusHistory
WHERE AcctNbr = #ACCOUNT_NUMBER)
INSERT INTO reportTemp
VALUES (#ACCOUNT_NUMBER, #EARLIESTSTATUSDATE, #LATESTSTATUSDATE)
IF (#COUNTER % 1000) = 0 BEGIN
PRINT #COUNTER
END -- IF
SET #COUNTER = #COUNTER + 1
END -- WHILE
DROP TABLE loopTemp
Unless I'm missing something, this should be extremely simple:
SELECT AcctNbr, MIN(StatusDate) AS EarliestStatusDate,
MAX(StatusDate) AS LatestStatusDate
FROM myTable
GROUP BY AcctNbr
You appear to be using SQL Server. For this, you can do:
select AcctNbr, min(StatusDate) as EarliestStatusDate,
max(StatusDate) as LatestStatusDate
into ReportTemp
from AutoStatusHistory
group by AcctNbr
The into statement saves the results into a table. You might want to use an actual temporary table, with the syntax:
into #ReportTemp
You can use
SELECT *
INTO TableName
FROM (
SELECT AcctNbr, MIN([Status Date]) AS EarliestStatusDate,
MAX([Status Date]) AS LatestStatusDate
FROM myTable
GROUP BY AcctNbr
) RequiredData