【问题标题】:How to simplify nested SQL cross join?如何简化嵌套 SQL 交叉连接?
【发布时间】:2014-06-11 17:18:01
【问题描述】:

我正在使用 Postgres 9.3 并拥有以下四个表格,以便在未来价格和/或税率/税率变化方面具有最大的灵活性(更多详细信息请参见下文):

CREATE TABLE main.products
(
  id serial NOT NULL,
  "productName" character varying(255) NOT NULL,
  "productStockAmount" real NOT NULL,
)

CREATE TABLE main."productPrices"
(
  id serial NOT NULL,
  product_id integer NOT NULL,
  "productPriceValue" real NOT NULL,
  "productPriceValidFrom" timestamp without time zone NOT NULL,
)

CREATE TABLE main."productTaxes"
(
  id serial NOT NULL,
  product_id integer NOT NULL,
  "productTaxValidFrom" timestamp without time zone NOT NULL,
  "taxRate_id" integer NOT NULL,
)

CREATE TABLE main."taxRateValues"
(
  id integer NOT NULL,
  "taxRate_id" integer NOT NULL,
  "taxRateValueValidFrom" timestamp without time zone NOT NULL,
  "taxRateValue" real,
)

我根据以下查询构建了一个视图以获取当前相关的值:

SELECT p.id, p."productName", p."productStockAmount", sub."productPriceValue", CHR(64+sub3."taxRate_id") AS taxRateId, sub3."taxRateValue" FROM main."products" p 
CROSS JOIN LATERAL (SELECT * FROM main."productPrices" pp2 WHERE pp2."product_id"=p."id" AND pp2."productPriceValidFrom" <= NOW() ORDER BY pp2."productPriceValidFrom" DESC LIMIT 1) AS sub 
CROSS JOIN LATERAL (SELECT * FROM main."productTaxes" pt WHERE pt."product_id"=p."id" AND pt."productTaxValidFrom" <= NOW() ORDER BY pt."productTaxValidFrom" DESC LIMIT 1) AS sub2 
CROSS JOIN LATERAL (SELECT * FROM main."taxRateValues" trv WHERE trv."taxRate_id"=sub2."taxRate_id" AND trv."taxRateValueValidFrom" <= NOW() ORDER BY trv."taxRateValueValidFrom" DESC LIMIT 1) AS sub3 

这很好用并且给了我正确的结果,但我假设如果数据库中有数千种产品、价格变化等会出现性能问题。

我可以做些什么来简化语句或整体数据库设计? 用词来描述所需的灵活性:

  • 价格可以更改,我必须记录哪个价格在哪个时间有效(存档,因此不仅需要当前价格)
  • 产品适用的税率可以更改(例如,由于法律的变化) - 也需要存档
  • 一般税率可以更改(法律也可以更改,但与单个产品无关,而是与具有此标识符的所有产品相关)

可能发生的事情的一些例子:

  • 产品 X 在 2014 年 5 月 9 日将价格从 100 更改为 200
  • 产品 X 在 2014-07-01 将税率从 A 更改为 B
  • 税率 A 的税率值在 2014 年 9 月 1 日从 16 更改为 19

【问题讨论】:

  • 一个非常好的问题,包含表定义以及清晰的描述和基本原理。无需进一步编辑即可清除。这就是它的完成方式。 (仅缺少您的 Postgres 版本。)
  • 感谢您的反馈。为了完整起见,我添加了 Postgres 版本;)

标签: sql performance postgresql join database-performance


【解决方案1】:

只要您获取所有行或多于所有行的百分之几,第一次聚合一次会大大加快每个表,然后然后加入。

我建议DISTINCT ON 为每个 id 选择最新的有效行:

SELECT p.id, p."productName", p."productStockAmount"
      ,pp."productPriceValue"
      ,CHR(64 + tr."taxRate_id") AS "taxRateId", tr."taxRateValue"
