confusion in using select command in postgresql with timestamp column - postgresql

I have table which has structure like this.
CREATE TABLE users (
id serial NOT NULL,
created_at timestamp NOT NULL
)
I have more than 1000 records in this table.
This is my first query.
1 query
select id,created_at from users where id in (1051,1052)
This returns two rows which is correct as as expected. However when I use
2nd Query
select id,created_at from users where created_at = '2020-06-28'
or
select id,created_at from users where created_at = date '2020-06-28'
It returns nothing, this is not expected result as it should return two rows against it.
Similarly if I use this
3rd Query
select id, created_at from users where created_at between date '2020-06-28' and date '2020-06-28'
It returns nothing however I think this should also return two rows.
While this
4th Query
select id, created_at from users where created_at between date '2020-06-28' and date '2020-06-29'
returns two rows.
Show timezone returns correct timezong in which currently i am
I did`t understand this, why the results are different in 2nd, 3rd and 4th query. How can i get same result as of query 1 using 3rd query.

One single reason for all your queries is that you are comparing timestamp with date
in Query 2
You are comparing 2020-06-28 13:02:53 = 2020-06-28 00:00:00 which will not match so returning no records.
in Query 3
You are using between i.e. 2020-06-28 13:02:53 between 2020-06-28 00:00:00 and 2020-06-28 00:00:00 which will not match so returning no records.
in Query 4
You are using between i.e. 2020-06-28 13:02:53 between 2020-06-28 00:00:00 and 2020-06-29 00:00:00. Here both records are falling in those timestamps and you are getting the records
So you have to compare date values. As right operand is a date type value, you have to convert the left operand to date. try this
for 2nd Query
select id,created_at from users where date(created_at) = '2020-06-28'
for 3rd Query
select id, created_at from users where date(created_at) between date '2020-06-28' and date '2020-06-28'
You should opt 3rd method if you want to compare a date range. For one day only you should use 2nd query.

Because what you are doing is:
test(5432)=# select '2020-06-28'::timestamp;
timestamp
---------------------
06/28/2020 00:00:00
You are selecting for created_at that is exactly at midnight and there is none. The same thing when you do:
select id, created_at from users where created_at between date '2020-06-28' and date '2020-06-28'
You already corrected the mistake in your 3rd query in the 4th query:
select id, created_at from users where created_at between date '2020-06-28' and date '2020-06-29'
where you span the time from midnight of 06/28/2020 to midnight 06/29/2020
An alternate solution is:
create table dt_test(id integer, ts_fld timestamp);
insert into dt_test values (1, '07/04/2020 8:00'), (2, '07/05/2020 1:00'), (3, '07/05/2020 8:15');
select * from dt_test ;
id | ts_fld
----+---------------------
1 | 07/04/2020 08:00:00
2 | 07/05/2020 01:00:00
3 | 07/05/2020 08:15:00
select * from dt_test where date_trunc('days', ts_fld) = '07/05/2020'::date;
id | ts_fld
----+---------------------
2 | 07/05/2020 01:00:00
3 | 07/05/2020 08:15:00
In your case:
select id, created_at from users where date_trunc('days', created_at) = '2020-06-28'::date;

Related

ORDER BY MIN date

I need to fetch the number of employees per month, having a first work in a selected period. And I have to display only the month when the employee appears for the first time. My request works fine, but I need to order the result by date. Here is my request:
SELECT TO_CHAR(sub.minStartDate,'mm/YYYY') as date,
COUNT(DISTINCT sub.id) AS nombre
FROM (
SELECT MIN(sw.start_date) as minStartDate,
e.id
FROM employee e
INNER JOIN social_work sw ON e.id = sw.employee_id
GROUP BY e.id
HAVING MIN(sw.start_date) BETWEEN '2020-01-01' AND '2022-12-31'
) sub
GROUP BY date
ORDER BY date
And the result:
date | nombre
--------------
04/2021 | 2
05/2020 | 1
Excepted output:
date | nombre
--------------
05/2020 | 1
04/2021 | 2
I've tried to put sub.minStartDate in the ORDER BY clause but then I also have to put it in GROUP BY clause, what gives me this output :
date | nombre
--------------
05/2020 | 1
04/2021 | 1
04/2021 | 1
And it's not what I want.
You're ordering by date, which is the result of the TO_CHAR() function. The TO_CHAR() function returns a text, so your ORDER BY clause results in an alphanumeric sort.
Since you don't want to ORDER BY sub.minStartDate, you could try changing your format to put the least significant variable of the date (in this case, the month) to the right: TO_CHAR(sub.minStartDate, 'YYYY/mm').
If you can't change your format either, then you'll probably have to resort to grouping and ordering by minStartDate:
SELECT
TO_CHAR(sub.minStartDate,'mm/YYYY') as date,
TO_CHAR(sub.minStartDate,'YYYY/mm') sortingDate,
COUNT(DISTINCT sub.id) AS nombre
FROM
-- omitted for simplicity
GROUP BY date, sortingDate
ORDER BY sortingDate

Count data per day in a specific month in Postgresql

I have a table with a create date called created_at and a delete date called delete_at for each record. If the record was deleted, the field save that date; it's a logic delete.
I need to count the active records in a specific month. To understand what is an active record for me, let's see an example:
For this example we'll use this hypothetical record:
id | created_at | deleted_at
1 | 23-01-2014 | 05-06-2014
This record is active for every days between its creation date and delete date. Including that last. So if I need count the active record for March, in this case, this record must be counted in every days of that month.
I have a query (really easy to do) that show the actives records for a specific month, but my principal problem is how to count that actives for each day in that month.
SELECT
date_trunc('day', created_at) AS dia_creacion,
date_trunc('day', deleted_at) AS dia_eliminacion
FROM
myTable
WHERE
created_at < TO_DATE('01-04-2014', 'DD-MM-YYYY')
AND (deleted_at IS NULL OR deleted_at >= TO_DATE('01-03-2014', 'DD-MM-YYYY'))
Here you are:
select
TO_DATE('01-03-2014', 'DD-MM-YYYY') + g.i,
count( case (TO_DATE('01-03-2014', 'DD-MM-YYYY') + g.i) between created_at and coalesce(deleted_at, TO_DATE('01-03-2014', 'DD-MM-YYYY') + g.i)
when true then 1
else null
end)
from generate_series(0, TO_DATE('01-04-2014', 'DD-MM-YYYY') - TO_DATE('01-03-2014', 'DD-MM-YYYY')) as g(i)
left join myTable on true
group by 1
order by 1;
You can add more specific condition for joining only relevant records from myTable, but even without it gives you idea how to achieve counting as desired.

Postgresql: using 'with clause' to iterate over a range of dates

I have a database table that contains a start visdate and an end visdate. If a date is within this range the asset is marked available. Assets belong to a user. My query takes in a date range (start and end date). I need to return data so that for a date range it will query the database and return a count of assets for each day in the date range that assets are available.
I know there are a few examples, I was wondering if it's possible to just execute this as a query/common table expression rather than using a function or a temporary table. I'm also finding it quite complicated because the assets table does not contain one date which an asset is available on. I'm querying a range of dates against a visibility window. What is the best way to do this? Should I just do a separate query for each day in the date range I'm given?
Asset Table
StartvisDate Timestamp
EndvisDate Timestamp
ID int
User Table
ID
User & Asset Join table
UserID
AssetID
Date | Number of Assets Available | User
11/11/14 5 UK
12/11/14 6 Greece
13/11/14 4 America
14/11/14 0 Italy
You need to use a set returning function to generate the needed rows. See this related question:
SQL/Postgres datetime division / normalizing
Example query to get you started:
with data as (
select id, start_date, end_date
from (values
(1, '2014-12-02 14:12:00+00'::timestamptz, '2014-12-03 06:45:00+00'::timestamptz),
(2, '2014-12-05 15:25:00+00'::timestamptz, '2014-12-05 07:29:00+00'::timestamptz)
) as rows (id, start_date, end_date)
)
select data.id,
count(data.id)
from data
join generate_series(
date_trunc('day', data.start_date),
date_trunc('day', data.end_date),
'1 day'
) as days (d)
on days.d >= date_trunc('day', data.start_date)
and days.d <= date_trunc('day', data.end_date)
group by data.id
id | count
----+-------
1 | 2
2 | 1
(2 rows)
You'll want to convert it to using ranges instead, and adapt it to your own schema and data, but it's basically the same kind of query as the one you want.

A table with infinite rows?

I have a table posts:
Column | Type | Modifiers
-------------------+--------------------------+----------------------------------------------------
body | text | not null
from | character varying(2000) | not null
date | timestamp with time zone | not null
and I'd like to count how many rows a user has in one day, one row for every day in a given month.
In oracle I would "generate" a table with as many days the current month has, and then join the "date" column with the "generated" date.
Something like
> select *
2 from (select sysdate + level l from dual connect by level < 10)
3 /
L
----------
2013-06-07
2013-06-08
2013-06-09
2013-06-10
2013-06-11
2013-06-12
2013-06-13
2013-06-14
2013-06-15
9 rows selected.
Is there something similar in postgres?
http://diethardsteiner.blogspot.com/2012/03/postgresql-auto-generating-sample.html
I found this with just one google hit. U might try using it.
Incase the author removes or web page gets wiped out.
WITH date_series AS (
SELECT
DATE(GENERATE_SERIES(DATE '2012-01-01', DATE '2012-01-10','1 day')) AS generateddate
)
SELECT
generateddate
, EXTRACT(DAY FROM generateddate) AS day
, EXTRACT(MONTH FROM generateddate) AS month
, EXTRACT(QUARTER FROM generateddate) AS quarter
, EXTRACT(YEAR FROM generateddate) AS year
FROM
date_series;

SQL query to convert date ranges to per day records

Requirements
I have data table that saves data in date ranges.
Each record is allowed to overlap previous record(s) (record has a CreatedOn datetime column).
New record can define it's own date range if it needs to hence can overlap several older records.
Each new overlapping record overrides settings of older records that it overlaps.
Result set
What I need to get is get per day data for any date range that uses record overlapping. It should return a record per day with corresponding data for that particular day.
To convert ranges to days I was thinking of numbers/dates table and user defined function (UDF) to get data for each day in the range but I wonder whether there's any other (as in better* or even faster) way of doing this since I'm using the latest SQL Server 2008 R2.
Stored data
Imagine my stored data looks like this
ID | RangeFrom | RangeTo | Starts | Ends | CreatedOn (not providing data)
---|-----------|----------|--------|-------|-----------
1 | 20110101 | 20110331 | 07:00 | 15:00
2 | 20110401 | 20110531 | 08:00 | 16:00
3 | 20110301 | 20110430 | 06:00 | 14:00 <- overrides both partially
Results
If I wanted to get data from 1st January 2011 to 31st May 2001 resulting table should look like the following (omitted obvious rows):
DayDate | Starts | Ends
--------|--------|------
20110101| 07:00 | 15:00 <- defined by record ID = 1
20110102| 07:00 | 15:00 <- defined by record ID = 1
... many rows omitted for obvious reasons
20110301| 06:00 | 14:00 <- defined by record ID = 3
20110302| 06:00 | 14:00 <- defined by record ID = 3
... many rows omitted for obvious reasons
20110501| 08:00 | 16:00 <- defined by record ID = 2
20110502| 08:00 | 16:00 <- defined by record ID = 2
... many rows omitted for obvious reasons
20110531| 08:00 | 16:00 <- defined by record ID = 2
Actually, since you are working with dates, a Calendar table would be more helpful.
Declare #StartDate date
Declare #EndDate date
;With Calendar As
(
Select #StartDate As [Date]
Union All
Select DateAdd(d,1,[Date])
From Calendar
Where [Date] < #EndDate
)
Select ...
From Calendar
Left Join MyTable
On Calendar.[Date] Between MyTable.Start And MyTable.End
Option ( Maxrecursion 0 );
Addition
Missed the part about the trumping rule in your original post:
Set DateFormat MDY;
Declare #StartDate date = '20110101';
Declare #EndDate date = '20110501';
-- This first CTE is obviously to represent
-- the source table
With SampleData As
(
Select 1 As Id
, Cast('20110101' As date) As RangeFrom
, Cast('20110331' As date) As RangeTo
, Cast('07:00' As time) As Starts
, Cast('15:00' As time) As Ends
, CURRENT_TIMESTAMP As CreatedOn
Union All Select 2, '20110401', '20110531', '08:00', '16:00', DateAdd(s,1,CURRENT_TIMESTAMP )
Union All Select 3, '20110301', '20110430', '06:00', '14:00', DateAdd(s,2,CURRENT_TIMESTAMP )
)
, Calendar As
(
Select #StartDate As [Date]
Union All
Select DateAdd(d,1,[Date])
From Calendar
Where [Date] < #EndDate
)
, RankedData As
(
Select C.[Date]
, S.Id
, S.RangeFrom, S.RangeTo, S.Starts, S.Ends
, Row_Number() Over( Partition By C.[Date] Order By S.CreatedOn Desc ) As Num
From Calendar As C
Join SampleData As S
On C.[Date] Between S.RangeFrom And S.RangeTo
)
Select [Date], Id, RangeFrom, RangeTo, Starts, Ends
From RankedData
Where Num = 1
Option ( Maxrecursion 0 );
In short, I rank all the sample data preferring the newer rows that overlap the same date.
Why do it all in DB when you can do it better in memory
This is the solution (I eventually used) that seemed most reasonable in terms of data transferred, speed and resources.
get actual range definitions from DB to mid tier (smaller amount of data)
generate in memory calendar of a certain date range (faster than in DB)
put those DB definitions in (much easier and faster than DB)
And that's it. I realised that complicating certain things in DB is not not worth it when you have executable in memory code that can do the same manipulation faster and more efficient.