PostgreSQL build working range from one date column - postgresql

I'm using PostgreSQL v. 11.2
I have a table
|id | bot_id | date |
| 1 | 1 | 2020-04-20 16:00:00|
| 2 | 2 | 2020-04-22 12:00:00|
| 3 | 3 | 2020-04-24 04:00:00|
| 4 | 1 | 2020-04-27 09:00:00|
And for example, I have DateTime range 2020-03-30 00:00:00 and 2020-04-30 00:00:00
I need to show get working ranges to count the total working hours of each bot.
Like this:
|bot_id | start_date | end_date |
| 1 | 2020-03-30 00:00:00 | 2020-04-20 16:00:00 |
| 2 | 2020-04-20 16:00:00 | 2020-04-22 12:00:00 |
| 3 | 2020-04-22 12:00:00 | 2020-04-24 04:00:00 |
| 1 | 2020-04-24 04:00:00 | 2020-04-27 09:00:00 |
| 1 | 2020-04-27 09:00:00 | 2020-04-30 00:00:00 |
I've tried to use LAG(date) but I'm not getting first and last dates of the range.

You could use a UNION ALL, with one part building the start_date/end_date couples from your values & the other part filling in the last period (from the last date to 2020-04-30 00:00:00):
WITH values (id, bot_id, date) AS (
VALUES (1, 1, '2020-04-20 16:00:00'::TIMESTAMP)
, (2, 2, '2020-04-22 12:00:00')
, (3, 3, '2020-04-24 04:00:00')
, (4, 1, '2020-04-27 09:00:00')
)
(
SELECT bot_id
, LAG(date, 1, '2020-03-30 00:00:00') OVER (ORDER BY id) AS start_date
, date AS end_date
FROM values
)
UNION ALL
(
SELECT bot_id
, date AS start_date
, '2020-04-30 00:00:00' AS end_date
FROM values
ORDER BY id DESC
LIMIT 1
)
+------+--------------------------+--------------------------+
|bot_id|start_date |end_date |
+------+--------------------------+--------------------------+
|1 |2020-03-30 00:00:00.000000|2020-04-20 16:00:00.000000|
|2 |2020-04-20 16:00:00.000000|2020-04-22 12:00:00.000000|
|3 |2020-04-22 12:00:00.000000|2020-04-24 04:00:00.000000|
|1 |2020-04-24 04:00:00.000000|2020-04-27 09:00:00.000000|
|1 |2020-04-27 09:00:00.000000|2020-04-30 00:00:00.000000|
+------+--------------------------+--------------------------+

Related

Postgres max value per hour with time it occurred

Given a Postgres table with columns highwater_datetime::timestamp and highwater::integer, I am trying to construct a select statement for a given highwater_datetime range, that generates rows with a column for the max highwater for each hour (first occurrence when dups) and another column showing the highwater_datetime when it occurred (truncated to the minute and order by highwater_datetime asc). e.g.
| highwater_datetime | max_highwater |
+--------------------+---------------+
| 2021-01-27 20:05 | 8 |
| 2021-01-27 21:00 | 7 |
| 2021-01-27 22:00 | 7 |
| 2021-01-27 23:00 | 7 |
| 2021-01-28 00:00 | 7 |
| 2021-01-28 01:32 | 7 |
| 2021-01-28 02:00 | 7 |
| 2021-01-28 03:00 | 7 |
| 2021-01-28 04:22 | 9 |
DISTINCT ON should do the trick:
SELECT DISTINCT ON (date_trunc('hour', highwater_datetime))
highwater_datetime,
highwater
FROM mytable
ORDER BY date_trunc('hour', highwater_datetime),
highwater DESC,
highwater_datetime;
DISTINCT ON will output the first row for each entry with the same hour according to the ORDER BY clause.

How to get all days in one table a date range even if no data exists also in SQL Server

