Add a generated column in postgres using to_char and a date - sql

I have a date column created_at in a postgres table. I have a long running query that groups the data by week, so part of the query is
(SELECT TO_CHAR(score.created_at, 'IYYY-IW') AS week,
customer_id, AVG(score) AS avg_week_score FROM score
This is inefficient, and I would like to use the new generated column functionality in Postgres 12 to rather store a text version of each date as it is inserted.
I tried to run
alter table score add column gen_week text generated always as (TO_CHAR(created_at, 'IYYY-IW')) stored;
but got
ERROR: generation expression is not immutable
I guessed that this has to do with the fact that created_at is dependent on locale, and especially timezone, so I tried
alter table score add column week_gen text generated always as (TO_CHAR(created_at::timestamptz at time zone 'UTC', 'IYYY-IW')) stored;
to make the timezone explicit, but this produces the same error.
Is there a way to make to_char behave immutably with a date object, or do I have to define my own immutable function based on to_char?

The problem is that to_char() could take modifies from the locale -- and functions either are or are not immutable.
You can do the same thing using extract():
alter table score add column gen_week text generated always as
((extract(isoyear from created_at) * 100 + extract(week from created_at))::text ) stored;
Actually, the above doesn't put the hyphen in, so:
alter table score add column gen_week text generated always as
(extract(isoyear from created_at)::text || '-' || lpad(extract(week from created_at)::text, 2, '0')) stored;
Here is a db<>fiddle.

Related

Timestamp to date | Oracle DB

I want to select a column which is from datatype "datetime" in Orcale DB in date format.
column
1/31/2006 22:00:00 AM
I tried the following queries and got errors like below
select DATE_FORMAT(column, 'YYYY-MM-DD') as column from table
ORA-00904: "DATE_FORMAT": invalid identifier
select TO_DATE(column, 'YYYY-MM-DD') as column from table
ORA-00918: column ambiguously defined
select TO_DATE(column, 'YYYY-MM-DD') from table
org.apache.avro.SchemaParseException: Illegal character in: to_date(column,'yyyy-mm-dd')
What is the right syntax for this?
You should be using a to_char to convert a date to a specific string format.
select to_char(dat_column, 'YYYY-MM-DD') as column from table;
If you are expecting a date as return type, then that is more about the client you are using. You could set a session level parameter like this:
alter session set nls_date_format='YYYY-MM-DD';
In Oracle, there is no "datetime" data type. There is either DATE or TIMESTAMP and they both have year, month, day, hour, minute and second components (TIMESTAMP also has optional fractional seconds and time zone components). Both of those data types are binary data types and do NOT store a format.
If you want to set the time component of a DATE to midnight then use:
SELECT TRUNC(date_column) AS date_column_at_midnight
FROM table_name;
If you want to display a DATE column with only year, month and day components then you need to convert to a data type that does support a formatting; i.e. a string.
SELECT TO_CHAR(date_column, 'YYYY-MM-DD') AS formatted_date_string
FROM table_name;
To convert a column of datatype TIMESTAMP to datatype DATE use the CAST function (docs) :
select CAST(column AS DATE) as dt_column from table
The error "ORA-00918: column ambiguously defined" indicates that the SQL engine is unable to determine what table the column belongs to. To avoid that make sure to alias all joined tables and use the alias for those columns. Like this:
select CAST(t1.column AS DATE) as dt_column from table t1

Converting all data in a Varchar column to a date format

I'm working on a table with a column, 'Expiry Date', as a varchar with all data formatted as DD/MM/YYYY.
The creator of the table has used the wrong type for this expiry date column and now the client needs to filter and show all records before and after the current date as the time. This means the type needs to be changed to date or datetime type to be able to use the CURDATE() function.
However, the current format of the values does not satisfy and wont allow the type to change unless the format is changed to YYYY-MM-DD (or similar).
Is there any way to mass format the values in this column and this column alone as there are thousands of entries and formatting one by one would be extremely time consuming.
Let me assume that you are using MySQL.
Perhaps the simplest method is to add a generated column that is a date:
alter table t add column expiry_date_date as
(str_to_date(expiry_date, '%d/%m/%Y'));
You can also fix the data:
update t
set expiry_date = str_to_date(expiry_date, '%d/%m/%Y');
This will implicitly convert the result of str_to_date() to a date, which will be in the YYYY-MM-DD format.
More importantly, you can then do:
alter table t modify column expiry_date date;
Here is a db<>fiddle.
You can do similar operations in other databases, but the exact code is a bit different.
What you need is an update on that column, but before doing it I suggest you to check if the result is what you want.
select replace(expiry_date, '/', '-') new_expiry_date
from table_name
If this returns the results you want you can run the following update:
update table_name
set expiry_date = replace(expiry_date, '/', '-')
Of course you will need to replace expiry_date and table_name with the names of your column and table.

How to order by date when a column is of type nvarchar in Microsoft SQL Server

I have an issue where I created a column sent_Date of type nvarchar while it's storing date and time.
Now when I try to sort it by date, it's not doing so correctly.
I am using this query:
select *
from tbl_skip
where sent_date > '9/27/2020 7:29:11 PM'
order by SENT_DATE desc
Like the comments have said, the real solution here is fix your design. That means changing the column's data type an nvarchar to a date and time data type, I'm going to use a datetime2(0) here, as your data is accurate to a second, so seems the most appropriate.
Firstly we need to convert the value to as ISO value. I'm also, however, going to create a new column called Bad_Sent_Date, to store values that could not be converted. Experience has taught many of us that systems that incorrectly use string data types to store dates (or numerical data) rarely have good data integrity rules on the value (because if they did, it wouldn't be an nvarchar) to start, so have bad values like '29/02/2019' or mix styles, such as having both '09/29/2020' and '29/09/2020'.
Based on the single example we have, I will assume your data is supposed to be in the format MM/dd/yy hh:mm:ss AM/PM:
ALTER TABLE dbo.tbl_skip ADD Bad_Sent_Date nvarchar(30) NULL;
GO
UPDATE TABLE dbo.tbl_skip
SET Bad_Sent_Date = CASE WHEN TRY_CONVERT(datetime2(0),Sent_date,101) IS NULL THEN Sent_date END,
Sent_Date = CONVERT(nvarchar(30),TRY_CONVERT(datetime2(0),Sent_date,101),126);
GO
Now we have an ISO format, we can change the table's data type:
ALTER TABLE dbo.tbl_skip ALTER COLUMN Sent_date datetime2(0) NULL;
Note that if you do have constraints on the column Sent_date, or it isn't NULLable, you will first need to DROP said CONSTRAINTs, change the column to be NULLable and then recreate said CONSTRAINTs after you have altered the column.
You can also review the "dates" that failed to be converted with the following:
SELECT bad_sent_date
FROM dbo.tbl_skip
WHERE bad_sent_date IS NOT NULL
AND Sent_date IS NULL;
Once that's all done, then your query simply needs an update to use an unambiguous date literal, and it'll work:
SELECT *
FROM tbl_skip
WHERE sent_date > '2020-09-27T19:29:11' --'9/27/2020 7:29:11 PM'
ORDER BY SENT_DATE DESC;
You can convert the data from string to datetime.
Please note i used 100 as an example to convert to date time. You can use below link to see if its behaving correctly. link -https://www.w3schools.com/sql/func_sqlserver_convert.asp
select *
from tbl_skip
where sent_date > convert(datetime,'9/27/2020 7:29:11 PM',100)
ORDER BY CONVERT(datetime,SENT_DATE,100) desc
You should be able to convert it to a datetime
select *
from tbl_skip
where sent_date > '9/27/2020 7:29:11 PM'
order by convert(datetime,SENT_DATE) desc
Just make sure the data in column is legit. If so, it would make sense to convert the column type to a datetime.
alter table tbl_skip alter column SENT_DATE datetime
If the data is mixed, you may need to fix it or use something like
order by try_convert(datetime,SENT_DATE) desc

