【问题标题】:Create a report with 2 rows of values, each of which is from a separate SELECT statement创建一个包含 2 行值的报告,每行来自一个单独的 SELECT 语句
【发布时间】:2021-09-06 10:50:07
【问题描述】:

我有一份报告(如果您愿意,可以使用Blazer),其中显示了jobs 表中最近更新或创建的行的类似数据:

5 Minutes | 1 Hour | 1 Day | Total
----------------------------------
        0        0     367  30,989

SQL 看起来像这样:

SELECT
    (SELECT COUNT(*)
    FROM public.jobs AS "Jobs"
    WHERE "Jobs"."updated_at" BETWEEN NOW() - INTERVAL '5 minutes' AND NOW()
    ) as "5 Minutes",
    
    (SELECT COUNT(*)
    FROM public.jobs AS "Jobs"
    WHERE "Jobs"."updated_at" BETWEEN NOW() - INTERVAL '1 Hours' AND NOW()
    ) as "1 Hour",

    (SELECT COUNT(*)
    FROM public.jobs AS "Jobs"
    WHERE "Jobs"."updated_at" BETWEEN NOW() - INTERVAL '1 Day' AND NOW()
    ) as "1 Day",

    (SELECT COUNT(*)
    FROM public.jobs AS "Jobs"
    ) as "Total"
;

我想为工作WHERE "Jobs"."active" IS TRUE 添加第二行。如何让这个显示另一行?

我希望最终结果是这样的:

Status | 5 Minutes | 1 Hour | 1 Day | Total
-------------------------------------------
*                0        0     367  30,989
Active           0        0     123  24,972

标签不是问题。唯一不明显的是如何创建新行。