I have one table name called Tab1. I would like to get all date even if any one of the days is missing also.
+-------------------+--------------------------+
|Name | dateCheck |
+-------------------+--------------------------+
| 1 | 2016-01-01 00:00:00.000 |
| 2 | 2016-01-02 00:00:00.000 |
| 3 | 2016-01-05 00:00:00.000 |
| 4 | 2016-01-07 00:00:00.000 |
+-------------------+--------------------------+
I need output like below :
+-------------------+--------------------------+
|Name | dateCheck |
+-------------------+--------------------------+
| 1 | 2016-01-01 00:00:00.000 |
| 2 | 2016-01-02 00:00:00.000 |
| 0 | 2016-01-03 00:00:00.000 |
| 0 | 2016-01-04 00:00:00.000 |
| 3 | 2016-01-05 00:00:00.000 |
| 0 | 2016-01-06 00:00:00.000 |
| 4 | 2016-01-07 00:00:00.000 |
You may use a calendar table:
SELECT
COALESCE(t2.Name, 0) AS Name,
t1.dateCheck
FROM
(
SELECT '2016-01-01' AS dateCheck UNION ALL
SELECT '2016-01-02' UNION ALL
SELECT '2016-01-03' UNION ALL
SELECT '2016-01-04' UNION ALL
SELECT '2016-01-05' UNION ALL
SELECT '2016-01-06' UNION ALL
SELECT '2016-01-07'
) t1
LEFT JOIN yourTable t2
ON t1.dateCheck = t2.dateCheck;

Tsql -> filter data 6 months from today, date field in table is YYYYMM

I need some help.
Currently is March 2017.
how do I extract all records 6 months ago from February 2017 until end of this year. the date format in my table is in YYYYMM
Here is my sql statement
select columns from budget
where month_number > = DATEADD(MONTH, -6, CURRENT_TIMESTAMP);
the output I am getting is as below:
+------------+-------+--------------+
| month_name | month | month_number |
+------------+-------+--------------+
| January | 1 | 201601 |
| February | 2 | 201602 |
| March | 3 | 201603 |
| April | 4 | 201604 |
| May | 5 | 201605 |
| June | 6 | 201606 |
| July | 7 | 201607 |
| August | 8 | 201608 |
| September | 9 | 201609 |
| October | 10 | 201610 |
| November | 11 | 201611 |
| December | 12 | 201612 |
| January | 1 | 201701 |
| February | 2 | 201702 |
| March | 3 | 201703 |
| April | 4 | 201704 |
| July | 7 | 201707 |
| December | 12 | 201712 |
+------------+-------+--------------+
I am not getting the right output. I am still getting data from Jan 2016 onwards. Please help
Thanks
Alternatively ..
declare #budget table (month_Number int)
insert #budget (month_number)
select 201601
union all
select 201602
union all
select 201702
union all
select 201705
union all
select 201709
select * from #budget
where month_number >= (YEAR(DATEADD(MONTH, -6, CURRENT_TIMESTAMP)) * 100) + MONTH(DATEADD(MONTH, -6, CURRENT_TIMESTAMP));
Select *
From Budget
Where month_number>= convert(varchar(6),DATEADD(MONTH, -6, CURRENT_TIMESTAMP),112)
Order By month_number
If 2012+
Select *
From Budget
Where month_number>= format(DATEADD(MONTH, -6, CURRENT_TIMESTAMP),'yyyyMM')
Order By month_number
Returns
month_name month month_number
September 9 201609
October 10 201610
November 11 201611
December 12 201612
January 1 201701
February 2 201702
March 3 201703
April 4 201704
July 7 201707
December 12 201712

Update using subquery sets same value for all records

