15

I want to query the rows of SQL column for whether it contains any one of multiple values.

For instance, return rows of a table where its column A contains any of the following words: ('cow','farmer','milk').

Easy enough when you know what the words are, but I want to write a sproc where I can feed in any array of strings and if the column A for a certain row contains any of them, it would return the row.

I would want to be able to feed in:

('cow','farmer','milk')

or

('cow','farmer','steak','yikes')

or

('cow','farmer','three', 'pigs', 'wolf')

It must be relatively straightforward, but I cannot for the life of me figure it out. I'm on SQL Server 2008

Matt
  • 25,943
  • 66
  • 198
  • 303
  • Can you please show sample data? E.g. does the table contain single values in each row, or can the table also contain a string like `cow,milk`? – Aaron Bertrand Aug 17 '12 at 00:00
  • Sorry, the data would be whole sentences and I want to see if any of those words are present. – Matt Aug 17 '12 at 00:02
  • You can try my solution just change the JOIN to be LIKE instead of =. Can't edit properly right now from a phone. – Aaron Bertrand Aug 17 '12 at 00:07

6 Answers6

17

One simple approach:

declare @candidates varchar = '|cow|farmer|three|pigs|wolf|'
select * from TableName where @candidates like '%|' + FieldName + '|%'

The best way to do this in SQL 2008 is with a table-valued parameter.

Av Pinzur
  • 2,188
  • 14
  • 14
  • +1 - the knee-jerk reaction to this problem is to use a split function, but in this case it's just not necessary. I'd say the `LIKE` is still more efficient than TVPs, but I'm not sure as I haven't tested those directly in this use case, and I'm going to post a TVP answer anyway so the OP can test. – Aaron Bertrand Aug 16 '12 at 23:47
  • 4
    I think OP wants rows in which FieldName *contains* one of the words - this checks whether FieldName *equals* one of the words. – Blorgbeard Aug 16 '12 at 23:52
  • @Blorgbeard I also interpreted "_contains any one of multiple values_" as "its value corresponds to any of the values", but you may be right. – Diego Aug 16 '12 at 23:56
  • 3
    I was struggling to get this to work and found it was because FieldName for me was an nvarchar(64) and I was just declaring @candidates as nvarchar. If I explicitly state (at)candidates is a nvarchar(64) and then set it to a list it works, regardless of the size of the list. – RyanfaeScotland May 12 '16 at 15:45
  • Is there any similar solution for the case where `FieldName` is of the form `potato|monkey|pigs|carrot|sandwich` and you want to match because it contains a value from `@candidates`? – Alex McMillan Oct 12 '17 at 22:10
  • @AlexMcMillan, unfortunately nothing occurs to me. I'd be thinking very hard about either (a) normalizing the data model, or if that's too high a barrier, (b) employing a table-valued parameter in the query (in which case you can flip the logic above). – Av Pinzur Oct 13 '17 at 14:04
4

SQL Server doesn't know what an "array" is. You can write a split table-valued function that turns each value into a row and then joins against the base table, but this is definitely sub-optimal compared to Av's answer. Or you can test that against a table-valued parameter. A TVP is definitely the best performer compared to all of the splitting techniques, even CLR.

CREATE TYPE dbo.FarmItems AS TABLE(Item VARCHAR(32));
GO

CREATE PROCEDURE dbo.FindFarmItems
  @List dbo.FarmItems READONLY
AS
BEGIN
  SET NOCOUNT ON;

  SELECT t.col1, t.col2, ...
    FROM dbo.table AS t
    INNER JOIN @List AS L
    ON t.columnA = L.Item;
END
GO

Then in C# you just construct a DataTable with your "array" and pass it in:

DataTable dt = new DataTable();
dt.Columns.Add("Item", typeof(string));
dt.Rows.Add("cow");
dt.Rows.Add("farmer");
...

using (SqlConnection conn = new ...)
{
    SqlCommand c = new SqlCommand("dbo.FindFarmItems", conn);
    2.CommandType = CommandType.StoredProcedure;
    SqlParameter tvp = c.Parameters.AddWithValue("@List", dt);
    tvp.SqlDbType = SqlDbType.Structured;
    // execute, get a reader, etc...
}
Aaron Bertrand
  • 272,866
  • 37
  • 466
  • 490
4

I had the same dilema, and came up with the following. For example, if table_a contains ('My Cow', 'My Goat', 'My Dog') and table_b contains ('Dog', 'Cow'), then...

create table #table_a (field1 varchar(128))
insert into #table_a values('My Cow')
insert into #table_a values('My Goat')
insert into #table_a values('My Dog')

create table #table_b (field1 varchar(128))
insert into #table_b values('Dog')
insert into #table_b values('Cow')

select * from #table_a table_a where (select count(*) from #table_b table_b where charindex(table_b.field1, table_a.field1) > 0) > 0

returns ('My Cow','My Dog')

Hope this helps.

raeldor
  • 503
  • 3
  • 11
3

You can use the XML data type to pass list data to your stored procedure:

create procedure dbo.LookItUp

  @idList xml

as

  declare @lookup table
  (
    id int not null
  )

  insert @lookup (id)
  select distinct t.id.value('.','int')
  from @idList.nodes('/ids/id') as t( id )
  where t.id is not null

  select *
  from dbo.my_data_table t
  join @lookup           lu on lu.id = t.object_id

  return 0
go

Easy!

Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
1

You can't pass arrays to Stored Procedures, as arrays don't exist in TSQL. You have a few options, I'm giving you two of them.

Option 1 - Quick and dirty (I don't recommend it)
Pass the values as a string and add it to a dynamic SQL Statement.

CREATE PROCEDURE MyProc
  @Values varchar(1000)
AS

DECLARE @SQL VARCHAR(2000)

SET @SQL = 'SELECT blahblah WHERE SomeField IN (' + @Values + ')'

-- Execute the statement and return the result

END

Apart from the obvious SQL Injection vulnerability, this approach won't work for big sets and it won't perform too well either. Also, it won't work if any value contains a comma. I really don't recommend it, although it may be useful for quick testing.

Option 2 - A more flexible solution
Store all your values in a temporary table which you will reference in your Stored Procedure.

CREATE TABLE #MyTempTable
-- Fields...

INSERT INTO #MyTempTable
-- Insert the values

CREATE PROCEDURE MyProc
  @Values varchar(1000)
AS

SELECT 
  SomeFields
FROM
  MyTable
  JOIN
  #MyTempTable ON
    -- Add join clause

END

This solution may scale better.

As other have suggested, you can also use an in-memory table (which I personally avoid, as I never had much luck with them, they always performed worse than temporary tables), or a table parameter, but you must declare its type beforehand.

Diego
  • 7,312
  • 5
  • 31
  • 38
  • With the new info, the first solution doesn't work because you can't combine LIKE an IN. For the second one, the key part of the code is missing entirely - how to get the array of values (presumably coming from an app) into the #temp table. – Aaron Bertrand Aug 17 '12 at 00:09
  • The OP didn't write "LIKE" in the original post, therefore I didn't use one. Obviously, now that he clarified his needs, my solution doesn't apply anymore. – Diego Aug 17 '12 at 00:12
-2

You can simply use WHERE column IN (coma separated strings)

Here is my complete example how to do that:

create table #bar (foo varchar(20))

insert into #bar (foo) values('cow')
insert into #bar (foo) values('cat')

select foo from #bar
where foo in ('cow','farmer','milk')

drop table #bar
Pa0l0
  • 167
  • 1
  • 6