views:

104

answers:

4

I have a query that goes something like this :

 ;WITH t as
(
select 1 as RowNumber, 1 as ObjectID, 10 as [Col1],  20 as [Col2],  20 as [Col3],  20 as [Col4] UNION ALL
select 2 as RowNumber, 2 as ObjectID, 20 as [Col1],  30 as [Col2],  40 as [Col3],  50 as [Col4]
)
SELECT  RowNumber, ObjectID,
        (
        SELECT  MAX(Amount)
        FROM    (
                SELECT  [Col1] AS Amount
                UNION ALL
                SELECT  [Col2]
                UNION ALL
                SELECT  [Col3]
                UNION ALL
                SELECT  [Col4]
                ) d
       WHERE   Amount > 0
        )
FROM    t

The query works fine, but I want to know is where the Max(Amount) comes from.

So in my result set, on top of having (RowNumber, ObjectId, Amount) I want the name of the column (Col1, Col2, Col3, Col4) as a String.

Is there any way to do that?

EDIT Question from the comments : If two columns have the same max, it could be either one? Yes, it could be either one. Any column name will do as long as I know where it could be coming from.

Using SQL Server 2008

+5  A: 

Don't MAX: use TOP which avoids the aggregate/GROUP BY.

It can also deal with duplicates using WITH TIES

I'm not sure if what you had was psuedo-code or a sub-query, but this should do what you want

 SELECT TOP 1 -- WITH TIES if needed
     *
 FROM
     (
     SELECT  RowNumber, ObjectID, [Col1] AS Amount, 'Col1' AS ColName
     FROM table
     UNION ALL
     SELECT  RowNumber, ObjectID, [Col2], 'Col2' AS ColName
     FROM table
     UNION ALL
     SELECT  RowNumber, ObjectID, [Col3], 'Col3' AS ColName
     FROM table
     UNION ALL
     SELECT  RowNumber, ObjectID, [Col4], 'Col4' AS ColName
     FROM table
     ) foo
 WHERE   Amount > 0
 ORDER BY Amount DESC

Your main problem is that you'll have to touch the table 4 times no matter how you do it because a subquery only returns one value. I can't see a ROW_NUMBER solution either (but there probably is one though... :-)

gbn
His query was doing it per row. I never knew you could do that.
Martin Smith
@Martin Smith: It works, but I'm not sure how to get columnname too because it's subquery which allows one value only. See these links: http://sqlblogcasts.com/blogs/simons/archive/2006/05/08/Neat-trick-to-find-max-value-of-multiple-columns.aspx or http://sqlblogcasts.com/blogs/simons/archive/2006/05/16/Performance-of-MAX-trick.aspx
gbn
@gbn - Yep never seen that before. Thanks for the links!
Martin Smith
@gbn - Thanks for your help. It is a sub-query, the full query is longer than that and has more than 4 columns that I could be getting the name of. This query will not be running over and over, so i'm not worried about efficiency. I just want accurate results. Will try your solution in the full query and mark as accepted if I can get it to work like that.
Hugo Migneron
+1. I've never seen this type of referencing of columns either.
8kb
+3  A: 

This is untested: however to see whats going on with your data, this might help. Not really production code quality:

SELECT  RowNumber, ObjectID,
        (
        SELECT  MAX(Amount)
        FROM    (
                SELECT str([Col1]) + ", col1, " AS Amount
                UNION ALL
                SELECT str([Col2]) + ", col2" 
                UNION ALL
                SELECT str([Col3]) + ", col3"
                UNION ALL
                SELECT str([Col4]) + ", col4"
                )
        WHERE   Amount > 0
        )
FROM    table

str() is the "toString()" function of your DBMS. Your SQL seems pretty weird, what DBMS are you using?

knb
You'd have to have `Amount > '0'` but this is quite neat + 1
gbn
That's a clever way of doing it. Didn't think about appending the column name as a String like that. Thanks
Hugo Migneron
+2  A: 

Adding a step to user202553's answer

;WITH t1 as(
select 1 as RowNumber, 1 as ObjectID, 10 as [Col1],  20 as [Col2],  20 as [Col3],  20 as [Col4] UNION ALL
select 2 as RowNumber, 2 as ObjectID, 20 as [Col1],  30 as [Col2],  40 as [Col3],  50000045 as [Col4]
),
t2 as(
SELECT  RowNumber, ObjectID,
        (
        SELECT  TOP 1 CAST(C AS BINARY(4)) + CAST(Amount as BINARY(4)) 
        FROM    (
                SELECT 'Col1' AS C, [Col1] AS Amount
                UNION ALL
                SELECT 'Col2' AS C, [Col2]
                UNION ALL
                SELECT 'Col3' AS C, [Col3]
                UNION ALL
                SELECT 'Col4' AS C, [Col4]
                ) d
       WHERE   Amount > 0
       ORDER BY Amount desc
        ) AS Top1
FROM    t1
)
SELECT RowNumber, 
       ObjectID,
       CAST(Left(Top1, 4) AS CHAR(4)) AS Col, 
       CAST(SUBSTRING(Top1,5,4) AS INT) AS Amount
FROM t2
Martin Smith
Thanks, that's pretty clean as well
Hugo Migneron
Even neater then string concat: fixed length "encoding"
gbn
+1  A: 

You can use a combination of UNPIVOT and OUTER APPLY:

;WITH t as     (
select 1 as RowNumber, 1 as ObjectID, 10 as [Col1],  20 as [Col2],  
       20 as [Col3],  20 as [Col4] UNION ALL
select 2 as RowNumber, 2 as ObjectID, 20 as [Col1],  30 as [Col2],  
       40 as [Col3],  50 as [Col4] )
SELECT 
  RowNumber,
  ObjectID,
  ColName,
  ColAmount
FROM t
OUTER APPLY (
  SELECT TOP 1 
    ColName,
    ColAmount
  FROM 
    (
      SELECT
        Col1,
        Col2,
        Col3,
        Col4
    ) x 
    UNPIVOT (
      ColAmount FOR ColName IN (Col1, Col2, Col3, Col4) 
    ) y
   WHERE ColAmount > 0
   ORDER BY ColAmount DESC
 ) z

Results:

RowNumber   ObjectID    ColName   ColAmount
----------- ----------- --------- -----------
1           1           Col2      20
2           2           Col4      50
8kb
Thanks, this turned out to be the best solution here. Didn't know about the UNPIVOT function but it's exactly what I was looking for.
Hugo Migneron