Dynamic queries and Dynamic Where clauses Solution - sql
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
Related
SQL while inserting into temp tables error stating already exist but I clean up tables prior
I am not sure why this is happening, but when I run some code that takes a long multi-value sub-value string, I'm getting a random error. The error is supposedly that I'm trying to do a insert into a temp table that already exists, though I'm not sure how this is possible since inside the top of the first base loop, I'm dropping the tables if they do exist. That way the prior run can use them, them drop and run through again if need be. Now I'm almost certain another error is failing, then causing a misleading return message but not sure. Here is the full stored procedure. Also, I'm a stack dev so if you have any good pointers for this query please feel free to make note. Thanks in advance. DATA string for testing that would possibly come into this procedure: MITUTOYO~103-217~DESC~ ^BROWN & SHARPE~73~DESC~ ^MITUTOYO~103-188~DESC~ ^MITUTOYO~103-224A~DESC~ ^MITUTOYO~103-225A~DESC~ ^MITUTOYO~103-225A~DESC~ ^MITUTOYO~103-189A~DESC~ ^ For each one of the main multi values within the ^ character, we would look up the Manufacturer and Model and see if it exist. If so return it, if not return it with a new suggestions list of multi-values in the suggestionsList column. Here is the example of one item sent in, then no matches found so does the fuzzy lookup. Manufacturer ManufacturerPartNumber ManufacturerDescription Price ItemType MfrFound ModelFound Score SuggestionGroup Analyzer Network, 0.00, NA, 0, 1, 0, STANDARD,,USE FOR HYPRO STANDARD BORE GAGES,31.00,6, ^ Harvey Wells,,FORCE GAUGE 0 35 GMS,93.50,0, ^ The full stored procedure: ALTER PROCEDURE DATA_FuzzyAssetMatchLARGE /* PROP: #LISTIN - The entire multi file string sent in from the data file of records. I=Since the records sent in are a multi value record of sub values meaning (v1.Mfr, v1.Model, v1.Description ^ v2.Mfr, v2.Model, v2.Description ^ ....) we could in theory have thousands of records to n(3) of string length unknown (n). This being said, we set the value to varchar(max). varchar(max) unlike varchar which holds (8000 characters max about 65k bytes), varchar(max) can hold 2 gigs worth of data. That equates to about 1 billion characters since in .NET and others one CHAR is a 2 byte system. This holds more than enough. */ #LISTIN VARCHAR(MAX) AS BEGIN SET NOCOUNT ON; SET ARITHABORT OFF SET ANSI_WARNINGS OFF SET FMTONLY OFF declare #errorMessage varchar(150) -- ideally would be nice to append to each row so we knew what failed but not break and continue. Not like that at moment -- ITEMS USED FOR TOP LEVEL RECURSIVE SPLIT LOOP -- declare #pos INT; declare #len INT; declare #value varchar(max); -- the value of the first splits (objects) delimed by the ^ character -- Counter variables used to loop and build the one column Suggestion of all matching items declare #MaxCount integer; declare #Count integer; declare #suggestionsStringBuilder varchar(350); --holds the built list of any suggestions. declare #objectManufacturer varchar(100); declare #objectModel varchar(150); declare #objectDescription varchar(150); --the context for this instance to run the params selects for Mfr, Model, Desc declare #counterIndex int = 0; declare #objectString varchar(250); --USED FOR SECOND LEVEL RECURSIVE SPLIT LOOP declare #objectPosVal INT; declare #objectLenVal INT; declare #objectSubstring varchar(250); -- the value used for the secondary split (each object properties such as the actual Mfr, Model, Description, so on....) --cursor declarations DECLARE #suggestionsListCursor CURSOR; DECLARE #CURSOR_Mfr varchar(150); DECLARE #CURSOR_Model varchar(150); DECLARE #CURSOR_Desc varchar(150); DECLARE #CURSOR_Type varchar(20); DECLARE #CURSOR_Price MONEY; DECLARE #CURSOR_Score int; --TEMP TABLE TO STORE FUZZY PREDFINED TABLES IN IF OBJECT_ID('BASE') IS NOT NULL DROP TABLE BASE --CREATE THE TEMPTABLE TO WORK WITH CREATE TABLE BASE ( Manufacturer varchar(150) null, ManufacturerPartNumber Varchar(100) null, ManufacturerDescription varchar(150) null, Score int , Price money, ItemType varchar(20), ModelFound bit not null, MfrFound bit not null, SuggestionGroup varchar(MAX) null ) --THIS IS THE LOOP TO PREPARE THE DELIM STRING AND DO A FUZZY MATH OR STRAIGHT MATCH ON EACH OBJECT ROW set #pos = 0; set #len = 0; while charindex('^', #LISTIN, #pos+1)>0 begin if object_id('tempdb..#TEMPPMFR') is not null drop table #TEMPPMFR if object_id('tmpdb..#TEMPPMFRMODEL') is not null drop table #TEMPPMFRMODEL -- stop if there was an error -- if(#errorMessage is not null and #errorMessage != '') break; set #suggestionsStringBuilder = ''; set #len = charindex('^', #LISTIN, #pos+1) - #pos; set #VALUE = substring(#LISTIN,#POS,#LEN-1); set #objectLenVal = 0; set #objectPosVal = 0; set #counterIndex = 0; set #suggestionsStringBuilder = '' set #objectManufacturer = '' set #objectModel = '' set #objectDescription = '' --THE LIST COMING IN IS A MULTI-DEMENSIONAL ARRAY OR MULTI-VALUE. IT IS A GROUP OF ENTITYIES, THEN EACH --ENTITYT IS A GROUP OF PROPERTIES. OUR OUTTER LOOP WE SPLIT ON ENTITITIES. -- EXAMPLE: #F, #M, #D = Manufacturer, odel, Description while charindex('~', #value, #objectPosVal+1)>0 begin set #objectLenVal = charindex('~', #value, #objectPosVal+1) - #objectPosVal set #objectSubstring = substring(#value, #objectPosVal, #objectLenVal) if(#counterIndex=0) set #objectManufacturer = LTRIM(RTRIM(#objectSubstring)) else if(#counterIndex=1) set #objectModel = LTRIM(RTRIM(#objectSubstring)) else begin set #objectDescription = LTRIM(RTRIM(#objectSubstring)) break end set #objectPosVal = charindex('~', #value, #objectPosVal+#objectLenVal) +1 end -- **** -- **** WE HAVE THE MANUFACTURER AND THE MODEL SO JUST RETURN THE DATA -- **** if((select top 1 1 FROM Products_OurProducts_Products where Manufacturer = #objectManufacturer and ManufacturerPartNumber = #objectModel) > 0) begin try insert into BASE ( Manufacturer ,ManufacturerPartNumber ,ManufacturerDescription ,Price,ItemType ,MfrFound ,ModelFound ,Score ,SuggestionGroup ) select POP.Manufacturer ,POP.ManufacturerPartNumber as Model ,POP.Description ,CONVERT(money,POP.Price) as Price ,POP.ItemType ,CAST('1' as bit) as MfrFound ,CAST('1' as bit) as ModelFound ,CAST('-1' as int) as Score ,'' as SuggestionGroup from Products_OurProducts_Products as POP where POP.Manufacturer = #objectManufacturer and POP.ManufacturerPartNumber = #objectManufacturer end try begin catch SET #errorMessage = ( select 'Number: ' + CAST(ERROR_NUMBER() as varchar(15)) + ' Message:' + ERROR_MESSAGE() AS ErrorMessage ); end catch else -- **** -- **** WE EITHER FOUND MANUFACTURER SO FUZZY ON MODEL OR VICE VERSA -- **** begin try if((select top 1 1 from Products_OurProducts_Products where Manufacturer = #objectManufacturer) > 0) begin --we have to build these temp tables os dynamic columns exist such as MfrFound, ModelFound select PMFR.Manufacturer ,PMFR.ManufacturerPartNumber ,PMFR.Description AS ManufacturerDescription ,convert(money,PMFR.Price) as Price ,PMFR.ItemType ,cast('1' as bit) as MfrFound ,cast('0' as bit) as ModelFound ,'' as SuggestionGroup into #TEMPPMFR from Products_OurProducts_Products as PMFR where PMFR.Manufacturer = #objectManufacturer set #SuggestionsListCursor = cursor for select top 5 P.Manufacturer ,P.ManufacturerPartNumber as Model ,P.ManufacturerDescription AS 'Description' ,P.Price ,fms.score as Score from #TEMPPMFR as P cross apply ( select dbo.FuzzyControlMatch(#objectModel, P.ManufacturerPartNumber) AS score ) as fms where P.Manufacturer = #objectManufacturer order by fms.score desc open #SuggestionsListCursor fetch next from #SuggestionsListCursor into #CURSOR_Mfr ,#CURSOR_Model ,#CURSOR_Desc ,#CURSOR_Price ,#CURSOR_Score while ##FETCH_STATUS = 0 begin if #suggestionsStringBuilder!='' set #suggestionsStringBuilder=#suggestionsStringBuilder + #CURSOR_Mfr + ',' + #CURSOR_Model + ',' + #CURSOR_Desc + ',' + convert(varchar(20),#CURSOR_Price) + ',' + convert(varchar(4),#CURSOR_Score) + ', ^ ' else set #suggestionsStringBuilder = #CURSOR_Mfr + ',' + #CURSOR_Model + ',' + #CURSOR_Desc + ',' + convert(varchar(20),#CURSOR_Price) + ',' + convert(varchar(4),#CURSOR_Score) + ', ^ ' fetch next from #SuggestionsListCursor into #CURSOR_Mfr ,#CURSOR_Model ,#CURSOR_Desc ,#CURSOR_Price ,#CURSOR_Score end --Now we insert the original Mfr, Model, Desc, and the suggestions list we build insert into BASE values( #objectManufacturer ,#objectModel ,#objectDescription ,'0' ,'0' ,'NA' ,'1' ,'0' ,#suggestionsStringBuilder ) close #SuggestionsListCursor deallocate #SuggestionsListCursor end else --IF HAVE A FUZZY AT MFR, THEN WE NEED TO GRAB BEST CHOICE AND GO DOWN. --WE COULD HAVE POSIBLY CANDIDATES FOR THIS SO WHEN TO STOP RECURSIVENESS AND SAY ADDING NEW ENTRY? begin --AT MOMENT JUST RETURN TOP FOUND MFR THEN SELECT FROM THAT TO SEE RESULT TESTS --FIRST LETS SEE IF SENT MODEL EXIST AND IF SO, PULL THAT THEN RANK AGAINST MFR FOR IT if((select top 1 1 from Products_OurProducts_Products where ManufacturerPartNumber = #objectModel) > 0) begin select Manufacturer ,ManufacturerPartNumber ,Description AS ManufacturerDescription ,CONVERT(money,Price) AS Price ,ItemType ,CAST('0' as bit) as MfrFound ,CAST('1' as bit) as ModelFound ,'' as SuggestionGroup into #TEMPPMFRMODEL from Products_OurProducts_Products where ManufacturerPartNumber = #objectModel set #SuggestionsListCursor = cursor for select top 5 P.Manufacturer ,P.ManufacturerPartNumber as Model ,P.ManufacturerDescription as 'Description' ,P.Price ,fms.score as Score from #TEMPPMFRMODEL AS P CROSS APPLY ( select dbo.FuzzyControlMatch(#objectManufacturer, P.Manufacturer) AS score ) AS fms where P.ManufacturerPartNumber = #objectModel order by fms.score desc --OPEN CURSOR NOW-- open #SuggestionsListCursor -- NOW LOOP THE RESULTS AND BUILD DEMLIMETER STRING OF SUGESTIONS FOR MFR-- fetch next from #SuggestionsListCursor into #CURSOR_Mfr, #CURSOR_Model, #CURSOR_Desc, #CURSOR_Price, #CURSOR_Score while ##FETCH_STATUS = 0 begin if #suggestionsStringBuilder != '' set #suggestionsStringBuilder = #suggestionsStringBuilder + #CURSOR_Mfr + ',' + #CURSOR_Model + ',' + #CURSOR_Desc + ',' + convert(varchar(20),#CURSOR_Price) + ',' + convert(varchar(4),#CURSOR_Score) + ', ^ ' else set #suggestionsStringBuilder = #CURSOR_Mfr + ',' + #CURSOR_Model + ',' + #CURSOR_Desc + ',' + convert(varchar(20),#CURSOR_Price) + ',' + convert(varchar(4),#CURSOR_Score) + ', ^ ' fetch next from #SuggestionsListCursor into #CURSOR_Mfr, #CURSOR_Model, #CURSOR_Desc, #CURSOR_Price, #CURSOR_Score end insert into BASE values( #objectManufacturer ,#objectModel ,#objectDescription ,'0' ,'0' ,'NA' ,'1' ,'0' ,#suggestionsStringBuilder ) close #SuggestionsListCursor ; deallocate #SuggestionsListCursor ; end else insert into BASE values( #objectManufacturer ,#objectModel ,#objectDescription ,'0' ,'0' ,'NA' ,'0' , '0' , '' ) end --THEN SEE IF EXIST USING FUZZY FOUND MFR, TO SEEK MODEL NUMBER ETC. END TRY BEGIN CATCH SET #errorMessage = ( select 'Number: ' + CAST(ERROR_NUMBER() as varchar(15)) + ' Message:' + ERROR_MESSAGE() AS ErrorMessage ); END CATCH SET #pos = CHARINDEX('^', #LISTIN, #pos+#len) +1 END if(#errorMessage is not null and #errorMessage != '') select #errorMessage as 'ErrorInfo' else select Manufacturer ,ManufacturerPartNumber ,ManufacturerDescription ,Price ,ItemType ,MfrFound ,ModelFound ,Score ,SuggestionGroup from BASE --NOW REMOVE THE TEMP TABLE If(OBJECT_ID('BASE') Is Not Null) BEGIN DROP TABLE BASE END IF(OBJECT_ID('tempdb..#TEMPPMFR') IS NOT NULL) BEGIN DROP TABLE #TEMPPMFR END IF(OBJECT_ID('tmpdb..#TEMPPMFRMODEL') IS NOT NULL) BEGIN DROP TABLE #TEMPPMFRMODEL END END GO
Select columns based on values from any table(number of columns is variable) SQL
I have a following table: Table 1 And I want to show only columns that have at least one value under 50: Table 2 I need a stored procedure that does this. The trick is that I want to use it on multiple tables but the number of columns can vary from table to table. Can this be done?
Wish this will have some help. SET NOCOUNT ON DECLARE #tablename VARCHAR(50) = 'Table1', #valuetocompare INT = 50, #otherfields VARCHAR(100) = 'Date, Hour,'; DECLARE #t AS TABLE (cname VARCHAR(10), cvalue INT) DECLARE #sql NVARCHAR(1000); DECLARE #cname VARCHAR(128); DECLARE c CURSOR FOR SELECT NAME FROM sys.[columns] AS c WHERE c.[object_id] = OBJECT_ID(#tablename) ; OPEN c; FETCH NEXT FROM c INTO #cname; WHILE ##FETCH_STATUS = 0 BEGIN SET #sql = 'select ''' + #cname + ''', ' + #cname + ' from ' + #tablename; INSERT INTO #t ( cname, cvalue ) EXECUTE (#sql); FETCH NEXT FROM c INTO #cname; END CLOSE c; DEALLOCATE c; DECLARE #cnames VARCHAR(100) = ''; WITH dcnames AS ( SELECT DISTINCT cname FROM #t WHERE cvalue < #valuetocompare ) SELECT #cnames = #cnames + cname + ',' FROM dcnames; IF #cnames = '' PRINT 'No column value is less than ' + CAST(#valuetocompare AS VARCHAR); ELSE BEGIN SET #sql = 'select ' + #otherfields + LEFT(#cnames, LEN(#cnames) - 1) + ' from ' + #tablename; EXECUTE (#sql); END
How to use a variable in SQL statement From Line
I am trying to create a procedure that allows for a variable to be used as a table name. Example: Select Temp1, Temp2 From #var The #var has the correct table name but changes with each iteration through the while loop. -- Above sets up all Variables WHILE #Count >= #I BEGIN SET #ObjectID = (SELECT ObjectID FROM #TmpTable WHERE ID = #I) SET #TableName = (SELECT TableName FROM #TmpTable WHERE ID = #I) IF #I = 8 BEGIN SELECT * INTO ##TMPTable FROM #TableName -- Additional Processing END END
DECLARE #SQLString varchar(max), #TempTableName varchar(100) SET #TempTableName = '##TMPTable' + cast( ##spid AS varchar(5)) WHILE #Count >= #I BEGIN SET #ObjectID = (SELECT ObjectID FROM #TmpTable WHERE ID = #I) SET #TableName = (SELECT TableName FROM #TmpTable WHERE ID = #I) IF #I = 8 BEGIN set #SQLString = ' SELECT * INTO ' + #TempTableName + ' FROM ' + #TableName EXECUTE ( #SQLString ) EXECUTE ( 'SELECT * from ' + #TempTableName) -- Additional Processing END END
T-SQL script refuses to continue after finishing a query-building loop
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.
Creating A Script To Replicate A Table And Its Contents?
I know you can create a script to replicate a table using: right click table > script table as > create to > new query editor window But how can I generate a script that contains a bunch of insert commands for each row in the table? Table1 Id1, Row1 Id2, Row2 Id3, Row3 Insert into Table1 values(Row1); Insert into Table1 values(Row2); Insert into Table1 values(Row3);
I ended up doing this right click database > Tasks > Generate Scripts ... > selected the tables > in the advanced options I set "Types of data to script" to "Schema and data"
Select 'Insert into Table ( IntField1 StringField2 Column3) values (' + IntField1 + ',' + + '''' + StringField2 + ''',' + Column2 + ')' as InsertQuery From Table Something like this, just remember if your string contains a single quote you will need to make sure you replace it like this replace(stringfield, '''', '''''')
So this isnt super pretty cuz I kind of took one of my sp's and hacked it up for this. But basically this will take any table and print a series of insert statements into a table called tbl_text (which you would need to create) The arguments are the table name and the table ID from sysobjects --this is how you get the tbl_id SELECT id FROM sysobjects WHERE type = 'U' AND name = 'tablename' CREATE PROCEDURE dbo.sp_export_table #tblhdr varchar(100), #tblID varchar(100) AS SET NOCOUNT ON IF object_id('tempdb..##temptable') IS NOT NULL BEGIN DROP TABLE ##temptable END DECLARE #identity bit DECLARE #typestmt nvarchar(100) DECLARE #typeval int DECLARE #rowstmt nvarchar(1000) DECLARE #rowID varchar(50) DECLARE #orderby nvarchar(100) DECLARE #clmnstmt varchar(200) DECLARE #clmnhdr varchar(50) DECLARE #clmnstring varchar(1000) DECLARE #valuestmt nvarchar(200) DECLARE #valuestring nvarchar(3000) DECLARE #value nvarchar(1000) DECLARE #insertstmt varchar(1000) DECLARE #params nvarchar(100) DECLARE #param2 nvarchar(100) SELECT #rowstmt = N'SELECT TOP 1 #inside_var = name FROM syscolumns WHERE id = ' + #tblID + ' ORDER BY colorder' SELECT #params = N'#inside_var NVARCHAR(1000) OUTPUT' EXEC sp_executesql #rowstmt, #params, #inside_var = #orderby OUTPUT SELECT #rowstmt = 'SELECT *, ROW_NUMBER() OVER (ORDER BY ' + #orderby + ') AS row INTO ##temptable FROM ' + #tblhdr exec(#rowstmt) IF object_id('tempdb..##temptable') IS NOT NULL BEGIN DECLARE row_cursor CURSOR FOR SELECT row FROM ##temptable OPEN row_cursor FETCH NEXT FROM row_cursor INTO #rowID --if table has identity and has records write identity_insert on SET #identity = 0 IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE OBJECTPROPERTY(OBJECT_ID(TABLE_NAME), 'TableHasIdentity') = 1 AND TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = #tblhdr) AND EXISTS(SELECT * FROM ##temptable) BEGIN SET #identity = 1 INSERT INTO dbo.tbl_text VALUES('SET IDENTITY_INSERT dbo.' + #tblhdr + ' ON') END WHILE ##FETCH_STATUS = 0 BEGIN SELECT #clmnstmt = 'DECLARE column_cursor CURSOR FOR SELECT name FROM syscolumns WHERE id = ' + #tblID + ' ORDER BY colorder' exec(#clmnstmt) OPEN column_cursor FETCH NEXT FROM column_cursor INTO #clmnhdr SELECT #clmnstring = '(' SELECT #valuestring = '(' WHILE ##FETCH_STATUS = 0 BEGIN IF #clmnhdr <> 'row' BEGIN SELECT #clmnstring = #clmnstring + #clmnhdr + ',' SELECT #valuestmt = N'SELECT #inside_var = ' + #clmnhdr + ' FROM ##temptable WHERE row = ' + #rowID EXEC sp_executesql #valuestmt, #params, #inside_var = #value OUTPUT SELECT #typestmt = N'SELECT #inside_var2 = xtype FROM syscolumns WHERE name = ''' + #clmnhdr + ''' AND id = ' + #tblID SELECT #param2 = N'#inside_var2 INT OUTPUT' EXEC sp_executesql #typestmt, #param2, #inside_var2 = #typeval OUTPUT IF #typeval NOT IN (48,52,56,59,60,62,104,108,122,127) BEGIN SET #value = REPLACE(#value,'''','''''') SET #value = '''' + #value + '''' SET #value = ISNULL(#value, '''''') END IF NOT (#typeval = 34) BEGIN SELECT #valuestring = #valuestring + #value + ',' END ELSE BEGIN SELECT #valuestring = #valuestring + '''''' + ',' END END FETCH NEXT FROM column_cursor INTO #clmnhdr END SET #clmnstring = LEFT(#clmnstring, LEN(#clmnstring) - 1) SET #valuestring = LEFT(#valuestring, LEN(#valuestring) - 1) INSERT INTO dbo.tbl_text VALUES('INSERT INTO dbo.' + #tblhdr + ' ' + #clmnstring + ') VALUES' + #valuestring + ')') FETCH NEXT FROM row_cursor INTO #rowID CLOSE column_cursor DEALLOCATE column_cursor END --if it wrote identity_insert on, turn it off IF (#identity = 1) BEGIN INSERT INTO dbo.tbl_text VALUES('SET IDENTITY_INSERT dbo.' + #tblhdr + ' OFF') END CLOSE row_cursor DEALLOCATE row_cursor END IF object_id('tempdb..##temptable') IS NOT NULL BEGIN DROP TABLE ##temptable END GO
If you've got an account on SSC, you can use the script I published last year. It works without cursors an it enables custom filtering. http://www.sqlservercentral.com/scripts/Script+Data/65998/ Hope this helps
Assuming Row is an INT NOT NULL. You could write a SELECT statement that outputs SQL; SELECT N'INSERT INTO Table1 VALUES (' + CAST(Row AS NVARCHAR(10)) + N');' FROM Table1 Then output your results to text.