Как разделить строку в BigQuery на несколько столбцов, не разбивая отдельные слова? - PullRequest
0 голосов
/ 06 августа 2020

Я пытаюсь разделить строку на два столбца, но только если общая длина строки превышает 25 символов. Если он короче 25 символов, мне нужен только второй столбец. Если он длиннее 25, я хочу, чтобы первая часть строки находилась в 1-м столбце, а вторая часть строки - во 2-м столбце. Вот кикер ... Я не хочу, чтобы слова прерывались. Итак, если общая длина строки равна 26, я знаю, что мне понадобятся два столбца, но мне нужно выяснить, где соединить строку, чтобы в каждом столбце были представлены только полные слова.

Например, строка - «Менеджер транспортного проекта». Поскольку в нем более 25 символов, я хочу, чтобы в первом столбце было написано «Транспортный проект», а во втором столбце - «Менеджер». «Транспортный проект» содержит менее 25 символов, но я хочу остановиться на этом, поскольку нет другого полного слова, которое могло бы уместиться в пределах 25 символов.

Другой пример - это строка «Социальный работник I». Поскольку он меньше 25 символов, я хочу, чтобы вся строка была представлена ​​в столбце 2.

Спасибо за ваше время!

Ответы [ 4 ]

0 голосов
/ 06 августа 2020

Ниже для BigQuery Standard SQL

#standardSQL
SELECT phrase, 
  IF(IFNULL(cut, len ) >= len, NULL, SUBSTR(phrase, 1, cut)) col1, 
  IF(IFNULL(cut, len ) >= len, phrase, SUBSTR(phrase, cut + 1)) col2
FROM (
  SELECT phrase, LENGTH(phrase) len,
    (
      SELECT cut FROM (
        SELECT -1 + SUM(LENGTH(word) + 1) OVER(ORDER BY OFFSET) AS cut
        FROM UNNEST(SPLIT(phrase, ' ')) word WITH OFFSET
      )
      WHERE cut <= 25
      ORDER BY cut DESC
      LIMIT 1
    ) cut
  FROM `project.dataset.table`  
)

Вы можете протестировать, поиграть с приведенными выше примерами данных (которые хорошо представлены в других ответах), как в примере ниже

#standardSQL
WITH `project.dataset.table` AS (
  SELECT 'Transportation Project Manager' AS phrase UNION ALL
  SELECT 'Caseworker I' UNION ALL
  SELECT "This's 25 characters long" UNION ALL
  SELECT "This's 25 characters long (not!)" UNION ALL
  SELECT 'Antidisestablishmentarianist' UNION ALL
  SELECT 'Trying to split a string with more than 25 characters in length' UNION ALL
  SELECT 'Trying to split'
)
SELECT phrase, 
  IF(IFNULL(cut, len ) >= len, NULL, SUBSTR(phrase, 1, cut)) col1, 
  IF(IFNULL(cut, len ) >= len, phrase, SUBSTR(phrase, cut + 1)) col2
FROM (
  SELECT phrase, LENGTH(phrase) len,
    (
      SELECT cut FROM (
        SELECT -1 + SUM(LENGTH(word) + 1) OVER(ORDER BY OFFSET) AS cut
        FROM UNNEST(SPLIT(phrase, ' ')) word WITH OFFSET
      )
      WHERE cut <= 25
      ORDER BY cut DESC
      LIMIT 1
    ) cut
  FROM `project.dataset.table`  
)   

с выводом

Row phrase                                                          col1                        col2     
1   Transportation Project Manager                                  Transportation Project      Manager  
2   Caseworker I                                                    null                        Caseworker I     
3   This's 25 characters long                                       null                        This's 25 characters long    
4   This's 25 characters long (not!)                                This's 25 characters long   (not!)   
5   Antidisestablishmentarianist                                    null                        Antidisestablishmentarianist     
6   Trying to split a string with more than 25 characters in length Trying to split a string    with more than 25 characters in length   
7   Trying to split                                                 null                        Trying to split     

Примечание: если вы хотите избавиться от начальных (в col2) и конечных (в col1) пробелов - вы можете просто добавить TRIM () для обработки этого небольшого дополнительного logi c

0 голосов
/ 06 августа 2020

Чтобы разделить строку на 2 столбца с соблюдением определенной максимальной длины (после logi c, описанного вами), мы будем использовать JavaScript Пользовательскую функцию в BigQuery (UDF) вместе с встроенная функция LENGTH .

Сначала будет проанализирована строка. Если символ после максимального порога является пробелом, он будет разделен на заданную максимальную длину строки. Однако, если это не так, каждый символ будет проверяться, считая в обратном порядке, пока не будет обнаружен пробел и строка не будет разделена. Эта процедура позволяет избежать функции разбиения слова, и оно всегда будет разделяться с соблюдением максимально допустимой длины.

Ниже приведен запрос с некоторыми образцами данных,

CREATE TEMP FUNCTION split_str_1(s string,len int64)
RETURNS string
LANGUAGE js AS """
var len_aux = len, prev = 0;

//first part of the string within the threshold 
output = [];

//the rest of the string wihtout the first part
output2 = [];

//if the next character in the string is a whitespace, them split the string  
if(s[len_aux++] == ' ') {

    output.push(s.substring(prev,len_aux));
    output2.push(s.substring(prev,s.length));

}
else{
      do {
          if(s.substring(len_aux - 1, len_aux) == ' ')
            {
                 output.push(s.substring(prev,len_aux));
                 prev = len_aux;
                 output2.push(s.substring(prev,s.length));  
                 break;
           }len_aux--;
       } while(len_aux > prev)     
      }
      
//outputting the first part of the string      
return output[0];
""";

