views:

307

answers:

6

I need to get maximum and minimum values but also I need to get row id of these maximum or minimum on the same row.

SELECT MIN([Value]), MAX([Value]), id 
FROM [AnalystEstimates].[dbo].[AnalystEstimateValues] 
GROUP BY indicatorid
A: 

Your query would return the max/min for each ID as you are grouping by the ID. Try something like this

SELECT tblFoo.ID, tblFoo.Value
FROM tblFoo
WHERE (((tblFoo.Value)=(SELECT MAX( [tblFoo]![Value] ) FROM tblFoo))) 
   OR (((tblFoo.Value)=(SELECT MIN( [tblFoo]![Value] ) FROM tblFoo)));
Kevin Ross
You too has lost the grouping construct.
Lasse V. Karlsen
A: 
SELECT TOP 1
    ID, 
    'min' as type, 
    value 
FROM 
    AnalystEstimateValues 
WHERE 
   value = (select min(value) from AnalystEstimateValues)
UNION
   SELECT TOP 1
    ID, 
    'max' as type, 
    value 
FROM 
    AnalystEstimateValues 
WHERE 
   value = (select max(value ) from AnalystEstimateValues)
Paul Creasey
You too has lost the grouping construct.
Lasse V. Karlsen
+1  A: 

If Min = Max, you'll the get same IDs (there may be tied Min and Max values)

If not, there is/are ID(s) per min/max value.

You can have 2 rows or 4 columns, basically.

SELECT
    Mn.ID, foo.MinVal,
    Mx.ID, foo.MaxVal     
FROM
    (
    SELECT
        MIN([Value]) AS MinVal,
        MAX([Value]) AS MaxVal,
        indicatorid
    FROM
        [AnalystEstimates].[dbo].[AnalystEstimateValues] 
    GROUP BY
        indicatorid
    ) foo
    JOIN
    [AnalystEstimates].[dbo].[AnalystEstimateValues] Mn ON
            foo.MinVal = Mn.[Value] AND foo.indicatorid = Mn.indicatorid
    JOIN
    [AnalystEstimates].[dbo].[AnalystEstimateValues] Mx ON
            foo.MaxVal = Mx.[Value] AND foo.indicatorid = Mx.indicatorid

Edit:

The TOP solutions won't give you rows where you have tied MIN/MAX values unless defined WITH TIES

gbn
ON foo.MinVal = Mn.[Value] and ON foo.MaxVal = Mx.[Value]is not possible because there might be duplicate values
Woland
So: which ID do you want if there are duplicates? How would you decide one ID over another?
gbn
If you want to allow returning multiple result like this, you should include the indicatorid in the join otherwise you could fetch rows from the wrong group. I.e. you need this: `ON foo.MinVal = Mn.[Value] AND foo.indicatorid = Mn.indicatorid`.
Mark Byers
@Mark: corrected, thanks
gbn
A: 

This might do it, though I don't have mssql so I can't test it. Especially the square brackets may need tweaking. Other than that it should be fairly standard sql and does what you want.

It gets all min (id/value) and max (id/value) grouped by indicatorid. In the same row.

SELECT mint.indicatorid, mint.min_id, mint.min_value, maxt.max_id, maxt.max_value
FROM (
    SELECT indicatorid, id as min_id, MIN([Value]) AS min_value
    FROM [AnalystEstimates].[dbo].[AnalystEstimateValues]
    HAVING [Value] = min_value
    GROUP BY indicatorid
) mint JOIN (
    SELECT indicatorid, id as max_id, MAX([Value]) AS max_value
    FROM [AnalystEstimates].[dbo].[AnalystEstimateValues]
    HAVING [Value] = max_value
    GROUP BY indicatorid
) maxt ON mint.indicatorid = maxt.indicatorid
Tor Valamo
I got this error "ID' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause."
Woland
+3  A: 

It's very unclear what you want from your question. Do you really want the GROUP BY indicatorid? If not then it's quite simple and you already have many answers. But if you do want to GROUP BY then it's more difficult and no-one has got it quite right yet. I also assume that you only want one row per indicatorid, and if there are duplicate rows that have the same max/min then it's better to just choose one of them arbitrarily instead of returning both.

Here's my attempt, using CTEs (requires SQL Server 2005 or newer):

WITH
    RowNumbers AS (
        SELECT ROW_NUMBER() OVER (ORDER BY indicatorid, value) AS RowNumber, *
        FROM [AnalystEstimates].[dbo].[AnalystEstimateValues]),
    MinRowNumbers AS (
        SELECT indicatorid, MIN(RowNumber) AS RowNumber FROM RowNumbers GROUP BY indicatorid),
    MaxRowNumbers AS (
        SELECT indicatorid, MAX(RowNumber) AS RowNumber FROM RowNumbers GROUP BY indicatorid)
