Группировка или Окно


13

У меня есть ситуация, я думаю, может быть решена с помощью оконной функции, но я не уверен.

Представьте себе следующую таблицу

CREATE TABLE tmp
  ( date timestamp,        
    id_type integer
  ) ;

INSERT INTO tmp 
    ( date, id_type )
VALUES
    ( '2017-01-10 07:19:21.0', 3 ),
    ( '2017-01-10 07:19:22.0', 3 ),
    ( '2017-01-10 07:19:23.1', 3 ),
    ( '2017-01-10 07:19:24.1', 3 ),
    ( '2017-01-10 07:19:25.0', 3 ),
    ( '2017-01-10 07:19:26.0', 5 ),
    ( '2017-01-10 07:19:27.1', 3 ),
    ( '2017-01-10 07:19:28.0', 5 ),
    ( '2017-01-10 07:19:29.0', 5 ),
    ( '2017-01-10 07:19:30.1', 3 ),
    ( '2017-01-10 07:19:31.0', 5 ),
    ( '2017-01-10 07:19:32.0', 3 ),
    ( '2017-01-10 07:19:33.1', 5 ),
    ( '2017-01-10 07:19:35.0', 5 ),
    ( '2017-01-10 07:19:36.1', 5 ),
    ( '2017-01-10 07:19:37.1', 5 )
  ;

Я хотел бы иметь новую группу при каждом изменении столбца id_type. Например, первая группа с 7:19:21 до 7:19:25, вторая начинается и заканчивается в 7:19:26 и так далее.
После того, как это сработает, я хочу включить больше критериев для определения групп.

На данный момент, используя запрос ниже ...

SELECT distinct 
    min(min(date)) over w as begin, 
    max(max(date)) over w as end,   
    id_type
from tmp
GROUP BY id_type
WINDOW w as (PARTITION BY id_type)
order by  begin;

Я получаю следующий результат:

begin                   end                     id_type
2017-01-10 07:19:21.0   2017-01-10 07:19:32.0   3
2017-01-10 07:19:26.0   2017-01-10 07:19:37.1   5

Пока я бы хотел:

begin                   end                     id_type
2017-01-10 07:19:21.0   2017-01-10 07:19:25.0   3
2017-01-10 07:19:26.0   2017-01-10 07:19:26.0   5
2017-01-10 07:19:27.1   2017-01-10 07:19:27.1   3
2017-01-10 07:19:28.0   2017-01-10 07:19:29.0   5
2017-01-10 07:19:30.1   2017-01-10 07:19:30.1   3
2017-01-10 07:19:31.0   2017-01-10 07:19:31.0   5
2017-01-10 07:19:32.0   2017-01-10 07:19:32.0   3
2017-01-10 07:19:33.1   2017-01-10 07:19:37.1   5

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

Версия Postgres: 8.4 (У нас есть Postgres с Postgis, поэтому его нелегко обновить. Функции Postgis меняют имена и возникают другие проблемы, но, надеюсь, мы уже все переписываем, и новая версия будет использовать более новую версию 9.X с Postgis 2.x)


Ответы:


4

На несколько очков

  • Не называйте невременную таблицу, tmpкоторая только сбивает с толку.
  • Не используйте текст для отметок времени (вы делаете это в вашем примере, мы можем сказать, потому что отметка времени не была усечена и имеет .0)
  • Не называйте поле, в котором есть время date. Если у него есть дата и время, это временная метка (и сохраните ее как единое целое)

Лучше использовать оконную функцию ..

SELECT id_type, grp, min(date), max(date)
FROM (
  SELECT date, id_type, count(is_reset) OVER (ORDER BY date) AS grp
  FROM (
    SELECT date, id_type, CASE WHEN lag(id_type) OVER (ORDER BY date) <> id_type THEN 1 END AS is_reset
    FROM tmp
  ) AS t
) AS g
GROUP BY id_type, grp
ORDER BY min(date);

Выходы

 id_type | grp |          min          |          max          
