如何在不写入所有可能的排列的情况下对多列进行查询?

时间:2016-05-05 14:50:36

标签: sql oracle

我是关于stackexchange的第一个问题,因为我在这件事上挣扎了几天:

我想在具有 col1,col2,col3,col4,col5 的表上进行复杂查询(PLSQL),其值为(名称:每列拆分一个部分)< / p>

+------+--------+--------+--------+------+
|  ID  | Col1   |  Col2  |  Col3  | Col4 |
+------+--------+--------+--------+------+
| (#1) | Andrew | Joan   | Bach   | Mike |
| (#2) | Mark   | Andrew | Livy   |      |
| (#3) | Joan   | Arch   | Donnie |      |
| (#4) | Joan   | Andrew | Lyx    |      |
+------+--------+--------+--------+------+

名称部分的数量从1到5不等。

我想搜索不同的组合:

  • 按此顺序搜索 Bach Joan Mike - 获取#1,#3,#4
  • 搜索 Andrew Bach - 按此顺序获取以下订单#1,#2,#4

我不喜欢使用looooong查询的想法,我将编写所有可能的排列以便为搜索字符串的每个部分加工

我想要实现的目标是:

  • 首次设置:匹配所有n个部分( Bach Joan Bach 匹配,在任何顺序)
  • 第二组:匹配n-1个部分(至少 N-1 我的搜索部分与该行匹配,按任意顺序排列)
  • 第三组:匹配 n-2 部分

我使用ORACLE数据库,我正在考虑在存储过程中创建它:match_my_set(query_str,col1,col2,col3,col4,col5)。我会写至少5个循环(循环到循环)以实现这一点,但我怀疑这是一个专业的想法。

感谢任何帮助。谢谢

3 个答案:

答案 0 :(得分:5)

如果您使用11g或更高版本,则可以将列拆分为行;这是使用CTE提供您的样本数据:

with t (id, col1, col2, col3, col4, col5) as (
  select 1, 'Andrew', 'Joan', 'Bach', 'Mike', null from dual
  union all select 2, 'Mark', 'Andrew', 'Livy', null, null from dual
  union all select 3, 'Joan', 'Arch', 'Donnie', null, null from dual
  union all select 4, 'Joan', 'Andrew', 'Lyx' , null, null from dual
)
select * from t
unpivot (name for col_no in (col1 as 1, col2 as 2, col3 as 3, col4 as 4, col5 as 5));

        ID     COL_NO NAME 
---------- ---------- ------
         1          1 Andrew
         1          2 Joan  
         1          3 Bach  
         1          4 Mike  
         2          1 Mark  
         2          2 Andrew
         2          3 Livy  
...

然后,您可以查找单个名称列的匹配项:

select distinct id
from (
  select * from t
  unpivot (name for col_no in (col1 as 1, col2 as 2, col3 as 3, col4 as 4, col5 as 5))
)
where name in ('Bach', 'Joan', 'Mike')
order by id;

        ID
----------
         1
         3
         4

你希望通过计算每行中匹配的术语数量来使排序更复杂。如果是这样,你可以这样做:

select id, count(*) as cnt
from (
  select * from t
  unpivot (name for col_no in (col1 as 1, col2 as 2, col3 as 3, col4 as 4, col5 as 5))
)
where name in ('Bach', 'Joan', 'Mike')
group by id;

        ID        CNT
---------- ----------
         1          3
         4          1
         3          1

然后有另一个级别的内联视图按顺序排序,以某种方式打破关系:

select id
from (
  select id, count(*) as cnt
  from (
    select * from t
    unpivot (name for col_no in (col1 as 1, col2 as 2, col3 as 3, col4 as 4, col5 as 5))
  )
  where name in ('Bach', 'Joan', 'Mike')
  group by id
)
order by cnt desc, id;

与样本数据的结果相同。将IN条件更改为用户('Andrew', 'Bach')也会在两个版本中获得1,2,4。

根据您获取所搜索的值的方式,您可能希望使用数组(通过表集合表达式和连接),或者标记包含所有搜索词的字符串,或其他一些变化

答案 1 :(得分:3)

您可以使用Oracle的集合(应该在10g或更高版本中运行)

Oracle安装程序

CREATE TABLE TABLE_NAME( ID, Col1, Col2, Col3, Col4 ) AS
SELECT 1, 'Andrew', 'Joan',   'Bach',   'Mike' FROM DUAL UNION ALL
SELECT 2, 'Mark',   'Andrew', 'Livy',   NULL FROM DUAL UNION ALL
SELECT 3, 'Joan',   'Arch',   'Donnie', NULL FROM DUAL UNION ALL
SELECT 4, 'Joan',   'Andrew', 'Lyx',    NULL FROM DUAL;

CREATE TYPE stringlist AS TABLE OF VARCHAR2(100);
/

<强>查询

SELECT id,
       col1,
       col2,
       col3,
       col4
FROM   (
  SELECT t.*,
         stringlist( col1, col2, col3, col4 )
           MULTISET INTERSECT
           stringlist( 'Bach', 'Joan', 'Mike' ) -- Search terms
           AS names
  FROM   TABLE_NAME t
)
WHERE  names IS NOT EMPTY
ORDER BY CARDINALITY( names ) DESC, ID;

<强>输出

        ID COL1   COL2   COL3   COL4
---------- ------ ------ ------ ----
         1 Andrew Joan   Bach   Mike 
         3 Joan   Arch   Donnie      
         4 Joan   Andrew Lyx         

答案 2 :(得分:-1)

这是未经测试的,但我认为它会起作用。首先,您需要一个将空格中的搜索字符串拆分为表格的函数:

CREATE function [dbo].[SplitSpace] (@StringList varchar(4000))

RETURNS @Result Table(Value varchar(50))

AS

BEGIN

    DECLARE @x XML
    SELECT @X = CAST('<A>' + REPLACE(@StringList, ' ', '</A><A>') + '</A>' AS XML)

    INSERT INTO @Result
    SELECT t.value('.', 'varchar(50)') as inVal
    FROM @X.nodes('/A') AS x(t)

    RETURN
END

此功能在其他地方也会派上用场,如果需要,您可以轻松地将其修改为逗号或任何其他值。

接下来,您需要创建一个返回所需结果的查询(当您完成测试时,您可以将其转换为存储过程并接受搜索字符串作为参数):

DECLARE @SearchString varchar(255) = 'Bach Joan Mike'
DECLARE @SearchTable TABLE(Value varchar(50))

INSERT INTO @SearchTable 
SELECT DISTINCT Value 
FROM SplitSpace(@SearchString)

SELECT DISTINCT Col1, Col2, Col3, Col4
FROM MyTable M
JOIN @SearchTable S
    ON S.Value = Col1
    OR S.Value = Col2
    OR S.Value = Col3
    OR S.Value = Col4
CROSS APPLY
    (
        SELECT COUNT(*) AS [Number of Hits]
        FROM @SearchTable
        WHERE Value = M.Col1
        OR Value = M.Col2
        OR Value = M.Col3
        OR Value = M.Col4
    ) t
ORDER BY t.[Number of Hits] DESC

基本上,你说“给我所有记录,其中一个或多个”名称“列存在于搜索字符串中。

然后,你说,通过Cross Apply,“对于每一行,告诉我我有多少次点击”。然后,您所要做的就是按命中次数排序,然后您就完成了设置。

注意:有人可能会输入“Andrew Andrew Bach”作为搜索字符串。如果他们这样做了,那么每个列中都会有2次点击,其中包含安德鲁,每个列只有一个符合巴赫。这就是你从函数返回表中选择不同值的原因;它消除了那些重复。此外,如果你有多个匹配,你会得到两次返回的记录,因为它是一个内连接,所以你从这些结果中选择不同的col1,col2,col3,col4,以消除那些重复。

如果您有任何疑问,请与我们联系。