组合 SQL 行

Posted

技术标签:

【中文标题】组合 SQL 行【英文标题】:Combining SQL Rows 【发布时间】:2010-02-02 17:47:34 【问题描述】:

我有一个 SQL Compact 数据库,其中包含一个 IP 数据包标头表。表格如下所示:

Table: PacketHeaders    

ID  SrcAddress  SrcPort  DestAddress  DestPort  Bytes
1   10.0.25.1   255      10.0.25.50   500       64
2   10.0.25.50  500      10.0.25.1    255       80
3   10.0.25.50  500      10.0.25.1    255       16
4   75.48.0.25  387      74.26.9.40   198       72
5   74.26.9.40  198      75.48.0.25   387       64
6   10.0.25.1   255      10.0.25.50   500       48

我需要执行查询以显示在本地网络上进行的“对话”。来自 A -> B 的数据包与来自 B -> A 的数据包属于同一对话的一部分。我需要执行查询以显示正在进行的对话。基本上我需要的是这样的东西:

Returned Query:

SrcAddress  SrcPort  DestAddress  DestPort  TotalBytes  BytesA->B  BytesB->A
10.0.25.1   255      10.0.25.50   500       208         112        96
75.48.0.25  387      74.26.9.40   198       136         72         64

如您所见,我需要查询(或一系列查询)来识别 A->B 与 B->A 相同,并相应地分解字节数。无论如何,我都不是 SQL 专家,但我们将不胜感激。

【问题讨论】:

【参考方案1】:

试试这个:

SELECT
    T1.SrcAddress,
    T1.SrcPort,
    T1.DestAddress,
    T1.DestPort,
    T1.Bytes + COALESCE(T2.Bytes, 0) AS TotalBytes,
    T1.Bytes AS A_to_B,
    COALESCE(T2.Bytes, 0) AS B_to_A
FROM (
    SELECT SrcAddress, SrcPort, DestAddress, DestPort, SUM(Bytes) AS Bytes
    FROM PacketHeaders
    GROUP BY SrcAddress, SrcPort, DestAddress, DestPort) AS T1
LEFT JOIN (
    SELECT SrcAddress, SrcPort, DestAddress, DestPort, SUM(Bytes) AS Bytes
    FROM PacketHeaders
    GROUP BY SrcAddress, SrcPort, DestAddress, DestPort) AS T2
ON T1.SrcAddress = T2.DestAddress
AND T1.SrcPort = T2.DestPort
AND T1.DestAddress = T2.SrcAddress
AND T1.DestPort = T2.SrcPort
WHERE T1.SrcAddress < T1.DestAddress OR
    (T1.SrcAddress = T1.DestAddress AND T1.SrcPort = T1.DestPort) OR
    T2.DestAddress IS NULL

关于这个测试数据:

CREATE TABLE PacketHeaders (ID INT, SrcAddress NVARCHAR(100), SrcPort INT, DestAddress NVARCHAR(100), DestPort INT, Bytes INT);
INSERT INTO PacketHeaders (ID, SrcAddress, SrcPort, DestAddress, DestPort, Bytes) VALUES
(1, '10.0.25.1', 255, '10.0.25.50', 500, 64),
(2, '10.0.25.50', 500, '10.0.25.1', 255, 80),
(3, '10.0.25.50', 500, '10.0.25.1', 255, 16),
(4, '75.48.0.25', 387, '74.26.9.40', 198, 72),
(5, '74.26.9.40', 198, '75.48.0.25', 387, 64),
(6, '10.0.25.1', 255, '10.0.25.50', 500, 48),
(7, '10.0.25.2', 255, '10.0.25.50', 500, 48),
(8, '10.0.25.52', 255, '10.0.25.50', 500, 48);

这给出了以下结果:

'10.0.25.1', 255, '10.0.25.50', 500, 208, 112, 96
'10.0.25.2', 255, '10.0.25.50', 500, 48, 48, 0
'10.0.25.52', 255, '10.0.25.50', 500, 48, 48, 0
'74.26.9.40', 198, '75.48.0.25', 387, 136, 64, 72

它的工作方式是首先对单向对话进行分组并计算字节数。这确保了每个对话都将被精确地表示两次——每个方向一次。然后,此结果将自连接以提供您需要的结果,通过强制 A 的(地址、端口)必须小于 B 来过滤重复项。左连接用于允许单向对话。

【讨论】:

