Count fields that do not meet specified condition

  • VIDSERVERIPBASESCOREEXPLOITABILITYSCANCOMPLETE

    51192.168.0.15H some date

    31192.168.0.19H some date

    42192.168.0.23POC some date

    64192.168.0.23H some date

    85192.168.0.35POC some date

    26192.168.0.49H some date

    37192.168.0.49POC some date

    Required output:

    Need a count of the number of VIDs with a BASESCORE >= 9 summarized on each SERVERIP.

    Problem:

    Some servers do not have any VIDs with a BASESCORE >=9. I want these SERVERIPs to be represented in the results with a count of '0'

    Desired output:

    SERVERIP [COUNT OF CRIT VIDs]

    192.168.0.11

    192.168.0.20

    192.168.0.30

    192.168.0.42

    The current query looks like this....the issue is that it does not show me the SERVERIP where [COUNT OF CRIT VIDs] = 0. The query below shows me a count of vulnerabilities (that meet the conditions in the where clause)

    for each SERVERIP. My challenge is figuring out how to represent SERVERIPs that have no VIDs with a BASESCORE >=9 in the results with a count of '0'

    WITH SUMMARY AS(

    SELECT SERVERIP, VID, BASESCORE, exploitability, scancomplete,

    ROW_NUMBER() OVER (PARTITION BY SERVERIP, VID ORDER BY SCANCOMPLETE DESC) AS RANK

    FROM VID_SERVER

    WHERE

    BASESCOREVALUE IS NOT NULL

    and BASESCORE >= 9

    and exploitabiltiy in ('H','POC','F')

    )

    SELECT SERVERIP, [BASESCORE], SCANCOMPLETE, COUNT(*) AS [COUNT]

    FROM SUMMARY x

    WHERE x.rank = 1 AND x.SCANCOMPLETE =

    (SELECT MAX(SCANCOMPLETE)

    FROM VID_SERVER C

    WHERE x.[SERVERIP] = C.SERVERIP)

    GROUP BY SERVERIP, [BASESCORE], SCANCOMPLETE

    ORDER BY [SERVERIP] DESC

    Thanks for your help!

  • I didn't have time to build a test table but I think something like this would work:

    WITH Summary AS

    (

    SELECT

    SERVERIP,

    COUNT(*) AS [COUNT]

    FROM

    VID_SERVER

    GROUP BY SERVERIP

    WHERE BASESCORE >= 9 and exploitabiltiy in ('H','POC','F'))

    SELECT V.SERVERIP, ISNULL(Summary.[COUNT],0) AS [COUNT]

    FROM VID_SERVER AS V

    LEFT JOIN Summary ON V.SERVERIP = Summary.SERVERIP

  • How about this?

    if object_id('tempdb..#t1') is not null

    drop table #t1

    create table #t1 ( SERVERIP varchar(20), BASESCORE int)

    insert into #t1 (BASESCORE,SERVERIP)

    values (5,'192.168.0.1')

    , (9,'192.168.0.1')

    , (3,'192.168.0.2')

    , (3,'192.168.0.2')

    , (5,'192.168.0.3')

    , (9,'192.168.0.4')

    , (9,'192.168.0.4')

    select SERVERIP

    , SUM( CASE WHEN BASESCORE >= 9 THEN 1 ELSE 0 END) CT

    from #t1

    GROUP BY SERVERIP

  • Just for interest, this can be written using the (now deprecated) GROUP BY ALL syntax. This example shows the equivalent query with and without using GROUP BY ALL:

    DECLARE @Example TABLE

    (

    ServerIP varchar(20) NOT NULL,

    VID integer NOT NULL

    );

    INSERT @Example

    (ServerIP, VID)

    VALUES

    ('192.168.0.1', 5),

    ('192.168.0.1', 9),

    ('192.168.0.2', 3),

    ('192.168.0.2', 3),

    ('192.168.0.3', 5),

    ('192.168.0.4', 9),

    ('192.168.0.4', 9);

    -- Deprecated GROUP BY ALL syntax, do not use

    SELECT

    e.ServerIP,

    COUNT_BIG(*)

    FROM @Example AS e

    WHERE

    e.VID >= 9

    GROUP BY ALL

    e.ServerIP

    -- Equivalent query plan

    SELECT

    U.ServerIP,

    COUNT_BIG(U.Union1001)

    FROM

    (

    SELECT

    e.ServerIP,

    NULL AS Union1001

    FROM @Example AS e

    UNION ALL

    SELECT

    e.ServerIP,

    0 AS Union1001

    FROM @Example AS e

    WHERE

    e.VID >= 9

    ) AS U

    GROUP BY

    U.ServerIP

  • Thank you for the replies. I am trying Paul's suggestion first which utilizes 'UNION ALL'

    The way the query is written below I get inflated results (counts) if a ServerIP host was scanned more than once during the period

    I need to do the following next with the script:

    - ensure that duplicates are removed (I was using a CTE for this previously)

    - ensure that I am getting the last scan data for each IP address. (This was also part of my original CTE)

    - to add [SCAN COMPLETE] columns to the final output:

    After reading up on 'UNION ALL' I have a general understanding of what it does. It will return the distinct results from the table. But how can I ensure I am only counting the results from the MAX(SCANCOMPLETE) time for each host?

    ----

    SELECT U.ServerIP, BASESCORE COUNT_BIG(U.Union1001)

    FROM

    (SELECT e.ServerIP, NULL AS Union1001

    FROM @Example AS e

    LEFT JOIN VULNS D ON d.vid = e.vid

    WHERE d.BASESCORE >= 9

    AND EXPLOITABILITY IN('H','F','POC')

    UNION ALL

    SELECT e.ServerIP, 0 AS Union1001, BASESCOREFROM @Example AS e

    LEFT JOIN VULNS D ON d.vid = e.vid

    WHERE d.BASESCORE >= 9

    AND EXPLOITABILITY IN('H','F','POC')

    ) AS UGROUP BY U.ServerIP, BASESCORE

  • c2k (1/11/2012)


    Thank you for the replies. I am trying Paul's suggestion first which utilizes 'UNION ALL'

    Please don't! My post was aimed at Cold Coffee, and was for interest only. You should base your code around Cold Coffee's solution.

    After reading up on 'UNION ALL' I have a general understanding of what it does. It will return the distinct results from the table.

    No, UNION ALL just concatenates two sets without removing duplicates. UNION (without the ALL) is the one that includes an implicit DISTINCT.

  • Thanks Paul.

    I have the following based on ColdCoffee's suggestion.

    SELECT SERVERIP, 9 AS BASESCOREVALUE, SUM(CASE WHEN (b.BASESCOREVALUE >= 9 and exploitabiltiy IN('H','F','POC') )

    THEN 1 ELSE 0 END) [COUNT]

    FROM EXAMPLETABLE A

    LEFT JOIN VULNS B ON b.vid = a.vid)

    GROUP BY SERVERIP

    ORDER BY [COUNT] DESC

    Which returns the correct number of distinct SERVERIPs (no duplicated). How easily can I incorporate changes that would accomplish the following:

    - Remove any duplicate VID, SERVERIP combinations (they do exists)...this is what my CTE was doing for me.

    - Ensure I am only counting vulns from the last SCANCOMPLETE date. A single serverip could have been scanned multiple times during a given period.

    Thanks!

  • Give this a whirl:

    SELECT

    S.SERVERIP,

    ISNULL(C.VIDCOUNT, 0) AS VIDCOUNT

    FROM

    (

    -- Distinct server list

    SELECT DISTINCT

    vs.SERVERIP

    FROM dbo.VID_SERVER AS vs

    ) AS S

    CROSS APPLY

    (

    -- Most recent scan for each server

    SELECT TOP (1)

    vs2.SCANCOMPLETE

    FROM dbo.VID_SERVER AS vs2

    WHERE

    -- Correlate to distinct server list

    vs2.SERVERIP = S.SERVERIP

    ORDER BY

    vs2.SCANCOMPLETE DESC

    ) AS SC

    OUTER APPLY

    (

    -- Count qualifying VIDs

    SELECT

    COUNT_BIG(DISTINCT vs3.VID) AS VIDCOUNT

    FROM dbo.VID_SERVER AS vs3

    JOIN dbo.VULNS AS v ON

    v.VID = vs3.VID

    WHERE

    -- Correlate to distinct server list

    vs3.SERVERIP = S.SERVERIP

    -- Correlate to latest scan date

    AND vs3.SCANCOMPLETE = SC.SCANCOMPLETE

    -- Qualifying exploitabilities

    AND vs3.EXPLOITABILITY IN ('H', 'F', 'POC')

    -- Minimum base score value from VULNS

    AND v.BASESCOREVALUE >= 9

    ) AS C;

  • Thanks a lot Paul! I got the script working in our environment and my initial QA work indicates that the numbers are spot on. I am still trying to figure out how the whole APPLY things works - I'll get it.

    In any case, I played around with the script trying to extend it's functionality to include counts of BASESCORE Values other than 9+. The output would look something like this:

    SERVERIP [BASESCORE][COUNT OF CRIT VIDs]

    192.168.0.19 5

    192.168.0.28 4

    192.168.0.37 6

    192.168.0.48 3

    192.168.0.51 3

    Basically a count of vulnerabilities by BASESCORE that are 'H','F', or 'POC'. When I try this I end up breaking the query. One of the factors is that EXPLOITABILITY is actually in decimal form (ie, 9.34531). I've been using LEFT(EXPLOITABILITY,1) to convert these to '9'...there are none that are '10' so I haven't run into a problem with this method.

    My attempt to do this looks something like this:

    SELECT S.SERVERIP, c.BASESCORE, ISNULL(C.VIDCOUNT, 0) AS VIDCOUNTFROM

    (

    -- Distinct server list

    SELECT DISTINCT vs.SERVERIP

    FROM dbo.VID_SERVER AS vs

    ) AS S

    CROSS APPLY

    (

    -- Most recent scan for each server

    SELECT TOP (1) vs2.SCANCOMPLETE

    FROM dbo.VID_SERVER AS vs2

    WHERE

    -- Correlate to distinct server list

    vs2.SERVERIP = S.SERVERIP

    ORDER BY vs2.SCANCOMPLETE DESC

    ) AS S

    COUTER APPLY

    (

    -- Count qualifying VIDs

    SELECT LEFT(v.basescore,1) AS BASESCORE, COUNT_BIG(DISTINCT vs3.VID) AS VIDCOUNT

    FROM dbo.VID_SERVER AS vs3

    JOIN dbo.VULNS AS v ON v.VID = vs3.VID

    WHERE

    -- Correlate to distinct server list

    vs3.SERVERIP = S.SERVERIP

    -- Correlate to latest scan date

    AND vs3.SCANCOMPLETE = SC.SCANCOMPLETE

    -- Qualifying exploitabilities

    AND vs3.EXPLOITABILITY IN ('H', 'F', 'POC')

    -- Minimum base score value from VULNS

    AND v.BASESCOREVALUE >= 9) AS C;

  • That's great news. I'm short of time right now, but I'll be back later to have a closer look (unless anyone else wants to chip in). In the meantime, here's a couple of links to my APPLY article, in case you haven't seen it before:

    http://www.sqlservercentral.com/articles/APPLY/69953/ (part one)

    http://www.sqlservercentral.com/articles/APPLY/69954/ (part two)

Viewing 10 posts - 1 through 9 (of 9 total)

You must be logged in to reply to this topic. Login to reply