Getting different results when converting timestamp to text - postgresql

I'm tying to convert timestamp field in format (YYYY-MM-DD HH:MI:SS.MS) to it's text representation. But for some reason getting different results:
If I'm trying to convert timestamp from table:
create table test_dt (dt timestamp);
insert into test_dt values ('2016-04-14 17:10:33.007');
insert into test_dt values ('2016-04-14 17:10:33');
Timestamps are getting truncated up to the seconds:
select dt::text from test_dt;
dt
---------------------
2016-04-14 17:10:33
2016-04-14 17:10:33
(2 rows)
But if Im using direct select statement, everything works:
select '2016-04-14 17:10:33.007'::timestamp::text;
varchar
-------------------------
2016-04-14 17:10:33.007
(1 row)
The question is not how to convert it to the text from table and include precision, but rather:
what am I doing wrong?
why those 2 approaches returns different result?
what's the rational behind this behaviour?
UPDATE
as #muistooshort suggested the following command gives the correct result:
select c::text from (select '2016-04-14 17:10:33.007'::timestamp union select '2016-04-14 17:10:33'::timestamp ) as t(c);
c
-------------------------
2016-04-14 17:10:33
2016-04-14 17:10:33.007
(2 rows)
and yes test_dt does have .007 :
select * from test_dt;
dt
-------------------------
2016-04-14 17:10:33
2016-04-14 17:10:33.007
(2 rows)
Also to_char gives milliseconds from the table:
select to_char(dt, 'YYYY-MM-DD HH:MI:SS.MS') from test_dt;
to_char
-------------------------
2016-04-14 05:10:33.000
2016-04-14 05:10:33.007
(2 rows)

Related

CROSSTAB PostgreSQL - Alternative for PIVOT in Oracle

