加入时包含 NULL 行

Sea*_*ean 4 postgresql join group-by

如何从 table_A 中选择所有行,然后连接来自其他表的结果,但包含一个 NULL 行以显示那些没有 table_A_id 的其他表的聚合值?

WITH RECURSIVE transactions_with_children AS (
  SELECT
    table_A_id,
    other_stuff,
    1 AS depth
  FROM transactions
  WHERE transactions.parent_transaction_id IS NULL
  UNION ALL
  SELECT
    table_A_id,
    other_stuff,
    depth + 1 AS depth
  FROM transactions children
    INNER JOIN transactions_with_children parents ON children.parent_transaction_id = parents.id
)

SELECT 
  table_A.id,
  view_B.aggregate_col1, view_B.aggregate_col2,
  view_C.aggregate_col1, view_C.aggregate_col2

FROM table_A
  -- Limit table_A to only records with data from transactions_with_children, but I also need to include
  -- a null row for all transactions_with_children that don't have a table_A_id
  INNER JOIN transactions_with_children on transactions_with_children.table_A_id = table_A.id
  LEFT JOIN (SELECT
               t.table_A_id,
               aggregate_col1, aggregate_col2
             FROM transactions_with_children t
             GROUP BY t.table_A_id
            ) view_B
    ON table_A.id = view_B.table_A_id
  LEFT JOIN (SELECT
               t.table_A_id,
               aggregate_col1, aggregate_col2
             FROM transactions_with_children t
             GROUP BY t.table_A_id
            ) view_C
    ON table_A.id = view_C.table_A_id
Run Code Online (Sandbox Code Playgroud)

递归 WITH 表都是来自大表的范围记录。我正在使用此表执行的每个 LEFT JOIN 只是从中聚合数据。SUM、COUNT 等。然后我加入公共 table_A_id,WITH 中的每个记录可能有也可能没有。

所以对transactions_with_children(我认为)进行RIGHT JOIN 的问题是我会从transactions_with_children 表中得到大量重复项。我想我可以创建一个 RIGHT JOIN 子选择,我从 transactions_with_children 中提取记录并分组。

就像是:

RIGHT JOIN (
  SELECT t.map_account_affiliate_id
  FROM transactions_with_children t 
  GROUP BY t.map_account_affiliate_id
) ids ON map_account_affiliates.id = ids.map_account_affiliate_id
Run Code Online (Sandbox Code Playgroud)

如果transactions_with_children.map_account_affiliate_id 有任何空值,我假设上面的连接选择将包含一个空字段,对吗?

这似乎是一种很老套的方法,但我对 SQL 也不是很好。

我尝试仅出于问题的目的简化查询,但也许更容易查看原始查询。

WITH RECURSIVE transactions_with_children AS (
  SELECT
    transactions.id,
    transactions.transaction_type,
    transactions.successful,
    transactions.test,
    transactions.amount_cents,
    transactions.parent_transaction_id,
    orders.map_account_affiliate_id AS map_account_affiliate_id,
    orders.t1_affiliate_tracking_id AS t1_affiliate_tracking_id,
    orders.t2_affiliate_tracking_id AS t2_affiliate_tracking_id,
    orders.id                       AS order_id,
    orders.front_end                AS front_end,
    1                               AS depth
  FROM transactions
    INNER JOIN line_items ON line_items.id = transactions.line_item_id
    INNER JOIN orders ON orders.id = line_items.order_id AND orders.campaign_id IN (104)
  WHERE transactions.parent_transaction_id IS NULL
        AND transactions.successful = TRUE
        AND transactions.transaction_type IN (3, 1, 22, 5, 55, 7, 8, 22, 30, 31, 32, 33)
  UNION ALL
  SELECT
    children.id,
    children.transaction_type,
    children.successful,
    children.test,
    children.amount_cents,
    children.parent_transaction_id,
    orders.map_account_affiliate_id AS map_account_affiliate_id,
    orders.t1_affiliate_tracking_id AS t1_affiliate_tracking_id,
    orders.t2_affiliate_tracking_id AS t2_affiliate_tracking_id,
    orders.id                       AS order_id,
    orders.front_end                AS front_end,
    depth + 1                       AS depth
  FROM transactions children
    INNER JOIN transactions_with_children parents ON children.parent_transaction_id = parents.id
    INNER JOIN line_items ON line_items.id = children.line_item_id
    INNER JOIN orders ON orders.id = line_items.order_id
  WHERE children.successful = TRUE
        AND children.transaction_type IN (3, 1, 22, 5, 55, 7, 8, 22, 30, 31, 32, 33)

)

SELECT *
FROM transactions_with_children;

