Что эквивалентно OTRANSLATE в BigQuery? - PullRequest
1 голос
/ 08 марта 2019

Я пытаюсь перевести запрос для запуска в BigQuery, который использует функцию OTRANSLATE из Teradata.Например,

SELECT OTRANSLATE(text, 'ehlo', 'EHLO')
FROM (
  SELECT 'hello world' AS text UNION ALL
  SELECT 'elliott'
);

Это должно привести к:

HELLO wOrLd
ELLiOtt

Есть ли способ выразить эту функцию в BigQuery?Не похоже, что есть прямой эквивалент.

Ответы [ 2 ]

2 голосов
/ 08 марта 2019

Другой, немного другой подход (BigQuery Standard SQL)

#standardSQL
CREATE TEMP FUNCTION OTRANSLATE(text STRING, from_string STRING, to_string STRING) AS ((
  SELECT STRING_AGG(IFNULL(y, a), '' ORDER BY pos)
  FROM UNNEST(SPLIT(text, '')) a WITH OFFSET pos
  LEFT JOIN (
    SELECT x, y
    FROM UNNEST(SPLIT(from_string, '')) x WITH OFFSET
    JOIN UNNEST(SPLIT(to_string, '')) y WITH OFFSET
    USING(OFFSET)
  )
  ON a = x
));
WITH `project.dataset.table` AS (
  SELECT 'hello world' AS text UNION ALL
  SELECT 'elliott'
)
SELECT text, OTRANSLATE(text, 'ehlo', 'EHLO') as new_text
FROM `project.dataset.table`   

с выходом

Row     text            new_text     
1       hello world     HELLO wOrLd  
2       elliott         ELLiOtt   

Примечание: вышеприведенная версия предполагает, что строки от и до равны по длине и без повторяющихся символов в строке

Обновление для отслеживания "расширенных ожиданий" для версии этой функции в BigQuery

#standardSQL
CREATE TEMP FUNCTION OTRANSLATE(text STRING, from_string STRING, to_string STRING) AS ((
  SELECT STRING_AGG(IFNULL(y, a), '' ORDER BY pos)
  FROM UNNEST(SPLIT(text, '')) a WITH OFFSET pos
  LEFT JOIN (
    SELECT x, ARRAY_AGG(IFNULL(y, '') ORDER BY OFFSET LIMIT 1)[OFFSET(0)] y
    FROM UNNEST(SPLIT(from_string, '')) x WITH OFFSET
    LEFT JOIN UNNEST(SPLIT(to_string, '')) y WITH OFFSET
    USING(OFFSET)
    GROUP BY x
  )
  ON a = x
));
SELECT -- text, OTRANSLATE(text, 'ehlo', 'EHLO') as new_text
  OTRANSLATE("hello world", "", "EHLO") AS empty_from, -- 'hello world'
  OTRANSLATE("hello world", "hello world1", "EHLO") AS larger_from_than_source, -- 'EHLLL'
  OTRANSLATE("hello world", "ehlo", "EHLO") AS equal_size_from_to, -- 'HELLO wOrLd'
  OTRANSLATE("hello world", "ehlo", "EH") AS larger_size_from, -- 'HE wrd'
  OTRANSLATE("hello world", "ehlo", "EHLOPQ") AS larger_size_to, -- 'hello world'
  OTRANSLATE("hello world", "ehlo", "") AS empty_to; -- 'wrd'

с результатом

Row empty_from  larger_from_than_source equal_size_from_to  larger_size_from    larger_size_to  empty_to     
1   hello world EHLLL                   HELLO wOrLd             HE wrd          HELLO wOrLd     wrd    
.   

Примечание: версия этой функции Teradata является рекурсивной, поэтому текущая реализация не является точной реализацией OTRANSLATE Teradata

Замечания по использованию (из документации teradata)
Если первый символ в from_string встречается в source_string, все его вхождения заменяются первым символом в to_string. Это повторяется для всех символов в from_string и для всех символов в from_string. Замена выполняется посимвольно, то есть замена второго символа выполняется в строке, полученной в результате замены первого символа.

Это может быть легко реализовано с помощью JS UDF, что тривиально, я думаю, что я не иду в этом направлении: o)

1 голос
/ 08 марта 2019

Да, вы можете сделать это, используя операции над массивами над строками.Вот одно из решений:

CREATE TEMP FUNCTION OTRANSLATE(s STRING, key STRING, value STRING) AS (
  (SELECT
     STRING_AGG(
       IFNULL(
         (SELECT value[OFFSET(
            SELECT o FROM UNNEST(SPLIT(key, '')) AS k WITH OFFSET o2
            WHERE k = c)]
         ),
         c),
       '' ORDER BY o1)
   FROM UNNEST(SPLIT(s, '')) AS c WITH OFFSET o1)
  )
);

SELECT OTRANSLATE(text, 'ehlo', 'EHLO')
FROM (
  SELECT 'hello world' AS text UNION ALL
  SELECT 'elliott'
);

Идея состоит в том, чтобы найти символ в той же позиции строки key в строке value.Если в строке key нет совпадающего символа, мы получим нулевое смещение, поэтому второй аргумент IFNULL заставляет его вернуть не отображенный символ.Затем мы агрегируем обратно в строку, упорядоченную по смещению символа.

Редактировать: вот вариант, который также обрабатывает различия в длине ключа и значения:

CREATE TEMP FUNCTION otranslate(s STRING, key STRING, value STRING) AS (
  IF(LENGTH(key) < LENGTH(value) OR LENGTH(s) < LENGTH(key), s,
  (SELECT
     STRING_AGG(
       IFNULL(
         (SELECT ARRAY_CONCAT([c], SPLIT(value, ''))[SAFE_OFFSET((
            SELECT IFNULL(MIN(o2) + 1, 0) FROM UNNEST(SPLIT(key, '')) AS k WITH OFFSET o2
            WHERE k = c))]
         ),
         ''),
       '' ORDER BY o1)
   FROM UNNEST(SPLIT(s, '')) AS c WITH OFFSET o1
  ))
);
SELECT
  otranslate("hello world", "", "EHLO") AS empty_from, -- 'hello world'
  otranslate("hello world", "hello world1", "EHLO") AS larger_from_than_source, -- 'hello world'
  otranslate("hello world", "ehlo", "EHLO") AS equal_size_from_to, -- 'HELLO wOrLd'
  otranslate("hello world", "ehlo", "EH") AS larger_size_from, -- 'HE wrd'
  otranslate("hello world", "ehlo", "EHLOPQ") AS larger_size_to, -- 'hello world'
  otranslate("hello world", "ehlo", "") AS empty_to; -- 'wrd'
...