Я выбираю несколько диапазонов дат с помощью tsrange, который включает определенный год.
Я получаю такой вывод:
{ time_range: '("2024-12-27 00:00:00","2025-02-01 00:00:00")' },
{ time_range: '("2025-05-01 00:00:00","2025-05-05 00:00:00")' },
{ time_range: '("2025-05-08 00:00:00","2025-05-20 00:00:00")' }
могу ли я посчитать все дни, сгруппированные по годам?
текущий код:
SELECT time_range FROM holidays WHERE time_range && tsrange($1, $2, '[)')
У тебя действительно есть верёвка вокруг тупеля?
@Luuk Я хочу посчитать, сколько дней в 2024 году и сколько дней в 2025 году.
Хорошо, вам нужно что-то вроде 2024 | 5, потому что в 2024 году у вас есть 17 декабря 2024 года .. 31 декабря 2024 года, а в 2025 году большее число с 01 января 2025 года .. 01 февраля 2025 года (эксклюзивное) и длина из двух других диапазонов? Однако я не понимаю, какое отношение ваш текущий запрос имеет к этому.
Вы можете начать здесь: Количество дней в диапазоне дат
@RichardHuxton да, я попробовал несколько других запросов, но все не сработало, поэтому я не писал здесь
@RichardHuxton «что-то вроде 2024 | 5, потому что в 2024 году у вас есть 17 декабря 2024 года .. 31 декабря 2024 года» - а, разве там не 15 дней (включительно)?
опечатка в моем комментарии - 27 декабря в вопросе