假设每个路径都有一个匹配的相反条目。所以这一切都需要是一个 OUTER JOIN,这意味着你需要 GROUP BY ISNULL(T1.SrcAddress, T2.DestAddress) 等等。 编辑删除了外部 GROUP BY 等。这仍然只有在每个数据包都对应于一个相反方向的数据包时才有效。 (但如果这个条件成立,我认为它会起作用......) 这仅适用于双向对话。并非所有对话都是双向的。如何修改联接以减轻这种情况? @lumberjack:您可以使用 LEFT JOIN 代替 JOIN,但您还必须在 SUM 上添加一个空检查。 愚蠢的问题。我可以在查询中进行空值检查并将值设置为 0,还是在我得到结果后必须这样做?【参考方案2】:

我可以看到执行此操作的两种基本方法... 1. 将其全部分组,忽略 a->b 和 b->a,然后自行加入结果。 2. 使用“src”字段中的“最低”IP 地址重新排列您的数据,同时创建一个“direction”字段。

选项 2 可能是我要走的路...

SELECT
    SrcAddress,
    SrcPort,
    DestAddress,
    DestPort,
    SUM(AtoB) + SUM(BtoA),
    SUM(AtoB),
    SUM(BtoA)
FROM
(
    SELECT
       CASE WHEN SrcAddress < DestAddress THEN SrcAddress  ELSE DestAddress END AS SrcAddress,
       CASE WHEN SrcAddress < DestAddress THEN SrcPort     ELSE DestPort    END AS SrcPort,
       CASE WHEN SrcAddress < DestAddress THEN DestAddress ELSE SrcAddress  END AS DestAddress,
       CASE WHEN SrcAddress < DestAddress THEN DestPort    ELSE ScrPort     END AS DestPort,
       CASE WHEN SrcAddress < DestAddress THEN Bytes       ELSE 0           END AS AtoB,
       CASE WHEN SrcAddress < DestAddress THEN 0           ELSE Bytes       END AS BtoA
    FROM
      PacketHeaders
)
    AS [data]
GROUP BY
    SrcAddress,
    SrcPort,
    DestAddress,
    DestPort

编辑

其他几个答案的版本与我所说的选项 1 相同。我也会尝试一下,而不是在人们的答案上发送垃圾邮件 :(

SELECT
   ISNULL([AtoB].SrcAddress,  [BtoA].DestAddress)
   ISNULL([AtoB].SrcPort,     [BtoA].DestPort)
   ISNULL([AtoB].DestAddress, [BtoA].SrcAddress)
   ISNULL([AtoB].DestPort,    [BtoA].SrcPort)
   ISNULL([AtoB].Bytes,0) + ISNULL([BtoA].Bytes,0),
   ISNULL([AtoB].Bytes,0),
   ISNULL([BtoA].Bytes,0)
FROM
   (
      SELECT   SrcAddress, SrcPort, DestAddress, DestPort, SUM(Bytes) AS Bytes
      FROM     PacketHeaders
      WHERE    SrcAddress <= DestAddress
      GROUP BY SrcAddress, SrcPort, DestAddress, DestPort
   )
   AS [AtoB]
FULL OUTER JOIN
   (
      SELECT   SrcAddress, SrcPort, DestAddress, DestPort, SUM(Bytes) AS Bytes
      FROM     PacketHeaders
      WHERE    SrcAddress > DestAddress
      GROUP BY SrcAddress, SrcPort, DestAddress, DestPort
   )
   AS [BtoA]
      ON  [AtoB].SrcAddress  = [BtoA].DestPort
      AND [AtoB].SrcPort     = [BtoA].DestAddress
      AND [AtoB].DestAddress = [BtoA].SrcPort
      AND [AtoB].DestPort    = [BtoA].SrcAddress

但我确实说过我不会那样做......

【讨论】:

如果我想查看总字节数列,我必须将其全部封装在另一个 SELECT 语句中。有没有更清洁的方法? 您确定 SQL Server CTE 支持 IsNull 吗?我找到了这篇文章:sqlserverce.org/blogs/faq/archive/2007/02/16/… 在这种情况下,您可以(正如您已经指出的那样)改用 COALESCE。但是,我仍然会使用“重新排序/清理数据”选项然后“聚合清理数据”。我不会尝试 JOIN 方法,还有更多的极端情况需要避免,它更难阅读,而且很可能更慢......(我对 CASE 语句所做的“清理”可以使用 UNION ALL 和 WHERE子句代替,这可能更快)

以上是关于组合 SQL 行的主要内容,如果未能解决你的问题,请参考以下文章

如何使用交叉连接查找行>列组合? [SQL]

SQL Server:如何组合行

组合行 + 连接大型数据集的值(将 SQL 导出转换为多值)

从SQL聚合行组合值并将NULL删除到单个行中

SQL Server中的组合

CodeIgniter 的 Active Records 中 0 和 sha1(value, true) 的值组合返回所有行的 SQL