【问题讨论】:

    标签: sql postgresql union-all aggregate-filter


    【解决方案1】:

    最简单的方法是对另一组查询进行 UNION,这些查询具有更严格的 where 子句:

    SELECT
        '*' as Kind,
    
        (SELECT COUNT(*)
        FROM public.jobs AS "Jobs"
        WHERE "Jobs"."updated_at" BETWEEN NOW() - INTERVAL '5 minutes' AND NOW()
        ) as "5 Minutes",
        
        (SELECT COUNT(*)
        FROM public.jobs AS "Jobs"
        WHERE "Jobs"."updated_at" BETWEEN NOW() - INTERVAL '1 Hours' AND NOW()
        ) as "1 Hour",
    
        (SELECT COUNT(*)
        FROM public.jobs AS "Jobs"
        WHERE "Jobs"."updated_at" BETWEEN NOW() - INTERVAL '1 Day' AND NOW()
        ) as "1 Day",
    
        (SELECT COUNT(*)
        FROM public.jobs AS "Jobs"
        ) as "Total"
    
    UNION ALL
    
    SELECT
        'Active', 
    
        (SELECT COUNT(*)
        FROM public.jobs AS "Jobs"
        WHERE "Jobs"."IsActive" IS TRUE AND "Jobs"."updated_at" BETWEEN NOW() - INTERVAL '5 minutes' AND NOW()
        ) as "5 Minutes",
        
        (SELECT COUNT(*)
        FROM public.jobs AS "Jobs"
        WHERE "Jobs"."IsActive" IS TRUE AND "Jobs"."updated_at" BETWEEN NOW() - INTERVAL '1 Hours' AND NOW()
        ) as "1 Hour",
    
        (SELECT COUNT(*)
        FROM public.jobs AS "Jobs"
        WHERE "Jobs"."IsActive" IS TRUE AND "Jobs"."updated_at" BETWEEN NOW() - INTERVAL '1 Day' AND NOW()
        ) as "1 Day",
    
        (SELECT COUNT(*)
        FROM public.jobs AS "Jobs"
        WHERE "Jobs"."IsActive" IS TRUE 
        ) as "Total"
    
    

    【讨论】:

      【解决方案2】:

      如果我是你,我更喜欢用这种方式来解决你的问题:

      select
        "Jobs"."active" as Status,
        sum(case when "Jobs"."updated_at" BETWEEN NOW() - INTERVAL '5 minutes' AND NOW() then 1 else 0 end) as "5 Minutes",
        sum(case when "Jobs"."updated_at" BETWEEN NOW() - INTERVAL '1 Hours' AND NOW() then 1 else 0 end) as "1 Hour",
        sum(case when "Jobs"."updated_at" "Jobs"."updated_at" BETWEEN NOW() - INTERVAL '1 Day' AND NOW() then 1 else 0 end) as "1 Day",
        count(*) as "Total"
      from public.jobs AS "Jobs"
      group by "Jobs"."active"
      

      通过这种方式,您可以读取您的表格 public.jobs 一次,而不是多次(每个 count 一次)。有了这个选择,按status 分组就是一个简单的group by 操作

      【讨论】:

        【解决方案3】:

        基本上,您需要条件聚合。在 Postgres 中,这通常会使用 filter:

        SELECT COUNT(*) FILTER (WHERE j."updated_at" BETWEEN NOW() - INTERVAL '5 minute' AND NOW()) as cnt_5_minutes,
               COUNT(*) FILTER (WHERE j."updated_at" BETWEEN NOW() - INTERVAL '1 hour' AND NOW()) as cnt_1_hour,
               COUNT(*) FILTER (WHERE j."updated_at" BETWEEN NOW() - INTERVAL '1 day' AND NOW()) as cnt_1_day,
               COUNT(*) as Total
        FROM public.jobs j;
        

        您可能没有未来的更新日期,因此可以更简单地写为:

        SELECT COUNT(*) FILTER (WHERE j."updated_at" >= NOW() - INTERVAL '5 minute') as cnt_5_minutes,
               COUNT(*) FILTER (WHERE j."updated_at" >= NOW() - INTERVAL '1 hour') as cnt_1_hour,
               COUNT(*) FILTER (WHERE j."updated_at" >= NOW() - INTERVAL '1 day') as cnt_1_day,
               COUNT(*) as Total
        FROM public.jobs j;
        

        另外,我建议你去掉updated_at 的双引号。在标识符周围使用双引号只是一个坏习惯。

        【讨论】:

          【解决方案4】:

          唯一不明显的是如何创建新行。

          基本上,使用UNION ALL 添加第二行。

          不过,首先要摆脱每个指标的所有单独的 SELECT 查询。那是不必要的昂贵(如果桌子不是很小,这很重要)。带有条件聚合的单个SELECT 可以替换您的所有原始内容(如 Gordon 建议的那样)。在 Postgres 9.4 或更高版本中,aggregate FILTER 子句 是要走的路。见:

          要获得另一行,您可以运行另一个查询,将过滤器 "active" IS TRUE 添加到每个表达式(归结为 active,因为 boolean 列不需要进一步评估)。

          但这会使成本再次翻倍,我们可以避免这种情况。在CTE 中运行单个SELECT,并在外部查询中使用UNION ALL 分割结果:

          WITH cte AS (
             SELECT count(*) FILTER (WHERE updated_at > now() - interval '5 min')             AS ct_5min
                  , count(*) FILTER (WHERE updated_at > now() - interval '5 min' AND active)  AS ct_5min_a
                  , count(*) FILTER (WHERE updated_at > now() - interval '1 hour')            AS ct_1h
                  , count(*) FILTER (WHERE updated_at > now() - interval '1 hour' AND active) AS ct_1h_a
                  , count(*) FILTER (WHERE updated_at > now() - interval '1 day')             AS ct_1d
                  , count(*) FILTER (WHERE updated_at > now() - interval '1 day' AND active)  AS ct_1d_a
                  , count(*)                       AS ct_all
                  , count(*) FILTER (WHERE active) AS ct_all_a
             FROM   public.jobs
             )
          SELECT '*' AS status, ct_5min, ct_1h, ct_1d, ct_all
          FROM   cte
          UNION ALL
          SELECT 'Active', ct_5min_a, ct_1h_a, ct_1d_a, ct_all_a
          FROM   cte
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-03-31
            • 1970-01-01
            • 1970-01-01
            • 2018-12-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多