madflow
madflow

Reputation: 8490

Find distinct rows in Mysql by using conditions

Vague title, sorry.

I am trying to convert client code to "pure" sql in Mysql 5.7 (If it makes any difference - a solution can also apply only to MariaDB 10.1, too).

I have a "codes" table and "code_translations" table. I am now trying to output the "best available translations" for each code in the code table.

If there is request for give me the translations for Code "A" and translations language "de" - try to find a translation with "de" first. If not found - use an available translation for "en" (default fallback). If there is not "en" - take the first translation available determined by the current ordering.

From the fiddle:

CREATE TABLE codes (id SERIAL, code TEXT, country TEXT);
CREATE TABLE code_translations (id SERIAL, code_id INT, language TEXT, label TEXT);

INSERT INTO codes (id, code, country) 
VALUES 
    (1, 'A', 'DE'),  
    (2, 'B', 'DE'),
    (3, 'C', 'DE');

INSERT INTO code_translations (code_id, language, label) 
VALUES
    (1, 'de', 'A-de'),
    (1, 'en', 'A-en'),
    (1, 'fr', 'A-fr'),
    (2, 'de', 'B-de'),
    (2, 'en', 'B-en'),
    (3, 'nl', 'C-nl'),
    (3, 'ru', 'C-ru');

Basic Select - Yields just all rows:

SELECT c.code,ct.label
FROM codes c
JOIN code_translations ct ON c.id=ct.code_id
-- WHERE ct.language=de
-- WHERE ct.language=fr
-- WHERE ct.language=ru

Desired output:

-- WHERE language = de
-- output should be
-- |A|A-de| -- de is the direct match
-- |B|B-de| -- de is the direct match
-- |C|C-nl| -- first match determined by ordering

-- WHERE language = fr
-- output should be
-- |A|A-fr| -- fr is the direct match
-- |B|B-en| -- no "fr" - take "en"
-- |C|C-nl| -- first match determined by ordering

-- WHERE language = ru
-- output should be
-- |A|A-en| -- no "ru" - take "en"
-- |B|B-en| -- no "ru" - take "en"
-- |C|C-ru| -- ru is the direct match

Upvotes: 1

Views: 55

Answers (2)

Gordon Linoff
Gordon Linoff

Reputation: 1269753

You can use a correlated subquery to get the language for each code. You can get the desired language first, then English. However, if those are not available there is no "first" language -- because SQL tables represent unordered sets. You can get an arbitrary language using:

select c.*, ct.language, ct.label
from (select c.*,
             (select ct.language
              from code_translations ct
              where ct.code_id = c.id
              order by ct.language = 'de' desc,
                       ct.language = 'en' desc
              limit 1
             ) as language
      from codes c
     ) c left join
     code_translations ct
     on ct.code_id = c.id and ct.language = c.language;

If you have an ordering to define the "first" language, you can include that as a third key in the order by.

Here is a db<>fiddle.

Upvotes: 1

forpas
forpas

Reputation: 164089

Use a correlated subquery that sorts the rows of code_translations for each code in such a way that the top row is either the queried language (if it exists), or the default fallback language (if it exists), or some other language.
This sorting can be done with the function FIELD().
Finally use LIMIT 1 to get only the top row:

SELECT c.code,
       (
         SELECT ct.label
         FROM code_translations ct
         WHERE ct.code_id = c.id
         ORDER BY FIELD(ct.language, ?2, ?1) DESC
         LIMIT 1
       ) label
FROM codes c

Replace ?1 with the queried language and ?2 with the fallback language.

See the demo.

Upvotes: 1

Related Questions