FROM   main.products p 
LEFT   JOIN  (
   SELECT DISTINCT ON (product_id)
          product_id, "productPriceValue"
   FROM   main."productPrices"
   WHERE  "productPriceValidFrom" <= now()
   ORDER  BY product_id, "productPriceValidFrom" DESC
   ) pp ON pp.product_id = p.id
LEFT   JOIN (
   SELECT DISTINCT ON (product_id)
          product_id, "taxRate_id"
   FROM   main."productTaxes"
   WHERE  "productTaxValidFrom" <= now()
   ORDER  BY product_id, "productTaxValidFrom" DESC
   ) pt ON pt.product_id = p.id
LEFT   JOIN (
   SELECT DISTINCT ON ("taxRate_id") *
   FROM   main."taxRateValues"
   WHERE  "taxRateValueValidFrom" <= now()
   ORDER  BY "taxRate_id", "taxRateValueValidFrom" DESC
   ) tr ON tr."taxRate_id" = pt."taxRate_id";

为了安全起见,使用LEFT JOIN。并非每个产品都可能在所有子表中都有条目。

我订阅了@Clodoaldo 关于双引号标识符的文章。我从不使用任何合法的小写名称。使用 Postgres 让您的生活更轻松。

DISTINCT ON的详细解释:
Select first row in each GROUP BY group?

【讨论】:

  • 非常感谢 - 查询显然与原始查询的工作方式完全相同,但如果您能进一步详细说明它为什么这样做,那就太好了。特别是,如何确保始终从子表中选择正确的条目。还是像“先执行 ORDER BY 而 DISTINCT 只取它看到的第一个条目”一样简单?
  • @fkerber:我为DISTINCT ON添加了详细解释的链接。是的,就是这么简单。 DISTINCT 步骤在聚合(GROUP BY - 此查询中不存在)、ORDER BY 和窗口函数(OVER 子句 - 此查询中也不存在)之后。
【解决方案2】:

不要创建带引号的标识符。一旦你这样做了,你就会永远被他们困住,你将不得不在任何地方引用和记住外壳。如果您在创建时不引用标识符,则可以随时使用驼峰式大小写。

我不明白您为什么需要cross lateral。我认为它可能只是

select
    p.id,
    p."productName",
    p."productStockAmount",
    pp2."productPriceValue",
    chr(64 + trv."taxRate_id") as "taxRateId",
    trv."taxRateValue"
from
    main."products" p
    left join (
        select *
        from main."productPrices"
        where "productPriceValidFrom" <= now()
        order by "productPriceValidFrom" desc
        limit 1
    ) pp2 on pp2."product_id" = p."id"
    left join (
        select "taxRate_id"
        from main."productTaxes"
        where "productTaxValidFrom" <= now()
        order by "productTaxValidFrom" desc
        limit 1
    ) pt on pt."product_id" = p."id"
    left join (
        select *
        from main."taxRateValues"
        where "taxRateValueValidFrom" <= now()
        order by "taxRateValueValidFrom" desc
        limit 1
    ) trv on trv."taxRate_id" = pt."taxRate_id"

【讨论】:

  • 我对 Postgres 不是很熟悉,但是在应用连接之前,子查询中的限制不会减少结果集吗?与其获取对应 ID 的单行,不如获取整个表中的单行。
  • @Allan:更重要的是,单一税率不会削减它。我们需要 每个 税率(正在使用)等的最新行。
  • 我认为你们都是对的。事实上,上面的代码并没有带来预期的结果。关于骆驼案例,数据库表最初是由 Laravel 自动创建的(使用 Eloquent ORM),所以我通常不直接使用数据库。
  • @Erwin 哦,我错过了。这就是为什么横向横向对他有用
  • @Allan:我现在看到你的评论暗示了同一个方向。
猜你喜欢
  • 2021-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-01
  • 1970-01-01
  • 2013-09-14
相关资源
最近更新 更多