If you want the min duration and its id at the same time then the simplest way to do this is to use two window functions.
SELECT DISTINCT ServiceName, MethodName
, MIN(Duration) OVER (PARTITION BY ServiceName, MethodName ORDER BY Duration ASC)
, FIRST_VALUE(Id) OVER (PARTITION BY ServiceName, MethodName ORDER BY Duration ASC)
FROM log
It may seem unnecessary to have the ORDER BY clause for the MIN of duration, however by reusing that partition both functions can be processed simultaneously and not have to be divided into separate sets and recombined. The best way to understand is to take the order by out and view the query plan and see how it adds Nested Loops and a lot of other extra steps. Long story short, this ends up producing a pretty short and efficient plan.
I hope it's obvious how the correct Id is retrieved. Basically this relies on the fact that sorting a set results in any value in the first row to be related to the min/max value(s) used in the sort.
If multiple Id's match the duration and you wanted to see them all, you could do the following instead. You can use TOP
to limit the result to a certain number of rows.
SELECT l1.ServiceName, l1.MethodName, l1.Duration, x.Id
FROM (
SELECT ServiceName, MethodName, MIN(Duration) Duration
FROM log GROUP BY ServiceName, MethodName
) l1
CROSS APPLY (
SELECT TOP 10 Id
FROM log l2 WHERE l2.ServiceName = l1.ServiceName
AND l2.MethodName = l1.MethodName
AND l2.Duration = l1.Duration
) x