SQL - 跨列的分组依据值

SQL - Group By values across columns

提问人:Tesla 提问时间:10/30/2023 最后编辑:Tesla 更新时间:11/2/2023 访问量:106

问:

我有一个类似 netflow 表的东西,并希望以这样一种方式对其进行分组,即按(src_ip、src_port、dst_ip、dst_port)对其进行分组,其中值可以在 src 和 dst 字段之间交换。

src_ip src_port dst_ip dst_port bytes_sent
192.168.1.1 123 192.168.10.5 321 111
192.168.10.5 321 192.168.1.1 123 222
10.0.0.5 50 172.0.0.5 55 500
172.0.0.5 55 10.0.0.5 50 300
192.168.1.1 123 192.168.10.5 321 1000
192.168.1.1 123 192.168.10.5 20 999

我希望从此表中获得以下结果:

src_ip src_port dst_ip dst_port bytes_sent bytes_recv
192.168.1.1 123 192.168.10.5 321 1111 222
10.0.0.5 50 172.0.0.5 55 500 300
192.168.1.1 123 192.168.10.5 20 999 0

基本上,试图在一行中捕获双向流量。因此,例如按 (src_ip, src_port) 和 (dst_ip, dst_port) 分组,这些值可以反转。实现这一目标的最佳方法是什么?

SQL PostgreSQL 分组

评论

2赞 jarlh 10/30/2023
看起来像是自加入。
1赞 Conffusion 10/30/2023
你怎么知道 192.168.1.1 调用了 192.168.10.5 而不是相反?我认为如果没有更多条件,每个实现都会返回 4 条记录。如果存在多个具有相同 src_ip 和 dest_ip组合的记录,该怎么办?
0赞 Tesla 10/30/2023
我不太在意在这里获得正确的方向 - 这是一个很好的见解!

答:

0赞 SelVazi 10/30/2023 #1

假设最小的 IP 是源 IP,最大的 IP 是目标 IP。

您可以使用 和 函数来确保对于最小和最大 IP 地址的每个组合,将选择一个条目:LEASTGREATEST

with cte as (
  select least(src_ip, dst_ip) as smallestIP, greatest(src_ip, dst_ip) as largestIP
  from mytable src
  group by least(src_ip, dst_ip), greatest(src_ip, dst_ip)
),
routes as (
  select distinct src_ip, src_port, dst_ip, dst_port 
  from (
    select src_ip, src_port, dst_ip, dst_port 
    from mytable t
    inner join cte c on t.src_ip = c.smallestIP
    union all
    select dst_ip as src_ip, dst_port as src_port, src_ip as dst_ip, src_port as dst_port
    from mytable t
    inner join cte c on t.dst_ip = c.smallestIP
  ) as s
)
select r.src_ip, r.src_port, r.dst_ip, r.dst_port,
       sum(case when r.src_ip = t.src_ip and r.src_port = t.src_port
                     and r.dst_ip = t.dst_ip and r.dst_port = t.dst_port
                then bytes_sent else 0 end ) as bytes_sent,
       sum(case when r.src_ip = t.dst_ip and r.src_port = t.dst_port
                     and r.dst_ip = t.src_ip and r.dst_port = t.src_port
                then bytes_sent else 0 end ) as bytes_recv
from routes r
inner join mytable t on (
                        r.src_ip = t.src_ip and r.src_port = t.src_port
                        and r.dst_ip = t.dst_ip and r.dst_port = t.dst_port)
                      or (
                        r.src_ip = t.dst_ip and r.src_port = t.dst_port
                        and r.dst_ip = t.src_ip and r.dst_port = t.src_port
                      )
group by r.src_ip, r.src_port, r.dst_ip, r.dst_port

在这里演示

评论

1赞 Tesla 10/30/2023
我稍微更新了我的问题示例:如果有多行,我们想要分组的(src_ip、src_port、dst_ip、dst_port)组合可以在两侧怎么办?
0赞 SelVazi 10/31/2023
你能检查一下我的编辑吗
0赞 Ajax1234 10/30/2023 #2

您可以使用 self-:join

with cte as (
   select row_number() over (order by greatest(n.src_ip, n.dst_ip)) r, n.src_ip, n.src_port, n.dst_ip, n.dst_port, min(n.bytes_sent) bytes_sent from netflow n
   group by n.src_ip, n.src_port, n.dst_ip, n.dst_port
)
select n.src_ip, n.src_port, n.dst_ip, n.dst_port, n.bytes_sent, 
   coalesce(n1.bytes_sent, 0) bytes_recieved
from cte n left join cte n1 on n1.src_port = n.dst_port
where not exists (select 1 from cte n2 where n2.r < n.r and n2.dst_port = n.src_port)

看小提琴

