【问题标题】:Selecting id's where all rows match选择所有行匹配的 id
【发布时间】:2016-06-01 02:28:24
【问题描述】:

我有两个表:DOCUMENT 和 METADATA。 DOCUMENT 存储一个 ID 和一些我们不感兴趣的信息,METADATA 存储这些文档的“标签”。标签由键和值组成。

所以对于一个文档,DOCUMENT 表中只有一个条目,但 METADATA 表中可能有很多。

现在我需要传递一组键/值,并从 METADATA 表中仅检索与所有键/值匹配的文档。这意味着“同时”检查不同的行,好吧,我真的不知道该怎么做。

快速示例:

META_KEY | META_VALUE | META_DOCUMENT_ID
----------------------------------------
Firstname| Chris      | 1
Lastname | Doe        | 1
Firstname| Chris      | 2
Lastname | Moe        | 2

因此,如果我使用以下标签进行查询:“Firstname”="Chris"、“Lastname”="Doe",我想要 1 作为结果。如果我只指定 "Firstname"="Chris" 我想要 1 和 2 作为结果。

非常感谢您的帮助!


编辑:

我计算必须匹配的标签数量怎么样? 像这样:

select meta_document_id, count(*) from metadata where (meta_key = 'Firstname' and meta_value = 'Chris') or (meta_key = 'Lastname' and meta_value = 'Doe') group by meta_document_id

通过 count(*),我可以轻松找出所有输入键/值对是否都匹配。这将如何在性能方面运行?

【问题讨论】:

  • 为什么不能使用 select META_DOCUMENT_ID from table where META_VALUE=lastname
  • @safinhacko 可能是因为他想获取的不是所有具有lastname 的id,而是lastname == smth 的id
  • 这在更大范围内看起来很糟糕,我可能需要检索具有 10-20 个元数据键/值的文档...
  • “如果我用...查询”是什么意思?您将如何将标签传递给查询?看来您需要使用纯 SQL 以外的东西基于您的标签构建动态查询。
  • 我将在 Java 中使用休眠。但事实上,我什至不知道如何传递论点。我想一个循环是不可避免的,但我需要查询是可靠的,有很多参数和/或表中有很多数据。

标签: sql oracle


【解决方案1】:

嗯,您正在使用一个名为“键值”或“实体属性值”的数据库模型。

这通常不是最佳选择,您可以在以下问题中了解更多信息:

对于这两种情况,您需要两个单独的查询,如下所示:

SELECT distinct META_DOCUMENT_ID
FROM METADATA 
WHERE meta_key = 'Firstname' and meta_value = 'Chris'

SELECT distinct m1.META_DOCUMENT_ID
FROM METADATA m1
JOIN METADATA m2
ON m1.META_DOCUMENT_ID = m2.META_DOCUMENT_ID
WHERE m1.meta_key = 'Firstname' and m1.meta_value = 'Chris'
  AND m2.meta_key = 'Lastname' and m2.meta_value = 'Doe'

编辑:

我想我必须为 N 个键/值对加入 N 次表?

这可以在没有连接的情况下完成,例如如下所示(假设每个 id 的 meta_key 值不超过 1 个):

SELECT META_DOCUMENT_ID
FROM METADATA 
WHERE (meta_key, meta_value) IN
   ( ('Firstname' ,'Chris'), ('Lastname', 'Doe' ) )
GROUP BY META_DOCUMENT_ID
HAVING COUNT(*) = 2 /* 2 means that we are looking for 2 meta keys */

这将如何在性能方面运行?

太糟糕了。请参阅上述链接中有关此模型的说明。

这个查询在很多情况下必须进行全表扫描(尤其是当我们要查找的属性/键的数量超过几个时),计算每个 id 的值,然后选择 count = 2 的这些 id。

在规范化模型中,这是一个简单的查询,它可以使用索引快速选择只有这几行 firstname = 'Chris'

SELECT *
FROM table 
WHERE firstname = 'Chris' and lastname = 'Doe' 

【讨论】:

  • 感谢您提供的良好链接。话虽如此,除了暴露该设计的弱点之外,我真的不认为两者中的任何一个都提供了不错的选择。如果我们找出键的“模式”,我可能会以不同的方式处理它。至于查询,我想我必须为 N 个键/值对加入 N 次表?这将如何在性能方面运行?
  • 我已经用一个例子更新了答案,如果没有加入,如何做到这一点。
【解决方案2】:

Oracle 设置

CREATE TYPE KEY_VALUE_PAIR IS OBJECT (
  KEY   VARCHAR2(50),
  VALUE VARCHAR2(50)
);
/

CREATE TYPE KEY_VALUE_TABLE IS TABLE OF KEY_VALUE_PAIR;
/

CREATE TABLE meta_data ( meta_key, meta_value, meta_document_id ) AS
SELECT 'Firstname',   'Chris',    1 FROM DUAL UNION ALL
SELECT 'Lastname',    'Doe',      1 FROM DUAL UNION ALL
SELECT 'Phonenumber', '555-2368', 1 FROM DUAL UNION ALL
SELECT 'Firstname',   'Chris',    2 FROM DUAL UNION ALL
SELECT 'Lastname',    'Moe',      2 FROM DUAL UNION ALL
SELECT 'Phonenumber', '555-0001', 2 FROM DUAL;

