Home > Enterprise >  Get back first non-null value grouped by some other columns
Get back first non-null value grouped by some other columns

Time:12-08

How to go from left to right?
Basically for each TIME-person combo, I need the first non-null value from the id columns. enter image description here

CodePudding user response:

Aggregate by time and person and take the max of the other 4 columns:

SELECT
    time,
    person,
    MAX(id1) AS id1,
    MAX(id2) AS id2,
    MAX(id3) AS id3,
    MAX(id4) AS id4
FROM yourTable
GROUP BY
    time,
    person;

CodePudding user response:

Best way that I know of (i.e. in my experience of practical situations where this comes up) is to use PIVOT and UNPIVOT (may not be necessary in the example above, but if you have multiple rows with id1's for a time/person combo it provides a nice mechanism to do this).

Unfortunately how to do this depends on which SQL DBMS you're using. Below example will work for MS SQL Server. Oracle has similar functionality. MySQL/PostgreSQL I don't know that well, but I don't think they have PIVOT/UNPIVOT though you may find something similar if you search for "lateral derived table" or "cross join lateral":

SELECT [Time], Person, id1, id2, id3, id4
FROM    (SELECT [Time], Person, id, idval
            FROM (SELECT [Time], Person, id1, id2, id3, id4 FROM #test) pvt
            UNPIVOT (idval FOR id IN (id1, id2, id3, id4)) AS unpvt
        ) AS unpivoted
PIVOT (
        MIN(idval) FOR [id] IN ([id1], [id2], [id3], [id4])
    ) AS pivoted

For this example, I assumed that by the "first" non-null value you meant the one with the lowest ID, i.e. MIN(idval), but this may not be what you're after. There are ways to use other columns to determine which is first, e.g. if time should determine what "first" means then something like this could work:

SELECT [Time], Person, id1%1000000 'id1', id2%1000000 'id2', id3%1000000 'id3', id4%1000000 'id4'
FROM    (SELECT [Time], Person, id, idval (1000000*[Time]) 'idval'
            FROM (SELECT [Time], Person, id1, id2, id3, id4 FROM #test) pvt
            UNPIVOT (idval FOR id IN (id1, id2, id3, id4)) AS unpvt
        ) AS unpivoted
PIVOT (
        MIN(idval) FOR [id] IN ([id1], [id2], [id3], [id4])
    ) AS pivoted

Last resort, you can also to something like this (ugly as hell IMHO and not great for performance either):

SELECT tp.Person, tp.[Time], t_id1.id 'id1', t_id2.id 'id', t_id3.id 'id3', t_id4.id 'id4'
    FROM (SELECT [Time], Person FROM #Test t GROUP BY [Time], Person) tP
    LEFT JOIN (SELECT [Time], Person, MIN(id1) 'id' FROM #Test t WHERE t.id1 IS NOT NULL GROUP BY [Time],Person) t_id1 ON t_id1.Person = tP.Person
    LEFT JOIN (SELECT [Time], Person, MIN(id2) 'id' FROM #Test t WHERE t.id2 IS NOT NULL GROUP BY [Time],Person) t_id2 ON t_id2.Person = tP.Person
    LEFT JOIN (SELECT [Time], Person, MIN(id3) 'id' FROM #Test t WHERE t.id3 IS NOT NULL GROUP BY [Time],Person) t_id3 ON t_id3.Person = tP.Person
    LEFT JOIN (SELECT [Time], Person, MIN(id4) 'id' FROM #Test t WHERE t.id4 IS NOT NULL GROUP BY [Time],Person) t_id4 ON t_id3.Person = tP.Person
  •  Tags:  
  • sql
  • Related