Related
In SQL I (sadly) often have to use "LIKE" conditions due to databases that violate nearly every rule of normalization. I can't change that right now. But that's irrelevant to the question.
Further, I often use conditions like WHERE something in (1,1,2,3,5,8,13,21) for better readability and flexibility of my SQL statements.
Is there any possible way to combine these two things without writing complicated sub-selects?
I want something as easy as WHERE something LIKE ('bla%', '%foo%', 'batz%') instead of this:
WHERE something LIKE 'bla%'
OR something LIKE '%foo%'
OR something LIKE 'batz%'
I'm working with SQl Server and Oracle here but I'm interested if this is possible in any RDBMS at all.
There is no combination of LIKE & IN in SQL, much less in TSQL (SQL Server) or PLSQL (Oracle). Part of the reason for that is because Full Text Search (FTS) is the recommended alternative.
Both Oracle and SQL Server FTS implementations support the CONTAINS keyword, but the syntax is still slightly different:
Oracle:
WHERE CONTAINS(t.something, 'bla OR foo OR batz', 1) > 0
SQL Server:
WHERE CONTAINS(t.something, '"bla*" OR "foo*" OR "batz*"')
The column you are querying must be full-text indexed.
Reference:
Building Full-Text Search Applications with Oracle Text
Understanding SQL Server Full-Text
If you want to make your statement easily readable, then you can use REGEXP_LIKE (available from Oracle version 10 onwards).
An example table:
SQL> create table mytable (something)
2 as
3 select 'blabla' from dual union all
4 select 'notbla' from dual union all
5 select 'ofooof' from dual union all
6 select 'ofofof' from dual union all
7 select 'batzzz' from dual
8 /
Table created.
The original syntax:
SQL> select something
2 from mytable
3 where something like 'bla%'
4 or something like '%foo%'
5 or something like 'batz%'
6 /
SOMETH
------
blabla
ofooof
batzzz
3 rows selected.
And a simple looking query with REGEXP_LIKE
SQL> select something
2 from mytable
3 where regexp_like (something,'^bla|foo|^batz')
4 /
SOMETH
------
blabla
ofooof
batzzz
3 rows selected.
BUT ...
I would not recommend it myself due to the not-so-good performance. I'd stick with the several LIKE predicates. So the examples were just for fun.
you're stuck with the
WHERE something LIKE 'bla%'
OR something LIKE '%foo%'
OR something LIKE 'batz%'
unless you populate a temp table (include the wild cards in with the data) and join like this:
FROM YourTable y
INNER JOIN YourTempTable t On y.something LIKE t.something
try it out (using SQL Server syntax):
declare #x table (x varchar(10))
declare #y table (y varchar(10))
insert #x values ('abcdefg')
insert #x values ('abc')
insert #x values ('mnop')
insert #y values ('%abc%')
insert #y values ('%b%')
select distinct *
FROM #x x
WHERE x.x LIKE '%abc%'
or x.x LIKE '%b%'
select distinct x.*
FROM #x x
INNER JOIN #y y On x.x LIKE y.y
OUTPUT:
x
----------
abcdefg
abc
(2 row(s) affected)
x
----------
abc
abcdefg
(2 row(s) affected)
With PostgreSQL there is the ANY or ALL form:
WHERE col LIKE ANY( subselect )
or
WHERE col LIKE ALL( subselect )
where the subselect returns exactly one column of data.
Another solution, should work on any RDBMS:
WHERE EXISTS (SELECT 1
FROM (SELECT 'bla%' pattern FROM dual UNION ALL
SELECT '%foo%' FROM dual UNION ALL
SELECT 'batz%' FROM dual)
WHERE something LIKE pattern)
The inner select can be replaced by another source of patterns like a table (or a view) in this way:
WHERE EXISTS (SELECT 1
FROM table_of_patterns t
WHERE something LIKE t.pattern)
table_of_patterns should contain at least a column pattern, and can be populated like this:
INSERT INTO table_of_patterns(pattern) VALUES ('bla%');
INSERT INTO table_of_patterns(pattern) VALUES ('%foo%');
INSERT INTO table_of_patterns(pattern) VALUES ('batz%');
I would suggest using a TableValue user function if you'd like to encapsulate the Inner Join or temp table techniques shown above. This would allow it to read a bit more clearly.
After using the split function defined at: http://www.logiclabz.com/sql-server/split-function-in-sql-server-to-break-comma-separated-strings-into-table.aspx
we can write the following based on a table I created called "Fish" (int id, varchar(50) Name)
SELECT Fish.* from Fish
JOIN dbo.Split('%ass,%e%',',') as Splits
on Name like Splits.items //items is the name of the output column from the split function.
Outputs
1 Bass
2 Pike
7 Angler
8 Walleye
I'm working with SQl Server and Oracle here but I'm interested if this is possible in any RDBMS at all.
Teradata supports LIKE ALL/ANY syntax:
ALL every string in the list.
ANY any string in the list.
┌──────────────────────────────┬────────────────────────────────────┐
│ THIS expression … │ IS equivalent to this expression … │
├──────────────────────────────┼────────────────────────────────────┤
│ x LIKE ALL ('A%','%B','%C%') │ x LIKE 'A%' │
│ │ AND x LIKE '%B' │
│ │ AND x LIKE '%C%' │
│ │ │
│ x LIKE ANY ('A%','%B','%C%') │ x LIKE 'A%' │
│ │ OR x LIKE '%B' │
│ │ OR x LIKE '%C%' │
└──────────────────────────────┴────────────────────────────────────┘
EDIT:
jOOQ version 3.12.0 supports that syntax:
Add synthetic [NOT] LIKE ANY and [NOT] LIKE ALL operators
A lot of times, SQL users would like to be able to combine LIKE and IN predicates, as in:
SELECT *
FROM customer
WHERE last_name [ NOT ] LIKE ANY ('A%', 'E%') [ ESCAPE '!' ]
The workaround is to manually expand the predicate to the equivalent
SELECT *
FROM customer
WHERE last_name LIKE 'A%'
OR last_name LIKE 'E%'
jOOQ could support such a synthetic predicate out of the box.
PostgreSQL LIKE/ILIKE ANY (ARRAY[]):
SELECT *
FROM t
WHERE c LIKE ANY (ARRAY['A%', '%B']);
SELECT *
FROM t
WHERE c LIKE ANY ('{"Do%", "%at"}');
db<>fiddle demo
Snowflake also supports LIKE ANY/LIKE ALL matching:
LIKE ANY/ALL
Allows case-sensitive matching of strings based on comparison with one or more patterns.
<subject> LIKE ANY (<pattern1> [, <pattern2> ... ] ) [ ESCAPE <escape_char> ]
Example:
SELECT *
FROM like_example
WHERE subject LIKE ANY ('%Jo%oe%','T%e')
-- WHERE subject LIKE ALL ('%Jo%oe%','J%e')
Use an inner join instead:
SELECT ...
FROM SomeTable
JOIN
(SELECT 'bla%' AS Pattern
UNION ALL SELECT '%foo%'
UNION ALL SELECT 'batz%'
UNION ALL SELECT 'abc'
) AS Patterns
ON SomeTable.SomeColumn LIKE Patterns.Pattern
One approach would be to store the conditions in a temp table (or table variable in SQL Server) and join to that like this:
SELECT t.SomeField
FROM YourTable t
JOIN #TempTableWithConditions c ON t.something LIKE c.ConditionValue
I have a simple solution, that works in postgresql at least, using like any followed by the list of regex. Here is an example, looking at identifying some antibiotics in a list:
select *
from database.table
where lower(drug_name) like any ('{%cillin%,%cyclin%,%xacin%,%mycine%,%cephal%}')
u can even try this
Function
CREATE FUNCTION [dbo].[fn_Split](#text varchar(8000), #delimiter varchar(20))
RETURNS #Strings TABLE
(
position int IDENTITY PRIMARY KEY,
value varchar(8000)
)
AS
BEGIN
DECLARE #index int
SET #index = -1
WHILE (LEN(#text) > 0)
BEGIN
SET #index = CHARINDEX(#delimiter , #text)
IF (#index = 0) AND (LEN(#text) > 0)
BEGIN
INSERT INTO #Strings VALUES (#text)
BREAK
END
IF (#index > 1)
BEGIN
INSERT INTO #Strings VALUES (LEFT(#text, #index - 1))
SET #text = RIGHT(#text, (LEN(#text) - #index))
END
ELSE
SET #text = RIGHT(#text, (LEN(#text) - #index))
END
RETURN
END
Query
select * from my_table inner join (select value from fn_split('ABC,MOP',','))
as split_table on my_table.column_name like '%'+split_table.value+'%';
Starting with 2016, SQL Server includes a STRING_SPLIT function. I'm using SQL Server v17.4 and I got this to work for me:
DECLARE #dashboard nvarchar(50)
SET #dashboard = 'P1%,P7%'
SELECT * from Project p
JOIN STRING_SPLIT(#dashboard, ',') AS sp ON p.ProjectNumber LIKE sp.value
May be you think the combination like this:
SELECT *
FROM table t INNER JOIN
(
SELECT * FROM (VALUES('bla'),('foo'),('batz')) AS list(col)
) l ON t.column LIKE '%'+l.Col+'%'
If you have defined full text index for your target table then you may use this alternative:
SELECT *
FROM table t
WHERE CONTAINS(t.column, '"bla*" OR "foo*" OR "batz*"')
I was also wondering for something like that. I just tested using a combination of SUBSTRING and IN and it is an effective solution for this kind of problem. Try the below query :
Select * from TB_YOUR T1 Where SUBSTRING(T1.Something, 1,3) IN ('bla', 'foo', 'batz')
In Oracle you can use a collection in the following way:
WHERE EXISTS (SELECT 1
FROM TABLE(ku$_vcnt('bla%', '%foo%', 'batz%'))
WHERE something LIKE column_value)
Here I have used a predefined collection type ku$_vcnt, but you can declare your own one like this:
CREATE TYPE my_collection AS TABLE OF VARCHAR2(4000);
I may have a solution for this, although it will only work in SQL Server 2008 as far as I know. I discovered that you can use the row-constructor described in https://stackoverflow.com/a/7285095/894974 to join a 'fictional' table using a like clause.
It sounds more complex then it is, look:
SELECT [name]
,[userID]
,[name]
,[town]
,[email]
FROM usr
join (values ('hotmail'),('gmail'),('live')) as myTable(myColumn) on email like '%'+myTable.myColumn+'%'
This will result in all users with an e-mail adres like the ones provided in the list.
Hope it's of use to anyone. The problem had been bothering me a while.
For Sql Server you can resort to Dynamic SQL.
Most of the time in such situations you have the parameter of IN clause based on some data from database.
The example below is a little "forced", but this can match various real cases found in legacy databases.
Suppose you have table Persons where person names are stored in a single field PersonName as FirstName + ' ' + LastName.
You need to select all persons from a list of first names, stored in field NameToSelect in table NamesToSelect, plus some additional criteria (like filtered on gender, birth date, etc)
You can do it as follows
-- #gender is nchar(1), #birthDate is date
declare
#sql nvarchar(MAX),
#subWhere nvarchar(MAX)
#params nvarchar(MAX)
-- prepare the where sub-clause to cover LIKE IN (...)
-- it will actually generate where clause PersonName Like 'param1%' or PersonName Like 'param2%' or ...
set #subWhere = STUFF(
(
SELECT ' OR PersonName like ''' + [NameToSelect] + '%'''
FROM [NamesToSelect] t FOR XML PATH('')
), 1, 4, '')
-- create the dynamic SQL
set #sql ='select
PersonName
,Gender
,BirstDate -- and other field here
from [Persons]
where
Gender = #gender
AND BirthDate = #birthDate
AND (' + #subWhere + ')'
set #params = ' #gender nchar(1),
#birthDate Date'
EXECUTE sp_executesql #sql, #params,
#gender,
#birthDate
If you are using MySQL the closest you can get is full-text search:
Full-Text Search, MySQL Documentation
This works for comma separated values
DECLARE #ARC_CHECKNUM VARCHAR(MAX)
SET #ARC_CHECKNUM = 'ABC,135,MED,ASFSDFSF,AXX'
SELECT ' AND (a.arc_checknum LIKE ''%' + REPLACE(#arc_checknum,',','%'' OR a.arc_checknum LIKE ''%') + '%'')''
Evaluates to:
AND (a.arc_checknum LIKE '%ABC%' OR a.arc_checknum LIKE '%135%' OR a.arc_checknum LIKE '%MED%' OR a.arc_checknum LIKE '%ASFSDFSF%' OR a.arc_checknum LIKE '%AXX%')
If you want it to use indexes, you must omit the first '%' character.
In Oracle RBDMS you can achieve this behavior using REGEXP_LIKE function.
The following code will test if the string three is present in the list expression one|two|three|four|five (in which the pipe "|" symbol means OR logic operation).
SELECT 'Success !!!' result
FROM dual
WHERE REGEXP_LIKE('three', 'one|two|three|four|five');
RESULT
---------------------------------
Success !!!
1 row selected.
Preceding expression is equivalent to:
three=one OR three=two OR three=three OR three=four OR three=five
So it will succeed.
On the other hand, the following test will fail.
SELECT 'Success !!!' result
FROM dual
WHERE REGEXP_LIKE('ten', 'one|two|three|four|five');
no rows selected
There are several functions related to regular expressions (REGEXP_*) available in Oracle since 10g version. If you are an Oracle developer and interested this topic this should be a good beginning Using Regular Expressions with Oracle Database.
No answer like this:
SELECT * FROM table WHERE something LIKE ('bla% %foo% batz%')
In oracle no problem.
In Teradata you can use LIKE ANY ('%ABC%','%PQR%','%XYZ%'). Below is an example which has produced the same results for me
--===========
-- CHECK ONE
--===========
SELECT *
FROM Random_Table A
WHERE (Lower(A.TRAN_1_DSC) LIKE ('%american%express%centurion%bank%')
OR Lower(A.TRAN_1_DSC) LIKE ('%bofi%federal%bank%')
OR Lower(A.TRAN_1_DSC) LIKE ('%american%express%bank%fsb%'))
;
--===========
-- CHECK TWO
--===========
SELECT *
FROM Random_Table A
WHERE Lower(A.TRAN_1_DSC) LIKE ANY
('%american%express%centurion%bank%',
'%bofi%federal%bank%',
'%american%express%bank%fsb%')
Sorry for dredging up an old post, but it has a lot of views. I faced a similar problem this week and came up with this pattern:
declare #example table ( sampletext varchar( 50 ) );
insert #example values
( 'The quick brown fox jumped over the lazy dog.' ),
( 'Ask not what your country can do for you.' ),
( 'Cupcakes are the new hotness.' );
declare #filter table ( searchtext varchar( 50 ) );
insert #filter values
( 'lazy' ),
( 'hotness' ),
( 'cupcakes' );
-- Expect to get rows 1 and 3, but no duplication from Cupcakes and Hotness
select *
from #example e
where exists ( select * from #filter f where e.sampletext like '%' + searchtext + '%' )
Exists() works a little better than join, IMO, because it just tests each record in the set, but doesn't cause duplication if there are multiple matches.
This is possible in Postgres using like or ilike and any or all with array. This is an example that worked for me using Postgres 9:
select id, name from tb_organisation where name ilike any (array['%wembley%', '%south%']);
And this prints out:
id | name
-----+------------------------
433 | South Tampa Center
613 | South Pole
365 | Bromley South
796 | Wembley Special Events
202 | Southall
111 | Wembley Inner Space
In T-SQL, this option works but it is not very fast:
CREATE FUNCTION FN_LIKE_IN (#PROC NVARCHAR(MAX), #ITENS NVARCHAR(MAX)) RETURNS NVARCHAR(MAX) AS BEGIN
--Search an item with LIKE inside a list delimited by "," Vathaire 11/06/2019
DECLARE #ITEM NVARCHAR(MAX)
WHILE CHARINDEX(',', #ITENS) > 0 BEGIN
SET #ITEM = LEFT(#ITENS, CHARINDEX(',', #ITENS) - 1)
--IF #ITEM LIKE #PROC
IF #PROC LIKE #ITEM
RETURN #PROC --#ITEM --1
ELSE
SET #ITENS = STUFF(#ITENS, 1, LEN(#ITEM) + 1, '')
END
IF #PROC LIKE #ITENS RETURN #PROC --#ITEM --1
RETURN NULL --0
END
Query:
SELECT * FROM SYS.PROCEDURES
WHERE DBO.FN_LIKE_IN(NAME, 'PRC%,SP%') IS NOT NULL
you can do this dynamically for a large number of elements, at the expense of performance, but it works.
DECLARE #val nvarchar(256),
#list nvarchar(max) = 'one,two,three,ten,five';
CREATE table #table (FIRST_NAME nvarchar(512), LAST_NAME nvarchar(512));
CREATE table #student (FIRST_NAME nvarchar(512), LAST_NAME nvarchar(512), EMAIL
nvarchar(512));
INSERT INTO #student (FIRST_NAME, LAST_NAME, EMAIL)
SELECT 'TEST', ' redOne' ,'test.redOne#toto.com' UNION ALL
SELECT 'student', ' student' ,'student#toto.com' UNION ALL
SELECT 'student', ' two' ,'student.two#toto.com' UNION ALL
SELECT 'hello', ' ONE TWO THREE' ,'student.two#toto.com'
DECLARE check_cursor CURSOR FOR select value from STRING_SPLIT(#list,',')
OPEN check_cursor
FETCH NEXT FROM check_cursor INTO #val
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #val
IF EXISTS (select * from #student where REPLACE(FIRST_NAME, ' ','')
like '%' + #val + '%' OR REPLACE(LAST_NAME, ' ','') like '%' + #val + '%')
BEGIN
INSERT INTO #table (FIRST_NAME, LAST_NAME )
SELECT TOP 1 FIRST_NAME, LAST_NAME VALUE from #student where
REPLACE(FIRST_NAME, ' ','') like '%' + #val + '%' OR REPLACE(LAST_NAME, ' ','')
like '%' + #val + '%'
END;
FETCH NEXT FROM check_cursor INTO #val
END
CLOSE check_cursor;
DEALLOCATE check_cursor;
SELECT * FROM #table;
DROP TABLE #table;
DROP TABLE #student;
use cursor in SQL SERVER and execute for every value:
table sample:
create table Gastos_ConciliacionExcluida(IdRegistro int identity(1,1), MascaraTexto nvarchar(50), Activa bit default 1, Primary key (IDRegistro))
insert into Gastos_ConciliacionExcluida(MascaraTexto) Values ('%Reembolso%')
alter procedure SP_Gastos_ConciliacionExcluidaProcesar
as
declare cur cursor for select MascaraTexto From Gastos_ConciliacionExcluida where Activa=1
declare #Txt nvarchar(50)
open cur
fetch next from cur into #Txt
while ##Fetch_Status = 0
begin
update Gastos_BancoRegistro set PresumibleNoConciliable = 1
where Concepto like #txt
fetch next from cur into #Txt
end
close cur
deallocate cur
do this
WHERE something + '%' in ('bla', 'foo', 'batz')
OR '%' + something + '%' in ('tra', 'la', 'la')
or
WHERE something + '%' in (select col from table where ....)
I am currently trying to overhaul the current keyword search I have put into the scripting software we use (the old one was vey basic and cumbersome) to something more refined. There are limitations to the software we use, and IT are tied up, so I do not have the option of creating a function or stored procedure which I appreciate would be the ideal solution.
The situation is that the end user might be using a generic script, when they should be using a specific script for their issue, so I want to create some SQL that detects if they should have gone to a different script using a keyword search.
I have a list of words associated with each script I feel they should be using, for example:
Repair script keywords:
repair, broken, chasing
Estate script keywords:
dirty, cleaning, garden
What I want to do is to make some SQL that assigns a numerical value of 1 to each instance of these words within the databox '{Script.Details01}', and then works out which set of keywords has the highest tally at the end.
This is what I have tired so far, I know it's likely not working due to the syntax. Sadly the software we are using is pretty vague when giving error messages so it's not much help. We are using aliases of V and D. D is the user display, so what they physically see, V is the value that the system reads and is not seen by the user. The databox which is where the string we are searching from is '{Script.Details01}'. As this information is stored within the software virtually we do not have to use the FROM field as we would do normally when referencing this location.
SELECT 'GO TO DIFFERENT SCRIPT' D, 'GO TO DIFFERENT SCRIPT' V,
CASE WHEN EXISTS(SELECT '{Script.Details01}' WHERE '{Script.Details01}' like '%repair%') THEN 1 ELSE 0 END +
CASE WHEN EXISTS(SELECT '{Script.Details01}' WHERE '{Script.Details01}' like '%broken%') THEN 1 ELSE 0 END +
CASE WHEN EXISTS(SELECT '{Script.Details01}' WHERE '{Script.Details01}' like '%chasing%') THEN 1 ELSE 0 END AS REP
CASE WHEN EXISTS(SELECT '{Script.Details01}' WHERE '{Script.Details01}' like '%dirty%') THEN 1 ELSE 0 END +
CASE WHEN EXISTS(SELECT '{Script.Details01}' WHERE '{Script.Details01}' like '%cleaning%') THEN 1 ELSE 0 END +
CASE WHEN EXISTS(SELECT '{Script.Details01}' WHERE '{Script.Details01}' like '%garden%') THEN 1 ELSE 0 END AS EST
WHERE REP = (SELECT MAX(REP)) AND REP <> 0 AND > EST
OR EST = (SELECT MAX(EST)) AND EST <> 0 AND > REP
Essentially what I'm looking for the code to do is to tell me if there is a higher tally for REP (repair) and EST (estate) or if there are no values registered against either. Apologies if I have not explained this well, there are a few restrictions within the software we are using so trying to explain it as best I can. Any ideas would be greatly appreciated.
Here is a first stab at solving this to some extent. It uses a CTE which might or might not be feasible in your case:
declare #inputText nvarchar(2000)
set #inputText = 'Caller wanting to complain about the repair they have chasing for days, as their boiler is garden broken and needs repair'
--------
declare #inputText nvarchar(2000)
set #inputText = 'Caller wanting to complain about the repair they have chasing for days, as their boiler is garden broken and needs repair'
;with SplitIntoWords(DataItem, Data) as (
select cast(LEFT(#inputText, CHARINDEX(' ', #inputText + ' ') - 1) as nvarchar(2000)),
cast(STUFF(#inputText, 1, CHARINDEX(' ', #inputText + ' '), '') as nvarchar(2000))
union all
select cast(LEFT(Data, CHARINDEX(' ', Data + ' ') - 1) as nvarchar(2000)),
cast(STUFF(Data, 1, CHARINDEX(' ', Data + ' '), '') as nvarchar(2000))
from SplitIntoWords
where Data > ''
)
select (
select count(*) from SplitIntoWords where DataItem in ('repair','broken','chasing')
) as rep,
(
select count(*) from SplitIntoWords where DataItem in ('dirty','cleaning','garden')
) as est,
(
select count(*) from SplitIntoWords where DataItem not in ('dirty','cleaning','garden','repair','broken','chasing')
) as other
Note: The CTE is an adaptation of a CTE in this answer.
So what does it actually do? It uses the CTEto split the input text into individual words, then it searches for the given keywords and performs a count. Output from sample:
+-----+-----+-------+
| rep | est | other |
+-----+-----+-------+
| 4 | 1 | 16 |
+-----+-----+-------+
So it has found 1 keyword which belongs to the Estate group, 4 keywords from the Repair group, and 16 others.
To adapt the sample for your use case, replace #inputText with '{Script.Details01}'.
--- EDIT ---
Try this then:
;with SplitIntoWords(DataItem, Data) as (
select cast(LEFT(#inputText, CHARINDEX(' ', #inputText + ' ') - 1) as nvarchar(2000)),
cast(STUFF(#inputText, 1, CHARINDEX(' ', #inputText + ' '), '') as nvarchar(2000))
union all
select cast(LEFT(Data, CHARINDEX(' ', Data + ' ') - 1) as nvarchar(2000)),
cast(STUFF(Data, 1, CHARINDEX(' ', Data + ' '), '') as nvarchar(2000))
from SplitIntoWords
where Data > ''
)
select top 1 scriptType, count(*) as typeCount
from (
select case when DataItem in ('repair','broken','chasing') then 'rep' when DataItem in ('dirty','cleaning','garden') then 'est' else '' end as scriptType,
DataItem
from SplitIntoWords
) as sub
where scriptType != ''
group by scriptType
order by count(*) desc
Output:
+------------+-----------+
| scriptType | typeCount |
+------------+-----------+
| rep | 4 |
+------------+-----------+
--- ONE MORE EDIT, Wildcard searching ---
Replace
select case when DataItem in ('repair','broken','chasing') then 'rep' when DataItem in ('dirty','cleaning','garden') then 'est' else '' end as scriptType,
with
select
case when
DataItem like '%repair%'
or DataItem like '%broken%'
or DataItem like '%chasing%' then 'rep'
when
DataItem like '%dirty&'
or DataItem like '%cleaning%'
or DataItem like '%garden%' then 'est'
else '' end as scriptType,
I don't know if you can store the keywords in the database, but that would imho be preferable over hard coding. That way you could keep the maintenance of the keywords outside the function.
The t-sql below works as is, but the keywords table can be ported to the db itself instead:
declare #keywords table(word varchar(50), type varchar(10)) --preferrably when stored in db, the type would be normalized to another table
insert into #keywords values
('repair', 'Rep'),
('broken', 'Rep'),
('chasing', 'Rep'),
('dirty', 'EST'),
('cleaning', 'EST'),
('garden', 'EST')
declare #teststring varchar(512) = 'When the film "chasing cars" was broken, we tried to repair it. It turned out it was dirty from lying in the garden, so we gave it a thorough cleaning'
select top 1 k.type, COUNT(1) cnt from #keywords k where #teststring like '%' + k.word + '%' group by k.type order by COUNT(1) desc
For each word inside the table, a like is performed (wildcards can be used inside the words as well). The group by checks the number of occurrences for each type and the order by and top 1 make sure you only get the one with most occurences (you could add an extra gravity to the types to sort on, in case both types have an equal number of occurences)
edit
Since storing in the db isn't an option: the same can also be done without a table variable:
select top 1 k.type from (values
('repair', 'Rep'),
('broken', 'Rep'),
('chasing', 'Rep'),
('dirty', 'EST'),
('cleaning', 'EST'),
('garden', 'EST')
) k(word,type)
where #teststring like '%' + k.word + '%' group by k.type order by COUNT(1) desc
Try this, note that I have a sample text for every keywords. you need to replace these with the one you have:
DECLARE #repairtext AS varchar(max);
SET #repairtext = 'repair, broken, chasing, garden dirty, cleaning, garden'
DECLARE #repair int,#RepairCounter int
SET #repair= PATINDEX('%repair%',#repairtext)
IF #repair<>0 SET #Repaircounter=1
WHILE #repair<>0
BEGIN
SET #repairtext = STUFF(#repairtext,1,#repair+6,'')
SET #repair= PATINDEX('%repair%',#repairtext)
IF #repair<>0 SET #RepairCounter=#RepairCounter+1
END;
DECLARE #brokentext AS varchar(max);
SET #brokentext = 'repair, broken, chasing, garden dirty, cleaning, garden'
DECLARE #broken int,#BrokenCounter int
SET #broken= PATINDEX('%broken%',#brokentext)
IF #broken<>0 SET #BrokenCounter=1
WHILE #broken<>0
BEGIN
SET #brokentext = STUFF(#brokentext,1,#broken+6,'')
SET #broken= PATINDEX('%broken%',#brokentext)
IF #broken<>0 SET #BrokenCounter=#BrokenCounter+1
END;
DECLARE #chasingtext AS varchar(max);
SET #chasingtext = 'repair, broken, chasing, garden dirty, cleaning, garden'
DECLARE #chasing int,#chasingCounter int
SET #chasing= PATINDEX('%chasing%',#chasingtext)
IF #chasing<>0 SET #chasingCounter=1
WHILE #chasing<>0
BEGIN
SET #chasingtext = STUFF(#chasingtext,1,#chasing+7,'')
SET #chasing= PATINDEX('%chasing%',#chasingtext)
IF #chasing<>0 SET #chasingCounter=#chasingCounter+1
END;
DECLARE #dirtytext AS varchar(max);
SET #dirtytext ='repair, broken, chasing, garden dirty, cleaning, garden'
DECLARE #dirty int,#dirtyCounter int
SET #dirty= PATINDEX('%dirty%',#dirtytext)
IF #dirty<>0 SET #dirtyCounter=1
WHILE #dirty<>0
BEGIN
SET #dirtytext = STUFF(#dirtytext,1,#dirty+5,'')
SET #dirty= PATINDEX('%dirty%',#dirtytext)
IF #dirty<>0 SET #dirtyCounter=#dirtyCounter+1
END;
DECLARE #cleaningtext AS varchar(max);
SET #cleaningtext = 'repair, broken, chasing, garden dirty, cleaning, garden'
DECLARE #cleaning int,#cleaningCounter int
SET #cleaning= PATINDEX('%cleaning%',#cleaningtext)
IF #cleaning<>0 SET #cleaningCounter=1
WHILE #cleaning<>0
BEGIN
SET #cleaningtext = STUFF(#cleaningtext,1,#cleaning+8,'')
SET #cleaning= PATINDEX('%cleaning%',#cleaningtext)
IF #cleaning<>0 SET #cleaningCounter=#cleaningCounter+1
END;
DECLARE #gardentext AS varchar(max);
SET #gardentext = 'repair, broken, chasing, garden dirty, cleaning, garden'
DECLARE #garden int,#gardenCounter int
SET #garden= PATINDEX('%garden%',#gardentext)
IF #garden<>0 SET #gardenCounter=1
WHILE #garden<>0
BEGIN
SET #gardentext = STUFF(#gardentext,1,#garden+6,'')
SET #garden= PATINDEX('%garden%',#gardentext)
IF #garden<>0 SET #gardenCounter=#gardenCounter+1
END;
DECLARE #REP int = #RepairCounter + #BrokenCounter + #chasingCounter
,#EST int = #dirtyCounter + #cleaningCounter + #gardenCounter;
IF #REP > #EST
SELECT #REP AS REP
ELSE IF #REP < #EST
SELECT #EST AS EST
ELSE SELECT #REP AS REP;
Does anyone know how to split words starting with capital letters from a string?
Example:
DECLARE #var1 varchar(100) = 'OneTwoThreeFour'
DECLARE #var2 varchar(100) = 'OneTwoThreeFourFive'
DECLARE #var3 varchar(100) = 'One'
SELECT #var1 as Col1, <?> as Col2
SELECT #var2 as Col1, <?> as Col2
SELECT #var3 as Col1, <?> as Col2
expected result:
Col1 Col2
OneTwoThreeFour One Two three Four
OneTwoThreeFourFive One Two Three Four Five
One One
If this is not possible (or if too long) an scalar function would be okay as well.
Here is a function I created that is similar to the "removing non-alphabetic characters". How to strip all non-alphabetic characters from string in SQL Server?
This one uses a case sensitive collation which actively seeks out a non-space/capital letter combination and then uses the STUFF function to insert the space. This IS a scalar UDF, so some folks will immediately say that it will be slower than other solutions. To that notion, I say, please test it. This function does not use any table data and only loops as many times as necessary, so it will likely give you very good performance.
Create Function dbo.Split_On_Upper_Case(#Temp VarChar(1000))
Returns VarChar(1000)
AS
Begin
Declare #KeepValues as varchar(50)
Set #KeepValues = '%[^ ][A-Z]%'
While PatIndex(#KeepValues collate Latin1_General_Bin, #Temp) > 0
Set #Temp = Stuff(#Temp, PatIndex(#KeepValues collate Latin1_General_Bin, #Temp) + 1, 0, ' ')
Return #Temp
End
Call it like this:
Select dbo.Split_On_Upper_Case('OneTwoThreeFour')
Select dbo.Split_On_Upper_Case('OneTwoThreeFour')
Select dbo.Split_On_Upper_Case('One')
Select dbo.Split_On_Upper_Case('OneTwoThree')
Select dbo.Split_On_Upper_Case('stackOverFlow')
Select dbo.Split_On_Upper_Case('StackOverFlow')
Here is a function I have just created.
FUNCTION
CREATE FUNCTION dbo.Split_On_Upper_Case
(
#String VARCHAR(4000)
)
RETURNS VARCHAR(4000)
AS
BEGIN
DECLARE #Char CHAR(1);
DECLARE #i INT = 0;
DECLARE #OutString VARCHAR(4000) = '';
WHILE (#i <= LEN(#String))
BEGIN
SELECT #Char = SUBSTRING(#String, #i,1)
IF (#Char = UPPER(#Char) Collate Latin1_General_CS_AI)
SET #OutString = #OutString + ' ' + #Char;
ELSE
SET #OutString = #OutString + #Char;
SET #i += 1;
END
SET #OutString = LTRIM(#OutString);
RETURN #OutString;
END
Test Data
DECLARE #TABLE TABLE (Strings VARCHAR(1000))
INSERT INTO #TABLE
VALUES ('OneTwoThree') ,
('FourFiveSix') ,
('SevenEightNine')
Query
SELECT dbo.Split_On_Upper_Case(Strings) AS Vals
FROM #TABLE
Result Set
╔══════════════════╗
║ Vals ║
╠══════════════════╣
║ One Two Three ║
║ Four Five Six ║
║ Seven Eight Nine ║
╚══════════════════╝
If a single query is needed 26 REPLACE can be used to check every upper case letter like
SELECT #var1 col1, REPLACE(
REPLACE(
REPLACE(
...
REPLACE(#var1, 'A', ' A')
, ...
, 'X', ' X')
, 'Y', ' Y')
, 'Z', ' Z') col2
Not the most beautiful thing but it'll work.
EDIT
Just to add another function to do the same thing in a different way of the other answers
CREATE FUNCTION splitCapital (#param Varchar(MAX))
RETURNS Varchar(MAX)
BEGIN
Declare #ret Varchar(MAX) = '';
declare #len int = len(#param);
WITH Base10(N) AS (
SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
UNION ALL SELECT 8 UNION ALL SELECT 9
), Chars(N) As (
Select TOP(#len)
nthChar
= substring(#param, u.N + t.N*10 + h.N*100 + th.N*1000 + 1, 1)
Collate Latin1_General_CS_AI
FROM Base10 u
CROSS JOIN Base10 t
CROSS JOIN Base10 h
CROSS JOIN Base10 th
WHERE u.N + t.N*10 + h.N*100 + th.N*1000 < #len
ORDER BY u.N + t.N*10 + h.N*100 + th.N*1000
)
SELECT #ret += Case nthChar
When UPPER(nthChar) Then ' '
Else ''
End + nthChar
FROM Chars
RETURN #ret;
END
This one uses the possibility of TSQL to concatenate string variable, I had to use the TOP N trick to force the Chars CTE rows in the right order
Build a Numbers table. There are some excellent posts on SO to show you how to do this. Populate it with values up the maximum length of your input string. Select the values from 1 through the actual length of the current input string. Cross join this list of numbers to the input string. Use the result to SUBSTRING() each character. Then you can either compare the resulting list of one-charachter values to a pre-populated table-valued variable or convert each character to an integer using ASCII() and choose only those between 65 ('A') and 90 ('Z'). At this point you have a list which is the position of each upper-case character in your input string. UNION the maximum length of your input string onto the end of this list. You'll see why in just a second. Now you can SUBSTRING() your input variable, starting at the Number given by row N and taking a length of (the Number given by row N+1) - (The number given by row N). This is why you have to UNION the extra Number on the end. Finally concatenate all these substring together, space-separated, using the algorithm of your choice.
Sorry, don't have an instance in front of me to try out code. Sounds like a fun task. I think doing it with nested SELECT statements will get convoluted and un-maintainable; better to lay it out as CTEs, IMHO.
I know that there are already some good answers out there, but if you wanted to avoid creating a function, you could also use a recursive CTE to accomplish this. It's certainly not a clean way of doing this, but it works.
DECLARE
#camelcase nvarchar(4000) = 'ThisIsCamelCased'
;
WITH
split
AS
(
SELECT
[iteration] = 0
,[string] = #camelcase
UNION ALL
SELECT
[iteration] = split.[iteration] + 1
,[string] = STUFF(split.[string], pattern.[index] + 1, 0, ' ')
FROM
split
CROSS APPLY
( SELECT [index] = PATINDEX(N'%[^ ][A-Z]%' COLLATE Latin1_General_Bin, split.[string]) )
pattern
WHERE
pattern.[index] > 0
)
SELECT TOP (1)
[spaced] = split.[string]
FROM
split
ORDER BY
split.[iteration] DESC
;
As I said, this isn't a pretty way to write a query, but I use things like this when I'm just writing up some ad-hoc queries where I would not want to add new artifacts to the database. You could also use this to create your function as an inline table valued function, which is always a tad nicer.
Please Try This:
declare #t nvarchar (100) ='IamTheTestString'
declare #len int
declare #Counter int =0
declare #Final nvarchar (100) =''
set #len =len( #t)
while (#Counter <= #len)
begin
set #Final= #Final + Case when ascii(substring (#t,#Counter,1))>=65 and
ascii(substring (#t,#Counter,1))<=90 then ' '+substring (#t,#Counter,1) else
substring (#t,#Counter,1) end
set #Counter=#Counter+1
end
print ltrim(#Final)
I have a procedure and input is comma separated like '1,2,3'.
I would like to query like
SELECT * FROM PERSON WHERE PERSON_ID IN(1,2,3).
Please note that PERSON_ID is integer.
I've seen this type of question so often I posted a blog on it here.
Basically you have three options (to the best of my knowledge)
The LIKE version that Gordon Lindoff suggested.
Using a split function like so.
DECLARE #InList varchar(100)
SET #InList = '1,2,3,4'
SELECT MyTable.*
FROM MyTable
JOIN DelimitedSplit8K (#InList,',') SplitString
ON MyTable.Id = SplitString.Item
Or using dynamic SQL.
DECLARE #InList varchar(100)
SET #InList = '1,2,3,4'
DECLARE #sql nvarchar(1000)
SET #sql = 'SELECT * ' +
'FROM MyTable ' +
'WHERE Id IN ('+#InList+') '
EXEC sp_executesql #sql
SearchList = ',' + inputSearchTerm + ','; /* e.g. inputSearchTerm is '1,2,3' */
SELECT * FROM PERSON WHERE CONTAINS(SearchList, ',' + cast(PERSON_ID as varchar) + ',');
Because contains seems like overkill (it is designed for fuzzy searching and uses a full text index), because charindex() is not standard SQL, and I abhor answers where varchar does not have length, let me give an alternative:
SELECT *
FROM PERSON
WHERE ','+#SearchList+',' like '%,'+cast(PERSON_ID as varchar(255))+',%';
The concatenation of commas for #SearchList makes sure that all values are surrounded by delimiters. These are then put around the particular value, to prevent 1 from matching 10.
Note that this will not be particularly efficient, because it will require a full table scan.
Here's a way of doing it using a recursive CTE:
declare #SearchList varchar(20)
set #SearchList= '1,2,3'
;with cte as
(select case charindex(',',#SearchList)
when 0 then cast(#SearchList as int)
else cast(left(#SearchList,charindex(',',#SearchList)-1) as int)
end searchVal,
case charindex(',',#SearchList)
when 0 then ''
else right(#SearchList,
len(#SearchList)-charindex(',',#SearchList) )
end remainStr
union all
select case charindex(',',remainStr)
when 0 then cast(remainStr as int)
else cast(left(remainStr,charindex(',',remainStr)-1) as int)
end searchVal,
case charindex(',',remainStr)
when 0 then ''
else right(remainStr,
len(remainStr)-charindex(',',remainStr) )
end remainStr
from cte
where remainStr > ''
)
select p.*
from cte
join person p on cte.searchVal = p.person_id
SQLFiddle here.
Is it possible to use an IF clause within a WHERE clause in MS SQL?
Example:
WHERE
IF IsNumeric(#OrderNumber) = 1
OrderNumber = #OrderNumber
ELSE
OrderNumber LIKE '%' + #OrderNumber + '%'
Use a CASE statement
UPDATE: The previous syntax (as pointed out by a few people) doesn't work. You can use CASE as follows:
WHERE OrderNumber LIKE
CASE WHEN IsNumeric(#OrderNumber) = 1 THEN
#OrderNumber
ELSE
'%' + #OrderNumber
END
Or you can use an IF statement like #N. J. Reed points out.
You should be able to do this without any IF or CASE
WHERE
(IsNumeric(#OrderNumber) AND
(CAST OrderNumber AS VARCHAR) = (CAST #OrderNumber AS VARCHAR)
OR
(NOT IsNumeric(#OrderNumber) AND
OrderNumber LIKE ('%' + #OrderNumber))
Depending on the flavour of SQL you may need to tweak the casts on the order number to an INT or VARCHAR depending on whether implicit casts are supported.
This is a very common technique in a WHERE clause. If you want to apply some "IF" logic in the WHERE clause all you need to do is add the extra condition with an boolean AND to the section where it needs to be applied.
You don't need a IF statement at all.
WHERE
(IsNumeric(#OrderNumber) = 1 AND OrderNumber = #OrderNumber)
OR (IsNumeric(#OrderNumber) = 0 AND OrderNumber LIKE '%' + #OrderNumber + '%')
There isn't a good way to do this in SQL. Some approaches I have seen:
1) Use CASE combined with boolean operators:
WHERE
OrderNumber = CASE
WHEN (IsNumeric(#OrderNumber) = 1)
THEN CONVERT(INT, #OrderNumber)
ELSE -9999 -- Some numeric value that just cannot exist in the column
END
OR
FirstName LIKE CASE
WHEN (IsNumeric(#OrderNumber) = 0)
THEN '%' + #OrderNumber
ELSE ''
END
2) Use IF's outside the SELECT
IF (IsNumeric(#OrderNumber)) = 1
BEGIN
SELECT * FROM Table
WHERE #OrderNumber = OrderNumber
END ELSE BEGIN
SELECT * FROM Table
WHERE OrderNumber LIKE '%' + #OrderNumber
END
3) Using a long string, compose your SQL statement conditionally, and then use EXEC
The 3rd approach is hideous, but it's almost the only think that works if you have a number of variable conditions like that.
Use a CASE statement instead of IF.
You want the CASE statement
WHERE OrderNumber LIKE
CASE WHEN IsNumeric(#OrderNumber)=1 THEN #OrderNumber ELSE '%' + #OrderNumber END
To clarify some of the logical equivalence solutions.
An if statement
if (a) then b
is logically equivalent to
(!a || b)
It's the first line on the Logical equivalences involving conditional statements section of the Logical equivalence wikipedia article.
To include the else, all you would do is add another conditional
if(a) then b;
if(!a) then c;
which is logically equivalent to
(!a || b) && (a || c)
So using the OP as an example:
IF IsNumeric(#OrderNumber) = 1
OrderNumber = #OrderNumber
ELSE
OrderNumber LIKE '%' + #OrderNumber + '%'
the logical equivalent would be:
(IsNumeric(#OrderNumber) <> 1 OR OrderNumber = #OrderNumber)
AND (IsNumeric(#OrderNumber) = 1 OR OrderNumber LIKE '%' + #OrderNumber + '%' )
I think that where...like/=...case...then... can work with Booleans. I am using T-SQL.
Scenario: Let's say you want to get Person-30's hobbies if bool is false, and Person-42's hobbies if bool is true. (According to some, hobby-lookups comprise over 90% of business computation cycles, so pay close attn.).
CREATE PROCEDURE sp_Case
#bool bit
AS
SELECT Person.Hobbies
FROM Person
WHERE Person.ID =
case #bool
when 0
then 30
when 1
then 42
end;
// an example for using a stored procedure to select users filtered by country and site
CREATE STORED PROCEDURE GetUsers
#CountryId int = null,
#SiteId int = null
AS
BEGIN
SELECT *
FROM Users
WHERE
CountryId = CASE WHEN ISNUMERIC(#CountryId) = 1 THEN #CountryId ELSE CountryId END AND
SiteId = CASE WHEN ISNUMERIC(#SiteId) = 1 THEN #SiteId ELSE SiteId END END
// take from the input countryId AND/OR siteId if exists else don't filter
WHERE (IsNumeric(#OrderNumber) <> 1 OR OrderNumber = #OrderNumber)
AND (IsNumber(#OrderNumber) = 1 OR OrderNumber LIKE '%'
+ #OrderNumber + '%')
CASE Statement is better option than IF always.
WHERE vfl.CreatedDate >= CASE WHEN #FromDate IS NULL THEN vfl.CreatedDate ELSE #FromDate END
AND vfl.CreatedDate<=CASE WHEN #ToDate IS NULL THEN vfl.CreatedDate ELSE #ToDate END
WHERE OrderNumber LIKE CASE WHEN IsNumeric(#OrderNumber) = 1 THEN #OrderNumber ELSE '%' + #OrderNumber END
In line case Condition will work properly.
In sql server I had same problem I wanted to use an and statement only if parameter is false and on true I had to show both values true and false so I used it this way
(T.IsPublic = #ShowPublic or #ShowPublic = 1)
The following example executes a query as part of the Boolean expression and then executes slightly different statement blocks based on the result of the Boolean expression. Each statement block starts with BEGIN and completes with END.
USE AdventureWorks2012;
GO
DECLARE #AvgWeight decimal(8,2), #BikeCount int
IF
(SELECT COUNT(*) FROM Production.Product WHERE Name LIKE 'Touring-3000%' ) > 5
BEGIN
SET #BikeCount =
(SELECT COUNT(*)
FROM Production.Product
WHERE Name LIKE 'Touring-3000%');
SET #AvgWeight =
(SELECT AVG(Weight)
FROM Production.Product
WHERE Name LIKE 'Touring-3000%');
PRINT 'There are ' + CAST(#BikeCount AS varchar(3)) + ' Touring-3000 bikes.'
PRINT 'The average weight of the top 5 Touring-3000 bikes is ' + CAST(#AvgWeight AS varchar(8)) + '.';
END
ELSE
BEGIN
SET #AvgWeight =
(SELECT AVG(Weight)
FROM Production.Product
WHERE Name LIKE 'Touring-3000%' );
PRINT 'Average weight of the Touring-3000 bikes is ' + CAST(#AvgWeight AS varchar(8)) + '.' ;
END ;
GO
Using nested IF...ELSE statements
The following example shows how an IF … ELSE statement can be nested inside another. Set the #Number variable to 5, 50, and 500 to test each statement.
DECLARE #Number int
SET #Number = 50
IF #Number > 100
PRINT 'The number is large.'
ELSE
BEGIN
IF #Number < 10
PRINT 'The number is small'
ELSE
PRINT 'The number is medium'
END ;
GO
If #LstTransDt is Null
begin
Set #OpenQty=0
end
else
begin
Select #OpenQty=IsNull(Sum(ClosingQty),0)
From ProductAndDepotWiseMonitoring
Where Pcd=#PCd And PtpCd=#PTpCd And TransDt=#LstTransDt
end
See if this helps.
USE AdventureWorks2012;
GO
IF
(SELECT COUNT(*) FROM Production.Product WHERE Name LIKE 'Touring-3000%' ) > 5
PRINT 'There are more than 5 Touring-3000 bicycles.'
ELSE PRINT 'There are 5 or less Touring-3000 bicycles.' ;
GO