SELECT
  map_account_affiliates.id                                                             AS resource_id,
  map_account_affiliates.display_name                                                   AS resource_name,
  'affiliate'                                                                           AS resource_type,
  ''                                                                                    AS parent_resource_type,
  ''                                                                                    AS parent_resource_id,
  COALESCE(sales.num_sales, 0)
                                                                                        AS num_sales,
  COALESCE(sales.num_scheduled_sales, 0)
                                                                                        AS num_scheduled_sales,
  COALESCE(sales.num_fe_sales, 0)
                                                                                        AS num_fe_sales,
  COALESCE(sales.gross_sales, 0)
                                                                                        AS total_gross_sales,
  COALESCE(sales.gross_scheduled_sales, 0)
                                                                                        AS total_gross_scheduled_sales,
  COALESCE(sales.gross_sales :: FLOAT / nullif(sales.num_fe_sales :: FLOAT, 0)
  , 0)
                                                                                        AS avg_gross_order_value,
  (
    COALESCE(sales.num_fe_sales / nullif(((SUM(sales.num_fe_sales)
    OVER ()) :: FLOAT), 0)
    , 0)
    * 100.0
  )                                                                                     AS percent_all_fe_sales,
  COALESCE((sales.gross_fe_sales :: FLOAT / nullif(sales.num_fe_sales :: FLOAT, 0)), 0) AS avg_fe_sale_amt,
  COALESCE(refunds.num_full_refunds, 0)
                                                                                        AS num_full_refunds,
  COALESCE(refunds.num_part_refunds, 0)
                                                                                        AS num_part_refunds,
  COALESCE(refunds.total_refunds, 0)
                                                                                        AS total_refunds,
  COALESCE(refunds.total_refunds :: FLOAT / nullif(sales.gross_sales, 0)
  , 0)
                                                                                        AS percent_refund_amount,
  (COALESCE(refunds.num_part_refunds, 0)
   +
   COALESCE(refunds.num_full_refunds, 0)
  ) :: FLOAT / nullif(sales.num_sales, 0) * 100.0                                       AS percent_refunds,
  COALESCE(chargebacks.num_chargebacks, 0)
                                                                                        AS num_chargebacks,
  COALESCE(chargebacks.num_chargebacks_won, 0)
                                                                                        AS num_chargebacks_won,
  COALESCE(chargebacks.total_chargebacks, 0)
                                                                                        AS total_chargebacks,
  COALESCE(chargebacks.total_chargebacks :: FLOAT / nullif(sales.gross_sales, 0)
  , 0)
  * 100.0                                                                               AS percent_chargeback_amount,
  (chargebacks.num_chargebacks) :: FLOAT / nullif(sales.num_sales, 0) * 100.0           AS percent_chargebacks,
  (
    COALESCE(sales.gross_sales, 0) -
    COALESCE(refunds.total_refunds, 0) -
    COALESCE(chargebacks.total_chargebacks, 0)
  )                                                                                     AS total_net_sales,
  (
    (
      COALESCE(sales.gross_sales, 0) -
      COALESCE(refunds.total_refunds, 0) -
      COALESCE(chargebacks.total_chargebacks, 0)
    ) / nullif(sales.num_fe_sales, 0)

  )                                                                                     AS avg_net_order_value

FROM map_account_affiliates

  LEFT JOIN (SELECT
               t.map_account_affiliate_id               AS map_account_affiliate_id,
               COUNT(DISTINCT t.order_id)               AS num_sales,
               COUNT(t.id)
                 FILTER (WHERE t.transaction_type = 22) AS num_scheduled_sales,
               SUM(t.amount_cents)
                 FILTER (WHERE t.transaction_type = 22) AS gross_scheduled_sales,
               SUM(t.amount_cents)                      AS gross_sales,
               COUNT(DISTINCT t.order_id)
                 FILTER (WHERE t.front_end = TRUE)      AS num_fe_sales,
               SUM(t.amount_cents)
                 FILTER (WHERE t.front_end = TRUE)      AS gross_fe_sales
             FROM transactions_with_children t
             WHERE t.transaction_type IN (3, 1, 22)
             GROUP BY t.map_account_affiliate_id
            ) sales
    ON map_account_affiliates.id = sales.map_account_affiliate_id
  LEFT JOIN (SELECT
               t.map_account_affiliate_id               AS map_account_affiliate_id,
               COUNT(t.id)
                 FILTER (WHERE t.transaction_type = 5)  AS num_full_refunds,
               COUNT(t.id)
                 FILTER (WHERE t.transaction_type = 55) AS num_part_refunds,
               SUM(t.amount_cents)                      AS total_refunds
             FROM transactions_with_children t
             WHERE t.transaction_type IN (5, 55)
             GROUP BY t.map_account_affiliate_id
            ) refunds
    ON map_account_affiliates.id = refunds.map_account_affiliate_id
  LEFT JOIN (SELECT
               t.map_account_affiliate_id              AS map_account_affiliate_id,
               COUNT(t.id)
                 FILTER (WHERE t.transaction_type = 7) AS num_chargebacks,
               COUNT(t.id)
                 FILTER (WHERE t.transaction_type = 8) AS num_chargebacks_won,
               (
                 COALESCE(SUM(t.amount_cents)
                            FILTER (WHERE t.transaction_type = 7), 0)
                 - COALESCE(SUM(t.amount_cents)
                              FILTER (WHERE t.transaction_type = 8), 0)
               )                                       AS total_chargebacks
             FROM transactions_with_children t
             WHERE t.transaction_type IN (7, 8)
             GROUP BY t.map_account_affiliate_id
            ) chargebacks
    ON map_account_affiliates.id = chargebacks.map_account_affiliate_id

WHERE map_account_affiliates.account_id = 1

      AND (num_sales > 0 OR num_fe_sales > 0 OR num_scheduled_sales > 0 OR num_full_refunds > 0
           OR num_part_refunds > 0 OR num_chargebacks > 0)

ORDER BY resource_name
Run Code Online (Sandbox Code Playgroud)

我知道这是一个冗长而凌乱的查询,但希望我的解释足以让我的问题得到理解。

Erw*_*ter 6

在代码中的注释之后使用RIGHT JOIN代替INNER JOIN

将 table_A 限制为仅包含来自 transactions_with_children 的数据的记录,但我还需要为所有没有 table_A_id 的 transactions_with_children 包含一个空行

RIGHT [OUTER] JOIN将表中的所有行保留在联接的右侧,并为联接左侧的表中没有与联接条件匹配的行添加 NULL 值。您的文字有点模棱两可,您可能需要LEFT JOIN,这与反面相同。

手册中的基础知识在这里。