SQL查询以查找子记录是特定集合的所有父记录

Bey*_*tun 5 postgresql relational-division

一个 Item 有许多 ItemDetails。ItemDetail 具有“type”、“value”和“item_id”字段。

当且仅当项目具有受某些可变条件限制的确切ItemDetails 时,我才需要找到所有项目。例如,我需要查找 ItemDetails 为 (type=10, value=1000) 和 (type=20 and value=2000) 的所有项目

我的第一个解决方案是这样的:

select p.*
from item p 
where not exists 
    ( 
      select c.id from item_detail c
      where c.item_id=p.id
      and (c.type<>10 or c.value<>1000)
      and (c.type<>20 or c.value<>2000)
    );
-- Execution Time: 17.819 ms
Run Code Online (Sandbox Code Playgroud)

但我意识到它只获取一个 ItemDetail(type=10, value=1000) 的项目。然后我发现了这个问题并改变了如下查询。

select p.*
from item p 
where not exists 
    ( 
      select c.id from item_detail c
      where c.item_id=p.id
      and (c.type<>10 or c.value<>1000)
      and (c.type<>20 or c.value<>2000)
    )
and 2 = (
  select count(c.item_id) from item_detail c
  where c.item_id=p.id);
-- Execution Time: 2426.596 ms
Run Code Online (Sandbox Code Playgroud)

但是第二个子查询导致了性能问题。第一个查询的执行时间为 5 毫秒,但第二个查询的执行时间为 800 毫秒。有没有更好的方法来做到这一点?

我正在使用 PostgreSQL 9.5。

这是它的小提琴

编辑1:这是那些无法到达的人的小提琴示例:

CREATE TABLE public.item
(
  id integer NOT NULL,
  name  character varying(10) NOT NULL, 
  CONSTRAINT item_pkey PRIMARY KEY (id)
);

CREATE TABLE public.item_detail
(
  id bigint NOT NULL,
  item_id integer NOT NULL,
  type  integer NOT NULL,
  value integer NOT NULL,
  CONSTRAINT item_detail_pkey PRIMARY KEY (id),
  CONSTRAINT fk_item_id FOREIGN KEY (item_id)
      REFERENCES item (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT uq_item_type_value UNIQUE (item_id, type, value)
);

INSERT INTO public.item VALUES (1, 'Item1'),(2, 'Item2'),(3, 'Item3'),(4, 'Item4'),(5, 'Item5');

INSERT INTO public.item_detail
VALUES
  (1,1,10,1000),
  (2,1,20,2000),
  (3,2,10,1000),
  (4,3,10,1000),
  (5,3,20,2000),
  (6,3,30,3000),
  (7,4,10,1000),
  (8,4,10,1500);
Run Code Online (Sandbox Code Playgroud)

编辑2:我想出了类似的解决方案这样的选择对我来说,从最好的选择这个源通过ypercube ??给出。

SELECT p.* FROM item p 
WHERE EXISTS (SELECT item_id FROM item_detail 
                WHERE item_id = p.id AND (type, value) = (10, 1000))
AND   EXISTS (SELECT item_id FROM item_detail 
                WHERE item_id = p.id AND (type, value) = (20, 2000))
AND NOT EXISTS (SELECT item_id FROM item_detail 
                WHERE item_id = p.id AND (type, value) NOT IN ((10, 1000), (20,2000)));
-- Execution Time: 0.984 ms
Run Code Online (Sandbox Code Playgroud)

ype*_*eᵀᴹ 4

我们试图解决的问题称为精确关系划分。它需要关系划分的额外条件,并且仍然有很多(实际上太多)方法来解决此类问题。

检查这个相关的SO问题,你会发现在Postgres和性能测试中有超过10种不同的方法来做到这一点。那里讨论的问题是(简单的,不精确的)关系划分,所以你必须调整答案:

如何在多通关系中过滤 SQL 结果

这是另一个:

select p.*
from item p 
  join 
    ( select item_id from item_detail 
      where (type, value) = (10, 1000)
      intersect
      select item_id from item_detail 
      where (type, value) = (20, 2000)
      except
      select item_id from item_detail 
      where (type, value) not in ((10, 1000), (20, 2000))
    ) as c
  on c.item_id = p.id ;
Run Code Online (Sandbox Code Playgroud)

我建议在 上建立索引(type, value, item_id)