Loop through SQL Table valued parameter - sql-server

I'm trying to achieve a advanced search functionality for my application in which i have a SQL Table Valued Parameter in the following structure,
ColumnName Operator Keyword
------------------------------------
Name StartsWith Ram
City Equals Chennai
My SQL table,
Name City CreatedDate
-----------------------------------
Ram Chennai 10/10/2014
Ramachan Kovai 02/03/2015
How can i loop thorough this TVP so that i can build the WHERE clause and can append it to the SELECT query which is faster since i have some 10 rows of search values(criteria).
The filters are associated with AND operator.
List of operators used:
Equals
Not equals
Starts with
Ends with
From(Date)
To(Date)

You can create a dynamic filtered expression like below and use it in your SQL. You need to be very careful when adding editing filters in your TVP and verifying it against respective datatypes as well
Create Type and Base Table with Data
/*
CREATE TYPE FilterTVP AS TABLE
(
ColumnName VARCHAR(30), Operator VARCHAR(30), Keyword VARCHAR(30)
);
GO
CREATE TABLE myTable
(
Name VARCHAR(50),
City VARCHAR(50),
CreatedDate DATE
)
INSERT INTO myTable VALUES('Ram','Chennai','10/10/2014'),('Ramachan','Kovai','02/03/2015')
*/
Query
DECLARE #Param FilterTVP
INSERT INTO #Param VALUES('Name','StartsWith','Ram'),('City','Equals','Chennai'),('CreatedDate','From','2014-05-05')
DECLARE #FilterExp NVARCHAR(MAX)
SELECT #FilterExp =
(SELECT
' AND ' + QUOTENAME(ColumnName,'[') + ' ' +
CASE Operator
WHEN 'Equals'
THEN '='
WHEN 'Not equals'
THEN '<>'
WHEN 'StartsWith'
THEN 'LIKE'
WHEN 'Endswith'
THEN 'LIKE'
WHEN 'From'
THEN '>='
WHEN 'To'
THEN '<='
END + ' ' +
CASE
WHEN Operator = 'Startswith' THEN QUOTENAME(Keyword + '%','''')
WHEN Operator = 'Endswith' THEN QUOTENAME('%' + Keyword ,'''')
ELSE QUOTENAME(Keyword,'''')
END
FROM #Param
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)')
SET #FilterExp = 'SELECT * FROM myTable WHERE 1=1 ' + ISNULL(#FilterExp,'')
PRINT #FilterExp
EXEC sp_executeSQL #FilterExp
Output
SQL Fiddle
Name City CreatedDate
--------------------------
Ram Chennai 2014-10-10

Build your statement and then execute it like for example:
CREATE TABLE f
(
ColumnName NVARCHAR(MAX) ,
Operator NVARCHAR(MAX) ,
KeyWord NVARCHAR(MAX)
)
CREATE TABLE t
(
Name NVARCHAR(MAX) ,
City NVARCHAR(MAX)
)
INSERT INTO f
VALUES ( 'Name', 'StartsWith', 'Ram' ),
( 'City', 'Equals', 'Chennai' )
INSERT INTO t
VALUES ( 'Ram', 'Chennai' ),
( 'Ramachan', 'Kovai' )
DECLARE #op NVARCHAR(MAX) ,
#v NVARCHAR(MAX)
DECLARE #statement NVARCHAR(MAX) = 'SELECT * FROM t WHERE Name '
SELECT #op = Operator ,
#v = KeyWord
FROM f
WHERE ColumnName = 'Name'
SET #statement = #statement + CASE #op
WHEN 'StartsWith' THEN 'LIKE ''' + #v + '%'''
ELSE ' = ''' + #v + ''''
END + ' AND City'
SELECT #op = Operator ,
#v = KeyWord
FROM f
WHERE ColumnName = 'City'
SET #statement = #statement + CASE #op
WHEN 'StartsWith' THEN 'LIKE ''%' + #v + '%'''
ELSE ' = ''' + #v + ''''
END
EXEC(#statement)
Output:
Name City
Ram Chennai

Related

How to Pass Tablename, Fieldnames, Values as perameters in Stored Procedure using CI?

I am developing a custom application using CodeIgniter and MSSQL Server. Here i am using stored procedures.
Now i am wondering to implement codeigniter query type functionality where i can create a universal stored procedure in SQL Server and at the time of using i can pass tablename, array of fields and values.
It can work for both insert and update.
Something like we do in CodeIgniter to execute the query,
$data = array('fieldname1' => 'value1',
'fieldname2' => 'value2');
$this->db->insert($tablename,$data);
Just like this if we can pass the table name and array of the data to stored procedure and stored procedure automatically execute it.
If this can be done, it can save lots n lots of man hours. If anyone have already done i will be very much happy to see the solution.
You need to make string very specific in this case.
Figure out your table name, Column name, Column values for insert. For update 2 more parameters are required Id column name and its value.
GO
---- exec InsertUpdate 'tablename', 'col1, col2, col3', 'val1, val2, val3', 'idcol', 'idval'
GO
Create proc InsertUpdate
( #TableName nvarchar(500),
#ColName nvarchar(max),
#ColValues nvarchar(max),
#IDColName nvarchar(100) = '', --- for update only otherwise null
#IdColValue nvarchar(Max) = '' --- for update only otherwise null
)
As
Begin
declare #Query nvarchar(max)
if (#IdColValue = '')
Begin
set #Query = ' Insert into ' + #TableName + ' (' + #ColName + ') values (' + #ColValues + ')'
End
Else
Begin
;with CtColumn as (
select ROW_NUMBER() over (order by (select 1000)) as Slno, * from Split(#ColName,',') )
, CtValue as (
select ROW_NUMBER() over (order by (select 1000)) as Slno, * from Split(#ColValues, ','))
, CTFinal as (
select CCOl.Slno, CCOl.Items as ColName, CVal.Items as ColValue from CtColumn as CCOl inner join CtValue as CVal on CCOl.Slno=CVal.Slno )
select #Query = 'update ' + #TableName + ' set ' +
stuff ( (select ',' + ColName + '=' + ColValue from CTFinal for xml path ('')) ,1,1,'') +
' where ' + #IDColName + '=' + #IdColValue
End
exec sp_executesql #Query
End
Go

populate two variables in one query

I am writing a stored procedure with a pivot in it. The pivot field names can change depending on the data in the table.
So I have the two variables below. However this seems quite inefficient because I run two queries on the same table, this is probably due to my lack of knowledge.
declare #code nvarchar(max) = ''
select #code = #code + '[' + Code + '],' from (select Code from myTbl) as c
set #code = substring(#code , 1, len(#code ) - 1)
declare #Name nvarchar(max) = ''
select #Name = #Name + '[' + Name + '],' from (select Name from myTbl) as c
set #Name = substring(#Name , 1, len(#Name ) - 1)
Is it possible to populate both variables and only query the table once?
Yes you can, here is a simple sample
CREATE TABLE T(
Code VARCHAR(45),
Name VARCHAR(45)
);
INSERT INTO T VALUES
('Code1', 'Name1'),
('Code2', 'Name2');
DECLARE #Code VARCHAR(MAX) = '',
#Name VARCHAR(MAX) = '';
SELECT #Code = #Code + QUOTENAME(Code) + ',',
#Name = #Name + QUOTENAME(Name) + ','
FROM T;
SELECT #Code, #Name;
Returns:
+------------------+------------------+
| No column name) | (No column name) |
+------------------+------------------+
| [Code1],[Code2], | [Name1],[Name2], |
+------------------+------------------+
If you have SQL Server 2017, then no need to use substring, you can just use STRING_AGG() as
SELECT STRING_AGG(QUOTENAME(Code), ','),
STRING_AGG(QUOTENAME(Name), ',')
FROM T;
Returns:
+------------------+------------------+
| (No column name) | (No column name) |
+------------------+------------------+
| [Code1],[Code2] | [Name1],[Name2] |
+------------------+------------------+
I have stripped down the subquery (select Code from myTbl) as c since I do not think it added something in this context.
Given that. I believe it could work like this:
declare #code nvarchar(max) = ''
declare #Name nvarchar(max) = ''
select #code = #code + '[' + Code + '],', #Name = #Name + '[' + Name + '],' from myTbl
set #code = substring(#code , 1, len(#code ) - 1)
set #Name = substring(#Name , 1, len(#Name ) - 1)

search a string in a table without knowing the column

Note: this is NOT asking
how to select a string where the column name is known.
how to select a string in ALL tables (all google results relate to this one)
This is asking search in only ONE table.
SQL returns error info conversion failed when converting the nvarchar value S3N2V5.
I want to locate the column name where S3N2V5 exists.
No manual methods please. There are 1000000 columns.
Input S3N2V5
Output columnname1ofthistable
Assuming I understand the question, here is one way to get a list of all columns from a single table that contain the search value, using CASE:
Create and populate sample table (Please save us this step in your future questions)
CREATE TABLE T
(
COL1 char(3),
COL2 char(3),
COL3 char(3),
COL4 int
)
INSERT INTO T VALUES
('abc', 'def', 'nop', 1),
('klm', 'nop', 'qrs', 2),
('tuv', 'wzy', 'zab', 3)
Build your dynamic sql:
DECLARE #Search nvarchar(5) = 'nop'
DECLARE #SQL nvarchar(max) = 'SELECT CASE #Search'
SELECT #SQL = #SQL +' WHEN '+ COLUMN_NAME + ' THEN '''+ COLUMN_NAME +''''
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'T'
AND LOWER(DATA_TYPE) LIKE '%char%' -- only search char, varchar, nchar and nvarchar columns
SELECT #SQL = 'SELECT ColumnName FROM (' +
#SQL + ' END As ColumnName FROM T) x WHERE ColumnName IS NOT NULL'
Execute: (Note that using sp_executeSQL is SQL Injection safe, since we do not concatenate the search parameter into the query, but using it as a parameter)
EXEC sp_executeSQL #SQL, N'#Search nvarchar(5)', #Search
Results:
ColumnName
COL3
COL2
DECLARE #MyValue NVarChar(4000) = 'searchstring';
SELECT S.name SchemaName, T.name TableName
INTO #T
FROM sys.schemas S INNER JOIN
sys.tables T ON S.schema_id = T.schema_id;
WHILE (EXISTS (SELECT * FROM #T)) BEGIN
DECLARE #SQL NVarChar(4000) = 'SELECT * FROM $$TableName WHERE (0 = 1) ';
DECLARE #TableName NVarChar(1000) = (
SELECT TOP 1 SchemaName + '.' + TableName FROM #T
);
SELECT #SQL = REPLACE(#SQL, '$$TableName', #TableName);
DECLARE #Cols NVarChar(4000) = '';
SELECT
#Cols = COALESCE(#Cols + 'OR CONVERT(NVarChar(4000), ', '') + C.name + ') = CONVERT(NVarChar(4000), ''$$MyValue'') '
FROM sys.columns C
WHERE C.object_id = OBJECT_ID(#TableName);
SELECT #Cols = REPLACE(#Cols, '$$MyValue', #MyValue);
SELECT #SQL = #SQL + #Cols;
select substring(#SQL,charindex('.',#SQL)+1,charindex('(',#SQL)-charindex('.',#SQL)-8) as 'TableName'
EXECUTE(#SQL);
DELETE FROM #T
WHERE SchemaName + '.' + TableName = #TableName;
END;
DROP TABLE #T;
This will give you table Name and the entire row from the table which contains the searchstring.
Apart from anwswers mentioned in post : Older Post
1) (using column name) SELECT table_name,table_schema FROM INFORMATION_SCHEMA.COLUMNS WHERE column_name='sort_method';
I hope better you can take dump ( in.sql format ) and you can easily search the content using IDEs like N++.

TSQL Control Flow Logic

I am working on building a procedure that uses basic dynamic SQL. I want to use the result of the dynamic SQL (#query) in another part of said procedure. Below is a shorthand version of the code I am attempting to complete.
WITHOUT THE USE OF sp_executesql, how can I go about passing the result value of #query into the IF blocks?
DECLARE #table VARCHAR(MAX)
DECLARE #query VARCHAR(MAX)
DECLARE #map VARCHAR(MAX)
SET #table = 'SomeTable'
SET #query = '
;WITH Assignment AS
(
SELECT
''' + #table + ''' AS src
,Type
,RANK () OVER(ORDER BY COUNT(type) as rnk
FROM ' + #table + '
GROUP BY Type
)
SELECT Type
FROM Assignment
WHERE rnk = ''1'''
IF (#query = 'typeA')
BEGIN
/* preform an upsert dynamically */
END
IF (#query = 'typeB')
BEGIN
/* preform a delete dynamically */
END
IF (#query = 'typeC')
BEGIN
/* preform an alter dynamically */
END
Why are you testing #query right after it has been set with some SQL?
You could do it with a temp table:
Create Table #temp(type...)
SET #query = '
;WITH Assignment AS
(
SELECT
''' + #table + ''' AS src
,Type
,RANK () OVER(ORDER BY COUNT(type) as rnk
FROM ' + #table + '
GROUP BY Type
)
Insert Into #temp(type)
SELECT Type
FROM Assignment
WHERE rnk = ''1'''
You can also build your dynamic query in your if statement although I am not sure it would work in your case:
SET #q1 = '
;WITH Assignment AS
(
SELECT
''' + #table + ''' AS src
,Type
,RANK () OVER(ORDER BY COUNT(type) as rnk
FROM ' + #table + '
GROUP BY Type
)'
set #q2 = 'SELECT Type
FROM Assignment
WHERE rnk = ''1'''
Case When #type = 'A' then #query = #q1 + 'Insert into... ' + #q2
Case When #type = 'B' then #query = #q1 + 'Update... ' + #q2
Case When #type = 'B' then #query = #q1 + 'delete from where type in (' + #q2 + ')' end
If you change you mind, it is also easy with sp_executesql:
create table #temp(type int)
insert into #temp
exec sp_executesql #query
or if there are not thousands of rows:
declare #temp table(type int)
insert into #temp
exec sp_executesql #query
If there is only one row, still with sp_executesql and a parameter, this is the best option:
declare #type varchar(10)
SET #query = '
declare #type varchar(10)
;WITH Assignment AS
(
SELECT
''' + #table + ''' AS src
,Type
,RANK () OVER(ORDER BY COUNT(type) as rnk
FROM ' + #table + '
GROUP BY Type
)
SELECT #type = Type
FROM Assignment
WHERE rnk = ''1''';
exec sp_executesql #query, N'#type varchar(10)', #type = #type
This is one way to get data out of dynamic SQL
DECLARE #SQL VARCHAR(MAX)
--Dynamic SQL
SET #SQL = '
--Do anything you like in here as long as you select the results in the #Data Table format at the end
SELECT 132'
--How to get the result out of the dynamic SQL (into a table)
DECLARE #Data TABLE (Value INT)
INSERT INTO #Data(Value)
EXEC(#SQL)
--Get the result out of the table into a local (if you need to)
DECLARE #MyValue INT
SELECT #MyValue = Value FROM #Data
--Do what you like with the value now we are back in normal SQL
PRINT #MyValue

SQL Server: executing dynamic/inline sql table name within EXEC and passing geometry as geometry parameter

I'm trying to execute an inline SQL statement within a stored procedure. I'm working with SQL Server 2008.
The problem is that I can't execute the first inline statement (with WHERE clause). It crashes because the string within EXEC(...) is dynamically created and all concatenated variables must be of type varchar.
Error that appears when calling procedure:
An expression of non-boolean type specified in a context where a
condition is expected, near 'ORDER'.
The procedure looks like:
CREATE PROCEDURE loadMyRows
#table_name nvarchar(50),
#bounding_box varchar(8000)
AS
BEGIN
-- *********************************** COMMENT *********************************
-- ** This two code lines are correct and will return true (1) or false (0), **
-- ** but they doesn't work within inline EXEC(...) **
--DECLARE #bb geometry = geometry::STGeomFromText(#bounding_box, 4326);
--select TOP(5) wkt.STWithin(#bb) AS 'bool'
-- *********************************** COMMENT *********************************
IF #bounding_box <> ''
BEGIN
DECLARE #bb geometry = geometry::STGeomFromText(#bounding_box, 4326);
EXEC(
'SELECT TOP (' + #row_limit + ') * ' +
'FROM ' + #real_table_name + ' ' +
'WHERE wkt.STWithin('+#bb+') ' + -- <-- doesn't work :-(
-- 'WHERE wkt.STWithin(geometry::STGeomFromText('''+#bounding_box+''', 4326)) ' +
-- ^^ doesn't work, too :-(
'ORDER BY id ASC '
);
END
ELSE
BEGIN
EXEC(
'SELECT TOP (' + #row_limit + ') * ' +
'FROM ' + #real_table_name + ' ' +
'ORDER BY id ASC'
);
END
END
I've found a working solution for this problem. The way the MSDN showed me was http://msdn.microsoft.com/en-US/library/ms175170.aspx. There's written:
[...] the string is executed as its own self-contained batch.
That let me know, if I want to execute a dynamic statement with a table variable as string, it's the same as I would execute the query without the EXECUTE command, like:
SELECT TOP(#row_limit) *
FROM #real_table_name
WHERE ...
ORDER BY id ASC;
And this would probably not work for the table name.
So, if I write instead:
DECLARE #sql_statement nvarchar(MAX) = 'SELECT TOP(#limit) *
FROM ' + #real_table_name + '
ORDER BY id ASC';
-- declaration of parameters for above sql
DECLARE #sql_param_def nvarchar(MAX) = '#limit int';
EXECUTE sp_executesql #sql_statement, #sql_param_def, #limit = #row_limit;
Then, this would work. This is because I define the #sql_statement simply as a concatenated string which will just resolve the dynamic table name at runtime to a string with the name of the real existing table. The #limit parameter is untouched and is still a parameter.
If we then execute the batch we only must pass a value for the #limit parameter and it works!
For the geometry parameter it works in the same way:
DECLARE #bb geometry = geometry::STGeomFromText(#bounding_box, 4326);
SET #sql_statement = 'SELECT TOP(#limit) *
FROM ' + #real_table_name + '
WHERE wkt.STWithin(#geobb) = 1
ORDER BY id ASC';
-- NOTE: This ' = 1' must be set to avoid my above described error (STWithin doesn't return a BOOLEAN!!)
-- declaration of parameters for above sql
SET #sql_param_def = '#limit int, #geobb geometry';
EXECUTE sp_executesql #sql_statement, #sql_param_def, #limit = #row_limit, #geobb = #bb;
Hope this was clear ;-)
create proc usp_insert_Proc_Into_temp
#tempTable nvarchar(10) output
as
begin
set #tempTable = '##temp'
declare #query nvarchar(200)
--Select statement
set #query = 'select 1 as A,2 as B, 3 as C into'+ ' '+#tempTable+''
exec(#query)
end
go
declare #tempTable nvarchar(10)
exec usp_insert_Proc_Into_temp #tempTable output
exec('select * from' + ' '+ #tempTable+'')
exec ('drop table'+ ' '+#tempTable+'')

Resources