Passing delimited string to stored procedure to search database - sql

How can i pass a string delimited by space or comma to stored procedure and filter result?
I'm trying to do something like -
Parameter Value
--------------------------
#keywords key1 key2 key3
Then is stored procedure i want to first
find all records with first or last
name like key1
filter step 1 with first or last
name like key2
filter step 2 with first or last name like key 3
Another example:
col1 | col2 | col3
------------------------------------------------------------------------
hello xyz | abc is my last name | and i'm a developer
hello xyz | null | and i'm a developer
If i search for any following it should return for each?
"xyz developer" returns 2 rows
"xyz abc" returns 1 row
"abc developer"returns 1 row
"hello" returns 2 rows
"hello developer" returns 2 rows
"xyz" returns 2 rows

Since you can't use a table parameter (not on SQL Server 2008), try passing in a CSV sting and have the stored procedure split it into rows for you.
There are many ways to split string in SQL Server. This article covers the PROs and CONs of just about every method:
"Arrays and Lists in SQL Server 2005 and Beyond, When Table Value Parameters Do Not Cut it" by Erland Sommarskog
You need to create a split function. This is how a split function can be used:
SELECT
*
FROM YourTable y
INNER JOIN dbo.yourSplitFunction(#Parameter) s ON y.ID=s.Value
I prefer the number table approach to split a string in TSQL but there are numerous ways to split strings in SQL Server, see the previous link, which explains the PROs and CONs of each.
For the Numbers Table method to work, you need to do this one time table setup, which will create a table Numbers that contains rows from 1 to 10,000:
SELECT TOP 10000 IDENTITY(int,1,1) AS Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)
Once the Numbers table is set up, create this split function:
CREATE FUNCTION [dbo].[FN_ListToTable]
(
#SplitOn char(1) --REQUIRED, the character to split the #List string on
,#List varchar(8000)--REQUIRED, the list to split apart
)
RETURNS TABLE
AS
RETURN
( ----------------
--SINGLE QUERY-- --this will not return empty rows
----------------
SELECT
ListValue
FROM (SELECT
LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(#SplitOn, List2, number+1)-number - 1))) AS ListValue
FROM (
SELECT #SplitOn + #List + #SplitOn AS List2
) AS dt
INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
WHERE SUBSTRING(List2, number, 1) = #SplitOn
) dt2
WHERE ListValue IS NOT NULL AND ListValue!=''
);
GO
You can now easily split a space delimited string into a table and join on it or use it however you need This codes is based on the OPs latest question edit:
CREATE TABLE YourTable (PK int, col1 varchar(20), col2 varchar(20), col3 varchar(20))
--data from question
INSERT INTO YourTable VALUES (1,'hello xyz','abc is my last name','and i''m a developer')
INSERT INTO YourTable VALUES (2,'hello xyz',null,'and i''m a developer')
CREATE PROCEDURE YourProcedure
(
#keywords varchar(1000)
)
AS
SELECT
#keywords AS KeyWords,y.*
FROM (SELECT
t.PK
FROM dbo.FN_ListToTable(' ',#keywords) dt
INNER JOIN YourTable t ON t.col1 LIKE '%'+dt.ListValue+'%' OR t.col2 LIKE '%'+dt.ListValue+'%' OR t.col3 LIKE '%'+dt.ListValue+'%'
GROUP BY t.PK
HAVING COUNT(t.PK)=(SELECT COUNT(*) AS CountOf FROM dbo.FN_ListToTable(' ',#keywords))
) dt
INNER JOIN YourTable y ON dt.PK=y.PK
GO
--from question
EXEC YourProcedure 'xyz developer'-- returns 2 rows
EXEC YourProcedure 'xyz abc'-- returns 1 row
EXEC YourProcedure 'abc developer'-- returns 1 row
EXEC YourProcedure 'hello'-- returns 2 rows
EXEC YourProcedure 'hello developer'-- returns 2 rows
EXEC YourProcedure 'xyz'-- returns 2 rows
OUTPUT:
KeyWords PK col1 col2 col3
-------------- ----- ---------- -------------------- --------------------
xyz developer 1 hello xyz abc is my last name and i'm a developer
xyz developer 2 hello xyz NULL and i'm a developer
(2 row(s) affected)
KeyWords PK col1 col2 col3
-------------- ----- ---------- -------------------- --------------------
xyz abc 1 hello xyz abc is my last name and i'm a developer
(1 row(s) affected)
KeyWords PK col1 col2 col3
-------------- ----- ---------- -------------------- --------------------
abc developer 1 hello xyz abc is my last name and i'm a developer
(1 row(s) affected)
KeyWords PK col1 col2 col3
-------------- ----- ---------- -------------------- --------------------
hello 1 hello xyz abc is my last name and i'm a developer
hello 2 hello xyz NULL and i'm a developer
(2 row(s) affected)
KeyWords PK col1 col2 col3
--------------- ----- ---------- -------------------- --------------------
hello developer 1 hello xyz abc is my last name and i'm a developer
hello developer 2 hello xyz NULL and i'm a developer
(2 row(s) affected)
KeyWords PK col1 col2 col3
-------------- ----- ---------- -------------------- --------------------
xyz 1 hello xyz abc is my last name and i'm a developer
xyz 2 hello xyz NULL and i'm a developer
(2 row(s) affected)

You could try something like:
select firstname, lastname from #test t1
inner join persondata t on t.firstname like '%' + t1.x + '%' or t.lastname like '%' + t1.x + '%'
group by firstname, lastname
having count(distinct x) = (select count(*) from #test)
where #test is the table with results of your split. If you have lots of columns in persondata, you might want to just return an ID from this query, and use it as a subquery for the one that actually returns data, so you don't have to group by so many columns.
Edit: you could also use a cursor and another temp table/table variable, but I have kind of an allergic reaction to cursors in SPs.

Related

Loop through rows of a query in T-SQL?

I have the following T-SQL script:
declare #Name nvarchar
declare data cursor for
select Name from MyDB.dbo.MyTable;
OPEN data;
-- Perform the first fetch.
FETCH NEXT FROM data;
-- Check ##FETCH_STATUS to see if there are any more rows to fetch.
WHILE ##FETCH_STATUS = 0
BEGIN
-- This is executed as long as the previous fetch succeeds.
FETCH NEXT FROM data INTO #Name;
Print 'Name: ' + #Name
END
CLOSE data;
DEALLOCATE data;
GO
I want to make a script that will compare each of the strings in a first column with each of the strings in the second column.
The problem is, I don't know how to loop through each of the rows and take a separate string value.
The code above prints only the first value in the query result.
What am I doing wrong?
To compare all values from one column to all values in another column you don't need a cursor, a simple join will do the work - since you didn't provide sample data and also not desired results, I had to make my own:
Create and populate sample table (Please save us this step in your future questions)
CREATE TABLE MyTable
(
Id int identity(1,1),
Name1 char(3),
Name2 char(3)
)
INSERT INTO MyTable (Name1, Name2) VALUES
('abc','def'),('zyx','abc'),
('ghi','jkl'),('yza','ghi'),
('mno','pqr'),('nml','mno'),('pqr','qpo'),
('stu','vwx'),('wvu','tsr'),('kji','hgf')
The query:
SELECT T1.Id, T1.Name1, T1.Name2, T2.Id, T2.Name1, T2.Name2
FROM MyTable T1
JOIN MyTable T2 ON T1.Name1 = T2.Name2
Result:
Id Name1 Name2 Id Name1 Name2
1 abc def 2 zyx abc
3 ghi jkl 4 yza ghi
5 mno pqr 6 nml mno
7 pqr qpo 5 mno pqr
You probably don't want to use a Cursor.
Are your columns in the same table? If so this is as simple as this;
-- Show All rows with [DIFFERENT] Name and Name2 fields
SELECT
Name,
Name2
FROM [MyDB].[dbo].[MyTable]
WHERE
Name <> Name2
-- Show All rows with [SAME] Name and Name2 fields
SELECT
Name,
Name2
FROM [MyDB].[dbo].[MyTable]
WHERE
Name = Name2
If not you will need to post the table definitions and names of columns to get a more concrete example

Passing space delimited string to stored procedure to search entire database

How can I pass a string space delimited to a stored procedure and filter the result?
I'm trying to do this
parameter value
__________________
#query key1 key2 key 3
Then in the stored procedure, I want to first
find all the results with key1.
filter step 1 with key2.
filter step2 with key3.
Another example:
col1 | col2 | col3
------------+-----------------------+-----------------------------------
hello xyz | abc is my last name | and I'm a developer
hello xyz | null | and I'm a developer
If I search for any following it should return for each?
"xyz developer" returns 2 rows
"xyz abc" returns 1 row
"abc developer"returns 1 row
"hello" returns 2 rows
"hello developer" returns 2 rows
"xyz" returns 2 rows
I'm using SQL Server 2016. I tried to use split_string to split query string. But I don't know how to pass this to the stored procedure.
Thanks in advance
Full Text Index is the way to go, but this will return your results.
One caveat (that I can think of). If your search expression/pattern contains a column name, that will generate a false-positive
Declare #YourTable table (col1 varchar(50),col2 varchar(50),col3 varchar(50))
Insert Into #YourTable values
('hello xyz','abc is my last name','and I''m a developer'),
('hello xyz', null ,'and I''m a developer')
Declare #Search varchar(max) = 'xyz abc'
Select A.*
From #YourTable A
Cross Apply (Select FullString=(Select A.* FOR XML Raw)) B
Where FullString like '%'+replace(#Search,' ','%')+'%'
Returns
col1 col2 col3
hello xyz abc is my last name and I'm a developer
EDIT - Multi-Word / Any Order Search
Try this not fully tested. I can't imagine this being very efficient especially with larger tables and numerous key words
Declare #YourTable table (col1 varchar(50),col2 varchar(50),col3 varchar(50))
Insert Into #YourTable values
('hello xyz','abc is my last name','and I''m a developer'),
('hello xyz', null ,'and I''m a developer')
Declare #Search varchar(max) = 'developer xyz'
Select *
From (
Select Distinct A.*
,Hits = sum(sign(charindex(C.Value,B.FullString))) over (partition by B.FullString)
,Req = C.Req
From #YourTable A
Cross Apply (Select FullString=(Select A.* FOR XML Raw)) B
Join (Select *,Req=sum(1) over () From String_Split(#Search,' ') ) C on charindex(C.Value,B.FullString)>0
) A
Where Hits=Req
http://dbfiddle.uk/?rdbms=sqlserver_2016&fiddle=c77123a71c810716b36d73a92ac714eb

How to use variable and changing user defined functions in query

I have defined some user defined function in sql server . for example :
select dbo.SumItems(1 , 2) will return 3
and
select dbo.MaxItems(5 , 3) will return 5
and some other functions may be more complicated.
also I keep variables and their formula expressions in a table:
IdVar Title Formula
----- ----- ---------------------
1 Sum dbo.SumItems(#a , #b)
2 Max dbo.maxItems(#a , #b)
I have my parameters in another table :
a b
-- --
1 2
5 3
now i want to join this two tables and get the following result :
Parameter a Parameter b Variable Title Result
----------- ----------- -------------- ------
1 2 Sum 3
1 2 Max 2
5 3 Sum 8
5 3 Max 5
also I have asked my problem from another view here.
Posted something very similar to this yesterday. As you know, you can only perform such function with dynamic sql.
Now, I don't have your functions, so you will have to supply those.
I've done something very similar in the past to calculate a series of ratios in one pass for numerous income/balance sheets
Below is one approach. (However, I'm not digging the 2 parameters ... seems a little limited, but I'm sure you can expand as necessary)
Declare #Formula table (ID int,Title varchar(25),Formula varchar(max))
Insert Into #Formula values
(1,'Sum' ,'#a+#b')
,(2,'Multiply','#a*#b')
Declare #Parameter table (a varchar(50),b varchar(50))
Insert Into #Parameter values
(1,2),
(5,3)
Declare #SQL varchar(max)=''
;with cte as (
Select A.ID
,A.Title
,ParameterA = A
,ParameterB = B
,Expression = Replace(Replace(Formula,'#a',a),'#b',b)
From #Formula A
Cross Join #Parameter B
)
Select #SQL = #SQL+concat(',(',ID,',',ParameterA,',',ParameterB,',''',Title,''',(',Expression,'))') From cte
Select #SQL = 'Select * From ('+Stuff(#SQL,1,1,'values')+') N(ID,ParameterA,ParameterB,Title,Value)'
Exec(#SQL)
-- Optional To Trap Results in a Table Variable
--Declare #Results table (ID int,ParameterA varchar(50),ParameterB varchar(50),Title varchar(50),Value float)
--Insert Into #Results Exec(#SQL)
--Select * from #Results
Returns
ID ParameterA ParameterB Title Value
1 1 2 Sum 3
2 1 2 Multiply 2
1 5 3 Sum 8
2 5 3 Multiply 15

SQL replacement for Recursive CTE

I have a table Test which contains
TEST
----
tablename|columnvalue|rankofcolumn
A|C1|1
A|C2|2
A|C3|3
A|C4|4
B|CX1|1
B|CX2|2
C|CY1|1
C|CY2|2
C|CY3|3
I want to generate the path along with other columns as follows
RESULT
----
tablename|columnvalue|rankofcolumn|path
A|C1|1|C1
A|C2|2|C1->C2
A|C3|3|C1->C2->C3
A|C4|4|C1->C2->C3->C4
B|CX1|1|CX1
B|CX2|2|CX1->CX2
C|CY1|1|CY1
C|CY2|2|CY1->CY2
C|CY3|3|CY1->CY2->CY3
As per this question, I can use recursive CTE to achieve this
WITH r ( tablename, columnvalue, rankofcolumn, PATH ) AS
(SELECT tablename,
columnvalue,
rankofcolumn,
columnvalue
FROM test
WHERE rankofcolumn = 1
UNION ALL
SELECT xx.tablename,
xx.columnvalue,
xx.rankofcolumn,
r.PATH || '->' || xx.columnvalue
FROM r
JOIN test xx
ON xx.tablename = r.tablename
AND xx.rankofcolumn = r.rankofcolumn + 1)
SELECT *
FROM r;
But I am using WX2 database which lacks this option at the moment. Is there a SQL alternative for this?
You could do the brute-force approach with a table that you gradually populate. Assuming your test table looks something like:
create table test (tablename varchar2(9), columnvalue varchar2(11), rankofcolumn number);
then the result table could be created with:
create table result (tablename varchar2(9), columnvalue varchar2(11), rankofcolumn number,
path varchar2(50));
Then create the result entries for the lowest rank:
insert into result (tablename, columnvalue, rankofcolumn, path)
select t.tablename, t.columnvalue, t.rankofcolumn, t.columnvalue
from test t
where t.rankofcolumn = 1;
3 rows inserted.
And repeatedly add rows building on the highest existing rank, getting the following values (if there are any for that tablename) from the test table:
insert into result (tablename, columnvalue, rankofcolumn, path)
select t.tablename, t.columnvalue, t.rankofcolumn,
concat(concat(r.path, '->'), t.columnvalue)
from test t
join result r
on r.tablename = t.tablename
and r.rankofcolumn = t.rankofcolumn - 1
where t.rankofcolumn = 2;
3 rows inserted.
insert into result (tablename, columnvalue, rankofcolumn, path)
select t.tablename, t.columnvalue, t.rankofcolumn,
concat(concat(r.path, '->'), t.columnvalue)
from test t
join result r
on r.tablename = t.tablename
and r.rankofcolumn = t.rankofcolumn - 1
where t.rankofcolumn = 3;
2 rows inserted.
insert into result (tablename, columnvalue, rankofcolumn, path)
select t.tablename, t.columnvalue, t.rankofcolumn,
concat(concat(r.path, '->'), t.columnvalue)
from test t
join result r
on r.tablename = t.tablename
and r.rankofcolumn = t.rankofcolumn - 1
where t.rankofcolumn = 4;
1 row inserted.
And keep going for the maximum possible number of columns (i.e. highest rankofcolumn for any table). You may be able to do that procedurally in WX2, iterating until zero rows are inserted; but you've made it sound pretty limited.
After all those iterations the table now contains:
select * from result
order by tablename, rankofcolumn;
TABLENAME COLUMNVALUE RANKOFCOLUMN PATH
--------- ----------- ------------ --------------------------------------------------
A C1 1 C1
A C2 2 C1->C2
A C3 3 C1->C2->C3
A C4 4 C1->C2->C3->C4
B CX1 1 CX1
B CX2 2 CX1->CX2
C CY1 1 CY1
C CY2 2 CY1->CY2
C CY3 3 CY1->CY2->CY3
Tested in Oracle but trying to avoid anything Oracle-specific; might need tweaking for WX2 of course.

SQL SELECT statement, column names as values from another table

I'm working on a database which has the following table:
id location
1 Singapore
2 Vancouver
3 Egypt
4 Tibet
5 Crete
6 Monaco
My question is, how can I produce a query from this which would result in column names like the following without writing them into the query:
Query result:
Singapore , Vancouver, Egypt, Tibet, ...
< values >
how can I produce a query which would result in column names like the
following without writing them into the query:
Even with crosstab() (from the tablefunc extension), you have to spell out the column names.
Except, if you create a dedicated C function for your query. The tablefunc extension provides a framework for this, output columns (the list of countries) have to be stable, though. I wrote up a "tutorial" for a similar case a few days ago:
PostgreSQL row to columns
The alternative is to use CASE statements like this:
SELECT sum(CASE WHEN t.id = 1 THEN o.ct END) AS "Singapore"
, sum(CASE WHEN t.id = 2 THEN o.ct END) AS "Vancouver"
, sum(CASE WHEN t.id = 3 THEN o.ct END) AS "Egypt"
-- more?
FROM tbl t
JOIN (
SELECT id, count(*) AS ct
FROM other_tbl
GROUP BY id
) o USING (id);
ELSE NULL is optional in a CASE expression. The manual:
If the ELSE clause is omitted and no condition is true, the result is null.
Basics for both techniques:
PostgreSQL Crosstab Query
You could do this with some really messing dynamic sql but I wouldn't recommend it.
However you could produce something like below, let me know if that stucture is acceptable and I will post some sql.
Location | Count
---------+------
Singapore| 1
Vancouver| 0
Egypt | 2
Tibet | 1
Crete | 3
Monaco | 0
Script for SelectTopNRows command from SSMS
drop table #yourtable;
create table #yourtable(id int, location varchar(25));
insert into #yourtable values
('1','Singapore'),
('2','Vancouver'),
('3','Egypt'),
('4','Tibet'),
('5','Crete'),
('6','Monaco');
drop table #temp;
create table #temp( col1 int );
Declare #Script as Varchar(8000);
Declare #Script_prepare as Varchar(8000);
Set #Script_prepare = 'Alter table #temp Add [?] varchar(100);'
Set #Script = ''
Select
#Script = #Script + Replace(#Script_prepare, '?', [location])
From
#yourtable
Where
[id] is not null
Exec (#Script);
ALTER TABLE #temp DROP COLUMN col1 ;
select * from #temp;