用于搜索可以位于三个不同表中的唯一ID的SQL查询

时间:2012-07-10 21:45:35

标签: sql postgresql postgresql-8.1

我有三个控制产品,颜色和尺寸的桌子。产品可以有或没有颜色和大小。颜色可以有或没有尺寸。

product      color                           size
-------      -------                         -------
id           id                              id
unique_id    id_product (FK from product)    id_product (FK from version)
stock        unique_id                       id_version (FK from version)
title        stock                           unique_id
                                             stock

所有表中都存在的unique_id列是一个串行类型(自动增量),它的计数器与三个表共享,基本上它作为它们之间的全局唯一ID。

它运行正常,但我想在unique_id选择一些字段时尝试提高查询性能。

由于我不知道我正在寻找的unique_id在哪里,我正在使用UNION,如下所示:

select title, stock
from product 
where unique_id = 10

UNION

select p.title, c.stock
from color c
join product p on c.id_product = p.id
where c.unique_id = 10

UNION

select p.title, s.stock
from size s
join product p on s.id_product = p.id
where s.unique_id = 10;

有更好的方法吗?谢谢你的任何建议!

编辑1

基于@ErwinBrandstetter和@ErikE的答案,我决定使用以下查询。主要原因是:

1)由于unique_id在所有表中都有索引,我将获得良好的性能

2)使用unique_id我会找到产品代码,所以我可以使用另一个简单的连接获得我需要的所有列

SELECT 

    p.title,
    ps.stock

FROM (

    select id as id_product, stock
    from product 
    where unique_id = 10

    UNION

    select id_product, stock
    from color
    where unique_id = 10

    UNION

    select id_product, stock
    from size
    where unique_id = 10

) AS ps

JOIN product p ON ps.id_product = p.id;

5 个答案:

答案 0 :(得分:5)

PL / pgSQL函数

要解决手头的问题,像下面这样的plpgsql函数应该更快:

CREATE OR REPLACE FUNCTION func(int)
  RETURNS TABLE (title text, stock int) LANGUAGE plpgsql AS
$BODY$
BEGIN

RETURN QUERY
SELECT p.title, p.stock
FROM   product p
WHERE  p.unique_id = $1; -- Put the most likely table first.

IF NOT FOUND THEN
    RETURN QUERY
    SELECT p.title, c.stock
    FROM   color c
    JOIN   product p ON c.id_product = p.id
    WHERE  c.unique_id = $1;
END;

IF NOT FOUND THEN
    RETURN QUERY
    SELECT p.title, s.stock
    FROM   size s
    JOIN   product p ON s.id_product = p.id
    WHERE  s.unique_id = $1;
END IF;

END;
$BODY$;

使用表限定列名更新了函数,以避免与OUT参数的命名冲突。

RETURNS TABLE需要PostgreSQL 8.4,RETURN QUERY需要8.2版。您可以将两者替换为旧版本。

不言而喻,您需要索引每个相关表格的列unique_idid应自动编入索引,作为主键。


重新设计

理想情况下,您可以单独从ID中分辨哪个表。您可以继续使用一个常见序列,但为第一个表添加100000000,为第二个表添加200000000,为第三个表添加300000000 - 或者根据您的需要添加integer。这样,数字中最不重要的部分很容易区分。

普通整数将数字从-2147483648跨越到+2147483647,如果这还不够,请移至bigint。如果可能的话,我会坚持使用bigint ID。它们比textWITH x(uid) AS (SELECT 10) -- provide unique_id here , a AS ( SELECT title, stock FROM x, product WHERE unique_id = x.uid ) , b AS ( SELECT p.title, c.stock FROM x, color c JOIN product p ON c.id_product = p.id WHERE NOT EXISTS (SELECT 1 FROM a) AND c.unique_id = x.uid ) , c AS ( SELECT p.title, s.stock FROM x, size s JOIN product p ON s.id_product = p.id WHERE NOT EXISTS (SELECT 1 FROM b) AND s.unique_id = x.uid ) SELECT * FROM a UNION ALL SELECT * FROM b UNION ALL SELECT * FROM c; 更小,更快。


CTE(实验!)

如果由于某种原因无法创建函数,则此纯SQL解决方案可能会执行类似的操作:

SELECT title, stock
FROM   product 
WHERE  unique_id = 10

UNION ALL
SELECT p.title, ps.stock
FROM   product p
JOIN  (
    SELECT id_product, stock
    FROM   color
    WHERE  unique_id = 10

    UNION ALL
    SELECT id_product, stock
    FROM   size
    WHERE  unique_id = 10
    ) ps ON ps.id_product = p.id;

不确定是否避免了我希望的其他扫描。必须进行测试。此查询至少需要PostgreSQL 8.4。


升级!

正如我刚刚了解到的那样,OP在PostgreSQL 8.1上运行 单独升级会大大加快操作速度。


查询PostgreSQL 8.1

由于您的选项有限,并且无法使用plpgsql函数,因此该函数应该比您拥有的函数更好。使用EXPLAIN ANALYZE进行测试 - 在v8.1中提供。

{{1}}

答案 1 :(得分:3)

我认为现在是重新设计的时候了。

你所使用的东西是条形码,它们在一个方面基本上都是相同的(它们是SerialNumberItems),但是因为它们在其他方面不同而被分成多个表。

我有几个想法:

更改默认值

只需让每件产品都有一种颜色“无颜色”和一种尺寸“无尺寸”。然后,您可以查询任何想要查找所需信息的表格。