PostgreSQL - Create table and set specific date format

I want to create a new table and set a date type with a specific format. Is that possible?
For example:
CREATE TABLE User (
...
EXPIRATION DATE " YYYY/MM"
...
)
I suggest a different approach: Never store date / time as character type (text, varchar(), ...) to begin with. Use an appropriate type, probably date in your case.
Also, never use reserved words as identifier. user is just not possible to begin with, you would have to double-quote, which I would discourage.
Could look like this:
CREATE TABLE usr (
usr_id serial PRIMARY KEY
,usr text UNIQUE
,expiration_date date
...
);
Now, various input formats are possible, as long as they are unambiguous. The related question #DrColossos has linked to in his comment has more on that.
The manual has all the details.
To enforce a particular input format, you could run the text literal through the to_date() function. Example:
INSERT INTO usr (usr, expiration_date)
VALUES ('flippin_user', to_date($my_date_literal, ' YYYY/MM');
Note: if you include the leading blank in the pattern, it is expected from the input!
Finally, you can format your date any way you like with to_char() on output:
SELECT usr, to_char(expiration_date, ' YYYY/MM') AS formatted_exp_date
WHERE usr_id = 1;

rails: date type and GetDate

This is a follow up to this question: Unique responses rails gem
I'm going to create an index based on the user id, url and a date type.
I want date type (not datetime type) because I want the day, the 24 hour day to be part of the index to avoid duplication of page views counts on the same day.
In other words: A view only counts once in a day by a visitor.
I also want the default value of that column (viewdate) to be the function GETDATE().
This is what I have in my migration:
execute "ALTER TABLEpage_viewsADD COLUMN viewdate datetime DEFAULTGETDATE()`"
But the value viewdate is always empty. What am I missing?
(as an aside, any other suggestions for accomplishing this goal?)
You're declaring the column as datetime type, not as date. Also I'm not sure MySQL supports default value seeding when altering the table.
Try this:
execute "ALTER TABLE page_views ADD COLUMN viewdate DATE"
PageView.update_all('viewdate=CURDATE()')