21

Running this query:

select name from folders order by name

returns these results:

alphanumeric
a test
test 20
test 19
test 1
test 10

But I expected:

a test
alphanumeric
test 1
test 10
test 19
test 20

What's wrong here?

Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192
el_quick
  • 4,656
  • 11
  • 45
  • 53
  • 1
    It looks weird: `'test 20' < 'test 19'`. What `LC_COLLATE` you have by examinating `SHOW lc_collate;` ? I have `en_US.UTF-8` and it returns exactly wanted output with `ORDER BY name ASC`. – Grzegorz Szpetkowski Aug 10 '11 at 22:32
  • I get the same thing, too. I just did `select 'alphanumeric' < 'a test'` and got `f`. – Ray Toal Aug 10 '11 at 22:34
  • 1
    for the record, i posted an answer pointing you to the manual entry for collation. it was donwvoted as not being huggy-lovey enough so i deleted it. but i think you should start there. – andrew cooke Aug 10 '11 at 22:37
  • @andrew cooke: I didn't downvote, that was good point, collation is responsible for ordering, however _per-column collation_, which could be useful here is supported only since Postgres 9.1. – Grzegorz Szpetkowski Aug 10 '11 at 22:41
  • Hello, thk for your comments... SHOW lc_collate; is returning es_SV.UTF-8 (I live in El Salvador) – el_quick Aug 10 '11 at 22:44
  • you can set collation for the entire db on creation. it's not clear that wouldn't work here - typically people want ordering to work everywhere in the same way, no? – andrew cooke Aug 10 '11 at 22:56
  • I installed `es_SV.UTF-8` on Debian, then created new db with that locale and surprisingly I have such output: `'alphanumeric', 'a test', 'test 1', 'test 10', 'test 19', 'test 20'` (same as for `en_US.UTF-8`). – Grzegorz Szpetkowski Aug 10 '11 at 23:13
  • Consider others values (this is not so clear if is go about numbers an others non alphabetical chars). Something like this: '3','50','100', '_9', '1b','2a','2 x', 'My Value 1','my Value 2', 'my value 3',' Others ' – Perlos Jun 13 '12 at 08:11

7 Answers7

27

You can simply cast name column to bytea data type allowing collate-agnostic ordering:

SELECT name
FROM folders
ORDER BY name::bytea;

Result:

     name     
--------------
 a test
 alphanumeric
 test 1
 test 10
 test 19
 test 20
(6 rows)
Say No To Censorship
  • 537
  • 1
  • 15
  • 32
Grzegorz Szpetkowski
  • 36,988
  • 6
  • 90
  • 137
19

All of this methods sorted my selection in alphabetical order:

test 1
test 10
test 2
test 20

This solution worked for me (lc_collate: 'ru_RU.UTF8'):

SELECT name
FROM folders
ORDER BY SUBSTRING(name FROM '([0-9]+)')::BIGINT ASC, name;

test 1
test 2
test 10
test 20
A Vlk
  • 191
  • 1
  • 2
15
select * from "public"."directory" where "directoryId" = 17888 order by
COALESCE(SUBSTRING("name" FROM '^(\d+)')::INTEGER, 99999999),
SUBSTRING("name" FROM '[a-zA-z_-]+'),
COALESCE(SUBSTRING("name" FROM '(\d+)$')::INTEGER, 0),
"name";

NOTE: Escape the regex as you need, in some languages, you will have to add one more "\".

In my Postgres DB, name column contains following, when I use simple order by name query:

  • 1
  • 10
  • 2
  • 21
  • A
  • A1
  • A11
  • A5
  • B
  • B2
  • B22
  • B3
  • M 1
  • M 11
  • M 2

Result of Query, After I have modified it:

  • 1
  • 2
  • 10
  • 21
  • A
  • A1
  • A5
  • A11
  • B
  • B2
  • B3
  • B22
  • M 1
  • M 2
  • M 11
Deepak Pandey
  • 1,322
  • 12
  • 21
8

You may be able to manually sort by splitting the text up in case there is trailing numerals, like so:

SELECT * FROM sort_test
ORDER BY SUBSTRING(text FROM '^(.*?)( \\d+)?$'),
         COALESCE(SUBSTRING(text FROM ' (\\d+)$')::INTEGER, 0);

This will sort on column text, first by all characters optionally excluding an ending space followed by digits, then by those optional digits.

Worked well in my test.

Update fixed the string-only sorting with a simple coalesce (duh).

OverZealous
  • 39,252
  • 15
  • 98
  • 100
  • Why the downvote? It works, and solves the situation. It's not the *best* solution, but it doesn't involve changing the database structure. At least comment if you feel the need to downvote. – OverZealous Aug 10 '11 at 23:54
  • -1 for bizarre kluge that doesn't solve the real problem. (What about strings with multiple spaces and/or numbers?) See the comments above about collations. – Ryan Culpepper Aug 10 '11 at 23:56
  • 1
    I wouldn't say it's a *bizarre kludge*. It adds the ability to sort trailing numbers numerically, without requiring a specific version of PG. It handles *trailing* numbers very well, so works well for sequentially numbered folders. It handles multiple spaces, because it's only checking to ensure that there is at least one space before the trailing digits. If you tried it, you'd see it worked, instead of assuming. – OverZealous Aug 10 '11 at 23:59
  • Not working for texts like: "H1C11", because it order for the first number, not for the tailing – DariusVE Jul 21 '14 at 15:58
5

OverZealous answer helped me but didn't work if the string in the database begun with numbers followed by additional characters.

The following worked for me:

SELECT name
FROM folders
ORDER BY
COALESCE(SUBSTRING(name FROM '^(\\d+)')::INTEGER, 99999999),
SUBSTRING(name FROM '^\\d* *(.*?)( \\d+)?$'),
COALESCE(SUBSTRING(name FROM ' (\\d+)$')::INTEGER, 0),
name;

So this one:

  1. Extracts the first number in the string, or uses 99999999.
  2. Extracts the string that follows the possible first number.
  3. Extracts a trailing number, or uses 0.
Tor Händevik
  • 61
  • 1
  • 4
3

A Vlk's answer above helped me a lot, but it sorted items only by the numeric part, which in my case came second. My data was like (desk 1, desk 2, desk 3 ...) a string part, a space and a numeric part. The syntax in A Vlk's answer returned the data sorted by the number, and at that it was the only answer from the above that did the trick. However when the string part was different, (eg desk 3, desk 4, table 1, desk 5...) table 1 would get first from desk 2. I fixed this using the syntax below:

    ...order by SUBSTRING(name,'\\w+'), SUBSTRINGname FROM '([0-9]+)')::BIGINT ASC;
oupoup
  • 75
  • 8
1

Tor's last SQL worked for me. However if you are calling this code from php you need add extra slashes.

SELECT name
FROM folders
ORDER BY
COALESCE(SUBSTRING(name FROM '^(\\\\d+)')::INTEGER, 99999999),
SUBSTRING(name FROM '^\\\\d* *(.*?)( \\\\d+)?$'),
COALESCE(SUBSTRING(name FROM ' (\\\\d+)$')::INTEGER, 0),
name;
no1uknow
  • 549
  • 1
  • 7
  • 18