【问题标题】:Matching all columns with all search phrases将所有列与所有搜索短语匹配
【发布时间】:2014-11-20 18:38:03
【问题描述】:

我想让用户在表中的所有列中搜索文本框中定义的一组短语(用空格分割术语)。 所以首先想到的是在 SQL 中找到一种方法来连接所有列,并在这个结果中只使用 LIKE 运算符(对于每个短语)。 我想到的另一个解决方案是编写一个算法,该算法采用所有搜索的短语,并将它们与所有列匹配。 所以我最终得到了以下结果:

String [] columns = {"col1", "col2", "col3", "col4"};
String [] phrases = textBox.Text.Split(' ');

然后我将所有可能的列和短语组合,并将其放入 sql 的 where-clause-format 中,然后结果是

"(col1 LIKE '%prase1%' AND col1 LIKE '%phrase2%') OR
(col1 LIKE '%phrase1%' AND col2 LIKE '%phrase2%') OR
(col1 LIKE '%phrase2%' AND col2 LIKE '%phrase1%') OR
(col2 LIKE '%phrase1%' AND col3 LIKE '%phrase2%')"

以上只是输出的一个示例sn-p,该算法中创建的条件数量由

conditions=columns^(phrases+1)

所以我观察到拥有 2 个搜索词组仍然可以提供良好的性能,但超过此数量肯定会大幅降低性能。

在所有列中搜索相同数据时的最佳做法是什么?

【问题讨论】:

  • 您要搜索多大的表?所有列都是字符字段吗?你需要担心区分大小写吗?
  • 表中只有大约 500 行。除“价格”列外,所有列都是文本。我们制作了一个网络爬虫并将其限制在一个域中以检索有关所有产品的数据。我不担心区分大小写。
  • 你应该寻找Full-Text Search
  • @VahidND 这是一个很好的建议,但我正在使用 Oracle(抱歉没有提及),看起来 CONTAINS 运算符使用加权因子,它返回包含至少一个的所有结果的短语。但我想我可以在需要创建的索引上使用 LIKE。
  • VahidND - 在他的示例中,他需要全文搜索不支持的后缀和前缀搜索(至少没有插入反向文本的技巧),并且对于 500 行来说这可能是矫枉过正。我可能会创建一个包含所有字段组合的视图,然后调用使用临时表进行匹配的存储过程。

标签: c# sql plsql


【解决方案1】:

您可以在 PL/SQL 中创建一个存储过程或函数来动态搜索表中的搜索词,然后返回任何匹配项的主键和列。下面的代码示例应该足以满足您的要求。

create table text_table(
    col1  varchar2(32),
    col2  varchar2(32),
    col3  varchar2(32),
    col4  varchar2(32),
    col5  varchar2(32),
    pk    varchar2(32)
);

insert into text_table(col1, col2, col3, col4, col5, pk)
values ('the','quick','brown','fox','jumped', '1');
insert into text_table(col1, col2, col3, col4, col5, pk)
values ('over','the','lazy','dog','!', '2');

commit;

declare 

  rc                  sys_refcursor;
  cursor_num          number;  
  col_count           number;
  desc_tab            dbms_sql.desc_tab;  
  vs_column_value     varchar2(4000);  
  search_terms        dbms_sql.varchar2a;
  matching_cols       dbms_sql.varchar2a;
  empty               dbms_sql.varchar2a;
  key_value           varchar2(32);

begin

  --words to search for (i.e. from the text box)
  search_terms(1) := 'fox';
  search_terms(2) := 'box';

  open rc for select * from text_table;

  --Get the cursor number
  cursor_num := dbms_sql.to_cursor_number(rc);

  --Get the column definitions
  dbms_sql.describe_columns(cursor_num, col_count, desc_tab);

  --You must define the columns first
  for i in 1..col_count loop
    dbms_sql.define_column(cursor_num, i, vs_column_value, 4000);    
  end loop;

  --loop through the rows    
  while ( dbms_sql.fetch_rows(cursor_num) > 0 ) loop 

    matching_cols := empty;

    for i in 1 .. col_count loop --loop across the cols

        --Get the column value
        dbms_sql.column_value(cursor_num, i, vs_column_value);

        --Get the value of the primary key based on the column name
        if (desc_tab(i).col_name = 'PK') then 
            key_value := vs_column_value;
        end if;

        --Scan the search terms array for a match
        for j in 1..search_terms.count loop
           if (search_terms(j) like '%'||vs_column_value||'%') then
               matching_cols(nvl(matching_cols.last,0) + 1) := desc_tab(i).col_name;
           end if;
        end loop;    
    end loop; 

    --Print the result matches
    if matching_cols.last is not null then
        for i in 1..matching_cols.last loop
            dbms_output.put_line('Primary Key: '|| key_value||'. Matching Column: '||matching_cols(i));
        end loop;
    end if;

  end loop;

