T-SQL从回车分隔的多个列中选择匹配的行(BGInfo输出)

时间:2014-07-02 21:10:06

标签: sql sql-server tsql xml-parsing sql-server-2012

BGInfo(Microsoft Sysinternals)能够将数据写入SQL服务器。网卡信息是一个字段,由CRLF CHAR(13)+ CHAR(10)分隔。有些计算机可能只有一个网卡。其他人可能有7张或更多的网卡。每个字段(Network_Card,IP_Address和Subnet_Mask)包含匹配网卡信息的CRLF分隔列表。

enter image description here

我想将CRLF分隔字段转换为与Network_Card的第一个条目匹配的多行,以及IP_Address和Subnet_Mask的第一个条目。然后,选择Default_Gateway与IP_address匹配的记录(指示哪个网卡处于活动状态)。

BGInfo将许多字段填充到表中,对于此示例,我将使用所需字段创建子集。

首先我们可以创建一个临时的BGInfo表:

IF OBJECT_ID('tempdb..#BGInfo') IS NOT NULL DROP TABLE #BGInfo
CREATE TABLE #BGInfo (
    [User_Name]         nvarchar(25),
    [Host_Name]         nvarchar(25),
    [Network_Card]      nvarchar(MAX),
    [IP_Address]        nvarchar(255),
    [Subnet_Mask]       nvarchar(255),
    [Default_Gateway]   nvarchar(25)
)

然后用一些示例数据填充它:

INSERT INTO #BGInfo
SELECT
    'User1',
    'PC-A',
    'nic1'+CHAR(13)+CHAR(10)+'nic2'+CHAR(13)+CHAR(10)+'nic3'+CHAR(13)+CHAR(10)+'nic4'+CHAR(13)+CHAR(10)+'nic5'+CHAR(13)+CHAR(10)+'nic6'+CHAR(13)+CHAR(10)+'nic7',
    '(none)'+CHAR(13)+CHAR(10)+'(none)'+CHAR(13)+CHAR(10)+'10.91.2.155'+CHAR(13)+CHAR(10)+'(none)'+CHAR(13)+CHAR(10)+'192.168.80.1'+CHAR(13)+CHAR(10)+'192.168.126.1'+CHAR(13)+CHAR(10)+'(none)',
    '(none)'+CHAR(13)+CHAR(10)+'(none)'+CHAR(13)+CHAR(10)+'255.255.255.128'+CHAR(13)+CHAR(10)+'(none)'+CHAR(13)+CHAR(10)+'255.255.255.0'+CHAR(13)+CHAR(10)+'255.255.255.0'+CHAR(13)+CHAR(10)+'(none)',
    '10.91.2.129'
UNION ALL
SELECT
    'User2',
    'PC-B',
    'nic1'+CHAR(13)+CHAR(10)+'nic2'+CHAR(13)+CHAR(10)+'nic3'+CHAR(13)+CHAR(10)+'nic4'+CHAR(13)+CHAR(10)+'nic5'+CHAR(13)+CHAR(10)+'nic6'+CHAR(13)+CHAR(10)+'nic7',
    '10.17.17.23'+CHAR(13)+CHAR(10)+'(none)'+CHAR(13)+CHAR(10)+'(none)'+CHAR(13)+CHAR(10)+'(none)'+CHAR(13)+CHAR(10)+'192.168.80.1'+CHAR(13)+CHAR(10)+'192.168.126.1'+CHAR(13)+CHAR(10)+'(none)',
    '255.255.240.0'+CHAR(13)+CHAR(10)+'(none)'+CHAR(13)+CHAR(10)+'(none)'+CHAR(13)+CHAR(10)+'(none)'+CHAR(13)+CHAR(10)+'255.255.255.0'+CHAR(13)+CHAR(10)+'255.255.255.0'+CHAR(13)+CHAR(10)+'(none)',
    '10.17.16.5'

我想如果我将CRLF转换为逗号分隔值:

REPLACE([IP_Address],CHAR(13)+CHAR(10),',')

然后我可以将其转换为XML并选择行。 (很多在线信息)但是......如何使用多列?我试过这个,但它只是创建了很多重复的数据:

