путаница с функцией postgresql - PullRequest
3 голосов
/ 13 марта 2012

если я напишу запрос так:

with WordBreakDown (idx, word, wordlength) as (
    select 
        row_number() over () as idx,
        word,
        character_length(word) as wordlength
    from
    unnest(string_to_array('yo momma so fat', ' ')) as word
)
select 
    cast(wbd.idx + (
        select SUM(wbd2.wordlength)
        from WordBreakDown wbd2
        where wbd2.idx <= wbd.idx
        ) - wbd.wordlength as integer) as position,
    cast(wbd.word as character varying(512)) as part
from
    WordBreakDown wbd;  

... Я получаю таблицу из 4 строк примерно так:

1;"yo"
4;"momma"
10;"so"
13;"fat"

... это то, что я хочу. ОДНАКО , если я оберну это в такую ​​функцию:

drop type if exists split_result cascade;
create type split_result as(
    position integer,
    part character varying(512)
);

drop function if exists split(character varying(512), character(1));    
create function split(
    _s character varying(512), 
    _sep character(1)
    ) returns setof split_result as $$
begin

    return query
    with WordBreakDown (idx, word, wordlength) as (
        select 
            row_number() over () as idx,
            word,
            character_length(word) as wordlength
        from
        unnest(string_to_array(_s, _sep)) as word
    )
    select 
        cast(wbd.idx + (
            select SUM(wbd2.wordlength)
            from WordBreakDown wbd2
            where wbd2.idx <= wbd.idx
            ) - wbd.wordlength as integer) as position,
        cast(wbd.word as character varying(512)) as part
    from
        WordBreakDown wbd;  

end;
$$ language plpgsql;

select * from split('yo momma so fat', ' ');

... Я получаю:

1;"yo momma so fat"

Я почесал голову от этого. Что я облажался?

UPDATE В соответствии с приведенными ниже предложениями я заменил функцию следующим образом:

CREATE OR REPLACE FUNCTION split(_string character varying(512), _sep character(1))
  RETURNS TABLE (postition int, part character varying(512)) AS
$BODY$
BEGIN
    RETURN QUERY
    WITH wbd AS (
        SELECT (row_number() OVER ())::int AS idx
              ,word
              ,length(word) AS wordlength
        FROM   unnest(string_to_array(_string, rpad(_sep, 1))) AS word
        )
    SELECT (sum(wordlength) OVER (ORDER BY idx))::int + idx - wordlength
          ,word::character varying(512) -- AS part
    FROM wbd;  
END;
$BODY$ LANGUAGE plpgsql;

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

Ответы [ 3 ]

1 голос
/ 14 марта 2012

У вас было несколько конструкций, которые, вероятно, не делали то, что вы думаете.

Вот в значительной степени упрощенная версия вашей функции, которая также немного быстрее:

CREATE OR REPLACE FUNCTION split(_string text, _sep text)
  RETURNS TABLE (postition int, part text) AS
$BODY$
BEGIN
    RETURN QUERY
    WITH wbd AS (
        SELECT (row_number() OVER ())::int AS idx
              ,word
              ,length(word) AS wordlength
        FROM   unnest(string_to_array(_string, _sep)) AS word
        )
    SELECT (sum(wordlength) OVER (ORDER BY idx))::int + idx - wordlength
          ,word -- AS part
    FROM wbd;  
END;
$BODY$ LANGUAGE plpgsql;

Объяснение

  • Используйте другую оконную функцию для суммирования длин слов.Быстрее, проще и чище.Это обеспечивает большую часть прироста производительности.Многие подзапросы замедляют работу.

  • Используйте тип данных text вместо character varying или даже character().character varying и character - ужасные типы, в основном только для совместимости со стандартом SQL и историческими причинами.Вряд ли можно что-либо сделать с теми, что нельзя было бы сделать лучше с text.Тем временем @Tometzky объяснил, почему character(1) был особенно плохим выбором для типа параметра.Я исправил это, используя text вместо.

  • Как продемонстрировал @Tometzky, unnest(string_to_array(..)) быстрее, чем regexp_split_to_table(..) - даже если чуть-чуть для маленьких строк, которые мы здесь используем(макс. 512 символов).Поэтому я вернулся к исходному выражению.

  • length() делает то же самое, что и character_length().

  • В запросе только с однимисточник таблицы (и никаких других возможных конфликтов имен), вы можете также не указывать имена столбцов с указанием таблицы.Упрощает код.

  • Нам нужно целочисленное значение в конце, поэтому я приведу все числовые значения (bigint в этом случае) к целому числу сразу, поэтому сложения и вычитания выполняютсяс целочисленной арифметикой, которая обычно самая быстрая.
    'value'::int - это просто более короткий синтаксис для cast('value' as integer) и в остальном эквивалентный.

1 голос
/ 14 марта 2012

Обратите внимание:

select length(' '::character(1));
 length
--------
      0
(1 row)

Причиной этой путаницы является странное определение типа character в стандарте SQL. От Документация Postgres для типов символов :

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

Так что вы должны использовать string_to_array(_s, rpad(_sep,1)).

0 голосов
/ 14 марта 2012

Я нашел ответ, но не понимаю его.

Функция string_to_array(_s, _sep) не разделяется с неизменяющимся символом;даже если бы я написал это так, это не сработало бы:

string_to_array(_s, cast(_sep as character_varying(1)))

НО , если я переопределил параметры следующим образом:

drop function if exists split(character varying(512), character(1));    
create function split(
    _s character varying(512), 
    _sep character varying(1)

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...