Mysql – select only one element from an priority ordered group with limits

group byMySQL

This question searches for an answer in pure sql (no db-server specific code)

-edit-
Due comments/answers:
"pure sql" – SQL92/99 because this works on all servers. I do not only use this code on mysql, this was just the first one I tried. I have to support Microsoft SQL Server (2000!), h2, PostGRE SQL and MySQL Server but the code should work on others too (db server independent). I'm sorry if "pure sql" was not detailed enough. Next time I'll specify requirement of the revision of SQL (3/4, 92/99)

I have to be backwards compatible.
And this is why I did not specify a db server. This was on purpose.
-end-of-edit-

I try to get all values from a table with given limits.
The data i have for the query is: possible locales and a max_priority

this is the table to work with

SELECT * FROM i18n

.

id|featureName|locale     |priority|text
--+-----------+-----------+--------+----
 1| type      |         en|       1|father
 2| type      |         de|       2|Vater
 3| type      |      de_AT|       3|Papa
 4| type      | de_AT_Wien|       4|Oida
 5| firstName |         en|       1|first name
 6| firstName |         de|       2|Vorname
 7| lastName  |         en|       1|last name
 8| lastName  |         de|       2|Nachname
 9| lastName  |         fr|       2|nom de famille
10| firstName |         fr|       2|prénom

so for example i have en, de, de_AT and a max_priority of 3 (which is the priority of de_AT)
i expect this as result: (the order of these results are not important!)

id|featureName|locale     |priority|text
--+-----------+-----------+--------+----
 3| type      |      de_AT|       3|Papa
 6| firstName |         de|       2|Vorname
 8| lastName  |         de|       2|Nachname

i tried to solve this with an left outer join
this is the best result i was able to get:

SELECT DISTINCT nls1.*
FROM i18n nls1
LEFT OUTER JOIN i18n nls2
ON (
    nls1.featureName = nls2.featureName
    and nls1.priority > nls2.priority
)
WHERE nls2.priority<=3
AND (nls1.locale='en' OR nls1.locale='de' OR nls1.locale='de_AT')
GROUP BY featureName
ORDER BY featureName, priority DESC

but this selects those rows:

id|featureName|locale     |priority|text
--+-----------+-----------+--------+----
 2| type      |         de|       2|Vater
 6| firstName |         de|       2|Vorname
 8| lastName  |         de|       2|Nachname

another try was

SELECT nls2.* 
FROM   i18n nls2 
       JOIN (SELECT Max(priority) max_priority, 
                    featurename, 
                    priority 
             FROM   i18n nls 
             WHERE  ( locale = 'en' 
                       OR locale = 'de' 
                       OR locale = 'de_AT' ) 
                    AND priority <= 3 
             GROUP  BY featurename) nls3 
         ON nls3.priority = nls2.priority 
            AND nls3.featurename = nls2.featurename 

but this one only returns the en version…

id|featureName|locale     |priority|text
--+-----------+-----------+--------+----
 1| type      |         en|       1|father
 5| firstName |         en|       1|first name
 7| lastName  |         en|       1|last name

NOTE: i'll update the question depending on the answers.

The main problem seems that i need a group by an column but the group also needs a order by?

Best Answer

Without window functions, the query will be complicated. One way to solve the problem in MySQL:

SELECT 
    n.*
FROM 
    ( SELECT DISTINCT featureName
      FROM i18n
    ) AS d
  JOIN
    i18n AS n
  ON  n.id = 
      ( SELECT i.id
        FROM i18n AS i
        WHERE i.featureName = d.featureName
          AND i.locale IN ('en', 'de', 'de_AT')
          AND i.priority <= 3
        ORDER BY i.priority DESC
        LIMIT 1
      ) 
ORDER BY 
    n.featureName ;           -- or whatever you like, eg:  BY n.id

Also note that the above query will fail it is in SQL Server (which uses TOP, not LIMIT) and possibly fail in other DBMS as well.