IF OBJECT_ID('tempdb..#XMLtable') IS NOT NULL DROP TABLE #XMLtable
CREATE TABLE #XMLtable (
    [User_Name]     nvarchar(25),
    Computer        nvarchar(25),
    Network_Card    nvarchar(255),
    IPAddr          xml,
    Subnet          xml,
    Default_Gateway nvarchar(25)
)

INSERT INTO #XMLtable
SELECT
    [User_Name],
    [Host_Name] as Computer,
    [Network_Card],
    CAST('<i>'+REPLACE(REPLACE([IP_Address],CHAR(13)+CHAR(10),','),',','</i><i>')+'</i>' as xml) AS IPAddr,
    CAST('<i>'+REPLACE(REPLACE([Subnet_Mask],CHAR(13)+CHAR(10),','),',','</i><i>')+'</i>' as xml) AS Subnet,
    Default_Gateway
 FROM #BGInfo bg

 SELECT
    [User_Name],
    [Computer],
    [Network_Card],
    IP_Address = i.value('.', 'varchar(MAX)'),
    IP_Address = s.value('.', 'varchar(MAX)'),
    Default_Gateway
FROM #XMLtable
CROSS APPLY IPaddr.nodes ('/i') AS IPAddr(i)
CROSS APPLY Subnet.nodes ('/i') AS Subnet(s); 

我想有一个这样的列表:(数据可能不匹配,但你明白了)

User_Name   Computer    Network_Card    IP_Address  Subnet_Mask     Default_Gateway
User1       PC-A        nic1        (none)  (none)  10.91.2.129
User1       PC-A        nic2        (none)  (none)  10.91.2.129
User1       PC-A        nic3        10.91.2.155 255.255.255.128 10.91.2.129
User1       PC-A        nic4        192.168.80.1    255.255.255.0   10.91.2.129
User1       PC-A        nic5        192.168.126.1   255.255.255.0   10.91.2.129
User2       PC-B        nic1        10.17.17.23 255.255.240.0   10.17.16.5
User2       PC-B        nic2        (none)  (none)  10.17.16.5
User2       PC-B        nic3        (none)  (none)  10.17.16.5
User2       PC-B        nic4        192.168.80.1    255.255.255.0   10.17.16.5
User2       PC-B        nic5        192.168.126.1   255.255.255.0   10.17.16.5

然后可以选择默认网关与IP_address匹配的ACTIVE网卡:

User_Name   Computer    Network_CardIP_Address  Subnet_Mask Default_Gateway
User1       PC-A        nic3            10.91.2.155 255.255.255.128 10.91.2.129
User2       PC-B        nic1            10.17.17.23 255.255.240.0   10.17.16.5

2 个答案:

答案 0 :(得分:1)

我认为问题来自于尝试从单个#XMLtable表中分解卡片,ips和子网。使用您所做的相同逻辑,我做了三个单独的查询(网卡,ips和子网各一个),为每个查询生成row_numbers,并根据row_number将它们连接在一起。然后我用PARSENAME来比较ip地址和网关。

select nics.User_Name, 
nics.Host_Name Computer, 
nics.nic Network_Card, 
ips.ip IP_Address, 
subnets.subnet Subnet_Mask, 
b.Default_Gateway
from
(
    select User_Name, Host_Name,
    nic.a.value('.', 'varchar(max)') as nic,
    ROW_NUMBER() over (partition by User_name, Host_name order by User_name, Host_name) Row_number
    from 
    (
        select b.User_name, b.Host_name, 
        CAST('<i>' + REPLACE(b.Network_Card, CHAR(13)+CHAR(10), '</i><i>') + '</i>' as xml) as nics
        from #BGInfo b
    ) a
    cross apply nics.nodes('/i') as nic(a)
) nics
join
(
    select User_Name, Host_Name,
    ip.a.value('.', 'varchar(max)') as ip,
    ROW_NUMBER() over (partition by User_name, Host_name order by User_name, Host_name) Row_number
    from 
    (
        select b.User_name, b.Host_name, 
        CAST('<i>' + REPLACE(b.IP_Address, CHAR(13)+CHAR(10), '</i><i>') + '</i>' as xml) as ips
        from #BGInfo b
    ) a
    cross apply ips.nodes('/i') as ip(a)
) ips on ips.User_Name = nics.User_Name and ips.Host_Name = nics.Host_Name and ips.Row_number = nics.Row_number
join
(
    select User_Name, Host_Name,
    subnet.a.value('.', 'varchar(max)') as subnet,
    ROW_NUMBER() over (partition by User_name, Host_name order by User_name, Host_name) Row_number
    from 
    (
        select b.User_name, b.Host_name, 
        CAST('<i>' + REPLACE(b.Subnet_Mask, CHAR(13)+CHAR(10), '</i><i>') + '</i>' as xml) as subnets
        from #BGInfo b
    ) a
    cross apply subnets.nodes('/i') as subnet(a)
) subnets on subnets.User_Name = nics.User_Name and subnets.Host_Name = nics.Host_Name and subnets.Row_number = nics.Row_number
join #BGInfo b on b.User_Name = nics.User_Name and b.Host_Name = nics.Host_Name
where PARSENAME(ips.ip, 4) = PARSENAME(b.Default_Gateway, 4) and
PARSENAME(ips.ip, 3) = PARSENAME(b.Default_Gateway, 3)