используя эти данные:
create table example (
x int,
ts tsrange);
INSERT INTO example VALUES
(1, '[2024-12-27 00:00:00,2025-02-01 00:00:00)'),
(2, '[2025-05-01 00:00:00,2025-05-05 00:00:00)'),
(3, '[2025-05-08 00:00:00,2025-05-20 00:00:00)');
Вы можете написать запрос, который получает все даты в диапазоне, а затем подсчитывает их.
WITH RECURSIVE days as (
SELECT
x,
LOWER(ts) as t
FROM example
UNION ALL
SELECT
x,
t+'1 day'
FROM days
where t < (SELECT UPPER(ts) FROM example where x=days.x)
)
SELECT
extract(year from t), count(*)
FROM days
GROUP BY extract(year from t)
ORDER BY extract(year from t)
;
см.: DBFIDDLE
выход:
или, если вы хотите получить их для каждого диапазона дат: DBFIDDLE
выход:
Кажется, вам нужен не tsrange, а скорее диапазон дат. В противном случае я сохранил ваши образцы данных. Ряд положений CTE предназначен только для облегчения чтения — возможно, вы захотите объединить некоторые шаги.
CREATE TEMP TABLE src (
id int PRIMARY KEY
, event_range daterange NOT NULL
);
INSERT INTO src VALUES
( 1, '(2024-12-27,2025-02-01)' )
, ( 2, '(2025-05-01,2025-05-05)' )
, ( 3, '(2025-05-08,2025-05-20)' )
;
WITH target_years (y) AS (
VALUES (2024), (2025), (2026)
)
, year_ranges (y, y_r) AS (
SELECT y, daterange( make_date(y, 1, 1), make_date(y+1, 1, 1), '[)' )
FROM target_years
)
, overlapping_ranges (y, o_r) AS (
SELECT
year_ranges.y
, year_ranges.y_r * src.event_range AS o_r
FROM
year_ranges
JOIN src ON year_ranges.y_r && src.event_range
)
, range_day_counts AS (
SELECT
y
, upper(o_r) - lower(o_r) AS days
FROM
overlapping_ranges
)
SELECT y, sum(days) AS tot_days
FROM range_day_counts
GROUP BY y
ORDER BY y
;
дает
y | tot_days
------+----------
2024 | 4
2025 | 45
(2 rows)
Как я могу фильтровать по user_id? Итак, где user_id = 4, я получаю, что столбец user_id не существует.
Вам понадобится столбец с названием user_id, чтобы фильтровать по нему. В вопросе нет такой графы.
Да, и я должен отметить, что я предполагаю, что предоставленные диапазоны дат не перекрывают друг друга. Их нет в данных вашего примера.
У вас это получилось очень хорошо, и спасибо, что не допускаете дублирования, вы лучшие! Я сделал это в другом запросе для проверки перекрытия. Мне нужно изучить больше sql, но в случае с user_id я пытался добавить user_id, но получил ошибку: ссылка на столбец "o_r" неоднозначна, я пробовал каждый cte или другие, но каждый раз получал ошибку, когда хотел фильтровать по user_id
Предполагая, что user_id находится в вашей исходной таблице, добавьте предложение WHERE в overlapping_ranges CTE. В какой-то момент вам придется выучить немного больше SQL, если вы боретесь с базовыми проблемами, такими как построение предложенияwhere.
См. пример.
Вопрос о том, включать или нет граничные даты в расчеты, остается открытым. Например ('2024-03-14', '2024-03-15') =0? =1? или =2?
Образец данных
create table holidays (id int, date_range tsrange);
insert into holidays values
(1,'("2024-12-27 00:00:00","2025-02-01 00:00:00")' )
,(2,'("2025-05-01 00:00:00","2025-05-05 00:00:00")' )
,(3,'("2025-05-08 00:00:00","2025-05-20 00:00:00")' )
;
Запрос
with cte1 as(
select *
,case when date_part('year',upper(date_range))<>date_part('year',lower(date_range))
then 1
else 0
end overyear
from holidays
)
,cte2 as(
select *
,lower(date_range) l
,upper(date_range) u
,date_part('day',upper(date_range)- lower(date_range)) d
,case when n=0 then date_part('year',lower(date_range))
else date_part('year',upper(date_range))
end yy
,case when overyear>0 and n=0 then
date_part('day',date_trunc('year',upper(date_range))-lower(date_range))
when overyear>0 and n=1 then
date_part('day',upper(date_range)-date_trunc('year',upper(date_range)))
else date_part('day',upper(date_range)- lower(date_range))
end lng
from cte1
left join (values (0),(1))t(n)
on n<=overyear
)
--select * from cte2;
select yy,sum(lng)lng
from cte2
group by yy
order by yy
;
Выход
Результат подзапроса
Нет сомнений в том, являются ли диапазоны дат включенными или исключительными. По умолчанию PostgreSQL использует значение (включительно, эксклюзивно), но позволяет вам указать именно то, что вы хотите. postgresql.org/docs/current/…
Да, в примере показано исключение границ. Однако на праздники лучше было бы включить бордюры :)
SELECT date_part('year',holidate) "year", count(distinct holidate)
FROM holidays CROSS JOIN
generate_series( +(not lower_inc(time_range))::int+lower(time_range)::date
,-(not upper_inc(time_range))::int+upper(time_range)::date
,'1 day')as a(holidate)
WHERE holidate <@ tstzrange('2024-12-27', '2025-05-20', '[)')
GROUP BY "year";
Если вы хотите предотвратить перекрытие диапазонов, добавьте ограничение исключения :
alter table holidays add constraint non_overlapping_ranges
exclude using gist(time_range with &&);
Подход, использующий только границы диапазона, будет работать лучше, но для получения достоверных результатов вам необходимо range_agg() затем unnest() объединить перекрывающиеся диапазоны, затем разделить их 1 января так, чтобы они полностью содержались в данном году, и только затем получить sum(upper()-lower()).
Спасибо за запрос, который я попробовал, и он сработал. Как мне установить индекс в этом случае?
@serverz Если вы решите добавить ограничение, вам не нужно его добавлять: оно будет добавлено автоматически, чтобы обеспечить соблюдение ограничения. В противном случае добавьте create index on holidays using gist (time_range);, но имейте в виду, что если вы разрешите перекрытие в этих диапазонах, этот ответ будет единственным, который даст вам действительные результаты.
Я создал индекс с exlucude, используя gist (user_id With =, time_range = &&) или что-то в этом роде. Прежде чем выполнить запрос, я также проверяю, перекрываются ли даты, отправленные пользователем, иначе я не запускаю ваш запрос. Поэтому я проверяю перекрытие, но в другом запросе
@serverz Звучит хорошо. Если наложения предотвращаются ограничением, а также останавливаются отдельной проверкой, то upper()-lower() (без фактического создания дат только для их подсчета) будет быстрее.
Хорошо, у вас есть пример, как я могу использовать только верхний/нижний?
Каков ваш ожидаемый результат по этому подсчету?
3потому что у вас есть 3 time_range или1потому что они все связаны... ?