I have a table in my database that automatically records progress, day by day.
This script...
1. Selects all distinct sub-contractors from this History table and inserts them into a table variable.
2. Selects all distinct dates in the History table.
3. Builds a query as a varchar to insert day-by-day tonnage per Sub-contractor (fabricator)
4. Attempts to print-to-screen the built variable
5. Executes the nvarchar'd SQL (commented out)
use database666
-- in-memory employee table to hold distinct PHFabricator
DECLARE #i int;
DECLARE #f int;
DECLARE #CreateTonnageTableQuery NVARCHAR(MAX);
DECLARE #TonnageTableQuery VARCHAR(MAX);
DECLARE #CurrentTonnageQuery VARCHAR(MAX);
DECLARE #SubbsTable TABLE ( sdx int Primary Key IDENTITY(1,1), OrgID int);
DECLARE #DatesTable TABLE ( idx int Primary Key IDENTITY(1,1), History_date datetime);
INSERT #SubbsTable SELECT distinct PHFabricator FROM tblpackagehistory ORDER BY PHFabricator;
INSERT #DatesTable SELECT distinct PHHistory_Date FROM tblpackagehistory ORDER BY PHHistory_Date;
SET #CreateTonnageTableQuery = 'DECLARE #TonnageTable TABLE ([Fabricator_ID] int primary key';
SET #i = 1;
WHILE (#i <= (SELECT COUNT(*) FROM #DatesTable))
BEGIN
SET #CreateTonnageTableQuery = #CreateTonnageTableQuery + ', [' + (SELECT 'COL'+CONVERT(varchar(6),idx) FROM #DatesTable WHERE idx = #i) + '] float';
SET #i = #i + 1;
END
SET #CreateTonnageTableQuery = #CreateTonnageTableQuery + '); ' + CHAR(13)+CHAR(10);
DECLARE #currentSubbie int
DECLARE #currentDate datetime
SET #TonnageTableQuery = '';
SET #CurrentTonnageQuery = '';
SET #f = 0
WHILE (#f <= (SELECT COUNT(*) FROM #SubbsTable))
BEGIN
SET #f = #f + 1;
SET #currentSubbie = (SELECT OrgID FROM #SubbsTable WHERE sdx = #f);
SET #CurrentTonnageQuery = 'INSERT INTO #TonnageTable VALUES (' + CONVERT(varchar(6),#currentSubbie);
SET #i = 1;
WHILE (#i <= (SELECT COUNT(*) FROM #DatesTable))
BEGIN
SET #currentDate = (SELECT History_date FROM #DatesTable WHERE idx = #i);
SET #CurrentTonnageQuery = #CurrentTonnageQuery + ', ' +
( SELECT CONVERT(varchar(20),(sum(PHIssued_Tonnage * PHPercent_Overall_Fabricated)))
FROM tblpackagehistory
WHERE PHFabricator = #currentSubbie AND PHHistory_Date = #currentDate
);
SET #i = #i + 1;
END
SET #CurrentTonnageQuery = #CurrentTonnageQuery + '); ' + CHAR(13)+CHAR(10);
PRINT #CurrentTonnageQuery;
SET #TonnageTableQuery = #TonnageTableQuery + #CurrentTonnageQuery;
PRINT CHAR(13)+CHAR(10) + #TonnageTableQuery + CHAR(13)+CHAR(10);
END
print 'just work dammit';
print 'omg ' + #TonnageTableQuery + ' omg';
print 'omfg';
--DECLARE #statement nvarchar(max);
--SET #statement = #CreateTonnageTableQuery + #TonnageTableQuery + 'SELECT * FROM #TonnageTable;';
--EXEC sp_executesql #statement;
To summarise, you will notice some print statements throughout the code, not just the ones near the end. All these work, the query is building as intended, I end up with one line per fabricator, with the fabricator's ID and one tonnage column per date in the History table.
However after the final loop, it doesn't seem to retain any variable data:
print 'just work dammit';
print 'omg ' + #TonnageTableQuery + ' omg';
print 'omfg';
outputs:
just work dammit
omfg
Where am I going wrong?
It looks like you are concatenating NULL and a string which results in NULL. This will propagate the NULLness throughout your whole algorithm. You can use the ISNULL function to substitute an appropriate string (like an empty string or the literal string NULL) for a NULL value.
The NULL might be coming from falling off the end of your variable table. Try changing your WHILE statement to:
WHILE (#f < (SELECT COUNT(*) FROM #SubbsTable))
Go to Query options and set CONCAT_NULL_YIELDS_NULL to false, and see if that gets you output. If so, one of your expressions is probably evaluating to null.
NB I don't recommend leaving it set to false other than for diagnostics, it's a connection level setting and could result in difficult to debug errors for production procs.
Related
I'm trying to increment a parameter set within my script by 1 every loop of my while.
This is an example of what I'm doing within my script:
DECLARE #I AS INT;
SET #I = 0;
DECLARE #SQL NVARCHAR(MAX)
SET #SQL =
'WHILE '+ Convert(Varchar, #I) +' < (SELECT statement here...)
BEGIN
SET '+ Convert(Varchar, #I) +' = '+ Convert(Varchar, (#I + 1))'
END'
There is a lot more to this script but this is the relevant part. I understand that '+ Convert(Varchar, #I) +' is just going to concatenate the value of #I to the string, but could anyone offer any advice in how I can make it so that the value of #I is incremented by 1.
Currently when executing the sql, the set command will end up like the following:
SET 0 = 0 + 1
where as I need it to change the actual variables value for the next loop.
Is this even possible?
You can use the '#I' as a variable in your query:
I'm trying to increment a parameter set within my script by 1 every loop of my while.
This is an example of what I'm doing within my script:
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = '
DECLARE #I AS INT;
SET #I = 0;
WHILE #I +' < (SELECT statement here...) +
'
BEGIN
SET #I = #I + 1
END'
I am using the cursor in my stored procedure; I am hoping to remove the cursor from my SP. Please help me come up with a solution for how to avoid the cursor statement to normal update statement with dynamic.
Example Below:
Update Tablename set columnname(variable) = value from table A join Table B on A.condition = B.Condition where name = 'Test'(variable) and age = 18(variable)
Update Tablename set columnname(variable) = value from table A join Table B on A.condition = B.Condition where name = 'kumar'(variable) and age = 19(variable)
Update Tablename set columnname(variable) = value from table A join Table B on A.condition = B.Condition where name = 'babu'(variable) and age = 30(variable)
This is how my cursor will work. 300 Combination dynamically pick the data from table and update into the main table
I am trying to take out the cursor, and update statement should work similar to this, instead of writing 300 update statements, I want to write one update where all the 300 combinations should execute.
Below is my code which needs this solution:
BEGIN
DECLARE #Type VARCHAR(100)
DECLARE #TargetColumn VARCHAR(100)
DECLARE #SourceColumn VARCHAR(100)
DECLARE #SQL varchar(max)
DECLARE a_cursor CURSOR STATIC
FOR
SELECT [Type],[SourceColumn],[TargetColumn] FROM ref.tblEdsMap
GROUP BY [Type],[SourceColumn],[TargetColumn]
OPEN a_cursor
FETCH NEXT FROM a_cursor INTO #Type,#SourceColumn,#TargetColumn
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SQL = 'UPDATE GCT SET GCT.' + #TargetColumn + ' = map.[TargetValue]
from EdsMap map
JOIN Table GCT
ON GCT.' + #SourceColumn + ' = map.[SourceValue]
where map.[Type]=''' + #Type + ''' and map.SourceColumn=''' + #SourceColumn+ ''''
Exec (#SQL)
PRINT #SQL
FETCH NEXT FROM a_cursor INTO #Type,#SourceColumn,#TargetColumn
END
CLOSE a_cursor
DEALLOCATE a_cursor
END
Rather than use an explicit cursor or a cursor cleverly disguised as a while loop, I prefer row concatenation operations for this type of problem.
DECLARE #cmd NVARCHAR(MAX) = N'';
SELECT #cmd += N'
UPDATE GCT
SET GCT.' + QUOTENAME(TargetColumn) + ' = map.TargetValue
FROM dbo.EdsMap AS map
INNER JOIN dbo.Table AS GCT
ON GCT.' + QUOTENAME(SourceColumn) + ' = map.SourceValue
WHERE map.[Type] = ''' + [Type] + '''
AND map.SourceColumn = ''' + [SourceColumn]+ ''';'
FROM ref.tblEdsMap
GROUP BY [Type], SourceColumn, TargetColumn;
EXEC sp_executesql #sql;
When I've done these in the past, I usually make up a transaction to encompass every update that's needed. Something like this:
CREATE TABLE #targets ([Type] VARCHAR(255),[SourceColumn] VARCHAR(255),[TargetColumn] VARCHAR(255));
INSERT INTO #targets
( [Type], [SourceColumn], [TargetColumn] )
SELECT [Type],[SourceColumn],[TargetColumn] FROM ref.tblEdsMap
GROUP BY [Type],[SourceColumn],[TargetColumn];
DECLARE #sql VARCHAR(MAX);
SET #sql = 'BEGIN TRAN' + CHAR(10) + CHAR(13);
SELECT #sql = #sql +
'UPDATE GCT SET GCT.' + [TargetColumn] + ' = map.[TargetValue]
from EdsMap map
JOIN Table GCT
ON GCT.' + [SourceColumn] + ' = map.[SourceValue]
where map.[Type]=''' + [Type] + ''' and map.SourceColumn=''' + [SourceColumn]+ ''';' + CHAR(10) + CHAR(13)
FROM #targets
SELECT #sql = #sql + 'COMMIT TRAN'
PRINT #sql
Exec (#SQL)
The update statements are still the same, i.e., you get one update per combination. But now you're running as one transaction batch. You could potentially be fancier with the dynamic SQL, so that you had just one update statement, but in my experience, it's too easy to get bad updates that way.
Doing it this way may not be any faster than a cursor. You'd have to test to be sure. With the examples where I've used this approach, it has generally been a faster approach.
Try using a table variable along with a WHILE loop instead, like so:
BEGIN
DECLARE #Type VARCHAR(100)
DECLARE #TargetColumn VARCHAR(100)
DECLARE #SourceColumn VARCHAR(100)
DECLARE #SQL varchar(max)
DECLARE #SomeTable TABLE
(
ID int IDENTITY (1, 1) PRIMARY KEY NOT NULL,
Type varchar(100),
SourceColumn varchar(100),
TargetColumn varchar(100)
)
DECLARE #Count int, #Max int
INSERT INTO #SomeTable (Type, SourceColumn, TargetColumn)
SELECT [Type],[SourceColumn],[TargetColumn]
FROM ref.tblEdsMap
GROUP BY [Type],[SourceColumn],[TargetColumn]
SELECT #Count = 1, #Max = COUNT(ID)
FROM #SomeTable
WHILE #Count <= #Max
BEGIN
SELECT
#Type = Type,
#SourceColumn = SourceColumn,
#TargetColumn = TargetColumn
FROM #SomeTable
WHERE ID = #Count
-- Your code
SET #SQL = 'UPDATE GCT SET GCT.' + #TargetColumn + ' = map.[TargetValue]
from EdsMap map
JOIN Table GCT
ON GCT.' + #SourceColumn + ' = map.[SourceValue]
where map.[Type]=''' + #Type + ''' and map.SourceColumn=''' + #SourceColumn+ ''''
Exec (#SQL)
PRINT #SQL
SET #Count = #Count + 1
END -- while
END
This week, I found myself in need of some dynamic queries. Now, dynamic queries and dynamic where clauses are nothing new and well documented all over the web. Yet, I needed something more. I needed a fluid way of pulling new where fields to the client and allowing the users to make as many filters as needed. Even have multiple filters on a single field. Even more so, I needed to have access to all the possible operators within SQL server. The following is code is one way to make this happen. I will attempt to point out highlights of the code with the complete code at the bottom.
Hope you enjoy the code.
REQUIREMENTS
The solution will never allow SQL injections. (No exec(command) can be used)
The caller of the stored procedure could be anything.
The data set must come from a Stored Procedure.
Any field can be filtered as many times as needed, with just about any operation.
Any combination of filters should be allowed.
The stored procedure should allow for mandatory parameters
First, let us look over the parameters.
CREATE PROCEDURE [dbo].[MyReport]
-- Add the parameters for the stored procedure here
#p_iDistributorUID INT, -- manditory
#p_xParameters XML = null --optional parameters (hostile)
The first parameter must always be sent, in this demo we have a distributor id that must be sent in. The second parameter is an XML document. These are the “Dynamic Where Clauses” and we consider these potential sql injections, or as I perceive this parameter as hostile.
<root>
<OrFilters>
<AndFilter Name="vcinvoicenumber" Operator="2" Value="inv12"/>
<AndFilter Name="vcID" Operator="1" Value="asdqwe"/>
</OrFilters>
<OrFilters>
<AndFilter Name="iSerialNumber" Operator="1" Value="123456"/>
</OrFilters>
NAME= field name(you could just use the object_id if you want to obfuscate)
OPERATOR = SQL operators such as <,>,=,like,ect.
VALUE is what the users has entered.
Here is what the final code would look like.
Select *
FROM someTable
Where (
vcinvoicenumber like ‘inv12%’
and vcID = ‘asdqwe’
)
Or
(
iSerialNumber = ‘123456’
)
First thing is to find out how many “OrFilters” tags there are.
SELECT #l_OrFilters = COUNT(1)
FROM #p_xParameters.nodes('/root/OrFilters') Tab(Col)
Next we need a temp table to hold the values in the XML doc.
CREATE TABLE #temp
(keyid int IDENTITY(1,1) NOT NULL,value varchar(max))
We now create a cursor for the first “OrFilters”tag.
DECLARE OrFilter_cursor CURSOR LOCAL
FOR
SELECT Tab.Col.value('#Name','varchar(max)') AS Name
,Tab.Col.value('#Operator','Smallint') AS Operator
,Tab.Col.value('#Value','varchar(max)') AS Value
FROM #p_xParameters.nodes('/root/OrFilters[sql:variable("#l_OrFilters")]/AndFilter') Tab(Col)
To make sure we have a valid field, we check against the system tables.
SELECT #l_ParameterInName = [all_columns].Name
,#l_ParameterDataType= [systypes].Name
,#l_ParameterIsVariable= Variable
,#l_ParameterMax_length=max_length
,#l_ParameterpPrecision=precision
,#l_ParameterScale =[all_columns].scale
FROM [AprDesktop].[sys].[all_views]
INNER JOIN [AprDesktop].[sys].[all_columns]
ON [all_views].object_id = [all_columns].object_id
INNER JOIN [AprDesktop].[sys].[systypes]
ON [all_columns].system_type_id = [systypes].xtype
WHERE [all_views].name = 'vw_CreditMemo_Lists'
and [all_columns].Name= #l_Name
Now we save the parameter to the temp table
IF ##ROWCOUNT = 1
BEGIN
INSERT INTO #temp (value) SELECT #l_Value
SET #l_FilterKey = ##IDENTITY
.
.
.
We make a call to a function that will actually build the where clauses.
SET #l_TemporaryWhere +=
dbo.sfunc_FilterWhereBuilder2(#l_Operator
,#l_ParameterInName
,#l_TemporaryWhere
,CAST(#l_FilterKey AS VARCHAR(10))
,#l_ParameterDataType
,#l_ParameterVariable)
Looking at this Function, you can see we used a case statement to genereate the where clause string.
set #l_CastToType = ' CAST( VALUE as ' + #p_DataType + #p_PrecisionScale + ') '
set #l_CastToString = ' CAST( '+#p_Field+' as VARCHAR(MAX)) '
-- Add the T-SQL statements to compute the return value here
SELECT #l_Return =
CASE
--EQUAL
--ex: vcUID = (select value FROM #temp where keyid = 1)
WHEN #p_Command = 1
THEN #p_Field + ' = (select '+#l_CastToType+' FROM #temp where keyid = ' + #p_KeyValue + ')'
--BEGIN WITH
--ex:vcInvoiceNumber LIKE (select value+'%' FROM #temp where keyid = 2)
WHEN #p_Command = 2
THEN #l_CastToString +' LIKE (select value+'+ QUOTENAME('%','''') +' FROM #temp where keyid = ' + #p_KeyValue + ')'
.
.
.
And finally call the sp_execute.
EXECUTE sp_executesql #l_SqlCommand ,#l_Parameters, #p_iDistributorUID
CALLING CODE
DECLARE #return_value int
DECLARE #myDoc xml
SET #myDoc =
'<root>
<OrFilters>
<AndFilter Name="vcinvoicenumber" Operator="1" Value="123"/>
</OrFilters>
</root>'
EXEC #return_value = [dbo].[spp_CreditMemo_Request_List_v2]
#p_siShowView = 1,
#p_iDistributorUID = 3667,
#p_xParameters = #myDoc
SELECT 'Return Value' = #return_value
MAIN STORED PROCEDURE
ALTER PROCEDURE [dbo].[MyReport]
-- Add the parameters for the stored procedure here
#p_iDistributorUID INT , --manditory
#p_xParameters XML = null --optional parameters(hostile)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #l_TemporaryWhere NVARCHAR(MAX)
-- declare variables
DECLARE #l_SqlCommand NVARCHAR(MAX)
DECLARE #l_Parameters NVARCHAR(MAX)
DECLARE #l_WhereClause NVARCHAR(MAX)
DECLARE #l_OrFilters INT
--cursor variables
DECLARE #l_Name VARCHAR(MAX)
DECLARE #l_Operator SMALLINT
DECLARE #l_Value VARCHAR(MAX)
--variables from the database views
DECLARE #l_ParameterInName NVARCHAR(128)
DECLARE #l_ParameterDataType NVARCHAR(128)
DECLARE #l_ParameterIsVariable BIT
DECLARE #l_ParameterMax_length SMALLINT
DECLARE #l_ParameterpPrecision TINYINT
DECLARE #l_ParameterScale TINYINT
--the variable that holds the latest ##identity
DECLARE #l_FilterKey INT
--init local variables
SET #l_SqlCommand =''
SET #l_Parameters =''
SET #l_WhereClause =''
BEGIN TRY
--verify manditory variables
if #p_iDistributorUID is null
raiserror('Null values not allowed for #p_iDistributorUID', 16, 1)
--Build the base query
-- only the fields needed in the tile should be selected
SET #l_SqlCommand =
' SELECT * ' +
' FROM vw_Lists '
--how many "OR" filters are there
SELECT #l_OrFilters = COUNT(1)
FROM #p_xParameters.nodes('/root/OrFilters') Tab(Col)
--create a temp table to
--hold the parameters to send into the sp
CREATE TABLE #temp
(
keyid int IDENTITY(1,1) NOT NULL,value varchar(max)
)
--Cycle through all the "OR" Filters
WHILE #l_OrFilters > 0
BEGIN
SET #l_TemporaryWhere = '';
--Create a cursor of the Next "OR" filter
DECLARE OrFilter_cursor CURSOR LOCAL
FOR
SELECT Tab.Col.value('#Name','varchar(max)') AS Name
,Tab.Col.value('#Operator','Smallint') AS Operator
,Tab.Col.value('#Value','varchar(max)') AS Value
FROM #p_xParameters.nodes('/root/OrFilters[sql:variable("#l_OrFilters")]/AndFilter') Tab(Col)
OPEN OrFilter_cursor
FETCH NEXT FROM OrFilter_cursor
INTO #l_Name, #l_Operator,#l_Value
WHILE ##FETCH_STATUS = 0
BEGIN
--verify the parameter actual exists
-- and get parameter details
SELECT #l_ParameterInName = [all_columns].Name
,#l_ParameterDataType= [systypes].Name
,#l_ParameterIsVariable= Variable
,#l_ParameterMax_length=max_length
,#l_ParameterpPrecision=precision
,#l_ParameterScale =[all_columns].scale
FROM [AprDesktop].[sys].[all_views]
INNER JOIN [sys].[all_columns]
ON [all_views].object_id = [all_columns].object_id
INNER JOIN [sys].[systypes]
ON [all_columns].system_type_id = [systypes].xtype
WHERE [all_views].name = 'vw_CreditMemo_Lists'
and [all_columns].Name= #l_Name
--if the paremeter exists, create a where clause
-- if the parameters does not exists, possible injection
IF ##ROWCOUNT = 1
BEGIN
--insert into the temp table the parameter value
--NOTE: we have turned in the ##identity as the key
INSERT INTO #temp (value) SELECT #l_Value
SET #l_FilterKey = ##IDENTITY
-- if the parameter is variable in length, add the length
DECLARE #l_ParameterVariable VARCHAR(1000)
IF #l_ParameterIsVariable = 1
BEGIN
SET #l_ParameterVariable ='(' + CAST(#l_ParameterMax_length as VARCHAR(MAX)) + ') '
END
ELSE
BEGIN
SET #l_ParameterVariable = ''
END
-- create the where clause for this filter
SET #l_TemporaryWhere +=
dbo.sfunc_FilterWhereBuilder2(#l_Operator
,#l_ParameterInName
,#l_TemporaryWhere
,CAST(#l_FilterKey AS VARCHAR(10))
,#l_ParameterDataType
,#l_ParameterVariable)
END
FETCH NEXT FROM OrFilter_cursor
INTO #l_Name, #l_Operator,#l_Value
END
-- clean up the cursor
CLOSE OrFilter_cursor
DEALLOCATE OrFilter_cursor
--add the and filers
IF #l_TemporaryWhere != ''
BEGIN
--if the where clause is not empty, we need to add an OR
IF #l_WhereClause != ''
BEGIN
SET #l_WhereClause += ' or ';
END
--add temp to where clause including the
SET #l_WhereClause += '(' + #l_TemporaryWhere + ')';
END
--get the next AND set
SET #l_OrFilters = #l_OrFilters - 1
END
--generate the where clause
IF #l_WhereClause != ''
BEGIN
SET #l_WhereClause ='('+ #l_WhereClause + ') AND '
END
--add in the first mandatory parameter
SET #l_WhereClause += ' vw_CreditMemo_Lists.iDistributorUID = #l_iDistributorUID '
SET #l_Parameters += '#l_iDistributorUID int'
--do we need to attach the where clause
if #l_WhereClause IS NOT NULL AND RTRIM(LTRIM(#l_WhereClause)) != ''
BEGIN
SET #l_SqlCommand += ' WHERE '+ #l_WhereClause;
END
print #l_SqlCommand
--query for the data
EXECUTE sp_executesql #l_SqlCommand ,#l_Parameters, #p_iDistributorUID
END TRY
BEGIN CATCH
DECLARE #ErrorUID int;
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
--write the to stored procedure log
EXEC #ErrorUID = spp_Errors_CreateEntry #l_SqlCommand
-- Use RAISERROR inside the CATCH block to return error
-- information about the original error that caused
-- execution to jump to the CATCH block.
RAISERROR (#ErrorUID, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
IF(CURSOR_STATUS('LOCAL','OrFilter_cursor') >= 0)
BEGIN
CLOSE OrFilter_cursor
END
IF(CURSOR_STATUS('LOCAL','OrFilter_cursor') = -1)
BEGIN
DEALLOCATE OrFilter_cursor
END
END CATCH
return
END
FUNCTION
ALTER FUNCTION [dbo].[sfunc_FilterWhereBuilder2]
(
#p_Command SMALLINT ,
#p_Field VARCHAR(1000) ,
#p_WhereClause VARCHAR(MAX) ,
#p_KeyValue VARCHAR(10) ,
#p_DataType VARCHAR(100) = NULL ,
#p_PrecisionScale VARCHAR(100) = NULL
)
RETURNS VARCHAR(MAX)
AS
BEGIN
-- Declare the return variable here
DECLARE #l_Return VARCHAR(MAX)
DECLARE #l_CastToType VARCHAR(4000)
DECLARE #l_CastToString VARCHAR(MAX)
set #l_CastToType = ' CAST( VALUE as ' + #p_DataType + #p_PrecisionScale + ') '
set #l_CastToString = ' CAST( '+#p_Field+' as VARCHAR(MAX)) '
-- Add the T-SQL statements to compute the return value here
SELECT #l_Return =
CASE
--EQUAL
--ex: vcBurnUID = (select value FROM #temp where keyid = 1)
WHEN #p_Command = 1
THEN #p_Field + ' = (select '+#l_CastToType+' FROM #temp where keyid = ' + #p_KeyValue + ')'
--BEGIN WITH
--ex:vcInvoiceNumber LIKE (select value+'%' FROM #temp where keyid = 2)
WHEN #p_Command = 2
THEN #l_CastToString +' LIKE (select value+'+ QUOTENAME('%','''') +' FROM #temp where keyid = ' + #p_KeyValue + ')'
--END WITH
--ex:vcInvoiceNumber LIKE (select '%'+value FROM #temp where keyid = 2)
WHEN #p_Command = 4
THEN #l_CastToString +' LIKE (select '+ QUOTENAME('%','''') +'+value FROM #temp where keyid = ' + #p_KeyValue + ')'
--END WITH
--ex:vcInvoiceNumber LIKE (select '%'+value+'%' FROM #temp where keyid = 2)
WHEN #p_Command = 8
THEN #l_CastToString +' LIKE (select '+ QUOTENAME('%','''') +'+value+'+ QUOTENAME('%','''') +' FROM #temp where keyid = ' + #p_KeyValue + ')'
--greater than
--ex: iSerialNumber > (select CAST(value as INT) FROM #temp where keyid = 1)
WHEN #p_Command = 16
THEN #p_Field +' > (select '+#l_CastToType+' FROM #temp where keyid = ' + #p_KeyValue + ')'
--greater than equal
--ex: iSerialNumber >= (select CAST(value as INT) FROM #temp where keyid = 1)
WHEN #p_Command = 32
THEN #p_Field +' >= (select '+#l_CastToType+' FROM #temp where keyid = ' + #p_KeyValue + ')'
--Less than
--ex: iSerialNumber < (select CAST(value as INT) FROM #temp where keyid = 1)
WHEN #p_Command = 64
THEN #p_Field +' < (select '+#l_CastToType+' FROM #temp where keyid = ' + #p_KeyValue + ')'
--less than equal
--ex: iSerialNumber <= (select CAST(value as INT) FROM #temp where keyid = 1)
WHEN #p_Command = 128
THEN #p_Field +' <= (select '+#l_CastToType+' FROM #temp where keyid = ' + #p_KeyValue + ')'
--less than equal
--ex: iSerialNumber != (select CAST(value as INT) FROM #temp where keyid = 1)
WHEN #p_Command = 256
THEN #p_Field +' != (select '+#l_CastToType+' FROM #temp where keyid = ' + #p_KeyValue + ')'
--default to an empty string
ELSE ''
END
if #l_Return != '' and LEN(#p_WhereClause) > 1
begin
set #l_Return = ' AND ' + #l_Return
end
-- Return the result of the function
RETURN #l_Return
END
So I was requested to make a few things.... (it is Monday morning and for some reason this whole thing is turning out to be really hard for me to explain so I am just going to try and post a lot of my code; sorry) Oh - the table idea has to stay. Anything else can be changed but the idea of this table and the parsed field was not my idea but it is my responsibility to execute and make work.
Edit: Sorry the post is long. I do suggest a few possible solutions throughout but my problem is ultimately how everything is returned in a dynamically defined table/table variable from something (sp,view,function,anything...). Please take a minute to read the whole post...
First, I needed a table:
CREATE TABLE TICKET_INFORMATION (
TICKET_INFO_ID INT IDENTITY(1,1) NOT NULL,
TICKET_TYPE INT,
TARGET_ID INT,
TARGET_NAME VARCHAR(100),
INFORMATION VARCHAR(MAX),
TIME_STAMP DATETIME DEFAULT GETUTCDATE()
)
-- insert this row for testing...
INSERT INTO TICKET_INFORMATION (TICKET_TYPE, TARGET_ID, TARGET_NAME, INFORMATION) VALUES (1,1,'RT_ID','IF_ID,int=1&IF_ID,int=2&OTHER,varchar(10)=val,ue3&OTHER,varchar(10)=val,ue4')
The Information column holds data that needs to be parsed into a table. This is where I am having problems. In the resulting table, Target_Name needs to become a column that holds Target_ID as a value for each row in the resulting table.
The string that needs to be parsed is in this format:
#var_name1,#var_datatype1=#var_value1&#var_name2,#var_datatype2=#var_value2&#var_name3,#var_datatype3=#var_value3
And what I ultimately need as a result (in a table or table variable):
RT_ID IF_ID OTHER
1 1 val,ue3
1 2 val,ue3
1 1 val,ue4
1 2 val,ue4
And I need to be able to join on the result. Initially, I was just going to make this a function that returns a table variable but for some reason I can't figure out how to get it into an actual table variable. Whatever parses the string needs to be able to be used directly in queries so I don't think a stored procedure is really the right thing to be using.
This is the code that parses the Information string... it returns in a temporary table. In the end, this needs to be passed either just the information string or an ID number or something... doesn't matter what. Or somehow it can be in a view with the ticket_information table.
-- create/empty temp table for var_name, var_type and var_value fields
if OBJECT_ID('tempdb..#temp') is not null drop table #temp
create table #temp (row int identity(1,1), var_name varchar(max), var_type varchar(30), var_value varchar(max))
-- just setting stuff up
declare #target_name varchar(max), #target_id varchar(max), #info varchar(max)
set #target_name = (select target_name from ticket_information where ticket_info_id = 1)
set #target_id = (select target_id from ticket_information where ticket_info_id = 1)
set #info = (select information from ticket_information where ticket_info_id = 1)
--print #info
-- some of these variables are re-used later
declare #col_type varchar(20), #query varchar(max), #select as varchar(max)
set #query = 'select ' + #target_id + ' as ' + #target_name + ' into #target; '
set #select = 'select * into ##global_temp from #target'
declare #var_name varchar(100), #var_type varchar(100), #var_value varchar(100)
declare #comma_pos int, #equal_pos int, #amp_pos int
set #comma_pos = 1
set #equal_pos = 1
set #amp_pos = 0
-- while loop to parse the string into a table
while #amp_pos < len(#info) begin
-- get new comma position
set #comma_pos = charindex(',',#info,#amp_pos+1)
-- get new equal position
set #equal_pos = charindex('=',#info,#amp_pos+1)
-- set stuff that is going into the table
set #var_name = substring(#info,#amp_pos+1,#comma_pos-#amp_pos-1)
set #var_type = substring(#info,#comma_pos+1,#equal_pos-#comma_pos-1)
-- get new ampersand position
set #amp_pos = charindex('&',#info,#amp_pos+1)
if #amp_pos=0 or #amp_pos<#equal_pos set #amp_pos = len(#info)+1
-- set last variable for insert into table
set #var_value = substring(#info,#equal_pos+1,#amp_pos-#equal_pos-1)
-- put stuff into the temp table
insert into #temp (var_name, var_type, var_value) values (#var_name, #var_type, #var_value)
-- is this a new field?
if ((select count(*) from #temp where var_name = (#var_name)) = 1) begin
set #query = #query + ' create table #' + #var_name + '_temp (' + #var_name + ' ' + #var_type + '); '
set #select = #select + ', #' + #var_name + '_temp '
end
set #query = #query + ' insert into #' + #var_name + '_temp values (''' + #var_value + '''); '
end
if OBJECT_ID('tempdb..##global_temp') is not null drop table ##global_temp
exec (#query + #select)
--select #query
--select #select
select * from ##global_temp
Okay. So, the result I want and need is now in ##global_temp. How do I put all of that into something that can be returned from a function (or something)? Or can I get something more useful returned from the exec statement? In the end, the results of the parsed string need to be in a table that can be joined on and used... Ideally this would have been a view but I guess it can't with all the processing that needs to be done on that information string.
Ideas?
Thanks!
Edit: Still looking for answers to this. I would love to have a function that returns a table variable but I don't know how to get the results into a table variable. The result is currently in a global temporary table. Would it work if I defined my table variable in the dynamic portion of the code and then it would just magically be there to return? Or can I somehow select into a table variable from my global temp table, without first defining the columns of the table variable? Or can I create the table variable when I execute the dynamic part? The whole problem is because the columns of the end result are dynamic....... so..... I'm not sure how I could clarify the issues I'm having more. If a function that returns a table is a good route to go - could someone please provide me with code or a link as an example for returning a table variable with a dynamic column definition from a function? Plz, thnx.
You could use a table valued function. This would allow you to return the results as a table to be joined to just like you asked for.
CREATE FUNCTION dbo.GET_TICKET_INFORMATION (... some parameters... )
RETURNS #TICKET_INFORMATION TABLE
(
TICKET_INFO_ID INT IDENTITY(1,1) NOT NULL,
TICKET_TYPE INT,
TARGET_ID INT,
TARGET_NAME VARCHAR(100),
INFORMATION VARCHAR(MAX),
TIME_STAMP DATETIME DEFAULT GETUTCDATE()
)
AS
...
I used stored procedure for returning a table with a dynamic column definition
i generated dynamic name for global table:
declare #sp varchar(3)
set #sp = cast( ##spid as varchar(3) )
if object_id ( N'tempdb.dbo.#Periods' ) is not null
drop table #Periods
if object_id ( N'tempdb.dbo.##Result' + #sp ) is not null
execute ( 'drop table ##Result' + #sp )
i have sp for return periods table:
create table #Periods
(
[PERIOD_NUM] int
,[START_DATE] datetime
,[END_DATE] datetime
)
insert into #Periods
exec GET_PERIODS_TABLE_SP #pFromDate, #pToDate, #pPeriodType, 0
some fields in result table are dynamic:
select #PeriodCount = ...
declare #PeriodsScript varchar(max)
set #PeriodsScript = ''
set #i = 1
while #i <= #PeriodCount
begin
set #PeriodsScript = #PeriodsScript + ',PERIOD' + cast (#i as varchar(3))
set #i = #i + 1
end
generated and inserted data into ##Result:
declare #script varchar(max)
set #script = 'create table ##Result' + #sp +
'(ROW_NUM int'+
',BRANCH_ID int' +
',PARAM_NAME varchar(25)' +
#PeriodsScript + ')'
execute ( #script )
execute(
'insert into ##Result' + #sp + '( ROW_NUM, BRANCH_ID, NOM_SIZE_ID, PARAM_NAME )' +
'select ( row_number() over( order by BRANCH_ID, NOM_SIZE_ID ) - 1 ) * 3 + 1' +
' ,BRANCH_ID' +
' ,NOM_SIZE_ID' +
' ,''Min.price''' +
' from ( ' +
' select distinct BRANCH_ID' +
' ,NOM_SIZE_ID' +
' from ##ResultNomSizePrices' + #sp +
' ) as t'
)
and finaly, select from result table:
set #script =
'select distinct gb.TINY_NAME'+
' ,r.GROUP_NAME_1 as group1'+
' ,r.GROUP_NAME_2 as group2'+
' ,r.GROUP_NAME_3 as group3'+
' ,r.PARAM_NAME'+
' ,r.ROW_NUM'+
#PeriodsScript +
' from ##Result' + #sp + ' as r'+
' inner join dbo.GD_BRANCHES as gb'+
' on r.BRANCH_ID = gb.BRANCH_ID'+
' order by gb.TINY_NAME'+
' ,r.GROUP_NAME_1'+
' ,r.GROUP_NAME_2'+
' ,r.GROUP_NAME_3'+
' ,r.ROW_NUM'
execute ( #script )
p.s. sry for my english
is it possible to do dynamic declarations?
I will explain: I have a table COLUMNAMES:
ID|Name
1|Country
2|City
3|District
4|Neighbourhood
For each record in that table I would like to do something like:
declare #i int = 1
declare #number int
set #number = (SELECT count(*) FROM COLUMNNAMES)
While #i <= #number
BEGIN
Execute ('Declare column' + #i +'varchar(25)')
Execute ('set column' + #i +' = (Select NAME from COLUMNAMES where id = ' + #i)
set #i = #i + 1
END
The idea is that I get a list of variables (strings) that I can use to create SELECT statements with dynamic table-aliases:
Execute ('Select SOMECOLUMN as ' + #columname + #i +', ANOTHERCOLUMN as ' + #columname + #i +', ATHIRDCOLUMN as ' + #columname + #i + ' FROM SOMETABLE')
Can this be done? If so, how?
Each Execute function as a different session.
So, in order to declare the variable, all the code must be in one Execute function.
No you can't declare variables like this, but you can use a temporary table with the data filled in.
Here is some help, but it is not a whole solution, just an idea what you can do instead of the not working declaration:
Create Table #ColumnNames(
NAME varchar(64)
)
While #i <= #number
BEGIN
INSERT INTO #ColumNames
Select NAME from COLUMNAMES where id = #i
set #i = #i + 1
END
DECLARE #Columns varchar(max)
SET #Columns = ''
SElECT #Columns = #Columns + NAME + ', '
FROM #ColumNames
This is not complete solution but a direction . you need to get the value 'SomeColumn' dynamically someway ,likely the way you are getting the aliases in the below solution.
declare #i int = 1
declare #number INT
DECLARE #ColName VARCHAR(25)
DECLARE #SQL VARCHAR(4000)=''
DECLARE #ColumnsWithAlias VARCHAR(4000) = ''
set #number = (SELECT count(*) FROM COLUMNNAMES)
While #i <= #number
BEGIN
Select #ColName= NAME from COLUMNAMES where id = #i)
SET #ColumnsWithAlias =#ColumnsWithAlias + 'SomeColumn'+ ' AS '+ #ColName + ' , '
set #i = #i + 1
END
SET #SQL= 'SELECT '#ColumnsWithAlias+' FROM TableName'
EXECUTE(#SQL)