Многоязычный контент MySQL: как выбрать тот или иной язык, если указанный не доступен? - PullRequest
3 голосов
/ 07 мая 2011

Я занимаюсь разработкой многоязычного веб-сайта PHP и хотел бы получать контент на данном языке, если он доступен, и на другом, если нет. Я постараюсь объяснить мою проблему ниже. Если что-то не понятно, пожалуйста, дайте мне знать.

Мои таблицы:

  • content: content_id, url, date
  • content_l10n: content_id, l10n_id, title, description
  • l10n: l10n_id, name, order

Первый случай:

  • Мой посетитель говорит по-французски.
  • Содержимое, которое я хочу отобразить, доступно на английском и французском языках.
  • На сайте должен отображаться французский контент.

→ Легко реализовать с помощью JOIN.

Второй случай:

  • Мой посетитель говорит по-французски.
  • Содержимое, которое я хочу отобразить, доступно только на английском языке .
  • На сайте должен отображаться английский контент.

→ Как это реализовать? Возможно ли это в одном запросе?

Некоторые заметки:

  • Это должно быть расширено более чем на два языка.
  • Решение должно работать с обоими случаями, поскольку я не знаю, доступен ли контент на нужном языке до выполнения запроса.
  • Чем быстрее, тем лучше.
  • Таблицы могут быть изменены при необходимости.
  • Я хотел бы выбрать несколько content строк (для списка названий, например).
  • Иногда title переводится, но description - это NULL. В идеале запрос должен был бы выбрать заголовок на данном языке, но использовать для описания другой язык.
  • Было бы замечательно установить порядок возврата (сначала: данный язык, затем: языки с l10n, упорядоченные по order ASC).

Ваша помощь будет по достоинству оценена! Заранее спасибо!

С уважением,

оливье

Ответы [ 2 ]

4 голосов
/ 07 мая 2011

Это решение работает для многих строк, но вам нужно 1 LEFT JOIN на язык, а порядок JOIns определяет приоритет.

SELECT   c.url, c.date, 
         COALESCE( c1.title, c2.title ),
         COALESCE( c1.description, c2.description )
FROM      content c
LEFT JOIN content_l10n c1 ON (c1.content_id = c.content_id AND c1.l10n_id=$1)
LEFT JOIN content_l10n c2 ON (c2.content_id = c.content_id AND c2.l10n_id=$2)

(Примечание: здесь я предполагаю, что $ 1 и $ 2 являются первыми 2 предпочтительными языками пользователя, и они кэшируются в сеансе, поэтому нет необходимости в дополнительных JOIN с l10n).

С вашей структурой таблицы это единственный значимый способ установить языковой порядок. Вам потребуется дополнительная таблица, чтобы указать языковые предпочтения каждого пользователя, а не хранить порядок в таблице l10n. Итак, предположим, у вас есть таблица

user_l10n( user_id, l10n_id, order )

И давайте предположим, что таблица l10n по умолчанию сохраняет свое поле "порядок".

Если вы сделаете это:

SELECT   ..., COALESCE(ul.order,l.order) AS order
FROM      
          content      c
JOIN      content_l10n cl USING (content_id)
JOIN      l10n         l  USING (l10n_id)                -- get default language order
LEFT JOIN user_l10n    ul ON    (ul.l10n_id=l.l10n_id    -- get user preferences if available
                                 AND ul.user_id=$user_id)
WHERE search condition on content, etc
ORDER BY content_id, COALESCE(ul.order,l.order)

Вы получите все документы, которые соответствуют, а также заданный пользователем (или по умолчанию) порядок, так что приложение может легко разобраться с этим.

Теперь идея состоит в том, чтобы избежать извлечения из базы данных всех строк на языках, которые «затенены» существующим переводом на язык, который предпочитает пользователь.

Естественным способом сделать это является GROUP BY, но MySQL не имеет агрегатной функции, которая бы работала здесь ...

Вы можете сделать зависимый подзапрос (для заголовка и описания); было бы неплохо захватить одну строку, но ужасно медленно, если вы хотите захватить много строк.

Но вы также можете сделать что-то еще! Это зависит от некоторого неясного поведения нестандартного предложения GROUP BY в MySQL ...

Сначала соберите список «content_id», которые вы хотите отобразить (результат поискового запроса с разбивкой по страницам, что угодно). Тогда вы можете сделать что-то вроде ужаса, который следует:

SELECT * FROM
(
    SELECT content_id, title FROM 
    (
        SELECT    c.content_id, c.title
        FROM      
                  content      c
        JOIN      content_l10n cl USING (content_id)
        JOIN      l10n         l  USING (l10n_id)
        LEFT JOIN user_l10n    ul ON    (ul.l10n_id=l.l10n_id AND ul.user_id=$user_id)
        WHERE cl.content_id IN ($list) AND c.title IS NOT NULL
        ORDER BY content_id, COALESCE(ul.order,l.order)
    ) d GROUP BY content_id
) t
JOIN
(
    SELECT content_id, description FROM 
    (
        SELECT    c.content_id, c.description
        FROM      
                  content      c
        JOIN      content_l10n cl USING (content_id)
        JOIN      l10n         l  USING (l10n_id)
        LEFT JOIN user_l10n    ul ON    (ul.l10n_id=l.l10n_id AND ul.user_id=$user_id)
        WHERE cl.content_id IN ($list) AND c.description IS NOT NULL
        ORDER BY content_id, COALESCE(ul.order,l.order)
    ) d GROUP BY content_id
)
USING (content_id)
1 голос
/ 07 мая 2011

это должно дать вам основу для второго случая:

SELECT   content.url, content.date, content_l10n.title, content_l10n.description
FROM     content, content_l10n, l10n
WHERE    content.content_id = content_l10n.content_id AND
         content_l10n.l10n_id = l10n.l10n_id AND
         content.content_id = {$contentId}
ORDER BY l10n.order ASC
LIMIT    1

Что касается производительности, вам нужен индекс в content.content_id, content_l10n.content_id, l10n.l10n_id и l10n.order.

...