使用分隔符以类似术语搜索多个术语

时间:2013-03-27 13:23:10

标签: sql sql-server dynamic-sql

昨天我遇到过这个问题并且认为我解决了这个问题,但我错了。出于此脚本的目的,我需要将变量放在带分隔符的字符串中(这是因为最终用户将在帐户标题中搜索术语)。

例如,假设最终用户想要搜索标题中包含“水”或“食物”的时间。他们不需要在同一个标​​题。结果可能会回归“世界之水”和“思考的食物”。

我知道我可以使用一些AND和OR语句轻松地写这个

 SELECT ...
 FROM ...
 WHERE 1=1
 and (rpt_title LIKE '%Food%' 
 or rpt_title LIKE '%Water%')

变量只取一个参数,但是作为一个字符串。 (即'食物|水')。我已经有一个函数,它将采用字符分隔的字符串并将其转换为多个变量。我不认为这是解决方案。我想我需要使用substring和charindex来定位分隔符(在这种情况下为|,因为它很可能不在标题中),然后将其转换为OR语句。

有什么想法或建议吗?

编辑:最终结果是输入变量的能力      (例如 - rpt_title LIKE'%'+ @ title +'%')

3 个答案:

答案 0 :(得分:1)

我不确定为什么你认为拆分会是一个坏主意。您可以创建一个相对有效的内联TVF并加入其中,例如假设你有一个Numbers表:

CREATE FUNCTION dbo.SplitStrings_Numbers
(
   @List       NVARCHAR(MAX),
   @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
   RETURN
   (
       SELECT Item = SUBSTRING(@List, Number, 
         CHARINDEX(@Delimiter, @List + @Delimiter, Number) - Number)
       FROM dbo.Numbers
       WHERE Number <= CONVERT(INT, LEN(@List))
         AND SUBSTRING(@Delimiter + @List, Number, LEN(@Delimiter)) = @Delimiter
   );
GO

(对于绝对最有效的方法,例如CLR,请参阅http://www.sqlperformance.com/2012/07/t-sql-queries/split-strings

现在你可以说:

SELECT t.rpt_title
  FROM dbo.table_name AS t
  INNER JOIN dbo.SplitStrings_Numbers(@Input, '|') AS x
  ON t.rpt_title LIKE '%' + x.Item + '%'
  GROUP BY t.rpt_title;

另一个想法是首先不使用像foo|bar|splunge这样的列表,而是使用TVP。首先创建一个表类型:

CREATE TYPE dbo.TitleMatches
(
  Pattern NVARCHAR(64)
);
GO

然后使用它的存储过程:

CREATE PROCEDURE dbo.Whatever
  @matches dbo.TitleMatches READONLY
AS
BEGIN
  SET NOCOUNT ON;

  SELECT t.rpt_title
    FROM dbo.table_name AS t
    INNER JOIN @matches AS m
    ON t.rpt_title LIKE '%' + m.Pattern + '%'
    GROUP BY t.rpt_title;
END
GO

然后你只需要传入你的DataTable或任何包含潜在匹配列表的结构,例如:在C#中,这可能看起来像:

DataTable DataTableName = new DataTable();

// ... populate data table here

SqlCommand cmd = new SqlCommand("dbo.Whatever", conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvp = cmd.Parameters.AddWithValue("@matches", DataTableName);
tvp.SqlDbType = SqlDbType.Structured;

// ... execute, consume results, etc.

http://www.sqlperformance.com/2012/08/t-sql-queries/splitting-strings-now-with-less-t-sql

的详细信息

答案 1 :(得分:0)

嗯,这有点脏,但它有效。原谅我,我有一段时间没有做过TSQL。

DECLARE @delim CHAR, @search NVARCHAR(100)
set @delim = '|'
set @search = 'world|food'

DECLARE @tblSearch TABLE(
string NVARCHAR(100)
)

DECLARE @tmpStr NVARCHAR(100)
WHILE (@search <> '')
BEGIN
    IF (CHARINDEX(@delim, @search) = 0)
        BEGIN
            INSERT INTO @tblSearch(string) VALUES('%' + @search + '%')
            SET @search = ''
        END
    ELSE
        BEGIN
            INSERT INTO @tblSearch(string)
            SELECT '%' + SUBSTRING(@search, 0, CHARINDEX(@delim, @search)) + '%'
            SET @search = SUBSTRING(@search, CHARINDEX(@delim, @search) + 1, LEN(@search))
        END
END

SELECT ...
FROM ... as m
INNER JOIN @tblSearch as s ON m.rpt_title like s.string

答案 2 :(得分:0)

听起来你需要使用动态tsql,如下所示。不幸的是,我多次读过它使用起来效率不高,因为它无法由服务器预编译,并且每次运行时都必须在运行时进行优化,这比你的方式要慢。但是,我认为这应该完成你所要求的。 要运行它,请调用它 proc_DelimitedQuery'aaa | bbb | etc'

create PROCEDURE [dbo].[proc_DelimitedQuery]

@delimitedString varchar(100)
as

DECLARE @delimiter int
declare @pos int
DECLARE @item varchar(500)
Declare @criteria varchar(5000)

set @criteria = '(rpt_title like ''%'

set @pos=1

--find the first delimiter
SET @delimiter = CHARINDEX('|', @delimitedString, @pos)

WHILE @delimiter<>0
BEGIN
    set @item=substring(@delimitedString, @pos, @delimiter-@pos)
    set @criteria = @criteria + @item + '%'' or rpt_title like ''%'

    --Find the next delimiter
    set @pos=@delimiter+1

    if charindex('|', @delimitedString, @pos)=0
    begin

        set @pos=@delimiter+1
        set @item=substring(@delimitedString, @pos, datalength(@delimitedString)-@delimiter)
        set @criteria = @criteria + @item + '%'')'      
        set @delimiter=0
    end
    else
    begin
        set @delimiter=charindex('|', @delimitedString, @pos)
    end


END

declare @sql varchar(500)
set @sql='select * from MyTable where 1=1 and ' + @criteria

    --change the next line to select @sql if you want to examine the sql statement
EXECUTE(@sql)
GO