在 PostgreSQL 中呈现来自 3 个类似查询的数据

Presenting data from 3 similar queries in PostgreSQL

在我的 PostgreSQL 数据库中,我有以下架构:

CREATE TABLE survey_results (
    id integer NOT NULL,
    scores jsonb DEFAULT '{}'::jsonb,
    raw jsonb,
    created_at timestamp without time zone,
    updated_at timestamp without time zone  
);

在这个table中我有以下数据:

INSERT INTO survey_results (id, scores, raw, created_at, updated_at)
    VALUES (1, '{"medic": { "social": { "total": "high" } } }', null, now(), now());

INSERT INTO survey_results (id, scores, raw, created_at, updated_at)
    VALUES (2, '{"medic": { "social": { "total": "medium" } } }', null, now(), now());

INSERT INTO survey_results (id, scores, raw, created_at, updated_at)
    VALUES (3, '{"medic": { "social": { "total": "low" } } }', null, now(), now());

INSERT INTO survey_results (id, scores, raw, created_at, updated_at)
    VALUES (4, '{}', '{ "survey": { "denied": true } }', now(), now());

我想从这个 table 中获取以下格式的数据:

{
  "positive": {
    "2018-01-15": 2,
  },
  "negative": {
    "2018-01-5": 1,
  }
  "declined": {
    "2018-01-15": 1,
  }
}

我可以像这样在 3 个单独的查询中获取此数据:

