views:

1004

answers:

10

I have a simple SQL query (in PostgreSQL 8.3) that grabs a bunch of comments. I've composed a bunch of ids before-hand and that gets fed into the WHERE IN clause like so...

SELECT * FROM "comments" WHERE ("comments"."id" IN (1,3,2,4))

This returns the comments in a natural order which in my case is the ids like 1,2,3,4.

What I'm wanting is to sort the returned rows in the order supplied by the IN clause (1,3,2,4).

How can I ORDER BY the IN clause value list?

A: 

I see no way to do this with straight SQL. I mean, at least not without a lot of blood sweat and tears.

Spencer Ruport
+1  A: 

On researching this some more I found this solution:

SELECT * FROM "comments" WHERE ("comments"."id" IN (1,3,2,4)) 
ORDER BY CASE "comments"."id"
WHEN 1 THEN 1
WHEN 3 THEN 2
WHEN 2 THEN 3
WHEN 4 THEN 4
END

However this seems rather verbose and might have performance issues with large datasets. Can anyone comment on these issues?

nutcracker
Sure, I can comment on them. There are things SQL is good at, and things it is not good at. SQL is not good at this. Just sort the results in whatever language you're making the queries from; it will save you much wailing and gnashing of teeth. SQL is a set-oriented language, and sets are not ordered collections.
kquinn
Hmmm ... Is that based on personal experience and testing? My tested experience is that this is a quite effective technique for ordering. (However, the accepted answer is better overall because it eliminates the "IN (...)" clause). Remember that for any reasonable result set size, deriving the set should be the expensive part. Once it's down to several hundred records or less, sorting is trivial.
le dorfier
+2  A: 

To do this, I think you should probably have an additional "ORDER" table which defines the mapping of IDs to order (effectively doing what your response to your own question said), which you can then use as an additional column on your select which you can then sort on.

In that way, you explicitly describe the ordering you desire in the database, where it should be.

McWafflestix
This seems like the right way to do it. However I'd like to create that ordering table on the fly. I've suggested using a constant table in one of the answers. Is this going to be performant when I'm dealing with hundreds or thousands of comments?
nutcracker
+1  A: 

I agree with all other posters that say "don't do that" or "SQL isn't good at that". If you want to sort by some facet of comments then add another integer column to one of your tables to hold your sort criteria and sort by that value. eg "ORDER BY comments.sort DESC " If you want to sort these in a different order every time then... SQL won't be for you in this case.

Trey
+1  A: 
SELECT * FROM "comments" JOIN (
  SELECT 1 as "id",1 as "order" UNION ALL 
  SELECT 3,2 UNION ALL SELECT 2,3 UNION ALL SELECT 4,4
) j ON "comments"."id" = j."id" ORDER BY j.ORDER

or if you prefer evil over good:

SELECT * FROM "comments" WHERE ("comments"."id" IN (1,3,2,4))
ORDER BY POSITION(','+"comments"."id"+',' IN ',1,3,2,4,')
Hafthor
A: 
create sequence serial start 1;

select * from comments c
join (select unnest(ARRAY[1,3,2,4]) as id, nextval('serial') as id_sorter) x
on x.id = c.id
order by x.id_sorter;

drop sequence serial;

[EDIT]

unnest is not yet built-in in 8.3, but you can create one yourself(the beauty of any*):

create function unnest(anyarray) returns setof anyelement
language sql as
$$
    select $1[i] from generate_series(array_lower($1,1),array_upper($1,1)) i;
$$;

that function can work in any type:

select unnest(array['John','Paul','George','Ringo']) as beatle
select unnest(array[1,3,2,4]) as id
Michael Buen
Thanks Michael but the unnest function doesn't seem to exist for my PSQL and I can't find any mention of it in the docs either. Is it 8.4 only?
nutcracker
unnest is not yet built-in in 8.3, but you can implement one yourself. see the code above
Michael Buen
+1  A: 

And here's another solution that works and uses a constant table (http://www.postgresql.org/docs/8.3/interactive/sql-values.html):

SELECT * FROM comments AS c,
(VALUES (1,1),(3,2),(2,3),(4,4) ) AS t (ord_id,ord)
WHERE (c.id IN (1,3,2,4)) AND (c.id = t.ord_id)
ORDER BY ord

But again I'm not sure that this is performant.

I've got a bunch of answers now. Can I get some voting and comments so I know which is the winner!

Thanks All :-)

nutcracker
your answer is almost the same with depesz, just remove the c.ID IN (1,3,2,4). anyway his is better, he uses JOIN, as much as possible use the ANSI SQL way of joining, don't use table comma table. i should have read your answer carefully, i'm having a hard time figuring out how to alias the two columns, first i tried this: (values(1,1) as x(id,sort_order), (3,2), (2,3), (4,4)) as y. but to no avail :-D your answer could have provided me a clue if i've read it carefully :-)
Michael Buen
A: 

sans SEQUENCE, works only on 8.4:

select * from comments c
join 
(
    select id, row_number() over() as id_sorter  
    from (select unnest(ARRAY[1,3,2,4]) as id) as y
) x on x.id = c.id
order by x.id_sorter
Michael Buen
+5  A: 

You can do it quite easily with (introduced in PostgreSQL 8.2) VALUES (), ().

Syntax will be like this:

select c.*
from comments c
join (
  values
    (1,1),
    (3,2),
    (2,3),
    (4,4)
) as x (id, ordering) on c.id = x.id
order by x.ordering
depesz
Thanks depesz. I thought there must be a clean way to do this. Go well.
nutcracker
A: 

Slight improvement over the version that uses a sequence I think:

CREATE OR REPLACE FUNCTION in_sort(anyarray, out id anyelement, out ordinal int)
LANGUAGE SQL AS
$$
    SELECT $1[i], i FROM generate_series(array_lower($1,1),array_upper($1,1)) i;
$$;

SELECT 
    * 
FROM 
    comments c
    INNER JOIN (SELECT * FROM in_sort(ARRAY[1,3,2,4])) AS in_sort
        USING (id)
ORDER BY in_sort.ordinal;