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.