选择已匹配n个条件中至少n-1个的所有记录

时间:2018-02-21 12:05:13

标签: sql oracle oracle12c

我正在使用Oracle 12c。我想知道是否有可能选择n个标准中有n-1个匹配的所有记录?

示例:

CREATE TABLE users
(id number, 
firstname varchar2(100), 
lastname varchar2(100), 
city  varchar2(100));

insert into users(id, firstname, lastname, city)
 values (1, 'John', 'Smith', 'London');
insert into users(id, firstname, lastname, city)
 values (2, 'Tom',  'Smith', 'London');
insert into users(id, firstname, lastname, city)
 values (3, 'John', 'Davis', 'London');
insert into users(id, firstname, lastname, city)
 values (4, 'John', 'Smith', 'Bristol');
insert into users(id, firstname, lastname, city)
 values (5, 'Tom',  'Davis', 'London');
insert into users(id, firstname, lastname, city)
 values (6, 'Tom',  'Davis', 'Bristol');

 select * from users 
  where firstname = 'John' 
    and lastname = 'Smith'
    and city= 'London'

此选择将仅返回一个匹配所有三个条件(id = 1)的记录。我需要的是一个查询,它返回所有匹配三个条件中至少两个的记录(id = 1,2,3,4)。

如果我们知道用户表有5百万条记录,那么它是否可以在Oracle中使用?

5 个答案:

答案 0 :(得分:21)

一般方法是将每个条件放入CASE返回1或0,并计算1的数量:

select * from users 
where (CASE WHEN firstname = 'John' THEN 1 ELSE 0 END
    + CASE WHEN lastname = 'Smith' THEN 1 ELSE 0 END
    + CASE WHEN city= 'London' THEN 1 ELSE 0 END) >= 2

每个匹配条件对总和贡献1,因此您可以检查已满足的条件数。

答案 1 :(得分:9)

您可以在where子句中使用表达式:

select *
from users 
where ( (case when firstname = 'John' then 1 else 0 end) +
        (case when lastname = 'Smith' then 1 else 0 end) +
        (case when city = 'London' then 1 else 0 end)
      ) = 2;

这很容易概括,但对于3个条件和2个匹配,很容易做到:

where (firstname = 'John' and lastname = 'Smith' and city <> 'London') or
      (firstname = 'John' and lastname <> 'Smith' and city = 'London') or
      (firstname <> 'John' and lastname = 'Smith' and city = 'London')

然而,这并不是很好。

答案 2 :(得分:5)

如果你经常运行这样的查询(可能是firstnamelastnamecity的不同输入,你需要匹配),你需要优先考虑这些查询其他查询(以及DML语句的性能),您可以创建三个复合索引:(firstname, lastname)(firstname, city)(lastname, city)

然后查询应该是UNION ALL。它将读取数据三次而不是一次传递 - 但它将从索引中读取,只要在三个条件中的每一个条件中只有一小部分行匹配,就会导致更快的性能。然后,实际上只能从磁盘中读取500万行中的一小部分。

select * from users where firstname = 'John' and lastname = 'Smith'
UNION ALL
select * from users where firstname = 'John' and city = 'London'
                          and (lastname  != 'Smith' or lastname  is null)
UNION ALL
select * from users where lastname = 'Smith' and city = 'London'
                          and (firstname != 'John'  or firstname is null)
;

您可以更改字符串以匹配绑定变量,以便'John''Smith''London'(或其他值!)在运行时提供,而不是硬编码到查询。

答案 3 :(得分:2)

如果您有一组动态过滤器需要传递给查询,那么您可以UNPIVOT数据并过滤值,然后GROUP BY id并使用HAVING确保至少匹配正确数量的过滤器:

SQL Fiddle

Oracle 11g R2架构设置

CREATE TABLE users(id, firstname, lastname, city) AS
  SELECT 1, 'John', 'Smith', 'London'  FROM DUAL UNION ALL
  SELECT 2, 'Tom',  'Smith', 'London'  FROM DUAL UNION ALL
  SELECT 3, 'John', 'Davis', 'London'  FROM DUAL UNION ALL
  SELECT 4, 'John', 'Smith', 'Bristol' FROM DUAL UNION ALL
  SELECT 5, 'Tom',  'Davis', 'London'  FROM DUAL UNION ALL
  SELECT 6, 'Tom',  'Davis', 'Bristol' FROM DUAL;

查询1

WITH filters ( key, value ) AS (
  SELECT 'FIRSTNAME', 'John'   FROM DUAL UNION ALL
  SELECT 'LASTNAME',  'Smith'  FROM DUAL UNION ALL
  SELECT 'CITY',      'London' FROM DUAL
)
SELECT id
FROM   users
UNPIVOT( value FOR key IN ( firstname, lastname, city ) ) kv
INNER JOIN filters f
ON ( f.key = kv.key AND f.value = kv.value )
GROUP BY id
HAVING COUNT(*) >= 2

<强> Results

| ID |
|----|
|  1 |
|  2 |
|  4 |
|  3 |

如果您想获取所有列,则可以将其加入原始表格。

答案 4 :(得分:0)

使用此查询(完全描述可能的匹配)

create index users_idx1 on users (lastname,firstname);
create index users_idx2 on users (lastname,city);
create index users_idx3 on users (city,firstname );

假设三列已定义索引,您可能期望链接索引访问由三个INDEX ACCESS操作组成。

您甚至可以定义一个涵盖匹配选项的两列索引:

-------------------------------------------------------------------------------------------
| Id  | Operation                    | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |            |    33 |  5577 |     5   (0)| 00:00:01 |
|   1 |  CONCATENATION               |            |       |       |            |          |
|*  2 |   TABLE ACCESS BY INDEX ROWID| USERS      |    11 |  1859 |     1   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | USERS_IDX3 |     1 |       |     3   (0)| 00:00:01 |
|*  4 |   TABLE ACCESS BY INDEX ROWID| USERS      |    11 |  1859 |     2   (0)| 00:00:01 |
|*  5 |    INDEX RANGE SCAN          | USERS_IDX3 |     1 |       |     1   (0)| 00:00:01 |
|*  6 |   TABLE ACCESS BY INDEX ROWID| USERS      |    11 |  1859 |     2   (0)| 00:00:01 |
|*  7 |    INDEX RANGE SCAN          | USERS_IDX1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("LASTNAME"='Smith')
   3 - access("CITY"='London')
   4 - filter(LNNVL("LASTNAME"='Smith') OR LNNVL("CITY"='London'))
   5 - access("CITY"='London' AND "FIRSTNAME"='John')
   6 - filter((LNNVL("FIRSTNAME"='John') OR LNNVL("CITY"='London')) AND 
              (LNNVL("LASTNAME"='Smith') OR LNNVL("CITY"='London')))
   7 - access("LASTNAME"='Smith' AND "FIRSTNAME"='John')

这导致遵循执行计划

  <?php
    //Using $db as the connection to your database
    if(isset($_GET['id'])){
       $id = intval($_GET['id']);
       $sql = "UPDATE players SET statuscomandamobila =  'in lucru' WHERE id = $id";
       mysqli_query($db,$sql);
    }
    ?>

请注意,第7行和第5行中的访问在两列上都使用谓词,第3行中的访问仅使用城市 - 如果这是性能问题,您必须根据数据基数调整索引定义。

如果Oracle Text Index不是您缺少的选项,您也可以查看。