TSQL query to use While loop iteratively to replace cursor [closed] - sql-server

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 months ago.
Improve this question
I have the following code where I am trying to replace the cursor where i want to iteratively go though each fileid one by one and assign the output of that single fileid each time to the variable #fil, but its printing everything for all the iterations. Explored many forums, but couldn't find the solution. I know the solution in cursor but this is new to me!
DECLARE #i INT = 0;
DECLARE #count INT, #fil varchar(100)
SELECT #count= COUNT(DISTINCT m.FileID)
FROM [EDW].[FileMaster] m
WHILE #i <= #count
BEGIN
#filid = SELECT DISTINCT m.FileID
FROM [EDW].[FileMaster] m
--OFFSET #i ROWS
--FETCH NEXT 1 ROWS ONLY
SET #i = #i + 1;
print #fileid
END
Expected output is abc.txt def.txt ghi.txt etc
Please help me! Thanks in advance.

You can try this, but you're missing the whole point of SQL...
DECLARE #i INT = 0;
DECLARE #count INT = COUNT(DISTINCT FileID) FROM [EDW].[FileMaster]);
DECLARE #fil varchar(100);
WHILE #i <= #count
BEGIN
SET #fil = SELECT DISTINCT FileID FROM [EDW].[FileMaster]
ORDER BY FileID OFFSET #i ROWS FETCH NEXT 1 ROWS ONLY
SET #i = #i + 1;
print #fil
END;
You can create a temp table with distinct FileIDs once and then cycle through it

Related

Can I assign value to variable or parameter after the execution code is defined?

