Как упорядочить с помощью переменной, выбирающей порядок сортировки с переключением по регистру, в PostgreSQL

#postgresql #sql-order-by #case-when #postgresql-12 #collate

#postgresql #sql-order-by #case-когда #postgresql-12 #сопоставлять

Вопрос:

Эта простая функция возвращает упорядоченный список title строк.

 create or replace function testfunction1 ()
returns table (
  id        bigint,
  lang_code tlang_code,
  title     varchar
)
stable language sql as $$
  select 
    id, lang_code, title
  from 
    testable
  order by
    title collate "es_ES";
$$;

select * from testfunction ();

id|lang_code|title           |
--|---------|----------------|
12|DE       |NOCH FESTZULEGEN|
16|DE       |NOCH FESTZULEGEN|
 8|DE       |NOCH FESTZULEGEN|
14|ES       |POR DETERMINAR  |
 6|ES       |POR DETERMINAR  |
10|ES       |POR DETERMINAR  |
 5|EN       |TO BE DETERMINED|
 9|EN       |TO BE DETERMINED|
13|EN       |TO BE DETERMINED|
11|FR       |À DÉTERMINER    |
15|FR       |À DÉTERMINER    |
 7|FR       |À DÉTERMINER    |
  

Однако, когда я пытаюсь ввести порядок сортировки с помощью collate , я не могу правильно настроить синтаксис, чтобы установить правильный порядок сортировки на основе параметра _lang_code .

 create or replace function testfunction2 (_lang_code tlang_code)
returns table (
    id        bigint,
    lang_code tlang_code,
    title     varchar
)
stable language sql as $$
    select 
        id, lang_code, title
    from 
        testable
    where 
        lang_code = _lang_code
    order by
        title collate 
            case _lang_code
                when 'EN' then "en_US" 
                when 'ES' then "es_ES"
                when 'FR' then "fr_FR"
                when 'DE' then "de_DE"
            end asc;
$$;
  

Ошибка SQL Error [42601]: ERROR: syntax error at or near "case" .

Я безуспешно пытался разместить case везде в order by предложении. Может быть, «en_US» не считается скалярным значением?


РЕДАКТИРОВАТЬ, которое я добавил where lang_code = _lang_code после комментария Лоренца Альбе. Это было отсутствующее предложение при переходе от моей реальной проблемы к этому упрощенному примеру.

Однако проблема с case остается с той же ошибкой SQL.


РЕШЕНИЕ

Как указал @Lorenz Albe в комментарии, "en_US" является идентификатором, а не скалярным значением. Это не позволяет case-when структуре возвращать его в любой из своих when ветвей. Таким образом, способа SQL не существует.

В качестве обходного пути, динамический SQL от @doctore или перемещение регистра, чтобы охватить все предложение, являются неэлегантными, но функциональными решениями проблемы.

Комментарии:

1. Вы пытаетесь смешивать разные collate значения для одного и того же процесса упорядочения, что в основном является причиной того, что ваш «первый запрос» работает (у него есть только один), но не следующие попытки. Вам нужно изменить подход и использовать подходящий, collate учитывая lang_code значение, и «смешивать разные фрагменты» так, как вы хотите (по одному «фрагменту» для каждого языка).

2. Я не уверен, что понимаю ваш комментарий. Очевидно, что я не хочу смешивать порядки сортировки, я просто хочу выбрать тот или иной в зависимости от _lang_code параметра. Очевидно, что это функция, collate представленная в предложении Postgre12 (?). Не уверен, что вы подразумеваете под «фрагментами»

3. Я мог бы переключить все предложение select с самого начала, но потерять преимущество наличия функции SQL.

4. Подход Ахилеша Мишры приводит к «смешиванию» или установке в два раза большего порядка сортировки, но это всего лишь артефакт из-за синтаксической ошибки.

5. Хорошо, теперь я понимаю «весь поток». Именно по этой причине я включил ответ, чтобы позволить вам разобраться с вашим вариантом использования

Ответ №1:

Принимая во внимание, что вы используете параметр _lang_code для выбора «внутреннего языка» для фильтрации. Следующий код PL / SQL позволяет динамически изменять collate в конечном запросе:

 create or replace function testfunction2 (_lang_code varchar)
