BigQuery 避免多个子查询

Posted

技术标签:

【中文标题】BigQuery 避免多个子查询【英文标题】:BigQuery avoiding multiple subqueries 【发布时间】:2018-06-16 02:14:04 【问题描述】:

我们正在开发一个应用程序,它将请求存储在一个表中,并将响应存储在另一个表中(当然)。每个请求可以有多个响应,并且我们将请求 ID 存储在两个表中。

最初,我认为我们可以使用请求中的左连接 -> 响应来计算每个匹配条件的总数:

SELECT source, COUNT(*) as requests, COUNT(responses.request_id) as responses
FROM DATASET.requests
LEFT JOIN DATASET.responses ON requests.id = responses.request_id
WHERE source = "source1"
GROUP BY source

有 70 个请求符合 WHERE 条件,30 个响应符合这个条件。预期输出为:“source1, 70, 30”。 从那以后,我了解了更多关于 JOIN 行为的信息,而我们得到了“source1, 259, 207”。两边有重复的ID。

我能够得到我想要的结果的唯一方法是创建一个巨大的查询,以及多个在给定条件过滤的 ID 集中匹配的完整子查询。然后使用过滤后的 ID 集来真正提取我们的字段、统计信息等。

SELECT * FROM
  (SELECT COUNT(*) as responses FROM DATASET.responses
  WHERE id IN (SELECT id FROM DATASET.requests WHERE source = 
  "source1"))
  ,
 (SELECT source, COUNT(*) as requests
  FROM  PUBDATA.requests
  WHERE id IN (SELECT id FROM DATASET.requests WHERE source = "source1")
  GROUP BY source)

这看起来很糟糕。我曾尝试使用 CTE 来收集我们想要的 ID 列表,并使用 WHERE id/request_id IN (cte.id) 但这显然是不可能的,除非我们在 cte 上加入,这再次产生错误和成倍的结果。

由于我们想在查询中添加额外的统计信息,这将需要更多的 WHERE 子句,我担心这个怪物会继续增长并且难以实现。

如果有更好的方法,请告诉我。谢谢!

编辑 - 请求的示例架构 请求

id (String), source (String), partner_ids (Integer array), user_agent (String), timestamp (Timestamp), ...

回复

request_id (String, from requests.id), partner_id (Integer), is_billed (boolean), price_charged (float, null if is_billed = false), response_categories (String array, not from requests), ...

挑战在于我们必须主要查询 Requests 表以获取与我们的条件匹配的 ID 值列表,然后查询每个表的统计信息(例如 counts、count where is_billed 等)以获取一份合并报告。我们可能还需要从每个表的条件中提取 ID 池(例如 where requests.source = 'source1' 和response.response_categories IN 'action')

【问题讨论】:

样本数据和期望的结果会有所帮助。 【参考方案1】:

也许我误会了什么,你为什么不只计算每个并加入 id 呢?

WITH
    sources
    AS
        (  SELECT COUNT (*) source_cnt, id
             FROM dataset.request
         GROUP BY id),
    responses
    AS
        (  SELECT COUNT (*) AS response_cnt, id
             FROM dataset.responses
         GROUP BY id)
SELECT source_cnt, response_cnt, sources.id
  FROM sources INNER JOIN responses ON sources.id = responses.id;

如果您想保留所有记录,可以将其修改为完全外部联接:

WITH
    sources
    AS
        (  SELECT COUNT (*) source_cnt, id
             FROM dataset.request
         GROUP BY id),
    responses
    AS
        (  SELECT COUNT (*) AS response_cnt, id
             FROM dataset.responses
         GROUP BY id)
SELECT COALESCE (sources.id, responses.id) AS id, source_cnt, response_cnt
  FROM sources FULL OUTER JOIN responses ON sources.id = responses.id

【讨论】:

【参考方案2】:

我认为你可以用union allgroup by 做你想做的事:

select source, sum(requests) as requests, sum(responses) as responses
from ((select source, count(*) as requests, 0 as response
       from dataset.requests
       group by source
      ) union all
      (select source, 0 as requests, count(*) as responses
       from dataset.responses
       group by source
      )
     ) rr
group by source;

这会对所有来源进行计算。

编辑:

对于修改后的版本,只需多加一个join

select source, sum(requests) as requests, sum(responses) as responses
from ((select source, count(*) as requests, 0 as response
       from dataset.requests rq
       group by rq.source
      ) union all
      (select rq.source, 0 as requests, count(*) as responses
       from dataset.responses r join
            (select distinct rq.id
             from dataset.requests rq
            ) rq
            on r.id = rq.id
       group by rq.source
      )
     ) rr
group by source;

如果每个请求最多有一个响应,您可以将其缩短为:

select rq.source, count(*) as requests, count(r.id) as responses
from dataset.requests rq left join
     dataset.responses r
     on r.id = rq.id
group by rq.source

【讨论】:

谢谢,但我也不认为这会奏效。我已添加说明,即源字段仅存在于 requests 表中 - ID 列是它们之间唯一常见的交集。 更新给了我 (source1, 70, 207)。所以它仍然在乘以响应计数,应该是 30。 @EvanTestvoid 。 . .你真的应该用样本数据来澄清这个问题。您不是在寻找 响应数。您正在寻找有响应的请求数。这些是完全不同的计算。 这是不正确的。我们想要一个独立的请求和响应计数,它们具有预先过滤的 ID 集中的 ID 值,然后在任一表中按 ID 及其相关列值排序。再重复一遍,请求中有 70 个条目,响应中有 30 个条目。这是每个表的大小。【参考方案3】:

老实说,我对您最终希望看到的内容感到有些困惑,而且我也不完全理解如果一个请求可以有多个响应,那么您如何有 70 个请求而只有 30 个响应。您的意思是某些请求可以有 0 个响应吗?还是您在计算不同的响应?

如果您希望计算请求总数以及与这些特定请求相关的响应总数,我相信对您的代码进行的这种轻微修改应该可以工作:

SELECT source, COUNT(DISTINCT id) as requests, COUNT(responses.request_id) as responses
FROM `dataset.requests` as requests
LEFT JOIN `dataset.responses` as responses ON requests.id = responses.request_id
WHERE source = "source1"
GROUP BY source

【讨论】:

以上是关于BigQuery 避免多个子查询的主要内容,如果未能解决你的问题,请参考以下文章

新列的多个 BigQuery 子选择

BigQuery - 使用子查询和OR语句加入多个条件

避免用于派生选择中的列的多个重复子查询

返回链接记录并避免多个子查询/循环

如何避免多个子查询作为表达式(SQL优化)

BigQuery - 联合上的相关子查询不起作用