---------+-----+-----------------------+-----------------------
       3 |   0 | 2017-01-10 07:19:21.0 | 2017-01-10 07:19:25.0
       5 |   1 | 2017-01-10 07:19:26.0 | 2017-01-10 07:19:26.0
       3 |   2 | 2017-01-10 07:19:27.1 | 2017-01-10 07:19:27.1
       5 |   3 | 2017-01-10 07:19:28.0 | 2017-01-10 07:19:29.0
       3 |   4 | 2017-01-10 07:19:30.1 | 2017-01-10 07:19:30.1
       5 |   5 | 2017-01-10 07:19:31.0 | 2017-01-10 07:19:31.0
       3 |   6 | 2017-01-10 07:19:32.0 | 2017-01-10 07:19:32.0
       5 |   7 | 2017-01-10 07:19:33.1 | 2017-01-10 07:19:37.1
(8 rows)

Explaination

Сначала нам нужно перезагрузить .. Мы генерируем их с lag()

SELECT date, id_type, CASE WHEN lag(id_type) OVER (ORDER BY date) <> id_type THEN 1 END AS is_reset
FROM tmp
ORDER BY date;

         date          | id_type | is_reset 
-----------------------+---------+----------
 2017-01-10 07:19:21.0 |       3 |         
 2017-01-10 07:19:22.0 |       3 |         
 2017-01-10 07:19:23.1 |       3 |         
 2017-01-10 07:19:24.1 |       3 |         
 2017-01-10 07:19:25.0 |       3 |         
 2017-01-10 07:19:26.0 |       5 |        1
 2017-01-10 07:19:27.1 |       3 |        1
 2017-01-10 07:19:28.0 |       5 |        1
 2017-01-10 07:19:29.0 |       5 |         
 2017-01-10 07:19:30.1 |       3 |        1
 2017-01-10 07:19:31.0 |       5 |        1
 2017-01-10 07:19:32.0 |       3 |        1
 2017-01-10 07:19:33.1 |       5 |        1
 2017-01-10 07:19:35.0 |       5 |         
 2017-01-10 07:19:36.1 |       5 |         
 2017-01-10 07:19:37.1 |       5 |         
(16 rows)

Тогда мы рассчитываем получить группы.

SELECT date, id_type, count(is_reset) OVER (ORDER BY date) AS grp
FROM (
  SELECT date, id_type, CASE WHEN lag(id_type) OVER (ORDER BY date) <> id_type THEN 1 END AS is_reset
  FROM tmp
  ORDER BY date
) AS t
ORDER BY date

         date          | id_type | grp 
-----------------------+---------+-----
 2017-01-10 07:19:21.0 |       3 |   0
 2017-01-10 07:19:22.0 |       3 |   0
 2017-01-10 07:19:23.1 |       3 |   0
 2017-01-10 07:19:24.1 |       3 |   0
 2017-01-10 07:19:25.0 |       3 |   0
 2017-01-10 07:19:26.0 |       5 |   1
 2017-01-10 07:19:27.1 |       3 |   2
 2017-01-10 07:19:28.0 |       5 |   3
 2017-01-10 07:19:29.0 |       5 |   3
 2017-01-10 07:19:30.1 |       3 |   4
 2017-01-10 07:19:31.0 |       5 |   5
 2017-01-10 07:19:32.0 |       3 |   6
 2017-01-10 07:19:33.1 |       5 |   7
 2017-01-10 07:19:35.0 |       5 |   7
 2017-01-10 07:19:36.1 |       5 |   7
 2017-01-10 07:19:37.1 |       5 |   7
(16 rows)

Тогда мы завернуть в подзапрос GROUP BYи ORDERи выберите мин макс (диапазон)

SELECT id_type, grp, min(date), max(date)
FROM (
  .. stuff
) AS g
GROUP BY id_type, grp
ORDER BY min(date);

16

1. Оконные функции плюс подзапросы

Подсчитайте шаги для формирования групп, похожих на идею Эвана , с изменениями и исправлениями:

SELECT id_type
     , min(date) AS begin
     , max(date) AS end
     , count(*)  AS row_ct  -- optional addition
