How do I get an average of the top two values in a result set ?

  • CREATE TABLE [dbo].[MemberLabResult] (

    [MemberLabId] [int] IDENTITY (1, 1) NOT NULL ,

    [MemberId] [int] NOT NULL ,

    [LabDate] [datetime] NOT NULL ,

    [LabId] [int] NOT NULL ,

    [LabValue] [float] )

    ON [PRIMARY]

    GO

    INSERT INTO MemberLabResult

    (MemberID, LabDate, LabID, Labvalue)

    SELECT '1','10/17/2008','1','3.2' UNION ALL

    SELECT '1','11/17/2008','1','3.9' UNION ALL

    SELECT '1','12/17/2008','1','2.0' UNION ALL

    SELECT '2','10/17/2008','1','4.2' UNION ALL

    SELECT '2','11/17/2008','1','3.0' UNION ALL

    SELECT '2','12/17/2008','1','2.0' UNION ALL

    SELECT '3','10/17/2008','1','3.2' UNION ALL

    SELECT '3','11/17/2008','1','2.0' UNION ALL

    SELECT '3','12/17/2008','1','4.0'

    Greetings -

    Based on the above data structure, I would like to pull the avg for the top

    two months for each MemberID where the avg lab value is less than four or

    greater than four. The data can be in the table in most any order.

    This query works for averaging all values:

    Select Memberid, avg(labvalue)

    From MemberLabResult

    where labid = 1

    Group by Memberid

    Having

    avg(labvalue) > 4 or

    avg(labvalue) < 4

    What would be the most efficient way to limit the average value to only the

    most two recent months ??

    Thanks

    Todd

  • Select Memberid, avg(labvalue)

    From MemberLabResult

    where labid = 1

    Group by Memberid

    Having

    avg(labvalue) > 4 or

    avg(labvalue) < 4

    Add your date filter to the where clause:

    WHERE labid = 1

    AND LabDate >= dateadd(month, datediff(month, 0, getdate()) - 2, 0)

    Jeffrey Williams
    “We are all faced with a series of great opportunities brilliantly disguised as impossible situations.”

    ― Charles R. Swindoll

    How to post questions to get better answers faster
    Managing Transaction Logs

  • Jeffrey -

    Thank you for the reply. I see that your solution will work if the lab

    values are there every month, but what if they skip one or two months ?

    I can't guarantee that these values will be added to the table. Any thoughts

    on how to average the top two regardless of date ??

    Thanks

    Todd

  • Hi,

    try this:

    select memberid, AVG(labvalue)

    FROM

    (

    Select Memberid

    , labvalue

    , ROW_NUMBER() OVER (PARTITION BY MemberID ORDER BY LabDate DESC) RowNum

    From MemberLabResult

    WHERE LabId = 1

    ) result

    WHERE RowNum <= 2

    GROUP BY MemberID

    HAVING

    avg(labvalue) > 4 or

    avg(labvalue)

    Hope this helps 🙂

  • Jetro -

    Thanks for the great solution but unfortunately I am still using SQL server 2000

    and have no row_numbers !!! Any other ideas ??

    Thanks

    Todd

  • Instead of the ROW_NUMBER() function you can use the TOP 2 clause in a derived table to get the 2 most recent rows.

    SELECT MemberId, AVG(LabValue)

    FROM (

    SELECT TOP (2) MemberId, LabValue, LabDate

    FROM MemberLabResult

    WHERE (LabId = 1)

    ORDER BY LabDate DESC

    ) Result

    GROUP BY MemberID

    HAVING AVG(LabValue) <> 4

  • Well, do you want the last two months average - or the average from the last two entries? Is it possible that you could have results for the last two weeks? Or, can you only have one result per month?

    For this particular test - if the patient misses a month, how does that affect the average? If in fact this test is performed monthly?

    Jeffrey Williams
    “We are all faced with a series of great opportunities brilliantly disguised as impossible situations.”

    ― Charles R. Swindoll

    How to post questions to get better answers faster
    Managing Transaction Logs

  • Andrew -

    Thanks for the suggestion but the "top" clause appears

    to literally return 2 rows - not 2 rows per member.

    Jeffrey -

    The query should not be data dependent, meaning that it should

    take the two most current rows regardless of date or interval.

    Thanks for you help

    Todd

  • andrewd.smith (4/14/2009)


    Instead of the ROW_NUMBER() function you can use the TOP 2 clause in a derived table to get the 2 most recent rows.

    SELECT MemberId, AVG(LabValue)

    FROM (

    SELECT TOP (2) MemberId, LabValue, LabDate

    FROM MemberLabResult

    WHERE (LabId = 1)

    ORDER BY LabDate DESC

    ) Result

    GROUP BY MemberID

    HAVING AVG(LabValue) <> 4

    This will only return the two most recent rows across all members. Not the two most recent rows per member. To get the two most recent rows per member - you are going to have to identify the minimum date for each member.

    Something along the lines of:

    Select MemberId, Labdate

    Into #LabDates

    From MemberLabResult m1

    Where labdate = (select top 1 labdate

    from (Select top 2 labdate

    from MemberLabResult m2

    where m2.memberid = m1.memberid

    order by labdate)

    order by labdate desc);

    Select Memberid, avg(labvalue)

    From MemberLabResult m1

    Join #LabDates l On l.MemberID = m1.MemberID

    And l.labdate = m1.labdate

    where labid = 1

    Group by Memberid

    Having

    avg(labvalue) > 4 or

    avg(labvalue) < 4

    The above is not tested at all - and, there are probably better ways of approaching this, but it should give you a good idea.

    Jeffrey Williams
    “We are all faced with a series of great opportunities brilliantly disguised as impossible situations.”

    ― Charles R. Swindoll

    How to post questions to get better answers faster
    Managing Transaction Logs

  • Sorry, I really should pay more attention.

    This modification uses a correlated subquery to get the 2 latest rows for each MemberId.

    It is untested so there could be more stupid mistakes.

    SELECT MLROUTER.MemberId, AVG(MLROUTER.LabValue)

    FROM MemberLabResult MLROUTER

    WHERE MLROUTER.MemberLabId IN (

    SELECT TOP (2) MLRINNER.MemberLabId

    FROM MemberLabResult MLRINNER

    WHERE (MLRINNER.LabId = 1)

    AND (MLRINNER.MemberId = MLROUTER.MemberId)

    ORDER BY MLRINNER.LabDate DESC

    )

    GROUP BY MLROUTER.MemberId

    HAVING AVG(MLROUTER.LabValue) <> 4

  • Todd, if you can use temp tables, maybe this will work for you. It seems to work with the sample data you provided, but may have unforeseen issues with a larger set of data.

    --Create temp table with ordered values by MemberID and date

    SELECT

    IDENTITY(INT,1,1) AS ID,

    MemberID,

    LabDate,

    LabID,

    LabValue

    INTO #lab

    FROM MemberLabResult

    ORDER BY MemberID,LabDate DESC

    --Now see the results

    SELECT

    MemberID,

    AVGLabValue = AVG(LabValue)

    FROM

    (

    SELECT

    l.*

    FROM #lab l,

    (

    SELECT

    ID = MIN(id),

    MemberID

    FROM #lab

    GROUP BY MemberID

    ) t1

    WHERE t1.id = l.id

    OR t1.id +1 = l.id

    ) t2

    GROUP BY t2.MemberID

    Greg
    _________________________________________________________________________________________________
    The glass is at one half capacity: nothing more, nothing less.

  • I just realized I did not address your need to exclude where the average = 4, so I added another member ID to test, where the average would be 4, and included the having clause. Sorry for the miss.

    INSERT INTO MemberLabResult

    (MemberID, LabDate, LabID, Labvalue)

    SELECT '1','10/17/2008','1','3.2' UNION ALL

    SELECT '1','11/17/2008','1','3.9' UNION ALL

    SELECT '1','12/17/2008','1','2.0' UNION ALL

    SELECT '2','10/17/2008','1','4.2' UNION ALL

    SELECT '2','11/17/2008','1','3.0' UNION ALL

    SELECT '2','12/17/2008','1','2.0' UNION ALL

    SELECT '3','10/17/2008','1','3.2' UNION ALL

    SELECT '3','11/17/2008','1','2.0' UNION ALL

    SELECT '3','12/17/2008','1','4.0' UNION ALL

    SELECT '4','10/17/2008','1','4.0' UNION ALL

    SELECT '4','11/17/2008','1','4.0' UNION ALL

    SELECT '4','12/17/2008','1','4.0'

    And add this after the last group by

    HAVING AVG(LabValue) <> 4

    Greg
    _________________________________________________________________________________________________
    The glass is at one half capacity: nothing more, nothing less.

  • Thanks a lot Greg,

    Your code works great and I'll use it as a solution !!

    Todd

  • Todd, if you've got data like this:

    INSERT INTO MemberLabResult

    (MemberID, LabDate, LabID, Labvalue)

    SELECT '1','10/17/2008','1','3.2' UNION ALL

    SELECT '1','12/17/2008','1','2.0' UNION ALL

    what should be the average for last 2 months?

    1.0 or 2.6?

    _____________
    Code for TallyGenerator

  • Sergiy -

    The average would be 2.6 - ( (3.2 + 2.0) / 2 ) using the

    "labvalue" field.

    Todd

Viewing 15 posts - 1 through 15 (of 29 total)

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