SELECT
    MinRowNumbers.indicatorid,
    RN1.Value AS MinValue,
    RN1.ID AS MinValueId,
    RN2.Value AS MaxValue,
    RN2.ID AS MaxValueId
FROM MinRowNumbers
JOIN MaxRowNumbers ON MinRowNumbers.indicatorid = MaxRowNumbers.indicatorid
JOIN RowNumbers RN1 ON MinRowNumbers.RowNumber = RN1.RowNumber
JOIN RowNumbers RN2 ON MaxRowNumbers.RowNumber = RN2.RowNumber

Here is some data I used to test it:

CREATE TABLE AnalystEstimateValues (ID int, indicatorid int, Value int);

INSERT INTO AnalystEstimateValues (ID, indicatorid , Value) VALUES
(1, 1, 4),
(2, 1, 4),
(3, 2, 6),
(4, 1, 2),
(5, 2, 2),
(6, 2, 5),
(7, 3, 0);

And here's the output I get:

indicatorid MinValue MinValueId MaxValue MaxValueId
          1        2          4        4          2
          2        2          5        6          3
          3        0          7        0          7

If this isn't what you want, can you please try to improve your question to tell us what you do want?


Update: Here's an alternative solution based on Craig Young's answer but using joins instead of subselects:

WITH
    UniqueIds AS (
        SELECT IndicatorId, Value, MIN(id) AS Id
        FROM AnalystEstimateValues
        GROUP BY IndicatorId, Value)
SELECT
    lims.IndicatorId,
    MinValue,
    T1.Id AS MinValueId,
    MaxValue,
    T2.Id AS MaxValueId 
FROM (
        SELECT
            IndicatorId,
            MIN(Value) as MinValue,
            MAX(Value) as MaxValue
        FROM AnalystEstimateValues
        GROUP BY IndicatorId) lims
JOIN UniqueIds T1 ON lims.IndicatorId = T1.IndicatorId AND lims.MinValue = T1.Value
JOIN UniqueIds T2 ON lims.IndicatorId = T2.IndicatorId AND lims.MaxValue = T2.Value

This is cleaner and probably also faster than my first version, although I haven't run performance tests to verify this.

Mark Byers
thank you that is it
Woland
OK, I was lucky. :) I think next time you should spend a little more time on your question so that the responses better match what you want. In fact I don't think this is the best way to solve this problem - it could be too slow. I see that you already accepted it, but I hope that you have confirmed that it is fast enough for your data before doing so. If someone can later solve this in a better way now that they can see what you want, then you should accept their answer instead of this one.
Mark Byers
I'm not familiar with the syntax you've used; I must read up on it. As a performance note, you have 2 GROUP BY sub-queries - one for MIN and the other for MAX. You should be able to combine these into a single sub-query and derive a significant performance improvement.
Craig Young
Yep that should be possible, and will also save a join at the end. I was hoping someone could find a way to do this without using rownumbers, because I think this will require a full table scan even though only the min and max are needed.
Mark Byers
You can check out my solution which doesn't use row numbers. http://stackoverflow.com/questions/1979046/sql-query-minimum-maximum/1979852#1979852 It uses 3 sub-queries in total, each having potential to benefit from the same (IndicatorId, Value) index depending on column stats.
Craig Young
+1  A: 

Important question
Mark Byers' sample data shows a scenario that you need to contemplate:

  • IndicatorId=1 has a max value of 4.
  • There are two Id's that share the same max value (1 & 2).

So which of the two id's should be displayed?

I have assumed it sufficient to display the lowest id. The following query should be the most efficient, and would benefit from an index on (indicatorid, Value).

SELECT  lims.*,
        (
        SELECT  MIN(id)
        FROM    AnalystEstimateValues m
        WHERE   m.IndicatorId = lims.IndicatorId
            AND m.Value = lims.MinValue
        ) AS MinId,
        (
        SELECT  MIN(id)
        FROM    AnalystEstimateValues m
        WHERE   m.IndicatorId = lims.IndicatorId
            AND m.Value = lims.MaxValue
        ) AS MaxId
FROM    (
        SELECT  IndicatorId,
                MIN(Value) as MinValue,
                MAX(Value) as MaxValue
        FROM    AnalystEstimateValues
        GROUP BY IndicatorId
        ) lims
Craig Young
This produces the correct result according to my example test data. It uses a correlated subselect so I'm not sure it will always be fast - if there are a very large number of very small indicatorid groups I suspect it will be slower. But it's very hard to guess these things without trying it on more realistic data. I still feel that there must be a better way to do this but +1 anyway.
Mark Byers
I have made a modified version of your solution and appended it to the end of my first answer. I basically do the same thing but using JOINs instead of correlated subselects. I *guess* this will be faster but it would require proper testing to find out for sure. I don't plan to do that - it's too much effort to create realistic test data - but if someone else does this (Woland?), I'd be very interested to know the results.
Mark Byers
PS: I'd guess that your solution is fine as there probably aren't all that many indicatorids.
Mark Byers