end;

【讨论】:

  • 这是一个很好的方法,但问题是您循环遍历列以查看它是否与搜索词之一匹配 - 它会立即将其添加到匹配的列结果中,即使有只有一场比赛。 我要求每个搜索词在指定行中至少出现一次。由于冗余的可能性,即使添加一个计数器变量来表示连续找到 X 个匹配项也可能是错误的(正如我对 Ed Mendez 答案的评论)
【解决方案2】:

埃德温,

我不知道您使用的是 ORACLE。我的解决方案是使用 SQL Server。希望您能了解解决方案的要点并翻译成 PL/SQL。

希望这对你有用。

我正在手动填充#search 临时表。你需要以某种方式做到这一点。或者寻找一些将分隔字符串并返回表的拆分函数。

IF OBJECT_ID('tempdb..#keywords') IS NOT NULL
    DROP TABLE #keywords;

IF OBJECT_ID('tempdb..#search') IS NOT NULL
    DROP TABLE #search;

DECLARE @search_count INT

-- Populate # search with all my search strings
SELECT *
INTO #search
FROM (
    SELECT '%ST%' AS Search

    UNION ALL

    SELECT '%CL%'
    ) T1

SELECT @search_count = COUNT(*)
FROM #search;

PRINT @search_count

-- Populate my #keywords table with all column values from my table with table id and values
-- I just did a select id, value union with all fields
SELECT *
INTO #keywords
FROM (
    SELECT client_id AS id
        ,First_name AS keyword
    FROM [CLIENT]

    UNION

    SELECT client_id
        ,last_name
    FROM [CLIENT]
    ) AS T1

-- see what is in there 
SELECT *
FROM #search

SELECT *
FROM #keywords

-- I am doing a count(distinct #search.Search). This will get me a count, 
--so if I put in 3 search values my count should equal 3 and that tells me all search strings have been found
SELECT #keywords.id
    ,COUNT(DISTINCT #search.Search)
FROM #keywords
INNER JOIN #search ON #keywords.keyword LIKE #search.Search
GROUP BY #keywords.id
HAVING COUNT(DISTINCT #search.Search) = @search_count

SELECT *
FROM [CLIENT]
WHERE [CLIENT].client_id IN (
        SELECT #keywords.id
        FROM #keywords
        INNER JOIN #search ON #keywords.keyword LIKE #search.Search
        GROUP BY #keywords.id
        HAVING COUNT(DISTINCT #search.Search) = @search_count
        )

【讨论】:

  • 如果我理解正确的话,你基本上是用被搜索的词创建一个临时表,然后在这个临时表和被搜索的表之间使用 UNION。因此,无论哪一行加入关键字的次数,都应该是匹配的。但是如果某行复制了数据例如: col1(url): "domain/phones/samsung/s5" col2(product_title): "samsung s5" 某个关键字会与同一列连接两次吗?每个关键字只能连续出现(计数)一次。
  • @Edwin,所以如果我有上面的 2 列,如您所说,并且我的搜索字符串是“三星”和“银河”,那么该行是否应该在结果中返回?另外,如果我有你上面所说的 2 列,并且我的搜索字符串是“三星”,是否应该返回这一行?
  • 如果我们取上面两列的数据“domain/phones/samsung/s5”和“samsung s5”,然后搜索“samsung”和“galaxy”,应该不会返回该行因为“星系”不在任何列中。但如果只搜索“samsung”,则必须返回该行。 @Ed Mendez
  • @edwin,这就是它的作用。在我的解决方案中。我将所有正在搜索的单词插入到#search 临时表中。然后我使用联合将需要搜索的表的所有列值和主键 ID 添加到 #keywords 临时表中。然后我根据 LIKE 进行分组并加入两个表,并仅返回搜索项的不同计数等于搜索项的计数的 Id。因此,如果我正在搜索 samsung 和 Galaxy,只有在任何列中找到三星和 Galaxy 时,我才会返回 ID。
  • 我错过了 GROUP BY。那时它确实会起作用。为了回答我自己的问题,我认为将这种方法与关键词表的 UNION 一起使用会更好(关于性能)。所以如果我有条件=列^(关键字+1);你有一个 UNION=columns*keywords;显然,您的解决方案的操作较少。谢谢!
猜你喜欢
  • 2018-04-21
  • 1970-01-01
  • 1970-01-01
  • 2014-07-04
  • 2019-08-17
  • 1970-01-01
  • 2019-11-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多