I'm migrating a query of Oracle pivot to PostgreSQL crosstab.
create table(cntry numeric,week numeric,year numeric,days text,day text);
insert into x_c values(1,15,2015,'DAY1','MON');
...
insert into x_c values(1,15,2015,'DAY7','SUN');
insert into x_c values(2,15,2015,'DAY1','MON');
...
values(4,15,2015,'DAY7','SUN');
I have 4 weeks with 28 rows like this in a table. My Oracle query looks like this:
SELECT * FROM(select * from x_c)
PIVOT (MIN(DAY) FOR (DAYS) IN
('DAY1' AS DAY1 ,'DAY2' DAY2,'DAY3' DAY3,'DAY4' DAY4,'DAY5' DAY5,'DAY6' DAY6,'DAY7' DAY7 ));
Result:
cntry|week|year|day1|day2|day3|day4|day4|day6|day7|
---------------------------------------------------
1 | 15 |2015| MON| TUE| WED| THU| FRI| SAT| SUN|
...
4 | 18 |2015| MON| ...
Now I have written a Postgres crosstab query like this:
select *
from crosstab('select cntry,week,year,days,min(day) as day
from x_c
group by cntry,week,year,days'
,'select distinct days from x_c order by 1'
) as (cntry numeric,week numeric,year numeric
,day1 text,day2 text,day3 text,day4 text, day5 text,day6 text,day7 text);
I'm getting only one row as output:
1|17|2015|MON|TUE| ... -- only this row is coming
Where am I doing wrong?
ORDER BY was missing in your original query. The manual:
In practice the SQL query should always specify ORDER BY 1,2 to ensure that the input rows are properly ordered, that is, values with the same row_name are brought together and correctly ordered within the row.
More importantly (and more tricky), crosstab() requires exactly one row_name column. Detailed explanation in this closely related answer:
Crosstab splitting results due to presence of unrelated field
The solution you found is to nest multiple columns in an array and later unnest again. That's needlessly expensive, error prone and limited (only works for columns with identical data types or you need to cast and possibly lose proper sort order).
Instead, generate a surrogate row_name column with rank() or dense_rank() (rnk in my example):
SELECT cntry, week, year, day1, day2, day3, day4, day5, day6, day7
FROM crosstab (
'SELECT dense_rank() OVER (ORDER BY cntry, week, year)::int AS rnk
, cntry, week, year, days, day
FROM x_c
ORDER BY rnk, days'
, $$SELECT unnest('{DAY1,DAY2,DAY3,DAY4,DAY5,DAY6,DAY7}'::text[])$$
) AS ct (rnk int, cntry int, week int, year int
, day1 text, day2 text, day3 text, day4 text, day5 text, day6 text, day7 text)
ORDER BY rnk;
I use the data type integer for out columns cntry, week, year because that seems to be the (cheaper) appropriate type. You can also use numeric like you had it.
Basics for crosstab queries here:
PostgreSQL Crosstab Query
I got this figured out from http://www.postgresonline.com/journal/categories/24-tablefunc
select year_wk_cntry.t[1],year_wk_cntry.t[2],year_wk_cntry.t[3],day1,day2,day3,day4,day5,day6,day7
from crosstab('select ARRAY[country :: numeric,week,year] as t,days,min(day) as day
from x_c group by country,week,year,days order by 1,2
','select distinct days from x_c order by 1')
as year_wk_cntry (t numeric[],day1 text,day2 text,day3 text,
day4 text, day5 text,day6 text,day7 text);
thanks!!

postgresql error : Multiple decimal points

So I'm having this query:
SELECT
TO_CHAR(date_part('hour', created_at), 'YYYY-MM-DD HH24'),
to_char(created_at, 'day') ",
COUNT(*) AS "
FROM table
GROUP BY 1,2
ORDER BY 1 DESC
When I execute the query I get this:
ERROR: multiple decimal points
Searching stackoverflow I found some recommendations here:
How to format bigint field into a date in Postgresql? but I don't get why do I have to divide by 1000 and how this would apply in the case of the date_part function.
I assume created_at is a timestamp?.. I'm choosing from date_part(text, timestamp) and date_part(text, interval), if so date_part will return a double precision, to which you try to apply the mask 'YYYY-MM-DD HH24', eg:
v=# select date_part('hour', now());
date_part
-----------
9
and I don't see how you could possibly get year, month, day and hour from nine...
Yet I assume you wanted to apply the mask against truncated date to the hour precision, which is done with date_trunc(text, timestamp):
v=# select date_trunc('hour', now());
date_trunc
------------------------
2017-06-20 09:00:00+01
(1 row)
so now you can apply the time format:
v=# select to_char(date_trunc('hour', now()),'YYYY-MM-DD HH24');
to_char
---------------
2017-06-20 09
(1 row)
but if this is what you want, then you don't need to truncate time at all:
v=# select to_char(now(),'YYYY-MM-DD HH24');
to_char
---------------
2017-06-20 09
(1 row)
https://www.postgresql.org/docs/current/static/functions-datetime.html

PostgreSql - Adding YEar and Month to Table

I am creating a Customer table and i want one of the attributes to be Expiry Date of credit card.I want the format to be 'Month Year'. What data type should i use? i want to use date but the format is year/month/day. Is there any other way to restrict format to only Month and year?
You can constrain the date to the first day of the month:
create table customer (
cc_expire date check (cc_expire = date_trunc('month', cc_expire))
);
Now this fails:
insert into customer (cc_expire) values ('2014-12-02');
ERROR: new row for relation "customer" violates check constraint "customer_cc_expire_check"
DETAIL: Failing row contains (2014-12-02).
And this works:
insert into customer (cc_expire) values ('2014-12-01');
INSERT 0 1
But it does not matter what day is entered. You will only check the month:
select
date_trunc('month', cc_expire) > current_date as valid
from customer;
valid
-------
t
Extract year and month separately:
select extract(year from cc_expire) "year", extract(month from cc_expire) "month"
from customer
;
year | month
------+-------
2014 | 12
Or concatenated:
select to_char(cc_expire, 'YYYYMM') "month"
from customer
;
month
--------
201412
Use either
char(5) for two-digit years, or
char(7) for four-digit years.
Code below assumes two-digit years, which is the form that matches all my credit cards. First, let's create a table of valid expiration dates.
create table valid_expiration_dates (
exp_date char(5) primary key
);
Now let's populate it. This code is just for 2013. You can easily adjust the range by changing the starting date (currently '2013-01-01'), and the "number" of months (currently 11, which lets you get all of 2013 by adding from 0 to 11 months to the starting date).
with all_months as (
select '2013-01-01'::date + (n || ' months')::interval months
from generate_series(0, 11) n
)
insert into valid_expiration_dates
select to_char(months, 'MM') || '/' || to_char(months, 'YY') exp_date
from all_months;
Now, in your data table, create a char(5) column, and set a foreign key reference from it to valid_expiration_dates.exp_date.
While you're busy with this, think hard about whether "exp_month" might be a better name for that column than "exp_date". (I think it would.)
As another idea you could essentially create some brief utilities to do this for you using int[]:
CREATE OR REPLACE FUNCTION exp_valid(int[]) returns bool LANGUAGE SQL IMMUTABLE as
$$
SELECT $1[1] <= 12 AND (select count(*) = 2 FROM unnest($1));
$$;
CREATE OR REPLACE FUNCTION first_invalid_day(int[]) RETURNS date LANGUAGE SQL IMMUTABLE AS
$$
SELECT (to_date($1[2]::text || $1[1]::text, CASE WHEN $1[2] < 100 THEN 'YYMM' ELSE 'YYYYMM' END) + '1 month'::interval)::date;
$$;
These work:
postgres=# select exp_valid('{04,13}');
exp_valid
-----------
t
(1 row)
postgres=# select exp_valid('{13,04}');
exp_valid
-----------
f
(1 row)
postgres=# select exp_valid('{04,13,12}');
exp_valid
-----------
f
(1 row)
Then we can convert these into a date:
postgres=# select first_invalid_day('{04,13}');
first_invalid_day
-------------------
2013-05-01
(1 row)
This use of arrays does not violate any normalization rules because the array as a whole represents a single value in its domain. We are storing two integers representing a single date. '{12,2}' is December of 2002, while '{2,12}' is Feb of 2012. Each represents a single value of the domain and is therefore perfectly atomic.

How to select total time from three time columns (SQL Server 2008)

I have table with columns of time(0) datatypes:
Name TimeOne TimeTwo TimeThree
------ ---------------- ---------------- ----------------
Sarah 06:45:00 03:30:00 NULL
John 06:45:00 NULL NULL
How to make SELECT statement so that the "CalculatedTotal" column is total from TimeOne, TimeTwo and TimeThree per row?
What I'd like to have is select query like this:
SELECT
Name,
TimeOne,
TimeTwo,
TimeThree,
TimeOne + TimeTwo + TimeThree As CalculatedTotal
FROM
MyTable
I'd like to get back resultset like this:
Name TimeOne TimeTwo TimeThree CalculatedTotal
------ ---------------- ---------------- ---------------- ---------------
Sarah 06:45:00 03:30:00 NULL 10:15:00
John 06:45:00 NULL NULL 06:45:00
Just using plus operator in select statement gives you an error:
Operand data type time is invalid for add operator.
You could determine the number of seconds for each time value, add them together and convert back to time:
select TimeOne, TimeTwo, TimeThree,
cast(dateadd(s, isnull(datediff(s, 0, TimeOne), 0) + isnull(datediff(s, 0, TimeTwo), 0) + isnull(datediff(s, 0, TimeThree), 0), 0) as time(0)) as CalculatedTotal
from MyTable
Try the below script.
select convert(time, CONVERT(datetime, '00:08:00.000') + CONVERT(datetime, '00:07:00.000'))
select convert(time,cast('00:08:00.000'as datetime)+cast('00:07:00.000' as datetime) ) as 'T'
I belive you can use the SUM() function
SELECT
Name,
TimeOne,
TimeTwo,
TimeThree,
SUM(TimeOne+TimeTwo+TimeThree) As CalculatedTotal
FROM
MyTable
But equally you might need to convert each to seconds first using the SECOND() function, then use SEC_TO_TIME() on the result.
Edit: I've just had a look at the manual:
The SUM() and AVG() aggregate functions do not work with temporal
values. (They convert the values to numbers, which loses the part
after the first nonnumeric character.) To work around this problem,
you can convert to numeric units, perform the aggregate operation, and
convert back to a temporal value. Examples:
SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(time_col))) FROM tbl_name;
SELECT FROM_DAYS(SUM(TO_DAYS(date_col))) FROM tbl_name;

