This may well be a duplicate but I can't seem to find anything close enough to my use case.
Due to various reasons like views, instead of triggers and so on, I'm unable to use the OUTPUT clause with an INSERT statement to retrieve the system generated GUID with NEWSEQUENTIALID().
An idea which I have, is to rather generate the GUID using a stored procedure:
SET NOCOUNT ON;
GO
IF ( OBJECT_ID( N'[dbo].[GETNEXTGUID]' ) IS NOT Null )
DROP PROCEDURE [dbo].[GETNEXTGUID];
GO
CREATE PROCEDURE [dbo].[GETNEXTGUID]
(#NextId UNIQUEIDENTIFIER OUTPUT)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Sequence TABLE([KEY] UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID(), [DUMMY] BIT);
INSERT INTO #Sequence ([DUMMY]) VALUES (0);
SELECT #NextId = [KEY] FROM #Sequence;
END
GO
DECLARE #NextId UNIQUEIDENTIFIER;
EXECUTE [dbo].[GETNEXTGUID] #NextId OUTPUT;
PRINT #NextId;
The generated GUID can then be inserted into the table and should also alleviate the issue that plagues IDENTITY columns with ##IDENTITY, IDENT_CURRENT, etc.
I don't have sufficient knowledge even after testing to authoritatively say whether this is truly a working solution or simply a long winded way of applying a "somewhat" cleaner NEWID() due to the fact that it's using a temp table which is being destroyed after every call.
I tried using TWO temp tables and their sequential numbers seem to tie up and running the procedure consecutively also seems to tie up.
SET NOCOUNT ON;
GO
IF (OBJECT_ID( N'[dbo].[GETNEXTGUID]') IS NOT Null)
DROP PROCEDURE [dbo].[GETNEXTGUID];
GO
CREATE PROCEDURE [dbo].[GETNEXTGUID]
(#NextId1 UNIQUEIDENTIFIER OUTPUT,
#NextId2 UNIQUEIDENTIFIER OUTPUT)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Sequence1 TABLE ([KEY] UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID(), [DUMMY] BIT);
DECLARE #Sequence2 TABLE ([KEY] UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID(), [DUMMY] BIT);
INSERT INTO #Sequence1 ([DUMMY]) VALUES (0);
INSERT INTO #Sequence2 ([DUMMY]) VALUES (0);
SELECT #NextId1 = [KEY] FROM #Sequence1;
SELECT #NextId2 = [KEY] FROM #Sequence2;
END
GO
DECLARE #NextId1 UNIQUEIDENTIFIER;
DECLARE #NextId2 UNIQUEIDENTIFIER;
EXECUTE [dbo].[GETNEXTGUID] #NextId1 OUTPUT, #NextId2 OUTPUT;
PRINT #NextId1; PRINT #NextId2;
EXECUTE [dbo].[GETNEXTGUID] #NextId1 OUTPUT, #NextId2 OUTPUT;
PRINT #NextId1; PRINT #NextId2;
EXECUTE [dbo].[GETNEXTGUID] #NextId1 OUTPUT, #NextId2 OUTPUT;
PRINT #NextId1; PRINT #NextId2;
EXECUTE [dbo].[GETNEXTGUID] #NextId1 OUTPUT, #NextId2 OUTPUT;
PRINT #NextId1; PRINT #NextId2;
EXECUTE [dbo].[GETNEXTGUID] #NextId1 OUTPUT, #NextId2 OUTPUT;
PRINT #NextId1; PRINT #NextId2;
Result:
AAAFD869-E814-EA11-A2FA-00155D0A020C
ABAFD869-E814-EA11-A2FA-00155D0A020C
ACAFD869-E814-EA11-A2FA-00155D0A020C
ADAFD869-E814-EA11-A2FA-00155D0A020C
AEAFD869-E814-EA11-A2FA-00155D0A020C
AFAFD869-E814-EA11-A2FA-00155D0A020C
B0AFD869-E814-EA11-A2FA-00155D0A020C
B1AFD869-E814-EA11-A2FA-00155D0A020C
B2AFD869-E814-EA11-A2FA-00155D0A020C
B3AFD869-E814-EA11-A2FA-00155D0A020C
I'd appreciate input from someone with more experience offering their insights.
I do not know, what you really need, but you might try something along this:
CREATE SEQUENCE dbo.testSeq_13Byte AS DECIMAL(28); /*read about START WITH to enforce a range*/
GO
SELECT NEXT VALUE FOR dbo.testSeq_13Byte;
GO
SELECT CAST(CAST(NEXT VALUE FOR dbo.testSeq_13Byte AS VARBINARY(16)) AS UNIQUEIDENTIFIER);
According to the docs a DECIMAL(28) takes 13 bytes, a DECIMAL(38) lives in 17 bytes and a GUID is using 16 bytes.
The numeric range of DECIMAL(28) is huge... you can cast this to VARBINARY(16) and this to UNIQUEIDENTIFIER. This should return a new id with a system-wide logic.
If you need this, you might even cast this back to a DECIMAL(28).
But we should keep in mind, that it is always a bit dangerous to rely on the internal binary representation of a datatype. This might change from version to version...
So I created the following stored procedure to test with. It creates 2 temp tables which will show whether the id's generated are in fact sequential.
SET NOCOUNT ON;
GO
IF (OBJECT_ID( N'[dbo].[GETNEXTGUID]') IS NOT Null)
DROP PROCEDURE [dbo].[GETNEXTGUID];
GO
CREATE PROCEDURE [dbo].[GETNEXTGUID]
(
#NextId1 UNIQUEIDENTIFIER OUTPUT,
#NextId2 UNIQUEIDENTIFIER OUTPUT
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Sequence1 TABLE ([KEY] UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID(), [DUMMY] BIT);
DECLARE #Sequence2 TABLE ([KEY] UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID(), [DUMMY] BIT);
INSERT INTO #Sequence1 ([DUMMY]) VALUES (0);
INSERT INTO #Sequence2 ([DUMMY]) VALUES (0);
SELECT #NextId1 = [KEY] FROM #Sequence1;
SELECT #NextId2 = [KEY] FROM #Sequence2;
END
GO
I then ran the following test across multiple connections at the same time. So the test will call it's own two persisted tables as well as the two temp tables within the stored procedure.
SET NOCOUNT ON;
GO
DECLARE #Sequence1 TABLE ([KEY] UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID(), [DUMMY] BIT);
DECLARE #Sequence2 TABLE ([KEY] UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID(), [DUMMY] BIT);
DECLARE #Count int = 0;
DECLARE #NextId1 UNIQUEIDENTIFIER;
DECLARE #NextId2 UNIQUEIDENTIFIER;
WHILE ( #Count < 100000 )
BEGIN
EXECUTE [dbo].[GETNEXTGUID] #NextId1 OUTPUT, #NextId2 OUTPUT;
PRINT #NextId1; PRINT #NextId2;
INSERT INTO #Sequence1 ([DUMMY]) VALUES (0);
INSERT INTO #Sequence2 ([DUMMY]) VALUES (0);
SELECT #NextId1 = [KEY] FROM #Sequence1;
SELECT #NextId2 = [KEY] FROM #Sequence2;
PRINT #NextId1; PRINT #NextId2;
SET #Count += 1;
END
GO
Taking the results from the various connections and sorting them showed that the sequence is sequential across all 4 tables, regardless of whether they're persisted or recreated with each call and therefore will fit the requirement perfectly.
Thanks for the input everyone!
Related
I've different different tables to categorically store data and a log table where all the transactions log are recorded
e.g. 1) VoucherNO, Add, ...
2) VoucherNO, Delete, ..
After I backup the database and restore in another server for my Reporting Purpose. That time I want to ensure all the log data and transaction are available in TestDB if not then I remove log from 'AUD_USER_ACTIVITY'.
To find the transaction exist or not, I create a dynamic sql select statement and check whether record is exist or not.
Basis on #RecExist Value I do the action like if records is not available in TestDB the log will be remove, if record exist immediately break this loop and going for next procedure
But #RecExist variable is not updating in Dynamic SQL Execution. Please guide me
declare #MvDocNo varchar(50)
DECLARE #SCtr as DECIMAL(10,0)
declare #LocationCode varchar(4)
declare #UName Nvarchar(40)
declare #toe varchar(30)
declare #QryTxt as nvarchar(MAX);
Declare #RecExist as INT =0;
SET #RecExist=0
WHILE #RecExist=0
BEGIN
select top 1 #MvDocNo=DOCNO, #SCtr=SrlNo,#LocationCode =DMLTYPE,#UName=TABLENAME
FROM R_AUDDB..AUD_USER_ACTIVITY
WHERE DBNAME='TestDB' and DMLTYPE not in ('AD','D','PD') ORDER BY SRLNO DESC;
select top 1 #toe=docno from TestDB..M_TYPEOFENTRY where TBLNAME=#UName;
set #QryTxt='Select #RecExist=1 From R_TestDB..'+#UName+ ' Where '+#toe+'='''+#MvDocNo+''''
exec (#QryTxt)
IF #RecExist=0
BEGIN
DELETE R_AUDDB..AUD_USER_ACTIVITY WHERE SRLNO=#SCtr
END
END
The following code sample demonstrates how to check for a row in a table with a specific column and value using dynamic SQL. You ought to be able to change the values of the first three variables to reference a table and column in your database for testing.
Note that SQL injection is still possible: there is no validation of the table or column names.
-- Define the table to check and the target column name and value.
declare #TableName as SysName = 'Things';
declare #ColumnName as SysName = 'ThingName';
declare #TestValue as NVarChar(32) = 'Beth';
-- Create a SQL statement to check for a row in the target table with the specified column name and value.
declare #SQL as NVarChar(1024);
declare #Result as Bit;
-- Note that only object names are substituted into the statement at this point and QuoteName() is used to reduce problems.
set #SQL = N'select #iResult = case when exists ( select 42 from dbo.' + QuoteName( #TableName ) +
N' where ' + QuoteName( #ColumnName ) + N' = #iTestValue ) then 1 else 0 end;'
select #SQL as SQL;
-- Execute the SQL statement.
-- Note that parameters are used for all values, i.e. the target value and return value.
execute sp_executesql #stmt = #SQL,
#params = N'#iTestValue NVarChar(32), #iResult Bit output',
#iTestValue = #TestValue, #iResult = #Result output
-- Display the result.
select #Result as Result;
I have some data which I bulk import into this table structure:
CREATE TABLE #Temp
(
WellKnownText NVARCHAR(MAX)
)
Some of the entries are not valid. So something like this:
SELECT geometry::STPolyFromText(WellKnownText,4326) FROM #Temp
does not work for all rows and thus falls over.
What is the best way to detect which WellKnownText are not valid? I have used MakeValid in the past - so ideally I would like to fix entries as much as possible.
PS:
This does not work:
SELECT * FROM #Temp
WHERE geometry::STPolyFromText(WellKnownText,4326).STIsValid() = 0
PPS:
I chose a loop based approach in the end along those lines:
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL DROP TABLE #Temp;
IF OBJECT_ID('tempdb..#Temp1') IS NOT NULL DROP TABLE #Temp1;
DECLARE #LoopCounter INT = 1;
DECLARE #MaxCounter INT;
DECLARE #Valid BIT;
DECLARE #ValidCounter INT;
DECLARE #WellKnownText NVARCHAR(MAX);
CREATE TABLE #Temp
(
Guid UNIQUEIDENTIFIER,
PostcodeFraction NVARCHAR(50),
WellKnownText NVARCHAR(MAX),
GeoJson NVARCHAR(MAX)
);
CREATE TABLE #Temp1
(
Guid UNIQUEIDENTIFIER,
PostcodeFraction NVARCHAR(50),
WellKnownText NVARCHAR(MAX),
GeoJson NVARCHAR(MAX)
);
BULK INSERT #Temp FROM 'D:\PolygonData.txt' WITH (FIELDTERMINATOR = '\t', FIRSTROW = 2, ROWTERMINATOR = '\n');
ALTER TABLE #Temp ADD Id INT IDENTITY(1,1);
SELECT #MaxCounter = MAX(Id) FROM #Temp
SET #ValidCounter = 0;
WHILE(#LoopCounter <= #MaxCounter)
BEGIN
BEGIN TRY
SELECT #WellKnownText = WellKnownText FROM #Temp WHERE Id = #LoopCounter;
SET #Valid = GEOMETRY::STGeomFromText(#WellKnownText,4326).STIsValid();
SET #ValidCounter = #ValidCounter + 1;
END TRY
BEGIN CATCH
SET #Valid = 0;
END CATCH
IF(#Valid = 1)
BEGIN
INSERT INTO #TEMP1
SELECT Guid, PostcodeFraction, WellKnownText, GeoJson FROM #Temp WHERE Id = #LoopCounter;
END
SET #LoopCounter = #LoopCounter + 1;
END
PRINT #ValidCounter;
SELECT * FROM #TEMP1;
As requested in the comments, some possible solutions
I guess you're really looking for a function that can be CROSS APPLYed, something like
SELECT * FROM #Temp T
CROSS APPLY IsWKTValidFunc(T.WellKnownText, 4326) F
WHERE F.IsValid = <somecondition>
(Or even added to as computed column to give you a flag that's set on inserting your WKT)
Stored Proc
https://gis.stackexchange.com/questions/66642/detecting-invalid-wkt-in-text-column-in-sql-server has a simple SP that wraps GEOMETREY::STGeomFromText in a try catch block.
However, stored procs cannot be CROSS APPLYed (or called from a UDF that can be) so this would result in a cursor based solution.
UDF
A UDF can be cross applied, but can't have a TRY-CATCH block. You also can't call the above SP from a UDF. So not much use there.
CLR UDF
Wrap the GEOMETREY::STGeomFromText call in a CLR UDF that can be CROSS APPLIED, can have try catch and other error checking, rules etc, and return a flag indicating valid text. I haven't tried this one out but this sounds like the best option if CLR is enabled in your environment.
Hope this gives you some ideas. Feedback in the comments to these suggestions appreciated.
I have following stored procedure defined:
USE [BcmMetrice]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[ActivityAdd_proc] #Name nvarchar(max),#Description nvarchar(max) =null ,#Users nvarchar(max),#Object_id nvarchar (15) =null, #Source nvarchar(10) =null, #TemplateId bigint =null, #UserID bigint =null
AS
DECLARE activityUsers_cursor CURSOR FOR
select s from dbo.SplitString(#Users, ';')
DECLARE
#new_ActivityId bigint,
#new_CommentId bigint,
#activityUser_l bigint
BEGIN TRY
INSERT INTO [BcmMetrice].[dbo].[Activity]
([Name]
,[Description]
,[Type]
,[Created])
VALUES
(#Name
,#Description
,ISNULL(#TemplateId,0)
,GETDATE())
SET #new_ActivityId = (SELECT SCOPE_IDENTITY())
INSERT INTO [BcmMetrice].[dbo].[Comment] ([UserID],[CommentText],[Timestamp])
VALUES (ISNULL(#UserID,151),'Activity has been created',GETDATE())
SET #new_CommentId = (SELECT SCOPE_IDENTITY())
INSERT INTO [BcmMetrice].[dbo].[ActivityComment] ([ActivityID],[CommentID])
VALUES (#new_ActivityId, #new_CommentId)
INSERT INTO [BcmMetrice].[dbo].[Log]([Timestamp],[Type],[Data],[StackTrace]) VALUES (GETDATE(),'SQL.ActivityAdd_proc','users='+ISNULL(CAST(#Users as varchar(max)),'empty'),null)
OPEN activityUsers_cursor
FETCH NEXT FROM activityUsers_cursor INTO #activityUser_l
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO [BcmMetrice].[dbo].[Log]([Timestamp],[Type],[Data],[StackTrace]) VALUES (GETDATE(),'SQL.ActivityAdd_proc','Inserting users='+ISNULL(CAST(#activityUser_l as varchar(max)),'empty'),null)
INSERT INTO [BcmMetrice].[dbo].[ActivityUser]
([ActivityId]
,[UserId]
,[Role])
VALUES
(#new_ActivityId
,#activityUser_l
,1)
FETCH NEXT FROM activityUsers_cursor INTO #activityUser_l
END
CLOSE activityUsers_cursor
DEALLOCATE activityUsers_cursor
END TRY
BEGIN CATCH
PRINT 'ERROR'
INSERT INTO [BcmMetrice].[dbo].[Log]([Timestamp],[Type],[Data],[StackTrace]) VALUES (GETDATE(),'SQL.ActivityAdd_proc','ERROR CATCHED!'+ERROR_MESSAGE(),null)
END CATCH
select #new_ActivityId
The thing I would like to do is to return from the procedure the id of a newly added activity. That is why at the very and I use line:
select #new_ActivityId
When testing this procedure in SQL Management Studio everything seems to be working fine. Problem starts when I try to use this procedure in my .NET project. I updated my edmx model form database, but when I execute this procedure the return value is invalid.
Procedure execution looks like this:
int ret = dc.Db.ActivityAdd_proc(name, description, users, object_id, source, templateId, userId);
Does anyone have an idea what I might be doing wrong?
I found solution to my problem. When you try to return data from stored procedure via select statement you must create Function Import in model browser of edmx. In a wizard you choose a type of returned collection (in my case it was scalar of type int64).
Perhaps a stupid question!
If I call a stored proc from an After Insert trigger (T-SQL) - then how do I get the values of the "just inserted" data?
e.g.
CREATE TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
EXEC createAuditSproc 'I NEED VALUES HERE!'
I don't have any identity columns to worry about - I just want to use some of the "just inserted" values to pass into my sproc.
Edit: For clarification - I need this to call a sproc and not do a direct insert to the table, since the sproc does more than one thing. I'm working with some legacy tables I can't currently amend to do things 'properly' (time/resource/legacy code), so I have to work with what I have :(
You get to the newly 'changed' data by using the INSERTED and DELETED pseudo-tables:
CREATE TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
INSERT INTO myTableAudit(ID, Name)
SELECT i.ID, i.Name
FROM inserted i;
END
Given the example tables
create table myTable
(
ID INT identity(1,1),
Name varchar(10)
)
GO
create table myTableAudit
(
ID INT,
Name varchar(10),
TimeChanged datetime default CURRENT_TIMESTAMP
)
GO
Edit : Apologies, I didn't address the bit about calling a Stored Proc. As per marc_s's comment, note that inserted / deleted can contain multiple rows, which complicates matters with a SPROC. Personally, I would leave the trigger inserting directly into the audit table without the encapsulation of a SPROC. However, if you have SQL 2008, you can use table valued parameters, like so:
CREATE TYPE MyTableType AS TABLE
(
ID INT,
Name varchar(10)
);
GO
CREATE PROC dbo.MyAuditProc #MyTableTypeTVP MyTableType READONLY
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO myTableAudit(ID, Name)
SELECT mtt.ID, mtt.Name
FROM #MyTableTypeTVP mtt;
END
GO
And then your trigger would be altered as like so:
ALTER TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #MyTableTypeTVP AS MyTableType;
INSERT INTO #MyTableTypeTVP(ID, Name)
SELECT i.ID, i.Name
FROM inserted i;
EXEC dbo.MyAuditProc #MyTableTypeTVP;
END
you can then test that this works for both a single and multiple inserts
insert into dbo.MyTable values ('single');
insert into dbo.MyTable
select 'double'
union
select 'insert';
However, if you are using SQL 2005 or lower, you would probably need to use a cursor to loop through inserted passing rows to your SPROC, something too horrible to contemplate.
As a side note, if you have SQL 2008, you might look at Change Data Capture
Edit #2 : Since you need to call the proc, and if you are certain that you only insert one row ...
ALTER TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SomeInt INT;
DECLARE #SomeName VARCHAR(10);
SELECT TOP 1 #SomeInt = i.ID, #SomeName = i.Name
FROM INSERTED i;
EXEC dbo.MyAuditProc #SomeInt, #SomeName;
END;
In my stored procedure I have multiple similar variables #V1, #V2 ... #V20 (let's say 20 of them) FETCHED from a record. How would I use dynamic SQL to make 20 calls to another stored procedure using those variables as parameters?
Of course #V[i] syntax is incorrect, but it expresses the intent
fetch next from maincursor into #status, #V1, #V2, ...
while #i<21
begin
-- ??? execute sp_executesql 'SecondSP', '#myParam int', #myParam=#V[i]
-- or
-- ??? execute SecondSP #V[i]
set #i = #i+1
end
As others have said, set up a temporary table, insert the values that you need into it. Then "iterate" through it executing the necessary SQL from those values. This will allow you to have 0 to MANY values to be executed, so you don't have to set up a variable for each.
The following is a complete sample of how you may go about doing that without cursors.
SET NOCOUNT ON
DECLARE #dict TABLE (
id INT IDENTITY(1,1), -- a unique identity column for reference later
value VARCHAR(50), -- your parameter value to be passed into the procedure
executed BIT -- BIT to mark a record as being executed later
)
-- INSERT YOUR VALUES INTO #dict HERE
-- Set executed to 0 (so that the execution process will pick it up later)
-- This may be a SELECT statement into another table in your database to load the values into #dict
INSERT #dict
SELECT 'V1Value', 0 UNION ALL
SELECT 'V2Value', 0
DECLARE #currentid INT
DECLARE #currentvalue VARCHAR(50)
WHILE EXISTS(SELECT * FROM #dict WHERE executed = 0)
BEGIN
-- Get the next record to execute
SELECT
TOP 1 #currentid = id
FROM #dict
WHERE executed = 0
-- Get the parameter value
SELECT #currentvalue = value
FROM #dict
WHERE id = #currentid
-- EXECUTE THE SQL HERE
--sp_executesql 'SecondSP', '#myParam int', #myParam =
PRINT 'SecondSP ' + '#myParam int ' + '#myParam = ' + #currentvalue
-- Mark record as having been executed
UPDATE d
SET executed = 1
FROM #dict d
WHERE id = #currentid
END
Use a #TempTable
if you are at SQL Server 2005 you can create a #TempTable in the parent stored procedure, and it is available in the child stored procedure that it calls.
CREATE TABLE #TempTable
(col1 datatype
,col2 datatype
,col3 datatype
)
INSERT INTO #TempTable
(col1, col2, col3)
SELECT
col1, col2, col3
FROM ...
EXEC #ReturnCode=YourOtherProcedure
within the other procedure, you have access to #TempTable to select, delete, etc...
make that child procedure work on a set of data not on one element at a time
remember, in SQL, loops suck performance away!
Why not just use the table variable instead, and then just loop through the table getting each value.
Basically treat each row in a table as your array cell, with a table that has one column.
Just a thought. :)
This seems like an odd request - will you always have a fixed set of variables? What if the number changes from 20 to 21, and so on, are you constantly going to have to be declaring new variables?
Is it possible, instead of retrieving the values into separate variables, to return them each as individual rows and just loop through them in a cursor?
If not, and you have to use the individual variables as explained, here's one solution:
declare #V1 nvarchar(100)
set #V1 = 'hi'
declare #V2 nvarchar(100)
set #V2 = 'bye'
declare #V3 nvarchar(100)
set #V3 = 'test3'
declare #V4 nvarchar(100)
set #V4 = 'test4'
declare #V5 nvarchar(100)
set #V5 = 'end'
declare aCursor cursor for
select #V1
union select #V2 union select #V3
union select #V4 union select #V5
open aCursor
declare #V nvarchar(100)
fetch next from aCursor into #V
while ##FETCH_STATUS = 0
begin
exec TestParam #V
fetch next from aCursor into #V
end
close aCursor
deallocate aCursor
I don't really like this solution, it seems messy and unscalable. Also, as a side note - the way you phrased your question seems to be asking if there are arrays in T-SQL. By default there aren't, although a quick search on google can point you in the direction of workarounds for this if you absolutely need them.