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

Sql 选择n个条件中至少有n-1个已匹配的所有记录,sql,oracle,oracle12c,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'); inser

我正在使用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)中两个条件的所有记录


如果我们知道users表中有500万条记录,在Oracle中是否可能?

您可以在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')
where(firstname='John'和lastname='Smith'和city'London')或
(名字为“约翰”,姓氏为“史密斯”,城市为“伦敦”)或
(姓‘约翰’,姓‘史密斯’,市‘伦敦’)

但是,这并不能很好地概括。

一般方法是将每个条件放在一个返回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,因此,您可以检查满足了多少条件。

如果您有一组需要传递到查询中的动态筛选器,那么您可以
取消PIVOT
数据并筛选值,然后
按id分组
,并使用
HAVING
确保至少匹配正确数量的筛选器:

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;
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
| ID |
|----|
|  1 |
|  2 |
|  4 |
|  3 |
查询1

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;
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
| ID |
|----|
|  1 |
|  2 |
|  4 |
|  3 |

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;
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
| ID |
|----|
|  1 |
|  2 |
|  4 |
|  3 |

如果要获取所有列,则可以将其连接回原始表。

如果您经常运行类似的查询(可能针对需要匹配的
firstname
lastname
city
的不同输入),则需要将这些查询的性能优先于其他查询(在DML语句的性能上),您可以创建三个复合索引:on
(firstname,lastname)
、on
(firstname,city)
和on
(lastname,city)

然后,该查询应该是一个UNION ALL。它将读取数据三次,而不是一次,但它将从索引中读取数据,只要只有一小部分行与这三个条件中的每一个条件匹配,就会产生更快的性能。然后,在500万行中,只有一小部分将在其整个过程中实际从磁盘读取y

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'
(或其他值!),而不是硬编码到查询中。

使用此查询(准确描述可能的匹配)

假设这三列已经定义了索引,您可能会期望一个由三个索引访问操作组成的串联索引访问

您甚至可以定义包含匹配选项的两列索引:

create index users_idx1 on users (lastname,firstname);
create index users_idx2 on users (lastname,city);
create index users_idx3 on users (city,firstname );
这将导致以下执行计划

-------------------------------------------------------------------------------------------
| 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')
请注意,第7行和第5行中的访问在两列上都使用谓词,第3行中的访问仅限于城市-如果这是一个性能问题,则必须根据数据基数调整索引定义


如果您没有选择,您也可以检查。

我正在考虑第一个解决方案,但您认为这对500万条记录有效吗?@pieniak…任何一个版本都应该具有类似的性能。似乎有必要进行完整的表扫描。可能有一些方法可以优化Oracle的性能,使用第二个版本和正确的索引。OP说至少应该满足两个条件;您为正好两个编写了它。这也可能会导致第二个解决方案出现问题,如果某些列可以为NULL:例如John Smith London应该与John Smith NULL匹配,但您的第二个版本将拒绝它。事实上,在c如果这两种解决方案不相等。@mathguy…我将问题解读为:“我想知道是否有可能选择n-1个条件中的n-1个条件已匹配的所有记录?”显然,对于更多的条件,
=
可以替换为
=
。或者为了保持灵活性:
具有COUNT(*)>=(选择COUNT(*)-1(来自过滤器)
?@AlexPoole-您不能按照建议的方式保持灵活性。您可以更改要匹配的firstname、lastname和city值-这是灵活性,不需要在查询中硬编码;但需要匹配哪些列(尤其是此类列的数量)否则,您必须使用动态SQL,而MT0在这里的意思似乎不是这样。这种方法将导致更差的性能。显示的数据已按id分组;取消激活它,您将丢失所有信息-只需重新分组(与其他操作相比,这是一个更昂贵的操作)。根据您在这里尝试的精神,您可以让查询更加灵活,方法是让
过滤器
CTE为一行三列,与OP的行类型(减去id)匹配。当然,最好的方法是使用bind变量,而不是WITH子句。旁注:感谢您提供INSERT语句和需要帮助的正确查询。为了使其更有用,您还应该包括CREATE TABLE语句;INSERT语句应该经过测试。(您发布的INSERT语句在Oracle中无效;您应该测试它,然后继续修复它,直到它正常工作。)“如果我们知道users表有500万条记录,在Oracle中是否可能?”是的,这是可能的,但是您需要什么样的性能呢?我假设您希望只使用SQL实现松散的匹配逻辑,并且您的示例过于简化。我建议您进行研究。为通过的每列选择1,否则选择0,求和并与列数进行比较。