TSQL to combine a date field and a time field

Using logparser to import IIS logs to a db results in one column that has the date value and a second field for time:
2010-05-25 00:00:00.000
and
2010-01-01 11:11:58.000
I'd like to code an after insert trigger that combines the 2 fields.
You can just add the two values after casting them to DATE and TIME datatypes, if you're using SQL Server 2008 or later. Here's an example.
declare #datet datetime;
set #datet = GETDATE();
select
#datet,
cast(#datet as date),
cast(#datet as time);
select
cast(cast(#datet as date) as datetime),
cast(cast(#datet as time) as datetime),
cast(cast(#datet as date) as datetime) + cast(cast(#datet as time) as datetime);
In case somebody else stumbles on this thread (or in case the original poster still can use this answer), look into the TO_TIMESTAMP ( date , time ) function in LogParser which lets you combine a date-only timestamp with a time-only timestamp into a full timestamp value... and saves you from having to convert in the db...
Try this:
DECLARE #Date varchar(23)
,#Time varchar(23)
,#Both datetime
SELECT #Date='2010-05-25 00:00:00.000'
,#Time='2010-01-01 11:11:58.000'
SELECT #Both=LEFT(#Date,10)+' '+RIGHT(#Time,12)
SELECT #Both
OUTPUT:
-----------------------
2010-05-25 11:11:58.000
(1 row(s) affected)
Set based:
DECLARE #INSERTED table(RowID int, DateOf varchar(23), TimeOf varchar(23), DateTimeOf datetime)
INSERT #INSERTED VALUES (1,'2010-05-25 00:00:00.000','2010-01-01 11:11:58.000',null)
INSERT #INSERTED VALUES (2,'2010-04-05 00:00:00.000','2010-01-01 12:34:56.789',null)
INSERT #INSERTED VALUES (3,'2010-03-15 00:00:00.000','2010-01-01 01:01:01.000',null)
UPDATE #INSERTED
SET DateTimeOf=LEFT(DateOf,10)+' '+RIGHT(TimeOf,12)
SELECT * FROM #INSERTED
OUTPUT:
RowID DateOf TimeOf DateTimeOf
------- ----------------------- ----------------------- -----------------------
1 2010-05-25 00:00:00.000 2010-01-01 11:11:58.000 2010-05-25 11:11:58.000
2 2010-04-05 00:00:00.000 2010-01-01 12:34:56.789 2010-04-05 12:34:56.790
3 2010-03-15 00:00:00.000 2010-01-01 01:01:01.000 2010-03-15 01:01:01.000
(3 row(s) affected)
If you have a choice to add the output into a new column, you can also make that new column as a computed column and define it to combine those 2 columns (at a design time).
Dev