WITH positive_count AS (
  SELECT
    COUNT(*) AS count_all,
    date(survey_results.created_at)
    FROM "survey_results"
    WHERE (scores#>>'{medic,social,total}' in('high','medium'))
    GROUP BY date(survey_results.created_at)
), negative_count AS (
  SELECT
    COUNT(*) AS count_all,
    date(survey_results.created_at)
    FROM "survey_results"
    WHERE (scores#>>'{medic,social,total}' in('low'))
    GROUP BY date(survey_results.created_at)
), declined_count AS (
  SELECT
    COUNT(*) AS count_all,
    date(survey_results.created_at)
    FROM "survey_results"
    WHERE (coalesce(raw#>>'{survey,denied}','f') = 'true')
    GROUP BY date(survey_results.created_at)
)

SELECT * from positive_count;

如何将这些结合起来,以我描述的格式通过一个查询获取此数据?或者用以下方式格式化会更容易:

  date      |positive_count|negative_count| declined_count|
------------+--------------+--------------+----------------
 2018-01-15 |       1      |       1      |        1      |

这里是sqlfiddle:

http://sqlfiddle.com/#!17/a9705/2

提前致谢。

要获得结果,请 table:

SELECT
  date(survey_results.created_at),
  COUNT(*) FILTER (WHERE (scores#>>'{medic,social,total}' in('high','medium'))) AS positive,
  COUNT(*) FILTER (WHERE (scores#>>'{medic,social,total}' in('low'))) AS negative,
  COUNT(*) FILTER (WHERE (coalesce(raw#>>'{survey,denied}','f') = 'true')) AS declined
  FROM survey_results
  GROUP BY date(survey_results.created_at)

获取json格式

select 
jsonb_build_object('positive', 
  json_agg(jsonb_build_object(date, positive)) FILTER (WHERE positive <> 0)
) ||
jsonb_build_object('negative',
  json_agg( jsonb_build_object(date, negative)) FILTER (WHERE negative <> 0)
) ||
jsonb_build_object('declined', 
  json_agg(jsonb_build_object(date, declined)) FILTER (WHERE declined <> 0)
)
from (
SELECT
  date(survey_results.created_at) as date,
  COUNT(*) FILTER (WHERE (scores#>>'{medic,social,total}' in('high','medium'))) AS positive,
  COUNT(*) FILTER (WHERE (scores#>>'{medic,social,total}' in('low'))) AS negative,
  COUNT(*) FILTER (WHERE (coalesce(raw#>>'{survey,denied}','f') = 'true')) AS declined
  FROM survey_results
  GROUP BY date(survey_results.created_at)
) as t1;

或json基于您的原始查询

WITH positive_count AS (
  SELECT
    jsonb_build_object(
      date(survey_results.created_at),
      COUNT(*)
    ) as j
    FROM "survey_results"
    WHERE (scores#>>'{medic,social,total}' in('high','medium'))
    GROUP BY date(survey_results.created_at)
), negative_count AS (
  SELECT
    jsonb_build_object(
      date(survey_results.created_at),
      COUNT(*)
    ) as j
    FROM "survey_results"
    WHERE (scores#>>'{medic,social,total}' in('low'))
    GROUP BY date(survey_results.created_at)
), declined_count AS (
  SELECT
    jsonb_build_object(
      date(survey_results.created_at),
      COUNT(*)
    ) as j
    FROM "survey_results"
    WHERE (coalesce(raw#>>'{survey,denied}','f') = 'true')
    GROUP BY date(survey_results.created_at)
)
SELECT
jsonb_build_object('positive', (SELECT json_agg(j) from positive_count)) ||
jsonb_build_object('negative', (SELECT json_agg(j) from negative_count)) ||
jsonb_build_object('declined', (SELECT json_agg(j) from declined_count))
;

编辑: 在评论之后,保留条目 0

WITH positive_count AS (
  SELECT
    jsonb_build_object(
      date(survey_results.created_at),
      COUNT(*) FILTER (WHERE (scores#>>'{medic,social,total}' in('high','medium')))
    ) as j
    FROM "survey_results"
    GROUP BY date(survey_results.created_at)
), negative_count AS (
  SELECT
    jsonb_build_object(
      date(survey_results.created_at),
      COUNT(*) FILTER (WHERE (scores#>>'{medic,social,total}' in('low')))
    ) as j
    FROM "survey_results"
    GROUP BY date(survey_results.created_at)
), declined_count AS (
  SELECT
    jsonb_build_object(
      date(survey_results.created_at),
      COUNT(*) FILTER (WHERE (coalesce(raw#>>'{survey,denied}','f') = 'true'))
    ) as j
    FROM "survey_results"
    GROUP BY date(survey_results.created_at)
)
SELECT
jsonb_build_object('positive', (SELECT json_agg(j) from positive_count)) ||
jsonb_build_object('negative', (SELECT json_agg(j) from negative_count)) ||
jsonb_build_object('declined', (SELECT json_agg(j) from declined_count))

编辑日期在前的结构(见评论)

根据我的查询(这更有效,因为它只查询 table 一次)

select 
json_agg(
jsonb_build_object(date, 
  jsonb_build_object('positive', positive)  ||
  jsonb_build_object('negative', negative)  ||
  jsonb_build_object('declined', declined)
))
from (
SELECT
  date(survey_results.created_at) as date,
  COUNT(*) FILTER (WHERE (scores#>>'{medic,social,total}' in('high','medium'))) AS positive,
  COUNT(*) FILTER (WHERE (scores#>>'{medic,social,total}' in('low'))) AS negative,
  COUNT(*) FILTER (WHERE (coalesce(raw#>>'{survey,denied}','f') = 'true')) AS declined
  FROM survey_results
  GROUP BY date(survey_results.created_at)
) as t1;

而且 WITH 查询会更复杂,因为 3 个查询中的每一个 return 都是日期,但我们只需要一个来源。当然,在条件进入 FILTER 之后,所有 3 个 WITH 查询 return 相同的行(只是差异计数)。所以我们可以从他们中的任何一个那里获取日期。但是为什么 运行 3 个查询,一个就够了?

我们确实需要 2 个查询(1 个子查询,就像我一样),因为有 2 个单独的聚合:1) 按日期聚合 2) 将此聚合的结果聚合到 1 行中。

您的 JSON 格式实施起来有点棘手。您尝试使用日期值作为键。这意味着,随着日期的增加,您的 JSON blob 中将有更多的键。

相反,我会在每个类别中创建一个列表。每个列表元素有两个键 created_at 和 count 并且可以重复。

{
  "positive": {[{"created_at": "2018-01-15","count" :2},{"created_at": "2018-01-14","count" :1}]},
  "negative": {[{"created_at": "2018-01-15","count" :1},{"created_at": "2018-01-14","count" :3}]},
  "declined": {[{"created_at": "2018-01-15","count" :1},{"created_at": "2018-01-14","count" :1}]}
}

这个查询让你到达那里。

WITH status_count AS (
         SELECT
                CASE
                    WHEN (survey_results.scores #>> '{medic,social,total}'::text[]) = ANY (ARRAY['high'::text, 'medium'::text]) THEN 'positive'::text
                    WHEN (survey_results.scores #>> '{medic,social,total}'::text[]) = 'low'::text THEN 'negative'::text
                    WHEN COALESCE(survey_results.raw #>> '{survey,denied}'::text[], 'f'::text) = 'true'::text THEN 'declined'::text
                    ELSE NULL::text
                END AS status,
            date(survey_results.created_at) AS created_at
           FROM survey_results
        )
 SELECT row_to_json(t.*) AS row_to_json
   FROM ( SELECT ( SELECT array_to_json(array_agg(row_to_json(d.*))) AS array_to_json
                   FROM ( SELECT status_count.created_at,
                            count(status_count.status) AS count
                           FROM status_count
                          WHERE status_count.status = 'positive'::text
                          GROUP BY status_count.created_at, status_count.status) d) AS positive,
            ( SELECT array_to_json(array_agg(row_to_json(d.*))) AS array_to_json
                   FROM ( SELECT status_count.created_at,
                            count(status_count.status) AS count
                           FROM status_count
                          WHERE status_count.status = 'negative'::text
                          GROUP BY status_count.created_at, status_count.status) d) AS negative,
            ( SELECT array_to_json(array_agg(row_to_json(d.*))) AS array_to_json
                   FROM ( SELECT status_count.created_at,
                            count(status_count.status) AS count
                           FROM status_count
                          WHERE status_count.status = 'declined'::text
                          GROUP BY status_count.created_at, status_count.status) d) AS declined) t;

对于简单的 table 选项,您需要创建一个枢轴 table。为此,您需要安装 tablefunc 扩展。

CREATE EXTENSION tablefunc

之后您可以运行下面的查询,它会给您想要的结果。

SELECT ct.created_at,
    ct.positive_count,
    ct.negative_count,
    ct.declined_count
FROM crosstab('
  WITH status_count AS 
    (SELECT
         CASE
          WHEN (scores#>>''{medic,social,total}'' in (''high'',''medium'')) THEN ''positive''
          WHEN (scores#>>''{medic,social,total}'' in(''low'') ) THEN ''negative''
          WHEN (coalesce(raw#>>''{survey,denied}'',''f'') = ''true'') THEN ''declined''
         END AS status,
         date(survey_results.created_at) AS created_at
     FROM survey_results)
   SELECT created_at, status, count(status) FROM status_count GROUP BY created_at, status ORDER BY created_at, status'::text) ct(created_at date, declined_count bigint, negative_count bigint, positive_count bigint);