jkelley
jkelley

Reputation: 2640

Approach to Selecting top item matching a criteria

EDIT: my apologies, this was a MSSQL2008 issue.

I have a SQL problem that I've come up against routinely, and normally just solved w/ a nested query. I'm hoping someone can suggest a more elegant solution.

It often happens that I need to select a result set for a user, conditioned upon it being the most recent, or the most sizeable or whatever.

For example: Their complete list of pages created, but I only want the most recent name they applied to a page. It so happens that the database contains many entries for each page, and only the most recent one is desired.

I've been using a nested select like:

SELECT pg.customName, pg.id
FROM (
     select id, max(createdAt) as mostRecent
     from pages
     where userId = @UserId
     GROUP BY id
) as MostRecentPages
JOIN pages pg
ON pg.id = MostRecentPages.id
AND pg.createdAt = MostRecentPages.mostRecent

Is there a better syntax to perform this selection?

Upvotes: 0

Views: 3554

Answers (6)

Will Marcouiller
Will Marcouiller

Reputation: 24142

Assuming SQL Server and your Pages table like so:

CREATE TABLE Pages (
    Id int IDENTITY(1, 1) PRIMARY KEY
    , CustomName nvarchar(20) NOT NULL
    , CreatedAt datetime NOT NULL DEFAULT GETDATE()
    , UserId int references Users(Id)
)

I would do the following:

select TOP 1 p.Id as PageId
        , p.CustomName
    from Pages p
    where p.UserId = @UserId
    order by p.Created desc

Or even:

select TOP 1 p.Id as PageId
        , p.CustomName
        , MAX(p.CreatedAt) DateTimeCreated
    from Pages p
    where p.UserId = @UserId
    group by p.Id
        , p.CustomName

I hope this helps! (If not, please provide further details so that we may be of better helping hand)

Upvotes: 0

araqnid
araqnid

Reputation: 133692

Looks like you want

SELECT id, customname
FROM (SELECT id, customname,
             row_number() OVER(PARTITION BY id ORDER BY createdat DESC) as pos
      FROM pages
      WHERE pages.userid = @UserId
     ) x
WHERE x.row_number = 1

(I'm assuming you're using SQL Server from the @UserId parameter. row_number() will work for SQL Server 2005, and tbh the above should also work for Oracle, Postgresql 8.4...)

This will select all the pages by userid and work out which is the most recent using a sort. An alternative would be sth like:

SELECT id, (SELECT TOP 1 customname
            FROM pages pages_inner
            WHERE pages_inner.id = pages_outer.id
            ORDER BY pages_inner.createdat DESC) as customname
FROM (SELECT DISTINCT id FROM pages WHERE pages.userid = @UserId) pages_inner

Which approach is better depends on how many pages rows per id you have compared to pages per userid, I guess.

Upvotes: 2

OMG Ponies
OMG Ponies

Reputation: 332681

For what database (including version)? What you posted could be MySQL, SQL Server, or Sybase...

Using:

SELECT pg.customName, 
       pg.id
  FROM PAGES pg
  JOIN (SELECT t.userid, 
               MAX(t.createdAt) as mostRecent
          FROM PAGES t
      GROUP BY t.userid) x ON x.id = pg.id
                          AND x.mostRecent = pg.createdAt
                          AND x.userid = @UserId 

This is the best approach for a portable query, assuming column references are correct. But there are alternatives for limiting the data set - SQL Server uses TOP, MySQL/Postgre/SQLite use LIMIT, Oracle uses ROWNUM.

What's best depends on your data & how the respective optimizer sees it, and your needs (portable vs not). Check the explain plan for the respective database to see how efficient the query is.

Upvotes: 1

Martin Smith
Martin Smith

Reputation: 453638

I'm not sure about better but a different syntax you could try is

SELECT pg.customName, pg.id
FROM pages pg
WHERE userId = @UserId  
AND NOT EXISTS 
        (
         SELECT * FROM pages pg2
         WHERE pg2.UserId = pg.UserId
         AND pg2.id = pg.id
         AND pg2.createdAt > pg.createdAt
         )

Another alternative would be an OUTER JOIN as in Bill Karwin's answer here How to get all the fields of a row using the SQL MAX function?

Upvotes: 1

Tommi
Tommi

Reputation: 8608

Are you using Oracle? Try to see if this query that uses analytic function would work for you. (Don't have access to db right now, so can't test myself.)

SELECT DISTINCT pg.id, 
FIRST_VALUE(pg.customName) OVER (PARTITION BY pg.id ORDER BY pg.createdAt DESC) AS customName
FROM pages pg

Upvotes: 0

Darknight
Darknight

Reputation: 2500

I don't know what your table looks like

Select top 1  pg.createdAt
             ,pg.customName
             ,pg.id
from          table pg
where         pg.UserId = @UserId
order by      pg.createdAt Desc

I need a bit more info on your table(s)

Upvotes: -1

Related Questions