我不知道它将如何针对大量行执行,但它适用于测试数据。

SQL Fiddle

答案 1 :(得分:1)

一种选择是转储BGInfo,并使用PowerShell脚本。 Powershell可以获得Nic信息,它可以执行SQL。如果您的桌面安装了PowerShell,那么这可能更容易构建和维护,并且可能更快。 我找到了两篇可以帮助你的文章:

  1. 获取尼克信息: http://techibee.com/powershell/powershell-get-ip-address-subnet-gateway-dns-serves-and-mac-address-details-of-remote-computer/1367
  2. 保存到db: http://www.sqlservercentral.com/Forums/Topic1463926-1351-1.aspx
  3. 如果你必须使用BGInfo,那么就个人而言,我会采取不同的方法,并将问题分开。

    1. 允许BGInfo以最简单,最开箱即用的方式写入SQL(即将多个IP地址填充到一个分隔列中
    2. 创建一个包含已解析的nic + IP地址信息的相关表。
    3. 创建一个简单的程序(不是SQL,更像是C#),它通过BGInfo表中的未处理行进行搅拌,将nic + IP地址信息解析为相关表。
    4. 如果BGInfoTable上的唯一键是Time_stamp + User_name + Host_name,那么您可以拥有这样的相关nic表:

      CREATE TABLE BGInfo_Nics (
         BGTime_Stamp DATETIME NOT NULL,
         User_Name NVARCHAR(250) NOT NULL, -- these cols should match BGInfoTable datatypes
         Host_Name NVARCHAR(250) NOT NULL,
         NicName NVARCHAR(250) NOT NULL, 
         IPAddress NVARCHAR(50) NULL,
         SubNet  NVARCHAR(50) NULL,
         GatewayIP NVARCHAR(50) NULL
      )
      GO
      CREATE UNIQUE INDEX IX_BGInfo_Nics ON BGInfo_Nics ( BGTime_Stamp,
          User_Name,
          Host_Name,
          NicName
      )
      GO
      

      该程序将不断运行,并且每隔一段时间,它就可以查询BGInfoTable以查看是否需要处理的行。处理将包括从BGInfoTable读取尚待处理的行,解析包括IP地址,子网和网关在内的nic信息,并将nic信息行写入新的(相关)表BGInfo_Nics。

      例如: - 使用它来获取需要处理的行:

      SELECT bg.*
      FROM BGInfoTable bg (NOLOCK) LEFT OUTER JOIN BGInfo_Nics nics  (NOLOCK) ON 
      bg.Time_Stamp = nics.BGTime_Stamp AND
      bg.[User_Name] = nics.[User_name] AND
      bg.[Host_Name] = nics.[Host_name]
      WHERE nics.BGTime_Stamp IS NULL
      

      或者,您可以向BGInfoTable添加几列以标记已经处理的行,并创建索引以快速获取未处理的行。

      有一个单独的程序进行处理(读取新的,解析和保存),我怀疑这种解决方案将提供更多的灵活性来解析并将数据保存为您真正想要报告的格式,并且表现非常好

      HTH

      麦克