超类型/子类型

如果没有太多修改,您可以使用超类型/子类型数据库设计模式。

在其中,有一个父表,其中所有不同的详细级别标识符都存在,子类型表的共享列进入超类型表(所有项目的方式相同)。对于项目不同的每种不同方式,有一个子类型表。如果需要子类型的互斥性(您可以有颜色或大小但不能同时使用),则父表将被赋予TypeID列,子类表对于ParentID和TypeID都具有FK。看看你的设计,事实上你不会使用互斥。

如果使用超类型表的模式,则确实存在必须分为两部分的问题,首先是超类型,然后是子类型。删除还需要以相反的顺序删除。但是,通过单个查询,您可以从超类型表中获取标题和库存等基本信息,从而获得巨大的好处。

您甚至可以为每个子类型创建模式绑定视图,使用可将插入,更新和删除转换为基表+子表上的操作的替代触发器。

更大的重新设计

您可以完全更改颜色和尺寸与产品的关系。

首先,你的“has-a”模式是:

  • 产品(什么都没有)
  • 产品>颜色
  • 产品>尺寸
  • 产品> Color->尺寸

这里有一个问题。显然,产品是其他东西(颜色和尺寸)的主要项目,但颜色没有大小!这是一项任意的任务。你也可以说Sizes有颜色 - 它没有什么区别。这表明您的表设计可能不是最好的,因为您正在尝试在父子关系类型中建模正交数据。真的,产品有ColorAndSize。

此外,当产品有颜色和尺寸时,颜色表中的uniqueid是什么意思?这样的产品可以订购没有尺寸,只有颜色?这个设计为一些东西(在我看来)永远不允许订购的东西分配一个唯一的ID - 但是你无法从Color表中找到这些信息,你必须首先比较颜色和尺寸表。这是一个问题。

我将其设计为:表格Product。表Size列出了任何产品可能的所有不同尺寸。表Color列出了所有产品可能的所有不同颜色。表OrderableProductProductIdColorIDSizeIDUniqueID(条形码值)。此外,每种产品必须有一种颜色和一种尺寸,否则就不存在。

基本上,颜色和大小就像X和Y坐标成网格;你正在填写允许组合的方框。哪一行是行,哪一列是无关紧要的。当然,一个人不是另一个人的孩子。

如果有任何合理的规则,一般而言,关于可以将哪些颜色或大小应用于各种产品子组,可能在ProductType表和ProductTypeOrderables表中有效,在创建新产品时,使用标准集填充OrderableProduct表 - 它仍然可以自定义,但可能比重新创建更容易修改。或者,它可以定义允许的颜色和大小范围。您可能需要单独的ProductTypeAllowedColor和ProductTypeAllowedSize表。例如,如果您正在销售T恤,您需要允许XXXS,XXS,XS,S,M,L,XL,XXL,XXXL和XXXXL,即使大多数产品从不使用所有这些尺寸。但对于软饮料,尺寸可能是6件装8盎司,24件装8盎司,2升等等,即使每种软饮料都不提供这种尺寸(软饮料也没有颜色)。

在这个新方案中,您只有一个表可供查询以查找正确的可订购产品。有了适当的指标,它应该是快速的。

您的问题

你问:

  在PostgreSQL中,你认为如果我在unique_id上​​使用索引,我会得到满意的表现吗?

用于重复查找数据的任何列或列集必须具有索引!任何其他模式每次都会导致全表扫描,这将是非常糟糕的性能。我确信这些索引会使您的查询快速闪烁,因为每个表只需要一次叶级读取。

答案 2 :(得分:1)

使用三个单独的auto_increment列生成唯一ID的更简单方法。只需在ID前加一个字母即可将其统一起来:

颜色

 C0000001
 C0000002
 C0000003

尺寸:

 S0000001
 S0000002
 S0000003
 ...

产品:

 P0000001
 P0000002
 P0000003
 ...

一些优点:

  • 您不需要跨表格序列化ID的创建以确保唯一性。这样可以提供更好的性能。
  • 您实际上不需要将该字母存储在表格中。同一个表中的所有ID都以相同的字母开头,因此您只需要存储该号码。这意味着您可以使用普通的auto_increment列来生成ID。
  • 如果你有一个ID,你只需要检查第一个字符,看看它可以找到哪个表。如果你只是想知道它是否是产品ID,你甚至不需要查询数据库。或者尺码ID。

缺点:

  • 它不再是一个数字。但是你可以通过使用1,2,3而不是C,S,P来解决这个问题。

答案 3 :(得分:1)

只要在unique_id上有一个索引,在每个表和连接列上的索引上,您的查询就会非常有效。

您可以将UNION转换为UNION ALL,但对于此查询,效果不会有任何不同。

答案 4 :(得分:1)

这有点不同。如果股票存在于多个{product,color,zsize}表中,我不明白预期的行为。 (UNION将删除重复项,但是对于整个行,例如{product_id,stock}元组。这对我没有意义。我只是采取第一个。(注意时髦的自我加入!!)< / p>

SELECT p.title
        , COALESCE (p2.stock, c.stock, s.stock) AS stock
FROM product p
LEFT JOIN product p2 on p2.id = p.id AND p2.unique_id = 10
LEFT JOIN color c on c.id_product = p.id AND c.unique_id = 10
LEFT JOIN zsize s on s.id_product = p.id AND s.unique_id = 10
WHERE COALESCE (p2.stock, c.stock, s.stock) IS NOT NULL
        ;