check constraint for day and month only in Oracle SQL [duplicate] - sql

This question already has answers here:
Using date in a check constraint, Oracle
(5 answers)
Closed 7 years ago.
So I need a constraint that will prevent a user from entering a day/month other than the first day of the quarter and last day of the quarter, as well as a date that the table will be 'locked' for editing. I did some searching online, and thought I found the answer, but when I tried it I got an error.
alter table TABLE_NAME
add constraint data_beg_dt_chk check
(trunc(data_beg_dt, 'DD-MON') = (trunc(to_date('01-JAN', 'DD-MON'))||(trunc(sysdate, 'YYYY')))
OR trunc(data_beg_dt, 'DD-MON') = (trunc(to_date('01-APR', 'DD-MON'))||(trunc(sysdate, 'YYYY')))
OR trunc(data_beg_dt, 'DD-MON') = (trunc(to_date('01-JUL', 'DD-MON'))||(trunc(sysdate, 'YYYY')))
OR trunc(data_beg_dt, 'DD-MON') = (trunc(to_date('01-OCT', 'DD-MON'))||(trunc(sysdate, 'YYYY'))));
I get the error:
SQL Error: ORA-02436: date or system variable wrongly specified in CHECK constraint
02436. 00000 - "date or system variable wrongly specified in CHECK constraint"
*Cause: An attempt was made to use a date constant or system variable,
such as USER, in a check constraint that was not completely
specified in a CREATE TABLE or ALTER TABLE statement. For
example, a date was specified without the century.
*Action: Completely specify the date constant or system variable.
Setting the event 10149 allows constraints like "a1 > '10-MAY-96'",
which a bug permitted to be created before version 8.
Does anyone know another way I can use just the day and month, but at the same time keep the current year that the user has enetered?
Thanks

You cannot have a date or datetime without a year, but you're trying to create such dates with the to_date() function. Dates are their own data type. You cannot create partial dates and concatenate them as if they were strings (though you could do that with actual char or varchar data). Moreover, you're making it a lot harder than it needs to be. Oracle's EXTRACT() function can extract day and / or month numbers from dates, and that's what you really want, especially given that you can do math on month numbers.
For example:
alter table TABLE_NAME
add constraint data_beg_dt_chk
check EXTRACT(day from data_beg_dt) = 1
and MOD(EXTRACT(month from data_beg_dt), 3) = 1;
Note that unlike the constraint you tried to write, the above does not constrain the dates to be in the current year. If Oracle even accepted such a constraint, it would result in a table in which every new year, every row would go from obeying the constraint to violating it, without any data having changed.
If you want the database to prevent the table from being modified outside of a certain date range, or if you want to restrict changes to certain rows based on the current date, then you should look into triggers instead of table constraints for that purpose.

Related

SQL - Conversion failed when converting date and/or time from character string

I have 3 tables in the database that I'm working on. Out of 3, two of the tables have columns that include dates. When I checked the information schema of the columns I found that dates have the wrong data type. If you see the picture below, the highlighted columns should be stored as DATE data type.
So, I used the following query to change their data type from varchar to DATE:
ALTER TABLE [dbo].[Customer]
ALTER COLUMN DOB DATE;
ALTER TABLE [dbo].[Transactions]
ALTER COLUMN tran_date DATE;
The error that I get is:
Conversion failed when converting date and/or time from character string.
Please let me know how I can fix this error. Thanks!
What you can do is update the value using try_convert() first and then alter the column name. Note: This will set any invalid values to NULL.
update customer
set dob = try_convert(date, dob);
alter table customer alter column dbo date;
If you want to see the bad values, then before you change the table, run:
select c.*
from customer c
where try_convert(date, dob) is null and dob is not null;
You may have other ideas on how to fix the values.
You can't change from varchar to date or time or datetime by altering the column. Why? Because SQL Server does not know if the field contains '1/1/2020' or 'My dog is cute'. You will have to rebuild the table with the proper data types and then CAST() or CONVERT() the values to a true date time.
Underneath the hood, this makes more sense. A char/varchar uses one byte per character. nchar/nvarchar uses 2 bytes per character. A datetime is a number, not a character. This means you need a routine that changes this into the correct number. If you want to get deeper, the number is the number of ticks (nanoseconds) since midnight on January 1, 0001 in the Gregorian Calendar. More info.