查询

SELECT meta_document_id
FROM   (
  SELECT meta_document_id,
         CAST(
           COLLECT(
             KEY_VALUE_PAIR( meta_key, meta_value )
           ) AS KEY_VALUE_TABLE
         ) AS key_values
  FROM   meta_data
  GROUP BY meta_document_id
)
WHERE  KEY_VALUE_TABLE(
         -- Your values here:
         KEY_VALUE_PAIR( 'Firstname', 'Chris' ),
         KEY_VALUE_PAIR( 'Lastname',  'Doe' )
       )
       SUBMULTISET OF key_values;

输出

 META_DOCUMENT_ID
------------------
                1

更新 - 使用嵌套表重新实现元数据表

Oracle 设置

CREATE TYPE KEY_VALUE_PAIR IS OBJECT (
  META_KEY   VARCHAR2(50),
  META_VALUE VARCHAR2(50)
);
/

CREATE TYPE KEY_VALUE_TABLE IS TABLE OF KEY_VALUE_PAIR;
/

CREATE TABLE meta_data (
  meta_document_id INT,
  key_values       KEY_VALUE_TABLE
) NESTED TABLE key_values STORE AS meta_data_key_values;

CREATE UNIQUE INDEX META_DATA_KEY_VALUES_IDX ON META_DATA_KEY_VALUES (
  NESTED_TABLE_ID,
  META_KEY,
  META_VALUE
);
/

-- Insert everything in one go:
INSERT INTO META_DATA VALUES(
  1,
  KEY_VALUE_TABLE(
    KEY_VALUE_PAIR( 'Firstname',   'Chris' ),
    KEY_VALUE_PAIR( 'Lastname',    'Doe' ),
    KEY_VALUE_PAIR( 'Phonenumber', '555-2368' )
  )
);

-- Insert everything in bits:
INSERT INTO meta_data VALUE ( 2, KEY_VALUE_TABLE() );

INSERT INTO TABLE( SELECT key_values FROM meta_data WHERE meta_document_id = 2 )
  ( meta_key, meta_value ) VALUES( 'Firstname', 'Chris' );
INSERT INTO TABLE( SELECT key_values FROM meta_data WHERE meta_document_id = 2 )
  ( meta_key, meta_value ) VALUES( 'Lastname', 'Moe' );
INSERT INTO TABLE( SELECT key_values FROM meta_data WHERE meta_document_id = 2 )
  ( meta_key, meta_value ) VALUES( 'Phonenumber', '555-0001' );

--Select all the key-value pairs:
SELECT META_DOCUMENT_ID,
       META_KEY,
       META_VALUE
FROM   META_DATA md,
       TABLE( md.KEY_VALUES );

查询

上面的改动让你大大简化了查询:

SELECT META_DOCUMENT_ID
FROM   meta_data
WHERE  KEY_VALUE_TABLE(
         -- Your values here:
         KEY_VALUE_PAIR( 'Firstname', 'Chris' ),
         KEY_VALUE_PAIR( 'Lastname',  'Doe' )
       )
       SUBMULTISET OF key_values;

【讨论】:

  • 哇,这真是一个绝妙的解决方案。我什至看不懂你写的一半! :D 话虽如此,你认为这会比 kordirko 发布的多连接子句查询更有效吗?
  • 诚实的答案是:“在您的数据集上分析两种解决方案并查看”。但是,这个解决方案只需要一次表扫描,解释计划只显示一个sort (group by)和一个filter,所以应该是合理的。
  • @ChristopheSchutz 更新了一个稍微修改的表结构,这应该会以稍微复杂的INSERTs 为代价提高SELECT 的性能(但性能不会显着降低) - 但是,对于我会为此考虑的用例,您可能会选择比插入更多的东西。
  • @ChristopheSchutz 我不使用休眠,但您应该能够将其设置为 pass an arrayuser defined type 直接到 Oracle,因此您不需要使用任何动态 SQL这个解决方案。
【解决方案3】:

如果您事先知道所有可能的 TAGS,则可以使用一些PIVOT

with METADATA (META_KEY, META_VALUE, META_DOCUMENT_ID) as
(
select 'Firstname', 'Chris',1 from dual union all
select 'Lastname', 'Doe',1 from dual union all
select 'Firstname', 'Chris',2 from dual union all
select 'Lastname', 'Moe',2 from dual
)
select *
from metadata
PIVOT  ( max (META_VALUE ) FOR (META_KEY) IN ('Firstname' AS Firstname, 'Lastname' AS Lastname))
where Firstname = 'Chris' /* and Lastname ='Doe' ...*/

【讨论】:

  • 我没有……否则我一开始就没有这种模型^^
  • @ChristopheSchutz 大概你会在运行时查询哪些标签?如果是这样,您应该能够根据您的要求动态构建枢轴。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-22
相关资源
最近更新 更多