PostgreSQL-изменено значение столбца-выберите оптимизация запросов
скажем, у нас есть стол:
CREATE TABLE p
(
id serial NOT NULL,
val boolean NOT NULL,
PRIMARY KEY (id)
);
заполняется несколько строк:
insert into p (val)
values (true),(false),(false),(true),(true),(true),(false);
ID VAL 1 1 2 0 3 0 4 1 5 1 6 1 7 0
Я хочу определить, когда значение было изменено. Поэтому результат моего запроса должен быть:
ID VAL 2 0 4 1 7 0
у меня есть решение с соединения и подзапросы:
select min(id) id, val from
(
select p1.id, p1.val, max(p2.id) last_prev
from p p1
join p p2
on p2.id < p1.id and p2.val != p1.val
group by p1.id, p1.val
) tmp
group by val, last_prev
order by id;
но это очень неэффективно и будет работать очень медленно для таблиц с большим количеством строк.
Я считаю, что может быть более эффективное решение с помощью окна PostgreSQL функции?
5 ответов
это как я бы сделал это с аналитиком:
SELECT id, val
FROM ( SELECT id, val
,LAG(val) OVER (ORDER BY id) AS prev_val
FROM p ) x
WHERE val <> COALESCE(prev_val, val)
ORDER BY id
обновление (некоторые пояснения):
аналитические функции работают как этап постобработки. Результат запроса разбивается на группы (partition by
) и аналитическая функция применяется в контексте группирования.
в этом случае запрос представляет собой выборку из p
. Применяемая аналитическая функция LAG
. Так как нет partition by
предложения есть только одна группировка: весь результирующий набор. Эта группировка упорядочена по id
. LAG
возвращает значение предыдущей строки в группировке, используя указанный порядок. В результате каждая строка имеет дополнительный столбец (псевдоним prev_val), который является val
предыдущей строки. Это подзапрос.
затем мы ищем строки, где val
не соответствует val
предыдущей строки (prev_val). The COALESCE
обрабатывает особый случай первой строки, которая не имеет предыдущее значение.
аналитические функции могут показаться немного странными сначала, но поиск по аналитическим функциям находит много примеров того, как они работают. Например: http://www.cs.utexas.edu/~cannata/dbms/Analytic%20Functions%20in%20Oracle%208i%20and%209i.htm просто помните, что это этап после обработки. Вы не сможете выполнять фильтрацию и т. д. На значение аналитической функции, если вы подзапрос его.
функция окна
вместо COALESCE
, вы можете указать значение по умолчанию из функции окна lag()
напрямую. Незначительная деталь в этом случае, так как все столбцы NOT NULL
. Но это может быть важно, чтобы отличить "нет предыдущей строки "от"NULL в предыдущей строке".
SELECT id, val
FROM (
SELECT id, val, lag(val, 1, val) OVER (ORDER BY id) <> val AS changed
FROM p
) sub
WHERE changed
ORDER BY id;
вычислить результат сравнения немедленно, так как Предыдущее значение не представляет интереса как таковое, только возможное изменение. Короче и может быть немного быстрее.
если вы считаете первая строка чтобы быть " измененным "(в отличие от вашего демонстрационного вывода), вам нужно наблюдать NULL
значения - даже если ваши столбцы определяются NOT NULL
. Basic lag()
возвращает NULL
в случае, если нет предыдущей строки:
SELECT id, val
FROM (
SELECT id, val, lag(val) OVER (ORDER BY id) IS DISTINCT FROM val AS changed
FROM p
) sub
WHERE changed
ORDER BY id;
или используйте дополнительные параметры lag()
еще раз:
SELECT id, val
FROM (
SELECT id, val, lag(val, 1, NOT val) OVER (ORDER BY id) <> val AS changed
FROM p
) sub
WHERE changed
ORDER BY id;
рекурсивный CTE
как доказательство концепции. :) Представление не отставать опубликовано альтернативы.
WITH RECURSIVE cte AS (
SELECT id, val
FROM p
WHERE NOT EXISTS (
SELECT 1
FROM p p0
WHERE p0.id < p.id
)
UNION ALL
SELECT p.id, p.val
FROM cte
JOIN p ON p.id > cte.id
AND p.val <> cte.val
WHERE NOT EXISTS (
SELECT 1
FROM p p0
WHERE p0.id > cte.id
AND p0.val <> cte.val
AND p0.id < p.id
)
)
SELECT * FROM cte;
С улучшением от @wildplasser.
SQL Fiddle демонстрируя всем.
можно даже сделать без функций окна.
SELECT * FROM p p0
WHERE EXISTS (
SELECT * FROM p ex
WHERE ex.id < p0.id
AND ex.val <> p0.val
AND NOT EXISTS (
SELECT * FROM p nx
WHERE nx.id < p0.id
AND nx.id > ex.id
)
);
UPDATE: Самосоединение нерекурсивного CTE (также может быть подзапросом вместо CTE)
WITH drag AS (
SELECT id
, rank() OVER (ORDER BY id) AS rnk
, val
FROM p
)
SELECT d1.*
FROM drag d1
JOIN drag d0 ON d0.rnk = d1.rnk -1
WHERE d1.val <> d0.val
;
этот нерекурсивный подход CTE удивительно быстр, хотя он нуждается в неявной сортировке.
через 2 row_number()
расчеты: это также можно сделать с обычной техникой SQL" острова и пробелы " (может быть полезно, если вы не можете использовать lag()
функция окна по какой-то причине:
with cte1 as (
select
*,
row_number() over(order by id) as rn1,
row_number() over(partition by val order by id) as rn2
from p
)
select *, rn1 - rn2 as g
from cte1
order by id
так этот запрос даст вам все острова
ID VAL RN1 RN2 G
1 1 1 1 0
2 0 2 1 1
3 0 3 2 1
4 1 4 2 2
5 1 5 3 2
6 1 6 4 2
7 0 7 3 4
видишь, как G
поле может использоваться для группировки этих островов вместе:
С cte1 как ( выбирать *, row_number () over (order by id) as rn1, row_number () over (раздел по порядку val по id) как rn2 от П ) выбирать мин(ИД) ИД, вал из cte1 группа по val, rn1 - rn2 заказ по 1
так что вы получите
ID VAL
1 1
2 0
4 1
7 0
единственное, что теперь вы должны удалить первую запись, которую можно сделать, получив min(...) over()
окно функции:
with cte1 as (
...
), cte2 as (
select
min(id) as id,
val,
min(min(id)) over() as mid
from cte1
group by val, rn1 - rn2
)
select id, val
from cte2
where id <> mid
результаты:
ID VAL
2 0
4 1
7 0
простое внутреннее соединение может сделать это. SQL Fiddle
select p2.id, p2.val
from
p p1
inner join
p p2 on p2.id = p1.id + 1
where p2.val != p1.val