I'm trying to calculate the weight of each record based on the value of a column (updated_at). When I run the following query:
UPDATE buyers
SET weight = RankedRecords.rank / (RankedRecords.count + 1.0)
FROM (
SELECT
id,
RANK() OVER (
PARTITION BY board_list_id ORDER BY 'updated_at' ASC
) AS rank,
COUNT(id) OVER (PARTITION BY board_list_id) AS count
FROM buyers
) RankedRecords
WHERE buyers.id = RankedRecords.id
All records with the same board_list_id get their weight updated to the same value. While I expect all weight values to be different and depend on rank.
Running just the subquery produces correct results (each record has different rank). But updating doesn't work as expected.
What should I change?
You have a very subtle mistake in your query. Try this instead:
UPDATE
buyers
SET
weight = RankedRecords.rank / (RankedRecords.count + 1.0)
FROM
(
SELECT
id,
rank() OVER (PARTITION BY board_list_id ORDER BY updated_at ASC) AS rank,
count(id) OVER (PARTITION BY board_list_id) AS count
FROM buyers
) RankedRecords
WHERE
buyers.id = RankedRecords.id ;
Your litle mistake: ORDER BY 'updated_at' is just ORDER BY 'constant-text'. If you want to refer to the column, you either use "updated_at" (with double quotes) or updated_at (without them, because the name of your column is just ASCII lowercase chars).
Tried with:
CREATE TABLE buyers
(
id integer not null primary key,
board_list_id integer not null,
updated_at timestamp not null default now(),
weight double precision
) ;
INSERT INTO buyers (id, board_list_id, updated_at)
VALUES
(1, 1, '2017-01-09'),
(2, 1, '2017-01-10'),
(3, 1, '2017-01-11'),
(4, 1, '2017-01-12'),
(5, 2, '2017-01-09'),
(6, 2, '2017-01-10'),
(7, 2, '2017-01-11'),
(8, 1, '2017-01-12') ;
The result of the previous UPDATE (with a RETURNING * clause) would be:
|----+---------------+---------------------+--------+----+------+-------|
| id | board_list_id | updated_at | weight | id | rank | count |
|----+---------------+---------------------+--------+----+------+-------|
| 1 | 1 | 2017-01-09 00:00:00 | 0.1667 | 1 | 1 | 5 |
|----+---------------+---------------------+--------+----+------+-------|
| 2 | 1 | 2017-01-10 00:00:00 | 0.3333 | 2 | 2 | 5 |
|----+---------------+---------------------+--------+----+------+-------|
| 3 | 1 | 2017-01-11 00:00:00 | 0.5 | 3 | 3 | 5 |
|----+---------------+---------------------+--------+----+------+-------|
| 8 | 1 | 2017-01-12 00:00:00 | 0.6667 | 8 | 4 | 5 |
|----+---------------+---------------------+--------+----+------+-------|
| 4 | 1 | 2017-01-12 00:00:00 | 0.6667 | 4 | 4 | 5 |
|----+---------------+---------------------+--------+----+------+-------|
| 5 | 2 | 2017-01-09 00:00:00 | 0.25 | 5 | 1 | 3 |
|----+---------------+---------------------+--------+----+------+-------|
| 6 | 2 | 2017-01-10 00:00:00 | 0.5 | 6 | 2 | 3 |
|----+---------------+---------------------+--------+----+------+-------|
| 7 | 2 | 2017-01-11 00:00:00 | 0.75 | 7 | 3 | 3 |
|----+---------------+---------------------+--------+----+------+-------|

Aggregate count by several weeks after field data in PostgreSQL

I have a query returns something like that:
registered_at - date of user registration;
action_at - date of some kind of action.
| registered_at | user_id | action_at |
-------------------------------------------------------
| 2015-05-01 12:00:00 | 1 | 2015-05-04 12:00:00 |
| 2015-05-01 12:00:00 | 1 | 2015-05-10 12:00:00 |
| 2015-05-01 12:00:00 | 1 | 2015-05-16 12:00:00 |
| 2015-04-01 12:00:00 | 2 | 2015-04-04 12:00:00 |
| 2015-04-01 12:00:00 | 2 | 2015-04-05 12:00:00 |
| 2015-04-01 12:00:00 | 2 | 2015-04-10 12:00:00 |
| 2015-04-01 12:00:00 | 2 | 2015-04-30 12:00:00 |
I'm trying to implement query that will returns me something like that:
weeks_after_registration - in this example limited by 3, in real task it will be limited by 6.
| user_id | weeks_after_registration | action_counts |
-------------------------------------------------------
| 1 | 1 | 1 |
| 1 | 2 | 1 |
| 1 | 3 | 1 |
| 2 | 1 | 2 |
| 2 | 2 | 1 |
| 2 | 3 | 0 |
You can use extract(days from (action_at - registered_at) / 7)+1 to get the number of weeks. Then count the number of actions grouped by the number of weeks.
select user_id, wk, count(*) actions
from (select user_id, extract(days from (action_at - registered_at) / 7)+1 wk from Table1) a
where wk <= 3
group by user_id, wk
If you must display rows where action_counts = 0 in the result, then you need to join with the all possible week numbers (1, 2, 3) and all possible user_ids (1, 2) like:
select b.user_id, a.wk, coalesce(c.actions, 0) actions
from (select * from generate_series(1, 3) wk) a
join (select distinct user_id from Table1) b on true
left join (
select user_id, wk, count(*) actions
from (select user_id, extract(days from (action_at - registered_at) / 7)+1 wk from Table1) a
where wk <= 3
group by user_id, wk
) c on a.wk = c.wk and b.user_id = c.user_id
order by b.user_id, a.wk;
fiddle