组合 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 导出转换为多值)
CodeIgniter 的 Active Records 中 0 和 sha1(value, true) 的值组合返回所有行的 SQL