returns table (
                  id        bigint,
                  lang_code varchar,
                  title     varchar
              )
language plpgsql
as $$
declare
  final_collate varchar;
  final_query varchar;
begin
  if (_lang_code = 'EN') then
    final_collate := 'en_US';
  elsif (_lang_code = 'ES') then
    final_collate := 'es_ES';
  end if;
  -- Include other use cases you need

  final_query := 'select t.id, t.lang_code, t.title ' ||
                 'from test_table t ' ||
                 'where t.lang_code = ''' || _lang_code || ''' ' ||
                 'order by t.title collate "' || final_collate || '" asc';

  --raise exception 'Final query: %', final_query;

  return query
    execute final_query;
end;$$
  

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

 select testfunction2('EN')
select testfunction2('ES')
  

PD: Я изменил тип _lang_code и lang_code на varchar , потому что я предполагаю, tlang_code что это пользовательский.

Комментарии:

1. Спасибо доктору. Извините, я должен был заменить tlang_code на char(2) . На самом деле это домен типа char(2) с проверками. К сожалению, это решение является PL / PGSQL, эквивалентным выполнению переключения с самого начала.

2. Если нет способа использовать pl/sql , тогда мы находимся «в начале» наших дебатов, и, как сказал вам @LaurenzAlbe, вероятно, то, что вы пытаетесь сделать, невозможно.

3. Что ж, Лоренц описал мою логическую ошибку, которая была исправлена, но не приблизилась к исходной синтаксической ошибке в структуре case-when.

4. Вы не можете включить «что-либо» между предложениями order by и case , однако, как вы обнаружили в ответе @AkhileshMishra, если вы удалите свой title collate PostgreSQL, он уведомит вас о другой ошибке, что в основном означает, you cannot use more than one collate value in the order by clause for the same column

5. Я выбрал ваше решение в качестве ответа на вопрос, однако where lang_code = _lang_code необходимо добавить к final_query . Я попытался отредактировать, но очередь заполнена. Спасибо

Ответ №2:

Напишите свой случай для заказа, как показано ниже:

 create or replace function testfunction2 (_lang_code tlang_code)
returns table (
    id        bigint,
    lang_code tlang_code,
    title     varchar
)
stable language sql as $$
    select 
        id, lang_code, title
    from 
        testtable
    order by  
            case _lang_code
                when 'EN' then title collate "en_US" 
                when 'ES' then title collate "es_ES"
                when 'FR' then title collate "fr_FR"
                when 'DE' then title collate "de_DE"
            end asc;
$$;
  

Комментарии:

1. Не работает. Как я уже сказал, я протестировал (почти) все 🙂 Ошибка теперь такова SQL Error [42P21]: ERROR: collation mismatch between explicit collations "en_US" and "es_ES"

2. Это другая проблема. Приведенное выше решение предназначено только для синтаксической ошибки, о которой вы упомянули в вопросе.

3. Это та же проблема. Анализатор ожидает скаляр после when , а не часть предложения

4. Смотрите postgresql.org/docs/12/… «Типы данных всех результирующих выражений должны быть преобразованы в один тип вывода».

5. Я даже пытался заключить title collate "en_US" в круглые скобки

Ответ №3:

Решение @doctore запускает функцию PL / PGSQL, поэтому это другой подход к перемещению случая, когда для охвата всего предложения select. Они оба далеки от элегантности, но доказывают, что вопрос имел смысл.

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

 create or replace function testfunction3 (_lang_code char(2))
returns table (
  id        bigint,
  lang_code char(2),
  title     varchar
)
stable language plpgsql as $$
begin
  case _lang_code
    when 'EN' then
      return query
      select *
      from testtable t 
      where t.lang_code = _lang_code  
      order by t.title collate "en_US";
     
    when 'ES' then
      return query
      select *
      from testtable t 
      where t.lang_code = _lang_code  
      order by t.title collate "es_ES";

    when 'FR' then
      return query
      select *
      from testtable t 
      where t.lang_code = _lang_code  
      order by t.title collate "fr_FR";

    when 'DE' then
      return query
      select *
      from testtable t 
      where t.lang_code = _lang_code  
      order by t.title collate "de_DE";
     
   end case;
end
$$;