FROM  (
   SELECT date, id_type, count(step OR NULL) OVER (ORDER BY date) AS grp
   FROM  (
      SELECT date, id_type
           , lag(id_type, 1, id_type) OVER (ORDER BY date) <> id_type AS step
      FROM   tmp
      ) sub1
   ) sub2
GROUP  BY id_type, grp
ORDER  BY min(date);

Это предполагает участие столбцов NOT NULL. Иначе вам нужно сделать больше.

Также при условии, dateчто он будет определен UNIQUE, в противном случае вам нужно добавить прерыватель связей в ORDER BYпункты, чтобы получить детерминированные результаты. Как: ORDER BY date, id.

Подробное объяснение (ответ на очень похожий вопрос):

Обратите внимание, в частности:

  • В связанных случаях, lag()с 3 параметрами может быть важно, чтобы элегантно покрыть угловой случай первой (или последней) строки. (3-й параметр используется по умолчанию, если нет предыдущей (следующей) строки.

    lag(id_type, 1, id_type) OVER ()

    Поскольку мы заинтересованы только в фактическом изменении в id_type( TRUE), не имеет значения в данном конкретном случае. NULLи FALSEоба не считаются step.

  • count(step OR NULL) OVER (ORDER BY date)это кратчайший синтаксис, который также работает в Postgres 9.3 или старше. count()учитывает только ненулевые значения ...

    В современном Postgres более чистый эквивалентный синтаксис был бы:

    count(step) FILTER (WHERE step) OVER (ORDER BY date)

    Детали:

2. Вычтите две оконные функции, один подзапрос

Аналогично идее Эрика с модификациями:

SELECT min(date) AS begin
     , max(date) AS end
     , id_type
FROM  (
   SELECT date, id_type
        , row_number() OVER (ORDER BY date)
        - row_number() OVER (PARTITION BY id_type ORDER BY date) AS grp
   FROM   tmp
   ) sub
GROUP  BY id_type, grp
ORDER  BY min(date);

Если dateопределено UNIQUE, как я упоминал выше (вы никогда не уточняли), dense_rank()было бы бессмысленно, поскольку результат такой же, как для, row_number()а последний существенно дешевле.

Если dateэто не определено UNIQUE(и мы не знаем , что только дублированные на (date, id_type)), все эти вопросы не имеют смысла, так как результат произволен.

Кроме того, подзапрос обычно дешевле, чем CTE в Postgres. Используйте CTE только тогда, когда они вам нужны .

Связанные ответы с дополнительным объяснением:

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

3. Высочайшая производительность с функцией plpgsql

Поскольку этот вопрос стал неожиданно популярным, я добавлю еще одно решение для демонстрации максимальной производительности.

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

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

CREATE OR REPLACE FUNCTION f_tmp_groups()
  RETURNS TABLE (id_type int, grp_begin timestamp, grp_end timestamp) AS
$func$
DECLARE
   _row  tmp;                       -- use table type for row variable
BEGIN
   FOR _row IN
      TABLE tmp ORDER BY date       -- add more columns to make order deterministic
   LOOP
      CASE _row.id_type = id_type 
      WHEN TRUE THEN                -- same group continues
         grp_end := _row.date;      -- remember last date so far
      WHEN FALSE THEN               -- next group starts
         RETURN NEXT;               -- return result for last group
         id_type   := _row.id_type;
         grp_begin := _row.date;
         grp_end   := _row.date;
      ELSE                          -- NULL for 1st row
         id_type   := _row.id_type; -- remember row data for starters
         grp_begin := _row.date;
         grp_end   := _row.date;
      END CASE;
   END LOOP;

   RETURN NEXT;                     -- return last result row      
END
$func$ LANGUAGE plpgsql;

Вызов:

SELECT * FROM f_tmp_groups();

Тест с:

EXPLAIN (ANALYZE, TIMING OFF)  -- to focus on total performance
SELECT * FROM  f_tmp_groups();

Вы можете сделать функцию универсальной с полиморфными типами и передавать имена табличных типов и столбцов. Детали:

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


dbfiddle для Postgres 9.6, сравнивающий производительность для всех трех. Изменен тестовый сценарийДжека.

dbfiddle для Postgres 8.4, где различия в производительности еще больше.


Прочитайте это несколько раз - все еще не уверены, о чем вы говорите с задержкой в ​​три аргумента, или когда вам придется использовать, count(x or null)или даже что она там делает. Может быть , вы могли бы показать некоторые образцы , где это необходимо, потому что это здесь не требуется. И что будет ключевым требованием для покрытия этих угловых случаев. Кстати, я изменил свое downvote на upvote только для примера pl / pgsql. Это действительно круто. (Но, как правило, я против ответов, которые суммируют другие ответы или охватывают угловые случаи - хотя мне неприятно говорить, что это угловой случай, потому что я его не понимаю).
Эван Кэрролл,

Я бы поставил их в два отдельных вопроса с самоотдачей, потому что я уверен, что я не единственный, кто задается вопросом, что count(x or null)делает. Я буду рад задать оба вопроса, если вы предпочитаете.
Эван Кэрролл,


7

Вы можете сделать это как простое вычитание ROW_NUMBER()операций (или, если ваши даты не уникальны, хотя все еще уникальны id_type, тогда вы можете использовать DENSE_RANK()вместо этого, хотя это будет более дорогой запрос):

WITH IdTypes AS (
   SELECT
      date,
      id_type,
      Row_Number() OVER (ORDER BY date)
         - Row_Number() OVER (PARTITION BY id_type ORDER BY date)
         AS Seq
   FROM
      tmp
)
SELECT
   Min(date) AS begin,
   Max(date) AS end,
   id_type
FROM IdTypes
GROUP BY id_type, Seq
ORDER BY begin
;

Посмотрите эту работу в БД Fiddle (или посмотрите версию DENSE_RANK )

Результат:

begin                  end                    id_type
---------------------  ---------------------  -------
2017-01-10 07:19:21    2017-01-10 07:19:25    3
2017-01-10 07:19:26    2017-01-10 07:19:26    5
2017-01-10 07:19:27.1  2017-01-10 07:19:27.1  3
2017-01-10 07:19:28    2017-01-10 07:19:29    5
2017-01-10 07:19:30.1  2017-01-10 07:19:30.1  3
2017-01-10 07:19:31    2017-01-10 07:19:31    5
2017-01-10 07:19:32    2017-01-10 07:19:32    3
2017-01-10 07:19:33.1  2017-01-10 07:19:37.1  5

Логически, вы можете думать об этом как простой DENSE_RANK()с микросхемой PREORDER BY, то есть, вы хотите, чтобы DENSE_RANKвсе элементы , которые ранжируются вместе, и вы хотите , чтобы они отсортированы по датам, вы просто должны иметь дело с надоедливой проблемой того факта , что при каждом изменении даты DENSE_RANKбудет увеличиваться. Вы делаете это, используя выражение, как я показал вам выше. Представьте, что у вас был такой синтаксис: DENSE_RANK() OVER (PREORDER BY date, ORDER BY id_type)где PREORDERисключается из расчета рейтинга и ORDER BYучитывается только значение .

Обратите внимание, что это важно GROUP BYкак для сгенерированного Seqстолбца, так и для id_typeстолбца. Seqсам по себе НЕ уникален, возможны совпадения - вы также должны группировать по id_type.

Для дальнейшего чтения по этой теме:

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


6

На Postgres 8.4 вы можете использовать функцию RECURSIVE .

Как они это делают

Рекурсивная функция добавляет уровень к каждому отдельному id_type, выбирая даты одну за другой в порядке убывания.

       date           | id_type | lv
--------------------------------------
2017-01-10 07:19:21.0      3       8
2017-01-10 07:19:22.0      3       8
2017-01-10 07:19:23.1      3       8
2017-01-10 07:19:24.1      3       8
2017-01-10 07:19:25.0      3       8
2017-01-10 07:19:26.0      5       7
2017-01-10 07:19:27.1      3       6
2017-01-10 07:19:28.0      5       5
2017-01-10 07:19:29.0      5       5
2017-01-10 07:19:30.1      3       4
2017-01-10 07:19:31.0      5       3
2017-01-10 07:19:32.0      3       2
2017-01-10 07:19:33.1      5       1
2017-01-10 07:19:35.0      5       1
2017-01-10 07:19:36.1      5       1
2017-01-10 07:19:37.1      5       1

Затем используйте группировку MAX (дата), MIN (дата) по уровню, id_type, чтобы получить желаемый результат.

with RECURSIVE rdates as 
(
    (select   date, id_type, 1 lv 
     from     yourTable
     order by date desc
     limit 1
    )
    union
    (select    d.date, d.id_type,
               case when r.id_type = d.id_type 
                    then r.lv 
                    else r.lv + 1 
               end lv    
    from       yourTable d
    inner join rdates r
    on         d.date < r.date
    order by   date desc
    limit      1)
)
select   min(date) StartDate,
         max(date) EndDate,
         id_type
from     rdates
group by lv, id_type
;

+---------------------+---------------------+---------+
| startdate           |       enddate       | id_type |
+---------------------+---------------------+---------+
| 10.01.2017 07:19:21 | 10.01.2017 07:19:25 |    3    |
| 10.01.2017 07:19:26 | 10.01.2017 07:19:26 |    5    |
| 10.01.2017 07:19:27 | 10.01.2017 07:19:27 |    3    |
| 10.01.2017 07:19:28 | 10.01.2017 07:19:29 |    5    |
| 10.01.2017 07:19:30 | 10.01.2017 07:19:30 |    3    |
| 10.01.2017 07:19:31 | 10.01.2017 07:19:31 |    5    |
| 10.01.2017 07:19:32 | 10.01.2017 07:19:32 |    3    |
| 10.01.2017 07:19:33 | 10.01.2017 07:19:37 |    5    |
+---------------------+---------------------+---------+

Проверьте это: http://rextester.com/WCOYFP6623


5

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

SELECT
  id_type,
  date AS begin,
  COALESCE(
    LEAD(prev_date) OVER (ORDER BY date ASC),
    last_date
  ) AS end
FROM
  (
    SELECT
      id_type,
      date,
      LAG(date) OVER (ORDER BY date ASC) AS prev_date,
      MAX(date) OVER () AS last_date,
      CASE id_type
        WHEN LAG(id_type) OVER (ORDER BY date ASC)
        THEN 0
        ELSE 1
      END AS is_start
    FROM
      tmp
  ) AS derived
WHERE
  is_start = 1
ORDER BY
  date ASC
;

is_startВычисляемый столбец в вложенном SELECT , маркирует начало каждого острова. Кроме того, вложенный SELECT предоставляет предыдущую дату каждой строки и последнюю дату набора данных.

Для строк, которые являются началом их соответствующих островов, предыдущая дата фактически является датой окончания предыдущего острова. Вот как его использует основной SELECT. Он выбирает только те строки, которые соответствуют is_start = 1условию, и для каждой возвращаемой строки он показывает собственную строку dateas beginи следующую строку prev_dateas end. Поскольку в последней строке нет следующей строки, LEAD(prev_date)для нее возвращается значение null, для которого функция COALESCE заменяет последнюю дату набора данных.

Вы можете поиграть с этим решением на dbfiddle .

При вводе дополнительных столбцов, идентифицирующих острова, вы, вероятно, захотите ввести подпункт PARTITION BY в предложение OVER каждой оконной функции. Например, если вы хотите обнаружить острова в группах, определенных как a parent_id, приведенный выше запрос, вероятно, должен выглядеть следующим образом:

SELECT
  parent_id,
  id_type,
  date AS begin,
  COALESCE(
    LEAD(prev_date) OVER (PARTITION BY parent_id ORDER BY date ASC),
    last_date
  ) AS end
FROM
  (
    SELECT
      parent_id,
      id_type,
      date,
      LAG(date) OVER (PARTITION BY parent_id ORDER BY date ASC) AS prev_date,
      MAX(date) OVER (PARTITION BY parent_id) AS last_date,
      CASE id_type
        WHEN LAG(id_type) OVER (PARTITION BY parent_id ORDER BY date ASC)
        THEN 0
        ELSE 1
      END AS is_start
    FROM
      tmp
  ) AS derived
WHERE
  is_start = 1
ORDER BY
  date ASC
;

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


5

Больше из академического интереса, чем как практическое решение, вы также можете достичь этого с помощью пользовательского агрегата . Как и другие решения, это будет работать даже на Postgres 8.4, но, как прокомментировали другие, обновите, если можете.

Агрегат обрабатывает так, nullкак если бы он был другим foo_type, поэтому прогоны с нулевыми значениями будут одинаковыми grp- это может или не может быть тем, что вы хотите.

create function grp_sfunc(integer[],integer) returns integer[] language sql as $$
  select array[$1[1]+($1[2] is distinct from $2 or $1[3]=0)::integer,$2,1];
$$;
create function grp_finalfunc(integer[]) returns integer language sql as $$
  select $1[1];
$$;
create aggregate grp(integer)(
  sfunc = grp_sfunc
, stype = integer[]
, finalfunc = grp_finalfunc
, initcond = '{0,0,0}'
);
select min(foo_at) begin_at, max(foo_at) end_at, foo_type
from (select *, grp(foo_type) over (order by foo_at) from foo) z
group by grp, foo_type
order by 1;
begin_at | end_at | foo_type
: -------------------- | : -------------------- | -------:
2017-01-10 07:19:21 | 2017-01-10 07:19:25 | 3
2017-01-10 07:19:26 | 2017-01-10 07:19:26 | 5
2017-01-10 07: 19: 27.1 | 2017-01-10 07: 19: 27.1 | 3
2017-01-10 07:19:28 | 2017-01-10 07:19:29 | 5
2017-01-10 07: 19: 30.1 | 2017-01-10 07: 19: 30.1 | 3
2017-01-10 07:19:31 | 2017-01-10 07:19:31 | 5
2017-01-10 07:19:32 | 2017-01-10 07:19:32 | 3
2017-01-10 07: 19: 33.1 | 2017-01-10 07: 19: 37.1 | 5

dbfiddle здесь


4

Это можно сделать, RECURSIVE CTEчтобы передать «время начала» из одного ряда в другой и некоторые дополнительные (удобные) приготовления.

Этот запрос возвращает желаемый результат:

WITH RECURSIVE q AS
(
    SELECT
        id_type,
        "date",
        /* We compute next id_type for convenience, plus row_number */
        row_number()  OVER (w) AS rn,
        lead(id_type) OVER (w) AS next_id_type
    FROM
        t
    WINDOW
        w AS (ORDER BY "date") 
)

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

, rec AS 
(
    /* Anchor */
    SELECT
        q.rn,
        q."date" AS "begin",
        /* When next_id_type is different from Look also at **next** row to find out whether we need to mark an end */
        case when q.id_type is distinct from q.next_id_type then q."date" END AS "end",
        q.id_type
    FROM
        q
    WHERE
        rn = 1

    UNION ALL

    /* Loop */
    SELECT
        q.rn,
        /* We keep copying 'begin' from one row to the next while type doesn't change */
        case when q.id_type = rec.id_type then rec.begin else q."date" end AS "begin",
        case when q.id_type is distinct from q.next_id_type then q."date" end AS "end",
        q.id_type
    FROM
        rec
        JOIN q ON q.rn = rec.rn+1
)
-- We filter the rows where "end" is not null, and project only needed columns
SELECT
    "begin", "end", id_type
FROM
    rec
WHERE
    "end" is not null ;

Вы можете проверить это на http://rextester.com/POYM83542

Этот метод плохо масштабируется. Для таблицы строк 8_641 это занимает 7 с, для таблицы вдвое больше, чем 28 с. Еще несколько примеров показывают время выполнения, похожее на O (n ^ 2).

Метод Эвана Кэррола занимает меньше 1 с (то есть: дерзайте!) И выглядит как O (n). Рекурсивные запросы абсолютно неэффективны и должны рассматриваться как последнее средство.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.