Want to Sort our Batting Leaders, the MAX result of DIVISION operation, in SQL Server 2012 (T-SQL)

StackOverflow https://stackoverflow.com/questions/21976884

  •  15-10-2022
  •  | 
  •  

Question

I'm using Lahman Baseball Database, and I want to sort out the batting leader (Max average = hits/at bats) for each year (yearID) and league (lgID).

The problem is I need a row_number or rank to be able to pull ONLY the leader (top result) for each yearID and lgID.

I have this query so far:

select
    Concat(m.namefirst,' ', m.namelast) as Player,
    b.yearID,
    b.lgID,
    b.yearID-m.birthyear as Age,
    left(round((b.h*1.000/b.ab),3),5) as Average
from
    batting b 
    inner join [master] m on b.playerID=m.playerID 
    inner join BR_WAR_2013 br on br.playerID=b.playerID and b.yearID=br.yearID
    inner join ops_plus o on b.lgID=o.lgID and b.yearID=o.yearID
    left outer join AwardsPlayersCollate a
    on b.playerID=a.playerID and b.yearID=a.yearID
where
    (b.ab + b.bb) > 500
group by
    b.yearID,b.yearID-m.birthyear,
    b.yearID,
    b.lgID,
    left(round((b.h*1.000/b.ab),3),5),
    Concat(m.namefirst,' ', m.namelast)
order by b.yearID desc,left(round(Max(b.h*1.000/b.ab),3),5) desc

Which Returns this:

Player            yearID    lgID    Age Average
Miguel Cabrera    2013           AL 30  0.348
Michael Cuddyer   2013           NL 34  0.331
Joe Mauer         2013           AL 30  0.324
Mike Trout        2013           AL 22  0.323
Chris Johnson     2013           NL 29  0.321

Which is basically returning ALL of 2013 batting averages that meet the WHERE statement. However, I want it to name top 1 (the leader) for each lgID and yearID then go to 2012 (yearID) and so forth...

I want this:

Player         yearID   lgID    Age Average
Miguel Cabrera  2013    AL  30  0.348
Michael Cuddyer 2013    NL  34  0.331
Buster Posey    2012    NL  25  0.336
Miguel Cabrera  2012    AL  29  0.330
Miguel Cabrera  2011    AL  28  0.344
Jose Reyes      2011    NL  28  0.337
...
..
.

Though I would do a sort (lgID) to keep AL then NL per yearID in order.


Per next comment: Yes, I've tried Row_Number () and Rank(), which didn't work for me, HOWEVER, I could be syntactically incorrect. I tried many combinations of this:

-- Trying Rank () and Row_Number() to sort out Batting Leaders
SELECT 
    Concat(m.namefirst,' ', m.namelast) as Player,
    b.yearID,
    b.lgID,
    b.yearID-m.birthyear as Age,
    left(round((b.h*1.000/b.ab),3),5) as Average,
    Rank() OVER (PARTITION BY b.lgID,b.yearID ORDER BY left(round((b.h*1.000/b.ab),3),5) DESC) AS Row
FROM
    batting b 
    inner join [master] m on b.playerID=m.playerID 
    inner join BR_WAR_2013 br on br.playerID=b.playerID and b.yearID=br.yearID
    inner join ops_plus o on b.lgID=o.lgID and b.yearID=o.yearID
    left outer join AwardsPlayersCollate a
    on b.playerID=a.playerID and b.yearID=a.yearID
WHERE (b.ab + b.bb) > 500 
ORDER BY b.yearID desc, left(round((b.h*1.000/b.ab),3),5) desc, b.lgID

Getting This:

Player          yearID  lgID    Age Average Row
Miguel Cabrera  2013    AL  30  0.348   1
Michael Cuddyer 2013    NL  34  0.331   1
Joe Mauer   2013    AL  30  0.324   2
Mike Trout  2013    AL  22  0.323   3
Chris Johnson   2013    NL  29  0.321   2
Yadier Molina   2013    NL  31  0.319   3
Freddie Freeman 2013    NL  24  0.319   3

But wanting This:

Player         yearID   lgID    Age Average
Miguel Cabrera  2013    AL  30  0.348
Michael Cuddyer 2013    NL  34  0.331
Buster Posey    2012    NL  25  0.336
Miguel Cabrera  2012    AL  29  0.330
Miguel Cabrera  2011    AL  28  0.344
Jose Reyes      2011    NL  28  0.337
...
..
.
Was it helpful?

Solution

Using simplified sample data since you can obviously join for the rest yourself, this should do it using RANK(). RANK() is better than ROW_NUMBER() for handling potential ties. It still works if you put in your LEFT(ROUND((h*1.000/ab),3),5) in place of my simplified 1.0*h/ab.

CREATE TABLE batting (playerID INT, yearID INT, leagueID CHAR(2), h INT, ab INT)

INSERT INTO batting 
SELECT 1, 2010, 'AL', 40, 100 UNION
SELECT 2, 2010, 'AL', 35, 100 UNION
SELECT 3, 2010, 'NL', 35, 100 UNION
SELECT 4, 2010, 'NL', 30, 100 UNION
SELECT 5, 2011, 'AL', 50, 100 UNION
SELECT 6, 2011, 'AL', 45, 100 UNION
SELECT 3, 2011, 'NL', 45, 100 UNION
SELECT 7, 2011, 'NL', 45, 100 UNION
SELECT 4, 2011, 'NL', 40, 100 

GO

;WITH cte AS (
  SELECT playerID
        ,yearID
        ,leagueID 
        ,1.0*h/ab battingAvg 
        ,RANK() OVER(PARTITION BY yearID, leagueID ORDER BY 1.0*h/ab DESC) battingRank
    FROM batting
) 
SELECT playerID
      ,yearID
      ,leagueID 
      ,battingAvg
  FROM cte 
    -- JOIN to whatever else you need
 WHERE battingRank = 1 

DROP TABLE batting
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top