【问题标题】:vertical coalesce onto multiple rows within a group垂直合并到组内的多行
【发布时间】:2021-04-30 18:03:26
【问题描述】:

这个表是一个大杂乱查询的(假例子)结果。

=> WITH data(run, host, status, field_ip, control_ip, field_data, control_data) AS (
    VALUES (1, 1, 'no_control', '10.0.0.1', NULL, 'foo', NULL),
           (1, 2, 'good', '10.0.0.1', '10.0.0.1', 'bar', 'bar'),
           (1, 3, 'problem_1', '10.0.0.1', NULL, 'bar', NULL),
           (1, 3, 'problem_2', '10.0.0.2', NULL, 'baz', NULL),
           (1, 3, NULL, NULL, '192.168.1.1', NULL, 'wallace'))
SELECT * FROM data;
 run | host |   status   | field_ip | control_ip  | field_data | control_data 
-----+------+------------+----------+-------------+------------+--------------
   1 |    1 | no_control | 10.0.0.1 |             | foo        | 
   1 |    2 | good       | 10.0.0.1 | 10.0.0.1    | bar        | bar
   1 |    3 | problem_1  | 10.0.0.1 |             | bar        | 
   1 |    3 | problem_2  | 10.0.0.2 |             | baz        | 
   1 |    3 |            |          | 192.168.1.1 |            | wallace

我想根据以下规则将“control_ip”和“control_data”字段从它们不为 NULL 的行合并到它们为 NULL 的行中:考虑具有相同“运行”和“主机”的每组行' 值独立。在每个这样的组内:

  1. 所有字段都为 NOT NULL 的每一行都将被原样输出并从组中删除。 (例如,第 (1,2) 行将保持原样输出。它是其组中的唯一行,因此该组现在已完成。)
  2. 对于“field_ip”和“field_data”字段不为空但“control_ip”和“control_data”字段为空的每一行,在组中找到相反的另一行,并复制其“control_ip” ' 和 'control_data' 字段到第一行。然后输出修改后的行并将其从组中删除。
    • 如果不存在“相反为真的另一行”,则原样输出第一行(例如,要原样输出第 (1,1) 行)。
    • 如果有多个“另一行相反的情况”,则任意选择一个,并在复制其值后将其丢弃。 (样本数据集中没有这样的例子。)
    • 如果只有一个“另一行相反的情况”,请不要在复制其值后丢弃它。 (例如,行 (1,3,NULL,NULL,'192.168.1.1',NULL,'wallace') 应该将其 control_ip/control_Data 值复制到 both (1,3,problem_1 ,10.0.0.1) 和 (1,3,problem_2,10.0.0.2) 行。)
  3. 重复第 2 步,直到组中不再有“field_ip”和“field_data”字段不为空的行。
  4. 丢弃组中所有剩余的行(它们都将具有 NULL field_ip 和 field_data)。

上述样本数据的期望输出是:

 run | host |   status   | field_ip | control_ip  | field_data | control_data 
-----+------+------------+----------+-------------+------------+--------------
   1 |    1 | no_control | 10.0.0.1 |             | foo        | 
   1 |    2 | good       | 10.0.0.1 | 10.0.0.1    | bar        | bar
   1 |    3 | problem_1  | 10.0.0.1 | 192.168.1.1 | bar        | wallace
   1 |    3 | problem_2  | 10.0.0.2 | 192.168.1.1 | baz        | wallace

这是与How to concatenate text from multiple rows into a single text string in SQL server? 类似的问题,但该答案对我不起作用,因为GROUP BY run, host plus 聚合只能为每组发出一行,而在某些情况下我需要为每组发出多行。我尝试了一些涉及GROUP BY run, host, field_ip 的事情,但是第三 (1,3) 行被视为自己的组,这不好。另外,我使用的是 PostgreSQL (12),而不是 SQL Server,并且 AIUI PIVOT 是一个 sql-server-ism。

注意:大杂乱查询的最后一步,生成显示的表,是运行、主机和 field_ip = control_ip 上的 FULL JOIN 的结果。如果从前体开始到 FULL JOIN 更容易完成我的要求,请告诉我。

你有什么建议?

【问题讨论】:

  • 哪些规则让您知道想要丢弃第三 (1,3) 行而不是应用“垂直合并”来填充它的 NULL 值?同样,对于您正在填写 NULL 值的行,您能否保证永远只有 一个 NOT NULL价值选择?最后,您是否只需要为 control_ 列执行此操作?
  • @MatBailie 我已经为我想要的问题添加了更明确的规则。
  • 我已经更新了我的答案,并将示例数据扩大了一行以展示一些行为。顺便问一下,你想要一份工作吗?我可以问,因为这条评论是短暂的。

标签: sql postgresql


【解决方案1】:

嗯。 . .规则不是 100% 明确的,但这确实符合您想要做的:

select t.*
from (select t.*,
             max(field_ip) over (partition by run, host) as imputed_field_ip,
             count(*) over (partition by run, host) as cnt
      from t
     ) t
where cnt = 1 or field_ip is null;

【讨论】:

    【解决方案2】:

    使用MAX() OVER () 可以用作一种“垂直合并”,假设只有一个NOT NULL 值可供选择。

    我在下面添加了一行示例数据,以显示我如何处理可能存在多个可供选择的值;

    • 我只从对应的field_ 列为NULL 的行中挑选。

    最后我排除了field_ 列都是NULL 的记录。

    WITH
      data(run, host, status, field_ip, control_ip, field_data, control_data)
    AS
    (
      VALUES (1, 1, 'no_control', '10.0.0.1', NULL, 'foo', NULL),
             (1, 2, 'good', '10.0.0.1', '10.0.0.1', 'bar', 'bar'),
             (1, 3, 'problem_0', '10.0.0.1', '192.168.2.1', 'bar', 'zaphod'),
             (1, 3, 'problem_1', '10.0.0.1', NULL, 'bar', NULL),
             (1, 3, 'problem_2', '10.0.0.2', NULL, 'baz', NULL),
             (1, 3, NULL, NULL, '192.168.1.1', NULL, 'wallace')
    ),
      replace_nulls AS
    (
      SELECT
        run,
        host,
        status,
        field_ip,
        COALESCE(
          control_ip,
          MAX(CASE WHEN field_ip IS NULL THEN control_ip END) OVER (PARTITION BY run, host)
        )
          AS control_ip,
        field_data,
        COALESCE(
          control_data,
          MAX(CASE WHEN field_data IS NULL THEN control_data END) OVER (PARTITION BY run, host)
        )
          AS control_data
      FROM
        data
    )
    SELECT
      replace_nulls.*
    FROM
      replace_nulls
    WHERE
      field_ip IS NOT NULL
      OR
      field_data IS NOT NULL
    ;
    

    演示:https://dbfiddle.uk/?rdbms=postgres_13&fiddle=204569771c9affb372b971e1e8740b80

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-11-19
    • 2020-02-16
    • 1970-01-01
    • 2020-10-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多