Add a generated column in postgres using to_char and a date

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.

Make a constraint for SYSDATE minus a date of birth

I would love to know what I'm doing wrong ,
I would like to add a CHECK constraint in oracle to make sure a user is over 18
So I did
ALTER TABLE User
ADD CONSTRAINT check_age CHECK(TO_CHAR(SYSDATE, 'yyyy/mm/dd')- TO_CHAR(dateOfBirth, 'yyyy/mm/dd')> 18)
But Im receiving error
Cause: An attempt was made to use a date constant or system variable,
such as USER, in a check constraint that was not completely
specified in a CREATE TABLE or ALTER TABLE statement. For
example, a date was specified without the century.
*Action: Completely specify the date constant or system variable.
Setting the event 10149 allows constraints like "a1 > '10-MAY-96'",
which a bug permitted to be created before version 8.
Why it's wrong?
I still don't understand why I can't add it and I would kindly love if someone can explain it to me
Thanks
Starting in Oracle 11g, while you can't directly use sysdate in a constraint, you could get the same effect with a virtual column:
create function over_18 (
p_birth_date in User.dateOfBirth%type
) return varchar2 deterministic is
begin
return case when months_between(sysdate, p_birth_date) /12 > 18 then 'Y' else 'N' end;
end over_18;
/
alter table User add (over_18_ind as (cast(over_18(dateOfBirth) as varchar2(1))));
/
alter table User add constraint check_age check (over_18_ind = 'Y');
/
Based on an article here.
Even if Oracle allowed you to use sysdate in a constraint, your constraint wouldn't work anyway, since the formats you converted your dates to can't be implicitly cast back to numbers to subtract (I supplied an alternate age check).

Create a table with default values that use the primary key column in a function

I just dabble in Oracle, so go easy on me if this is basic...I've searched extensively for an answer and the answer doesn't exist, it's not possible, or my searches haven't used the correct terminology.
Anyway, I'm writing DDL that will create a date dimension table. I've got a date key, which is the primary key for the table, that I'm trying to propagate across all the other columns in the table to use as the default value for those columns when a new value is added to the date key column.
Here's a snippet of what I've tried so far, but gave me the error "ORA-00984: column not allowed here":
CREATE TABLE DATE_DIM
(
DATE_KEY DATE NOT NULL,
CONSTRAINT DATE_KEY_PK PRIMARY KEY (DATE_KEY),
DAY_NUMBER NUMBER(2) DEFAULT TO_NUMBER(TO_CHAR(DATE_KEY,'DD')),
DAY_NAME VARCHAR(36) DEFAULT TO_CHAR(DATE_KEY,'Day')
)
If I substitute SYSDATE for DATE_KEY, I don't return any errors. But, assuming everything that I'm conceptualizing comes to fruition, I will need to insert a more specific date than sysdate...and just for clarity's sake, no other columns would be update-able.
Ultimately, this idea would expand to include other tables with different types of data, so I understand if you're wondering why I wouldn't just create a large table full of dates using a script, Excel, etc. I just thought the date dimension provided a good example to explain what I'm trying to achieve.
Thanks for your help.
You can use virtual columns for something like this. These are calculated on the fly based on the expression that defines the columns:
CREATE TABLE date_dim (
Date_Key DATE NOT NULL,
Day_Number AS (EXTRACT(DAY FROM Date_Key)),
Day_Name AS (TO_CHAR(Date_Key, 'FMDay'))
)
Some notes:
I've generally found it easier to not specify the type for a virtual column. You can, but often something weird happens. Your table was no exception: Oracle complained about the VARCHAR2(36) for Day_Name, saying it should be at least VARCHAR2(75).
I used the EXTRACT function to get the day number because I think it's more descriptive for this; you should use whatever you're most comfortable with.
I used the format string FMDay for Day_Name because the Day format code returns a string that's the length of the longest day name (Wednesday in English), so all other day names will have trailing spaces. The FM format code does a lot of useful things, and one of them is to trim trailing spaces.

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()')