【问题标题】:SQL query to search an unique ID that can be in three different tables用于搜索可以在三个不同表中的唯一 ID 的 SQL 查询
【发布时间】:2012-07-10 21:45:34
【问题描述】:

我有三个控制产品、颜色和尺寸的表格。产品可以有或没有颜色和尺寸。颜色可以有大小,也可以没有。

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;

【问题讨论】:

  • 为什么不使用 unique_id 加入所有表?
  • @03Usr,如果产品有多种颜色和至少一种尺寸,则查询将返回每种颜色的元组,而不是我所期望的一行。
  • 我看不到您在查询中使用unique_id 的位置。
  • @ypercube,感谢您的建议,我已在 where 子句中进行了更新。
  • 请从标签中删除所有非特定数据库,除非它确实正在在所述特定数据库上运行。说“应该只使用标准 SQL”是一回事 ..

标签: sql postgresql postgresql-8.1


【解决方案1】:

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 - 或任何适合您的需要。这样,数字中最不重要的部分就很容易区分了。

普通整数的范围从 -2147483648 到 +2147483647,如果这对您来说还不够,请移至 bigint。不过,如果可能的话,我会坚持使用integer ID。它们比biginttext 更小更快。


CTE(实验性!)

如果您由于某种原因无法创建函数,这个纯 SQL 解决方案可能会执行类似的技巧:

WITH 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;

不确定它是否能像我希望的那样避免额外的扫描。将不得不进行测试。此查询至少需要 PostgreSQL 8.4。


升级!

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


PostgreSQL 8.1 查询

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

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;

【讨论】:

  • 我不知道“快得多”如果使用好的索引..但它可能“足够快”以保证使用所以+1。
  • 很好的解决方案!问题是我的主机不允许 plpgsql 函数 =(
  • @pst:应该快两倍。主要是因为它平均只搜索 1,5 个表。查询计划也使用 plpgsql 函数进行缓存——这里是一个小小的额外胜利。
  • @ErwinBrandstetter 我想我通常只考虑一个数量级(以 10 为底)的较大部分或影响 O(n) 的差异,这很重要:)例如将 TABLE SCAN 更改为 INDEX SEEK 或将 bum 计划修复为 MERGE JOINS 而不是错误猜测的 LOOP JOIN。
  • @pst:我大体上同意,但我每天都会采用一个数量级(以 2 为基数),如果无法实现更好的话。不过,为了适应您的输入,将“更快”调低为“更快”。
【解决方案2】:

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

您有一些物品用作物品的条形码,这些物品在某一方面基本相同(它们是 SerialNumberItems),但由于其他方面不同而被分成多个表。

我有几个想法给你:

更改默认值

只需使每个产品都需要一种颜色“无颜色”和一种尺寸“无尺寸”。然后你可以查询任何你想找到你需要的信息的表。

超类型/子类型

无需太多修改,您就可以使用超类型/子类型数据库设计模式。

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

如果您使用超类型表的模式,您确实会遇到必须插入两部分的问题,首先是超类型,然后是子类型。删除也需要以相反的顺序删除。但是,您可以通过单个查询从超类型表中获取基本信息,例如 Title 和 Stock。

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

更大的重新设计

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

首先,您的“has-a”模式如下:

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

这里有问题。显然产品是具有其他东西(颜色和尺寸)但颜色没有尺寸的主要项目!那是任意分配。您可能会说尺寸有颜色 - 它没有任何区别。这表明您的表设计可能不是最好的,因为您正在尝试以父子类型的关系对正交数据进行建模。真的,产品有 ColorAndSize。

此外,当产品有颜色和尺寸时,颜色表中的uniqueid 是什么意思?可以订购没有尺寸、只有颜色的产品吗?这种设计为(在我看来)不应该被订购的东西分配了一个唯一的 ID——但是您无法从颜色表中找到此信息,您必须先比较颜色和尺寸表。这是个问题。

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

基本上,Color 和 Size 就像 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 上使用索引会获得令人满意的性能吗?

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

【讨论】:

  • 主题“更大的重新设计”有一些我将分析的观点。在这一点上,我认为重新设计是不可能的,但它在其他项目中会很有用。我将尝试改进索引并继续分析性能。
【解决方案3】:

使用三个单独的 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 来解决这个问题。

【讨论】:

  • 感谢您的建议。在我的例子中,unique_id 就像条形码一样工作。例如,如果我有一个名为 X 的产品,它有绿色和红色,每种颜色都必须有一个唯一的条形码。我的查询模拟查询查找某个条码,可以在productcolorsize
【解决方案4】:

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

您可以将这些 UNION 转换为 UNION ALL,但对于此查询,性能不会有任何差异。

【讨论】:

    【解决方案5】:

    这有点不同。如果库存存在于多个 {product,color,zsize} 表中,我不理解预期的行为。 (UNION 将删除重复项,但对于整个行,例如 {product_id,stock} 元组。这对我来说毫无意义。我只取第一个。(注意时髦的自加入!!)

    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
            ;
    

    【讨论】:

      猜你喜欢
      • 2020-06-17
      • 2013-01-20
      • 2015-06-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-20
      • 1970-01-01
      • 2012-08-16
      相关资源
      最近更新 更多