views:

115

answers:

4

Is it possible to return groups as an associative array? I'd like to know if a pure SQL solution is possible. Note that I release that I could be making things more complex unnecessarily but this is mainly to give me an idea of the power of SQL.

My problem: I have a list of words in the database that should be sorted alphabetically and grouped into separate groups according to the first letter of the word.

For example:

ape
broom
coconut
banana
apple

should be returned as

array(
'a' => 'ape', 'apple',
'b' => 'banana', 'broom',
'c' => 'coconut'
)

so I can easily created sorted lists by first letter (i.e. clicking "A" only shows words starting with a, "B" with b, etc. This should make it easier for me to load everything in one query and make the sorted list JavaScript based, i.e. without having to reload the page (or use AJAX).

Side notes: I'm using PostgreSQL but a solution for MySQL would be fine too so I can try to port it to PostgreSQL. Scripting language is PHP.

A: 

Run 26 separate queries. Or run one query and separate the results alphabetically server side.

Joe Philllips
A: 

Are you familiar with the LIKE syntax?

as in

SELECT * FROM words WHERE col LIKE a% ORDER BY col

would give you the a's in order and so on. Build the hash accordingly.

Carl
Yes but I'd like this functionality in a single query if possible.
EarthMind
A single query is not always as good as mutliple queries.
Lizard
I like Bill's solution, but it mandates a level of familiarity with SQL syntax. A more intellectually bite-size query might be preferable for your development situation in terms of readability/maintainability/etc.
Carl
Yeah, as the saying goes: "Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?" (Brian Kernighan)
Bill Karwin
A: 

I'm no PostgreSQL expert (or user) but I believe in the later versions you can do something like this:

SELECT 
    ROW_NUMBER() OVER (PARTITION BY SUBSTRING(whatever from 1 for 1) ORDER BY whatever) AS __ROW,
    whatever
FROM yourtable;

This is ANSI SQL. I don't know what the support on MySql is like yet. Oracle and SQL Server both work with this syntax and I heard it through Google that PostgreSQL 8.4 supports windowing functions.

Dave Markle
FWIW, MySQL does not support windowing functions. :-(
Bill Karwin
It seems like PostgreSQL 8.4 and up do support window functions. Source: postgresql.org.
EarthMind
It seems that it doesn't work as wanted as it returns row numbers which don't group the correct words with each other.
EarthMind
+5  A: 

MySQL:

SELECT LEFT(word, 1) AS first_letter, 
  GROUP_CONCAT(word) AS word_list
FROM MyTable
GROUP BY LEFT(word, 1);

PostgreSQL 8.4:

SELECT SUBSTRING(word FOR 1) AS first_letter, 
  ARRAY_TO_STRING(ARRAY_AGG(word), ',') AS word_list
FROM MyTable
GROUP BY SUBSTRING(word FOR 1);

See http://mssql-to-postgresql.blogspot.com/2007/12/cool-groupconcat.html for more about emulating MySQL's GROUP_CONCAT() in PostgreSQL.

See http://www.postgresonline.com/journal/index.php?/archives/126-PostgreSQL-8.4-Faster-array-building-with-array_agg.html for more on ARRAY_AGG().

Bill Karwin
+1: Wow, that is succinct
OMG Ponies
I edited the PostgreSQL example after I discovered that PostgreSQL 8.4 supports `ARRAY_AGG()` so you don't have to declare your own custom aggregate function. If you use PostgreSQL <8.4, see the blog I link to for solutions.
Bill Karwin
If you can use the windowing functions in PostgreSQL, use them instead. It'll be a lot easier to port down the road when MySql grows up enough to support them too.
Dave Markle
That's what I wanted. I did modify the query a bit: `SELECT LOWER(SUBSTRING(column_name FOR 1)) AS first_letter, ARRAY_TO_STRING(ARRAY_AGG(column_name), ',') AS word_list FROM table_name GROUP BY first_letter ORDER BY first_letter`
EarthMind
One more question: How would I group words starting with a non-alphabetic character together while keeping the current functionality?
EarthMind
If you can come up with an expression that maps non-alpha characters to a single value, you can group by it. For example: `SELECT CASE WHEN LOWER(SUBSTRING(column_name FOR 1)) BETWEEN 'a' AND 'z' THEN LOWER(SUBSTRING(column_name FOR 1)) ELSE '*' END AS first_letter ...`
Bill Karwin