Перевести запрос кросс-таблицы postgres на TSQL - PullRequest
0 голосов
/ 03 марта 2019

У меня есть следующее, чего я хочу достичь:

БД, в которой хранятся переводы на разные языки.С помощью одного запроса получите все строки на желаемом языке, и если этот перевод не существует, используйте второй лучший язык в качестве запасного и т. Д. Для каждого языка запасные варианты могут отличаться (например, FR-DE-EN против DE-EN-FR),

Я реализовал это с помощью pgsql с помощью перекрестного запроса и хотел перевести это на SQL Server, но застрял там.Я думаю, что PIVOT будет языковой функцией для достижения желаемого результата, но я еще не выяснил, как правильно его использовать.

Определения MWE и тестовые данные:

-- load tablefunc extension for crosstab
drop extension if exists tablefunc;
create extension tablefunc;
-- crosstab only allows single column - define int and varchar tuples for this purpose
DROP TYPE IF EXISTS intT; CREATE TYPE intT AS  (module int, id int );
DROP TYPE IF EXISTS strT; CREATE TYPE strT AS (lang varchar, txt varchar);

drop table if exists texts;
drop table if exists langs;

create table texts
(   module int not null
,   id int not null
,   lang varchar not null
,   txt varchar not null);

create table langs -- for each language (first) store up to 3 languages (lang) and their priority (lower = would be used first)
(   first varchar not null
,   lang varchar not null
,   priority int not null);

insert into texts (module, id, lang, txt) values
    (0,0,'def','HelloDEF'),
    (0,1,'def','WorldDEF'),
    (0,0,'en','Hello'),
    (0,1,'en','World'),
    (0,0,'de','Hallo'),
    (0,1,'de','Welt'),
    (0,0,'jp','Konnichiwa'),
    (0,1,'fr','Monde'),
    (1,0,'def','Switzerland'),
    (1,0,'de','Schweiz'),
    (1,0,'fr','Suisse'),
    (1,0,'jp','Suisu');

insert into langs (first, lang, priority) values
    ('jp','jp',0),
    ('jp','en',1),
    ('jp','def',2),
    ('en','en',0),
    ('en','def',1),
    ('en','def',2),
    ('de','de',0),
    ('de','en',1),
    ('de','def',2),
    ('fr','fr',0),
    ('fr','de',1),
    ('fr','def',2);

Запрос (pgsql):

select (mod_id).*, (coalesce(a,b,c)).* -- unpack tuple types here to get nice table
from crosstab($$
    select (module,id) as mod_id, priority, (lang,txt) as lang_txt
    from texts
    join langs using (lang)
    where first = 'fr' --! language goes here
    and module = 0 --! module integer goes here
    order by id, priority asc
$$,$$
    select generate_series(0,2) -- always return 0,1,2 here.
$$) as ct (mod_id intT, a strT, b strT, c strT);

Вывод:

 module | id | lang |  txt
--------+----+------+-------
      0 |  0 | de   | Hallo
      0 |  1 | fr   | Monde

1 Ответ

0 голосов
/ 04 марта 2019

Насколько я понял вопрос, этот результат может быть достигнут с помощью стандартного SQL без необходимости поворота данных.Простого ROW_NUMBER() должно быть достаточно.Приведенный ниже запрос будет работать в SQL Server и Postgres.

Пример данных

create table #texts
(   module int not null
,   id int not null
,   lang varchar(50) not null
,   txt varchar(50) not null);

create table #langs -- for each language (first) store up to 3 languages (lang) and their priority (lower = would be used first)
(   first varchar(50) not null
,   lang varchar(50) not null
,   priority int not null);

insert into #texts (module, id, lang, txt) values
    (0,0,'def','HelloDEF'),
    (0,1,'def','WorldDEF'),
    (0,0,'en','Hello'),
    (0,1,'en','World'),
    (0,0,'de','Hallo'),
    (0,1,'de','Welt'),
    (0,0,'jp','Konnichiwa'),
    (0,1,'fr','Monde'),
    (1,0,'def','Switzerland'),
    (1,0,'de','Schweiz'),
    (1,0,'fr','Suisse'),
    (1,0,'jp','Suisu');

insert into #langs (first, lang, priority) values
    ('jp','jp',0),
    ('jp','en',1),
    ('jp','def',2),
    ('en','en',0),
    ('en','def',1),
    ('en','def',2),
    ('de','de',0),
    ('de','en',1),
    ('de','def',2),
    ('fr','fr',0),
    ('fr','de',1),
    ('fr','def',2);

Запрос

Я принялВаш внутренний запрос и добавил ROW_NUMBER там.Ясно, что нам просто нужно выбрать только строку с самым высоким priority для каждого id (поэтому в определении ROW_NUMBER есть PARTITION BY id и ORDER BY priority).Если вам нужны результаты для нескольких module с одновременно, а не только для одного конкретного модуля, то добавьте module к PARTITION BY предложению.

SELECT
    #texts.module
    ,#texts.id
    ,#langs.priority
    ,#langs.lang
    ,#texts.txt
    ,ROW_NUMBER() OVER (PARTITION BY #texts.id ORDER BY #langs.priority) AS rn
FROM
    #texts
    INNER JOIN #langs ON #langs.lang = #texts.lang
WHERE
    #langs.first = 'fr' --! language goes here
    AND #texts.module = 0 --! module integer goes here
ORDER BY
    #texts.id, #langs.priority asc
;

Result

+--------+----+----------+------+----------+----+
| module | id | priority | lang |   txt    | rn |
+--------+----+----------+------+----------+----+
|      0 |  0 |        1 | de   | Hallo    |  1 |
|      0 |  0 |        2 | def  | HelloDEF |  2 |
|      0 |  1 |        0 | fr   | Monde    |  1 |
|      0 |  1 |        1 | de   | Welt     |  2 |
|      0 |  1 |        2 | def  | WorldDEF |  3 |
+--------+----+----------+------+----------+----+

Окончательный запрос

WITH
CTE
AS
(
    SELECT
        #texts.module
        ,#texts.id
        ,#langs.priority
        ,#langs.lang
        ,#texts.txt
        ,ROW_NUMBER() OVER (PARTITION BY #texts.id ORDER BY #langs.priority) AS rn
    FROM
        #texts
        INNER JOIN #langs ON #langs.lang = #texts.lang
    WHERE
        #langs.first = 'fr' --! language goes here
        AND #texts.module = 0 --! module integer goes here
)
SELECT
    module
    ,id
    ,lang
    ,txt
FROM CTE
WHERE rn = 1
ORDER BY
    id
;

Результат

+--------+----+------+-------+
| module | id | lang |  txt  |
+--------+----+------+-------+
|      0 |  0 | de   | Hallo |
|      0 |  1 | fr   | Monde |
+--------+----+------+-------+

Очистить

drop table #texts;
drop table #langs;
...