I have a quite large script which is shrunk and simplified in this question.The overall principal is that I have some code that need to be run several times with only small adjustments for every iteration. The script is built with a major loop that has several subloops in it. Today the whole select-statement is hard coded in the loops. My thought was that I could write the select-statement once and only let the parts that needs to be changed for every loop be the only thing that changes in the loop. The purpose is easier maintaining.
Example of the script:
declare
#i1 int,
#i2 int,
#t nvarchar(50),
#v nvarchar(50),
#s nvarchar(max)
set #i1 = 1
while #i1 < 3
begin
if #i1 = 1
begin
set #i2 = 1
set #t = 'Ansokningsomgang'
set #s = '
select ' + #v + '_Ar,count(*) as N
from (
select left(' + #v + ',4) as ' + #v + '_Ar
from Vinnova_' + #t + '
) a
group by ' + #v + '_Ar
order by ' + #v + '_Ar
'
while #i2 < 4
begin
if #i2 = 1
begin
set #v = 'diarienummer'
exec sp_executesql
#stmt = #s,
#params = N'#tab as nvarchar(50), #var as nvarchar(50)',
#tab = #t, #var = #v
end
else if #i2 = 2
begin
set #v = 'utlysning_diarienummer'
exec sp_executesql
#stmt = #s,
#params = N'#tab as nvarchar(50), #var as nvarchar(50)',
#tab = #t, #var = #v
end
else if #i2 = 3
begin
set #v = 'utlysning_program_diarienummer'
exec sp_executesql
#stmt = #s,
#params = N'#tab as nvarchar(50), #var as nvarchar(50)',
#tab = #t, #var = #v
end
set #i2 = #i2 + 1
end
end
else
print('Nr: ' + cast(#i1 as char))
set #i1 = #i1 + 1
end
This script doesn't work. It runs through but have no outputs. If I declare #v above the declaration of #s it works, but then I need to declare #s for every time I need to change the value for #v. Then there is no point in doing this.
#i1 iterates far more times than what is shown here.
The else statement to "if #i1" doesn't exist in the real script. It replaces a bunch of subloops that run for every value that is aloud for #i1 in this example.
I also tried to just execute #s like:
exec(#s)
in every loop. Same result.
So what am I missing?
Database engine is MS SQL Server.
Your parallel-structured tables are not 'normalized' to any degree,
and you are now suffering the consequence. Typically, the best
approach is to go ahead and make the data more normalized before you
take any other action.
Dynamic sql could work for making this task easier, and it is okay
as long as it's an ad-hoc task that hopefully you use to begin
building permanent tables in the name of making your various
parallel tables obsolete. It is not okay if it is part of a
regular process because someone could enter in some malicious
code into one of your table values and do some damage. This is
particularly true in your case because your use of left
functions imply that you're columns are character based.
Here's some code to put your data in more normal form. It can be
made more normal after this, so it would only be the first step.
But it gets you to the point where using it for your purpose is
far easier, and so hopefully will motivate you to redesign.
-- plug in the parallel tables you want to normalize
declare #tablesToNormalize table (id int identity(1,1), tbl sysname);
insert #tablesToNormalize values ('Ansokningsomgang', 'Ansokningsomgang2');
-- create a table that will hold the restructured data
create table ##normalized (
tbl sysname,
rowKey int, -- optional, but needed if restructure is permanent
col sysname,
category varchar(50),
value varchar(50)
);
-- create template code to restructure and insert a table's data
-- into the normalized table (notice the use of #tbl as a string,
-- not as a variable)
declare #templateSql nvarchar(max) = '
insert ##normalized
select tbl = ''Vinnova_#tbl'',
rowKey = t.somePrimaryKey, -- optional, but needed if restructure is permanent
ap.col,
category = left(ap.value, 4),
ap.value
from Vinnova_#tbl t
cross apply (values
(''diarienummer'', diarienummer),
(''utlysning_diarienummer'', utlysning_diarienummer),
(''utlysning_program_diarienummer'', utlysning_program_diarienummer)
// ... and so on (much better than writing a nested loop for ever row)
) ap (col, value)
';
-- loop the table names and run the template (notice the 'replace' function)
declare #id int = 1;
while #id <= (select max(id) from #tablesToNormalize)
begin
declare #tbl sysname = (select tbl from #tablesToNormalize where id = #id);
declare #sql nvarchar(max) = replace(#templateSql, '#t', #tbl);
exec (#tbl);
end
Now that your data is in a more normal form, code for your purpose
is much simpler, and the output far cleaner.
select tbl, col, category, n = count(value)
from ##normalized
group by tbl, col, category
order by tbl, col, category;

Get pattern matched substring from string in sql server [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
Get pattern matched sub string from string in SQL server
for example my string is 'Test{Ab}{CV}Ad testing'
I need out put of above string 'Ab' and 'CV' which is available in '{}'
I need out put without brackets and selected sub string in rows
DECLARE #Template NVARCHAR(MAX) = 'Test{Ab}{CV}Ad testing'
The following will work even when you have more than two {} placeholders:
DECLARE #Template NVARCHAR(MAX) = 'Test{Ab}{CV}Ad testing'
DECLARE #counter INT = 1
DECLARE #inside INT = 0
DECLARE #curr VARCHAR(1) = ''
DECLARE #output VARCHAR(MAX) = ''
WHILE(#counter < LEN(#Template))
BEGIN
SET #curr = SUBSTRING(#Template, #counter, #counter)
SET #inside = CASE
WHEN #curr = '{' THEN 1
WHEN #curr = '}' THEN 0
ELSE #inside
END
SET #output = CASE
WHEN #inside = 1 THEN #output + #curr
WHEN #curr = '}' THEN #output + #curr + '_'
ELSE #output
END
SET #counter = #counter + 1
--SELECT #curr, #inside, #output
END
SELECT * FROM STRING_SPLIT( #output ,'_')

Is there a speed difference between using EXEC sp_executesql and direct SQL [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 years ago.
Improve this question
If it was a big query would it be faster on the second stored procedure ?
CREATE PROCEDURE Customers_GetCustomer
#CustId CHAR(5),
#type int,
#search nvarchar
AS
BEGIN
DECLARE #SQL NVARCHAR(2000)
SET #SQL = 'SELECT ContactName FROM Customers WHERE CustomerId = '+CONVERT(nvarchar(20), #CustId)
if(#type = 1)
BEGIN
#SQL += 'AND NAME='+#search
END
if(#type = 2)
BEGIN
#SQL += 'AND ADDRESS='+#search
END
EXEC sp_executesql #SQL
END
Versus:
CREATE PROCEDURE Customers_GetCustomer
#CustId CHAR(5),
#type int,
#search nvarchar
AS
BEGIN
SELECT ContactName FROM Customers
WHERE CustomerId = #CustId
AND (NAME = #search OR #type <> 1)
AND (ADDRESS = #search OR #type <> 2)
END
What is the better choice between first and second?
With a simple query like this, there will be virtually no difference even at 1000s of executions/sec. (Based on my own testing when I had the same question.)
Assuming it's properly parameterised, a complex query will only have the additional overhead of hashing the longer string to match the execution cache.
But I'd suggest testing it yourself, with https://www.brentozar.com/archive/2015/05/how-to-fake-load-tests-with-sqlquerystress/ for example.

Alteration of stored procedure containing loop is taking too long

I didn't find any appropriate solution for my problem so I want to ask here if someone could help me.
I have a stored procedure named spImportWord which downloads word files from a file location on another server to a local folder and saves values from each word file to a table in the database. For each file I'm calling a console application that saves the files to the local folder. For downloading I'm using a while loop. Before I used a cursor but as far as I know you should stay away from cursors.
Since I changed from cursor to while I can't even alter my stored procedure. It takes an eternity to finish. Is there any way to improve my stored procedure? (Note: With the cursor the SP could be altered but execution displayed that the subquery returned more than 1 value etc.)
My code so far:
ALTER PROCEDURE spImportWord
#fileId INT
AS
DECLARE #cmd VARCHAR(200)
DECLARE #filename VARCHAR(200)
DECLARE #foldername VARCHAR(200)
DECLARE #year VARCHAR(200)
DECLARE #remotePath VARCHAR(MAX)
DECLARE #localPath VARCHAR(MAX)
DECLARE #organisationId INT
DECLARE #folderId INT
DECLARE #import TABLE(ImportId INT)
DECLARE #counter INT
SELECT #filename = Files.FileName
, #foldername = Files.FolderName
FROM Files WHERE Files.FileId = #fileId
CREATE TABLE #organisation ([OrganisationId] INT, [FolderId] INT, [Year] VARCHAR(4) NULL)
INSERT INTO #organisation
SELECT [tabInstitution].[OrganisationId]
, [tabInstitution].[FolderId]
, [tabInstitution].[UploadDate] AS [Year]
FROM [tabInstitution]
SET #organisationId = 0
SET #counter = 0
WHILE (#counter <= (SELECT COUNT(*) FROM #organisation))
BEGIN
SELECT #organisationId = MIN([#organisation].[OrganisationId])
, #folderId = [#organisation].[FolderId]
, #year = [#organisation].[Year]
FROM [#organisation]
WHERE [#organisation].[OrganisationId] > #organisationId
GROUP BY [FolderId], [Year]
SET #remotePath = '\\somepath.path.com\somefolder\' + #organisationId + '\' + #folderId + '\' + #filename + '.docx'
SET #localPath = 'C:\Files\' + #year + '\' + #foldername + '\' + #organisationId + '.docx'
SET #cmd = 'C:\App\ImportWordFiles.exe --SourcePath ' + #remotePath + ' --TargetPath ' + #localPath
EXEC xp_cmdshell #cmd, no_output
-- Log into database
INSERT INTO WordImport
OUTPUT Inserted.ImportId INTO #import
VALUES(GETDATE())
INSERT INTO WordImportItem
VALUES((SELECT ImportId FROM #import), #organisationId, #folderId, #localPath)
SET #counter = #counter + 1
END
Instead of the while loop I had before:
DECLARE MY_CURSOR CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT [#organisation].[OrganisationId]
, [#organisation].[FolderId]
, [#organisation].[Year]
FROM [#organisation]
OPEN MY_CURSOR
FETCH NEXT FROM MY_CURSOR INTO #organisationId, #folderId, #year
WHILE ##FETCH_STATUS = 0
BEGIN
[...]
FETCH NEXT FROM MY_CURSOR INTO #organisationId, #folderId, #year
END
I hope I explained my question understandable.
You know, you should really understand the reasons why cursors are considered bad. In your case, there's no reason not to use cursors, and in fact, your while solution is much uglier anyway.
As for your "subquery problem", (SELECT ImportId FROM #import) is to blame - you're continuously adding rows to #import, and the subquery returns all the ImportIds, not just the last one (or whatever). That will fail once #import has more than a single row. It's hard to tell what exactly you're trying to do - I assume you're just trying to get the last inserted ID, and there's no point in maintaining a whole table of all the import IDs. Just delete all the data from #import after you read it.

Need to repeat the result rows based on a column value

I need a help in generating SQL result based on a column value to calculate per day amount.
I have a SQL like below.
select guid, reccharge, dayCareDaysCharged, dayCareHoursPerDay, RATE from tableA.
Here if reccharge = 0 (weekly) then the result should have 7 rows with RATE as RATE/7 for each row (per day amount);
if reccharge = 1 (monthly) then the result should have 30 rows with RATE as RATE/30 for each row;
if reccharge = 2 (yearly) then the result should have 365 rows with RATE as RATE/365 for each row;
if reccharge = 3 (hourly) then there should be a row as calculate it for a day;
How can I achieve this. Please help.
The question seems a little strange to me, but if I've followed it correctly then maybe this;
CREATE TABLE [#temp1] (reccharge tinyint,rdays smallint)
GO
declare #rcount smallint
set #rcount=0
while #rcount<7
begin
set #rcount=#rcount+1
insert #temp1 values (0,7)
end
set #rcount=0
while #rcount<30
begin
set #rcount=#rcount+1
insert #temp1 values (1,30)
end
set #rcount=0
while #rcount<365
begin
set #rcount=#rcount+1
insert #temp1 values (2,365)
end
insert #temp1 values (3,1)
GO
select t1.[guid], t1.reccharge, t1.dayCareDaysCharged, t1.dayCareHoursPerDay, t1.RATE/t2.rdays as RATE from tableA t1
inner join #temp1 t2 on t1.reccharge=t2.reccharge
order by t1.[guid]
I have not idea to do it in single query, but you can do it by using CURSOR as like below :
declare #reccharge int
declare #count int
declare #i int
declare #strSQL nvarchar(max) = 'select 1, 1 where 1 <> 1'
declare CurTest cursor for select reccharge from test
open CurTest
fetch next from CurTest into #reccharge
while ##fetch_status = 0
begin
set #i = 0;
select #count = case when #reccharge = 0 then 7 when #reccharge = 1 then 30 when #reccharge = 2 then 365 else 1 end
while #i < #count
begin
set #strSQL = #strSQL + ' union all select reccharge, Rate/' + cast(#count as varchar(10)) + ' from test where reccharge = ' + cast(#reccharge as varchar(10))
set #i = #i + 1;
end
fetch next from CurTest into #reccharge
end
close CurTest
deallocate CurTest
exec(#strSQL)

Resources