CREATE TEMP FUNCTION split_str_2(s string,len int64)
RETURNS string
LANGUAGE js AS """
var len_aux = len, prev = 0;

//first part of the string within the threshold 
output = [];

//the rest of the string wihtout the first part
output2 = [];

//if the next character in the string is a whitespace, them split the string  
if(s[len_aux++] == ' ') {

    output.push(s.substring(prev,len_aux));
    output2.push(s.substring(prev,s.length));

}
else{
      do {
          if(s.substring(len_aux - 1, len_aux) == ' ')
            {
                 output.push(s.substring(prev,len_aux));
                 prev = len_aux;
                 output2.push(s.substring(prev,s.length));  
                 break;
           }len_aux--;
       } while(len_aux > prev)     
      }
      
//outputting the first part of the string      
return output2[0];
""";

WITH data AS (
SELECT "Trying to split a string with more than 25 characters length" AS str UNION ALL
SELECT "Trying to split"  AS str
)
SELECT  str,
        IF(LENGTH(str)>25, split_str_1(str,25), null) as column_1,
        CASE WHEN LENGTH(str)>25 THEN split_str_2(str,25) ELSE str END AS column_2
FROM data

И выход,

введите описание изображения здесь

Обратите внимание, что существует 2 JavaScript UDF, потому что первый возвращает первую часть строки, а второй возвращает вторую часть, если строка длиннее 25 символов. Кроме того, в качестве аргумента передается максимально допустимая длина, но ее можно статически определить в UDF как len=25.

0 голосов
/ 06 августа 2020

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

Использование других представленных фраз в качестве примеров данных:

 with sample_data as(
  select 'Transportation Project Manager' as phrase union all
  select 'Caseworker I'as phrase union all
  select "This's 25 characters long" as phrase union all
  select "This's 25 characters long (not!)" as phrase union all
  select 'Antidisestablishmentarianist' as phrase union all
  select 'Trying to split a string with more than 25 characters in length' as phrase union all
  select 'Trying to split' as phrase
),
temp as (
  select 
    phrase,
    length(phrase) as phrase_len,
    -- Find the first space before the 25th character
    -- by reversing the first 25 characters
    25-strpos(reverse(substr(phrase,1,25)),' ') as first_space_before_25 
  from sample_data
)
select 
  phrase,
  phrase_len,
  first_space_before_25,
  case when phrase_len <= 25 or first_space_before_25 = 25 then null
       when phrase_len > 25 then substr(phrase,1,first_space_before_25)
       else null
  end as col1,
  case when phrase_len <= 25 or first_space_before_25 = 25 then phrase
       when phrase_len > 25 then substr(phrase,first_space_before_25+1, phrase_len)
       else null
  end as col2
from temp

Полученные результаты

Я думаю, что это довольно близко к вам, используя базовые c sql манипуляции со строками. Вам может потребоваться / вы захотите немного очистить это в зависимости от того, хотите ли вы, чтобы col2 начинался с пробела или был обрезан, и в зависимости от вашей точки отсечки (вы упомянули меньше 25 и больше 25, но не совсем 25) .

0 голосов
/ 06 августа 2020

Вау, это отличный вопрос для интервью! Вот что я придумал:

    WITH sample_data
      AS (
  SELECT 'Transportation Project Manager' AS phrase
   UNION ALL
  SELECT 'Caseworker I' AS phrase
   UNION ALL
  SELECT "This's 25 characters long" AS phrase
   UNION ALL
  SELECT "This's 25 characters long (not!)" AS phrase
   UNION ALL
  SELECT 'Antidisestablishmentarianist' AS phrase
         ),
         unnested_words --Make a dataset with one row per "word" per phrase
      AS (
  SELECT *,
         --To preserve the spaces for character counts, prepend one to every word but the first
         CASE WHEN i = 0 THEN '' ELSE ' ' END || word AS word_with_space
    FROM sample_data
   CROSS
    JOIN UNNEST(SPLIT(phrase, ' ')) AS word WITH OFFSET AS i
         ),
         with_word_length
      AS (
  SELECT *,
         --This doesn't need its own CTE, but done here for clarity
         LENGTH(word_with_space) AS word_length
    FROM unnested_words
         ),
         running_sum --Mark when the total character length exceeds 25
      AS (
  SELECT *,
         SUM(word_length) OVER (PARTITION BY phrase ORDER BY i) <= 25 AS is_first_25
    FROM with_word_length
         ),
         by_subphrase --Make a subphrase of words in the first 25, and one for any others
      AS (
  SELECT phrase,
         ARRAY_TO_STRING(ARRAY_AGG(word), '') AS subphrase
    FROM running_sum
GROUP BY phrase, is_first_25
         ),
         by_phrase --Put subphrases into an array (back to one row per phrase)
      AS (
  SELECT phrase, ARRAY_AGG(subphrase) AS subphrases FROM by_subphrase GROUP BY 1
         )
  SELECT phrase,
         --Break the array of subphrases into colummns per your rules
         CASE WHEN ARRAY_LENGTH(subphrases) = 1 THEN subphrases[OFFSET(0)] ELSE subphrases[OFFSET(1)] END, 
         CASE WHEN ARRAY_LENGTH(subphrases) = 1 THEN NULL ELSE subphrases[OFFSET(0)] END
    FROM by_phrase

Не очень красиво, но получается.

...