评论

0赞 Tesla 10/30/2023
我稍微更新了我的问题示例:如果有多行,我们想要分组的(src_ip、src_port、dst_ip、dst_port)组合可以在两侧怎么办?
0赞 Ajax1234 10/31/2023
@Tesla 请看我最近的编辑
0赞 Tesla 10/31/2023
嗯,似乎无法正常工作。bytes_recv计数不正确,如果我添加一个与不同端口通信的类似 IP,它没有正确分组它们。请参见:dbfiddle.uk/uonmF4N2
0赞 Ajax1234 10/31/2023
@Tesla 请从该示例中发布所需的输出。另外,您需要澄清 1.为什么和似乎被总结为 while 保持不变, 2.您的分组标准,以及 3.记录条目的排序以建立联接关联300500800222
0赞 Tesla 10/31/2023
抱歉 - 800 是错别字。我刚刚将其更新为正确的 300。现在,所需的输出在主题描述中是正确的。
0赞 Thorsten Kettner 10/31/2023 #3

为了确定哪个 IP、端口和方向,您必须在汇总结果中制定一个规则,将谁视为发送方和接收方。让我们将较小的 IP 作为源,将较大的 IP 作为目标。然后,一次又一次地使用几乎相同的表达式来决定将哪个原始列放入哪个结果列中。完成此操作后,汇总数据。CASE

with 
  data as
  (
    select
      case when src_ip < dst_ip then  src_ip      else  dst_ip      end as source_ip,
      case when src_ip < dst_ip then  dst_ip      else  src_ip      end as dest_ip,
      case when src_ip < dst_ip then  src_port    else  dst_port    end as source_port,
      case when src_ip < dst_ip then  dst_port    else  src_port    end as dest_port,
      case when src_ip < dst_ip then  bytes_sent  else  0           end as sent,
      case when src_ip < dst_ip then  0           else  bytes_sent  end as received
    from mytable
  )
select
  source_ip, source_port, dest_ip, dest_port,
  sum(sent) as bytes_sent,
  sum(received) as bytes_received
from data
group by source_ip, source_port, dest_ip, dest_port
order by source_ip, source_port, dest_ip, dest_port;
0赞 Namsi Lydia 11/2/2023 #4

您可以通过使用以下语句的组合来实现所需的输出:GROUP BY、CASE 和 SUM 函数来聚合函数

可以按如下方式完成以下查询:

SELECT
    CASE WHEN src_ip < dst_ip THEN src_ip ELSE dst_ip END AS src_ip,
    CASE WHEN src_ip < dst_ip THEN src_port ELSE dst_port END AS src_port,
    CASE WHEN src_ip < dst_ip THEN dst_ip ELSE src_ip END AS dst_ip,
    CASE WHEN src_ip < dst_ip THEN dst_port ELSE src_port END AS dst_port,
    SUM(CASE WHEN src_ip < dst_ip THEN bytes_sent ELSE 0 END) AS bytes_sent,
    SUM(CASE WHEN src_ip < dst_ip THEN 0 ELSE bytes_sent END) AS bytes_recv
FROM your_table
GROUP BY
    CASE WHEN src_ip < dst_ip THEN src_ip ELSE dst_ip END,
    CASE WHEN src_ip < dst_ip THEN src_port ELSE dst_port END,
    CASE WHEN src_ip < dst_ip THEN dst_ip ELSE src_ip END,
    CASE WHEN src_ip < dst_ip THEN dst_port ELSE src_port END;

上面的 CASE 语句将根据 src 和 dst 值的词法顺序确定它们的顺序,以确保两个方向的分组一致。而 SUM 和 CASE 语句用于分别聚合不同方向(正向和反向)的bytes_sent值。

0赞 Zegarek 11/24/2023 #5

您可以将其简化为 least()greatest()聚合过滤器Demo@db<>小提琴:

select least(   src_ip,   dst_ip  ) AS src_ip,
       least(   src_port, dst_port) AS src_port,
       greatest(src_ip,   dst_ip  ) AS dst_ip,
       greatest(src_port, dst_port) AS dst_port,
       coalesce( sum(bytes_sent)filter(where src_ip=least(src_ip,dst_ip))
                ,0) AS bytes_sent,
       coalesce( sum(bytes_sent)filter(where dst_ip=least(src_ip,dst_ip))
                ,0) AS bytes_recv
from netflow group by 1,2,3,4;
src_ip src_port dst_ip dst_port bytes_sent bytes_recv
10.0.0.5 50 172.0.0.5 55 500 300
192.168.1.1 20 192.168.10.5 123 999 0
192.168.1.1 123 192.168.10.5 321 1111 222

如果你喜欢任何没有流量的地方,你可以放弃将其换成 .null0