My output without where clause ( sample )
ID
NAMEN
NAME
BEGINN
ENDE
108129
Kürbis, Gartenkürbis, Feldkürbis
Blüten
Juli
Juli
126611
Nussbaum, Walnuss, Christnuss
Schalen
August
September
126611
Nussbaum, Walnuss, Christnuss
Früchte
Oktober
Oktober
126611
Nussbaum, Walnuss, Christnuss
Blätter
Juni
Juni
92542
Beifuß Stabkraut, Besenkraut
Blätter
Juni
Juni
92542
Beifuß Stabkraut, Besenkraut
Wurzeln
September
November
Hi i try to select entries between beginn (e.g Juli) and ende (e.g. Dezember) (varchar2).
select * from (
select * from VIEW_USER_CON_PFLANZE_ERNTE
)
where sysdate
between to_date(beginn, 'MONTH', 'NLS_DATE_LANGUAGE=German')
and to_date(ende, 'MONTH', 'NLS_DATE_LANGUAGE=German')
but i get the error: ORA-01843: not a valid month
at the end, i want to realize this:
where sysdate
...
and last_day(to_date(ende, 'DD.MONTH', 'NLS_DATE_LANGUAGE=German'))
the select of to_date(beginn, 'MONTH', 'NLS_DATE_LANGUAGE=German')* and *to_date(ende, 'MONTH', 'NLS_DATE_LANGUAGE=German') from dual works and i get MM/DD/YYYY for both
Most likely there are invalid months names in your table. Another proof that you should never store date/time values as string. Use always proper DATE/TIMESTAMP data type.
If you use Oracle 18 or newer, then you can find invalid values like this:
SELECT *
FROM VIEW_USER_CON_PFLANZE_ERNTE
WHERE
TO_DATE(beginn DEFAULT NULL ON CONVERSION ERROR, 'MONTH', 'NLS_DATE_LANGUAGE=German') IS NULL
OR
TO_DATE(ende DEFAULT NULL ON CONVERSION ERROR, 'MONTH', 'NLS_DATE_LANGUAGE=German') IS NULL
or this one:
WITH t AS (
SELECT beginn, ende,
VALIDATE_CONVERSION(beginn AS DATE, 'MONTH', 'NLS_DATE_LANGUAGE=German') as begin_valid,
VALIDATE_CONVERSION(ende AS DATE, 'MONTH', 'NLS_DATE_LANGUAGE=German') as end_valid
FROM VIEW_USER_CON_PFLANZE_ERNTE)
SELECT *
FROM t
WHERE begin_valid= 0 or end_valid = 0
okay my solution is simple... I found some exceptions in my field values of the underlying table and removed them... it works...
learnings
just because the view works, the underlying table doesn't work, so you can't use a where between clause even if your view works
if it not work, search for exception in your fields
and don't use varchar2 for date comparison unless absolutely necessary
It is really a bad practice to store date fields as varchar2. I have seen it many times and it is always a source for problems and issues.
Looking are your data sample, it looks to me like the reason why you got the error is because you really have invalid months in your data. I am not even considering that the "between" you are applying lacks of basic logic, as between should include both respectively. The reason is the conversion from to_date from a month that gives you the first day at midnight. Perhaps you should consider using last_day as well in order to include the data of the second month used in the between condition.
Demonstration
SQL> create table t1 ( c1 varchar2(30) , c2 varchar2(30) ) ;
Table created.
SQL> insert into t1 values ( 'JANUAR' , 'MAI' ) ;
1 row created.
SQL> insert into t1 values ( 'JANUAR' , 'FEBRUAR' );
1 row created.
SQL> select * from t1 ;
C1 C2
------------------------------ ------------------------------
JANUAR MAI
JANUAR FEBRUAR
So, now we have two fields with varchar2 data type but they contain the month in German Language. Applying your filter works perfectly
SQL> select to_date(c1,'MONTH', 'NLS_DATE_LANGUAGE=German' ) as c1 ,
2 to_date(c2, 'MONTH', 'NLS_DATE_LANGUAGE=German' ) as c2,
3 sysdate-180 as date_calculation
4 from t1
5 where sysdate-180 between to_date(c1,'MONTH', 'NLS_DATE_LANGUAGE=German' )
6* and to_date(c2, 'MONTH', 'NLS_DATE_LANGUAGE=German' )
SQL> /
C1 C2 DATE_CALC
--------- --------- ---------
01-JAN-21 01-MAY-21 25-MAR-21
The only possible explanation is that you have indeed data which is not a valid month. You can use the answer provided by #Wernfried Domscheit to find out which rows don't contain valid months.
db<>fiddle
Related
I am inserting data into tables and have come across an issue. I am needing the date to be: "29-SEP-2000", but am getting "29-Sep-2000". Any help would be much appreciated!
INSERT INTO movies (title_id, title, description, rating, category, release_date)
VALUES
(title_id_seq.NEXTVAL, 'Being the Ricardos', 'Follows Lucy and Desi as they face a crisis that could end their careers and another that could end their marriage.', 'R', 'DRAMA', (TO_DATE('December 2, 2021', 'Mon DD, YYYY')));
Wrong approach.
Dates aren't stored in "uppercase" nor "lowercase"; Oracle uses 7 bytes to store that info in its internal format. All you have to do is to insert a valid DATE datatype value:
SQL> CREATE TABLE movies
2 (
3 title_id NUMBER,
4 title VARCHAR2 (30),
5 description VARCHAR2 (200),
6 rating VARCHAR2 (5),
7 category VARCHAR2 (20),
8 release_date DATE
9 );
Table created.
SQL> CREATE SEQUENCE title_id_seq;
Sequence created.
Insert: note the release_date value - I used date literal which always looks like that: date keyword followed by value in yyyy-mm-dd format enclosed into single quotes.
SQL> INSERT INTO movies (title_id,
2 title,
3 description,
4 rating,
5 category,
6 release_date)
7 VALUES (title_id_seq.NEXTVAL,
8 'Being the Ricardos',
9 'Follows Lucy and Desi ...',
10 'R',
11 'DRAMA',
12 DATE '2021-12-02');
1 row created.
Date could've also been to_date function with appropriate format mask, e.g.
SQL> update movies set release_date = to_date('02.12.2021', 'dd.mm.yyyy');
1 row updated.
SQL> update movies set release_date = to_date('2021, Dec 02', 'yyyy, Mon dd', 'nls_date_language=english');
1 row updated.
All those values represent the same date: 2nd of December 2021.
It is up to you to present that value any way you want. How? By applying TO_CHAR function with desired format model.
By default, in my database (which means that yours might display it differently), it looks like this:
SQL> select release_date from movies;
RELEASE_
--------
02.12.21
The way you wanted it:
SQL> select to_char(release_date, 'MON dd, yyyy') release_date from movies;
RELEASE_DATE
------------
DEC 02, 2021
Error you posted in a comment (ORA-01722: invalid number) has nothing to do with dates; it is related to something different. Can't tell what; it is raised when you try to insert a character into a NUMBER datatype column. Are you sure you matched column names and appropriate data types in your INSERT statement? Because, everything works OK with my sample table.
I am new to Oracle APEX, i am using Oracle APEX version 20.2, i have page item called P720_DDL_MONTH_FROM.
We are getting the value for that page item for date picker Month From is like below query.
From the list of values page attribute:
SELECT MON.NAME D, MON.MONTH R
FROM (SELECT DISTINCT(NAME) "NAME", TO_NUMBER(MONTH) "MONTH"
FROM
MONTH_YEAR) MON
ORDER BY MON.MONTH
From the Default values for pageItem:
DECLARE L_VALUE NUMBER;
BEGIN
SELECT DISTINCT(MONTH) INTO L_VALUE FROM MONTH_YEAR
WHERE UPPER(NAME) IN (SELECT TO_CHAR(ADD_MONTHS(TO_DATE(SYSDATE), -1), 'MON') FROM DUAL);
RETURN L_VALUE;
END;
I am facing an NO DATA FOUND issues while getting the default value for the above sql and plsql block, can anyone please clarify what is the issues from the above query, and let me due to some functionalities may be deprecated in apex latest version
Thanks,
If you got no_data_found, it means that there are no rows that satisfy condition(s) you wrote.
For sample table
SQL> SELECT * FROM month_year;
MONTH NAME
---------- --------------------
4 APR
6 JUN
query returns something (a value for June) (note that you don't need a subquery; directly use TO_CHAR(...)):
SQL> SELECT DISTINCT month
2 FROM month_year
3 WHERE UPPER (name) = TO_CHAR (ADD_MONTHS (TO_DATE (SYSDATE), -1), 'MON');
MONTH
----------
6
SQL>
If you got nothing, you should check contents of the month_year table and see what's going on.
Really basic question but i have zero experience with SQL. I'm using Tableau to do visualisation with data stored in my company's Oracle server, which contains multiple sheets. The primary table i am working with is named YQ005. One of the fields in the primary table I'm working with contains dates but stored as a String in YYYYMMDD format.
I have to convert this to Date format but doing it through Tableau raises the error "ORA-01843: Not a valid month". How can i do a custom SQL query to select this field, convert it to Date-time format and place this new data in a new column?
Littlefoot has a solid answer but it is definitely not for the inexperienced.
The basic function to convert the string to a date is:
select to_date(yyyymmdd, 'yyyymmdd')
If you are having problems with the month, you can just extract it out to check it:
select (case when substr(yyyymmdd, 5, 2) between '01' and '12'
then to_date(yyyymmdd, 'yyyymmdd')
end)
You can also add a check that the value is all numbers:
select (case when regexp_like(yyyymmdd, '^[0-9]{8}') and
substr(yyyymmdd, 5, 2) between '01' and '12'
then to_date(yyyymmdd, 'yyyymmdd')
end)
Validating dates in Oracle gets much more complicated if you have to validate the whole date -- each month has a different number of days and leap years further complicate matters. But months should always be between 01 and 12.
Error you got means that some values in that table - on 5th and 6th place - don't have a valid month value. For example, it might be 20191823 (there's no 18th month, is there?).
Unfortunately, that's what happens when people store dates as strings. There's no easy way out. If you want to do it with SQL only, you might fail or succeed (if you're VERY lucky). For example, have a look at this example:
SQL> desc yq005
Name Null? Type
----------------------------------------- -------- ----------------
DATUM VARCHAR2(8)
SQL> select * From yq005;
DATUM
--------
20191221
13000815
00010101
19302533 -- 25th month and 33rd day
2013Ab12 -- Ab month
2ooo0513 -- year with letters "o" instead of digits "0"
6 rows selected.
SQL>
A query whose where clause tries to identify invalid values:
SQL> alter session set nls_date_format = 'dd.mm.yyyy.';
Session altered.
SQL> select to_date(datum, 'yyyymmdd') result
2 from yq005
3 where substr(datum, 1, 4) between '0000' and '9999'
4 and substr(datum, 5, 2) between '00' and '12'
5 and substr(datum, 7, 2) between '01' and '31'
6 and regexp_like(datum, '^\d+$');
RESULT
-----------
21.12.2019.
15.08.1300.
01.01.0001.
SQL>
lines #3, 4 and 5 are trying to identify valid year/month/day. The first two are OK, more or less, but - it'll miserably fail on at least half of all months because e.g. 20191131 is "valid", but there are no 31 days in November
line #6 eliminates values that aren't all digits
Just to check that 20191131:
SQL> insert into yq005 values ('20191131');
1 row created.
SQL> select to_date(datum, 'yyyymmdd') result
2 from yq005
3 where substr(datum, 1, 4) between '0000' and '9999'
4 and substr(datum, 5, 2) between '00' and '12'
5 and substr(datum, 7, 2) between '01' and '31'
6 and regexp_like(datum, '^\d+$');
ERROR:
ORA-01839: date not valid for month specified
no rows selected
SQL>
As I said, it won't work; the same goes for other 30-days months, as well as February.
You could try to create a function which converts string to date; if it succeeds, fine. If not, skip that value:
SQL> create or replace function f_valid_date_01 (par_datum in varchar2)
2 return number
3 is
4 -- return 1 if PAR_DATUM is a valid date; return 0 if it is not
5 l_date date;
6 begin
7 -- yyyymmdd is format you expect
8 l_date := to_date(par_datum, 'yyyymmdd');
9 return 1;
10 exception
11 when others then
12 return 0;
13 end;
14 /
Function created.
SQL>
Let's use it:
SQL> select datum original_value,
2 to_char(to_date(datum, 'yyyymmdd'), 'dd.mm.yyyy') modified_value
3 from yq005
4 where f_valid_date_01 (datum) = 1;
ORIGINAL MODIFIED_V
-------- ----------
20191221 21.12.2019
13000815 15.08.1300
00010101 01.01.0001
SQL>
Just the opposite - fetch invalid dates:
SQL> select datum
2 from yq005
3 where f_valid_date_01 (datum) = 0;
DATUM
--------
19302533
2013Ab12
2ooo0513
20191131
SQL>
This is just one option you might use; there certainly are others, just Google for them. The bottom line is: always store dates into a DATE datatype column and let the database take care about (in)valid values.
[EDIT: how to populate a new column with a valid date]
If there's no date datatype column in the table, add it:
SQL> alter table yq005 add new_datum date;
Table altered.
Now run the update; mind the where clause:
SQL> update yq005 set
2 new_datum = to_date(datum, 'yyyymmdd')
3 where f_valid_date_01(datum) = 1;
3 rows updated.
SQL> select * From yq005;
DATUM NEW_DATUM
-------- -----------
20191221 21.12.2019.
13000815 15.08.1300.
00010101 01.01.0001.
19302533
2013Ab12
2ooo0513
20191131
7 rows selected.
SQL>
The best solution would be to have whoever maintains your database alter the table definition to store dates using the DATE datatype instead of some form of string.
But if you can't or don't wish to alter the Oracle schema, then I would try using the DATEPARSE() function in Tableau, as follows (assuming your date field is named XXX_DATE)
In Tableau, rename XXX_DATE to XXX_DATE_ORGINAL
Define a calculated field called XXX_DATE as DATEPARSE("YYYYMMdd", [XXX_DATE_ORIGINAL])
Hide the original field XXX_DATE_ORIGINAL
Now you can use your XXX_DATE field as a date in Tableau
The renaming and hiding of the original field is not strictly necessary. I just find it helps keep the data source understandable. For more info, see the Tableau online help for DateParse
This is my Oracle query which gets records on the basis of date where differences of dates should be one month:
select *
from pbxhbl.HBL_TRANSACTIONS
where dat_creation between '10-apr-2013' and '10-jun-2013'
and MONTHS_BETWEEN('10-jun-2013','10-apr-2013') = 1
My question is, when difference is more than one I want to show a message:
'date duration should be one month'
MONTHS_BETWEEN('10-jun-2013','10-apr-2013')
Firstly, '10-jun-2013' is not a DATE, it is a STRING. Oracle will do an implicit data type conversion. Please avoid this, and always use TO_DATE to explicitly convert a string into date.
Secondly, months between the two dates that you have posted will never be 1, it will always be 2.
SQL> SELECT MONTHS_BETWEEN(to_date('10-jun-2013','dd-mon-yyyy'),to_date('10-apr-2013','dd-mon-yyyy')) dt
2 FROM dual;
DT
----------
2
SQL>
So, ideally your query will always return no rows.
SQL> SELECT *
2 FROM dual
3 WHERE MONTHS_BETWEEN(to_date('10-jun-2013','dd-mon-yyyy'),to_date('10-apr-2013','dd-mon-yyyy')) =1;
no rows selected
SQL>
Coming back to your question,
You could use a CASE expression.
However, if you are filtering out rows, then you would not have anything to display. So, remove the filter, such that whenever the value of MONTHS_BETWEEN is more than 1, you could return the message.
For example,
SQL> WITH DATA AS(
2 SELECT to_date('01-03-2015','DD-MM-YYYY') dt1, to_date('01-04-2015','DD-MM-YYYY') dt2 FROM dual UNION ALL
3 SELECT to_date('01-03-2015','DD-MM-YYYY') dt1, to_date('10-04-2015','DD-MM-YYYY') dt2 FROM dual UNION ALL
4 SELECT to_date('01-03-2015','DD-MM-YYYY') dt1, to_date('01-05-2015','DD-MM-YYYY') dt2 FROM dual
5 )
6 SELECT DT1,
7 DT2,
8 CASE
9 WHEN months_between(dt2, dt1) >1
10 THEN 'date duration should be one month'
11 ELSE 'OK'
12 END MESSAGE
13 FROM DATA;
DT1 DT2 MESSAGE
--------- --------- ---------------------------------
01-MAR-15 01-APR-15 OK
01-MAR-15 10-APR-15 date duration should be one month
01-MAR-15 01-MAY-15 date duration should be one month
SQL>
What you need should be done inside a function.
Create a function that takes two parameters as input (date, date) and as an output it returns a record of table type.
Inside a function, before passing your query to output insert a conditional control structure like CASE or IF to check whether or not given dates match your criteria. If yes, simply pass the query to the result, and if no (assuming you would like someone to see it in application) return a query that is built just as the record you would normally return and include your message in one of columns while the rest should remain NULL.
For the above I assumed that raising an error is not what you actually need.
Pseudocode:
CREATE FUNCTION check_dates_for_hbl_transactions(date, date)
RETURNS record
AS $function$
BEGIN
IF ( MONTHS_BETWEEN( $1 - $2 ) > 1 )
THEN RETURN QUERY SELECT 'Date duration should be one month at max'::text, NULL::bigint, ...
ELSE
RETURN QUERY SELECT * FROM HBL_TRANSACTIONS WHERE dat_creation BETWEEN $1 AND $2
END IF;
END
$function$
Important note: The reason I wrote 'message'::text, NULL::bigint, ... was to show that your select statement must match the returning set (in this case all the columns and their types from table HBL_TRANSACTIONS).
Also consider adding an additional check for date $2 to be larger than date $1 argument.
Side note: Consider returning rowtype to make it easier. I do not prefer that method as it is error prone in case of row type being changed. I'd rather have the function to fail and check manually for the cause than have it pass normally as usual with wrong value.
In my application am trying to format and sort the date, i am using to_char() function to format the date to my required format, but when i sort them it is sorting it as string sorting. But i want them to be sorted as date.
I need some help to achieve both in the same query. Kindly help me on the same.
The query which i used was,
SELECT to_char( t1.your_date_column1, your_format_mask ) as alias,
FROM your_table1 t1,your_table2
ORDER BY t1.your_date_column1
It sounds like you want something like
SELECT to_char( your_date_column, your_format_mask )
FROM your_table
ORDER BY your_date_column
In the SELECT list, you want to return a character string that represents the date in your preferred format. In the ORDER BY clause, you want to order by the actual date. Using the standard EMP and DEPT tables, for example
SQL> ed
Wrote file afiedt.buf
1 select to_char( hiredate, 'DD-MM-YYYY' )
2 from emp,
3 dept
4 where emp.deptno = dept.deptno
5* order by hiredate
SQL> /
TO_CHAR(HI
----------
17-12-1980
20-02-1981
22-02-1981
02-04-1981
01-05-1981
09-06-1981
08-09-1981
28-09-1981
17-11-1981
03-12-1981
03-12-1981
23-01-1982
19-04-1987
23-05-1987
14 rows selected.
If you add a DISTINCT, the problem is that Oracle doesn't know that the function you are applying (in this case TO_CHAR) provides a one-to-one mapping from the data in the table to the data in the output. For example, two different dates (October 1, 2010 10:15:15 and October 1, 2010 23:45:50) might generate the same character output, forcing Oracle to eliminate one of the two '01-10-2010' strings but the two dates would sort differently. You can rectify that problem by nesting your query and converting the string back to a date after doing the DISTINCT and before doing the ORDER BY
SQL> ed
Wrote file afiedt.buf
1 select hire_date_str
2 from (
3 select distinct to_char( hiredate, 'DD-MM-YYYY' ) hire_date_str
4 from emp,
5 dept
6 where emp.deptno = dept.deptno
7 )
8* order by to_date(hire_date_str,'DD-MM-YYYY')
SQL> /
HIRE_DATE_
----------
17-12-1980
20-02-1981
22-02-1981
02-04-1981
01-05-1981
09-06-1981
08-09-1981
28-09-1981
17-11-1981
03-12-1981
23-01-1982
19-04-1987
23-05-1987
13 rows selected.
SELECT
to_char( your_date_column, your_format_mask ) as formate_purpose,
FROM your_table
ORDER BY to_date (formate_purpose)
Try the above code
The easiest way is to retrieve the same field with the query again and doing sorting based upon that filed
In your example
SELECT
to_char( your_date_column, your_format_mask ) as formate_purpose,
your_date_column as sorting_purpose
FROM your_table
ORDER BY your_date_column
You don't say what your application is written in, but in some environments (e.g. Oracle APEX, Oracle Reports) the solution is to not use to_char in the query, but then to apply the desired formatting in the tool's "column properties" or similar.
If you let Oracle sort (recommended), just do it like described in Justin Cave's answer. If, for some reason, you do the sorting in Java, do not use to_char; get the dates as Date objects instead and use e.g. a SimpleDateFormat to do the formatting in Java (after sorting).
For sqlplus, use alter session set nls_date_format to what you want the date format to be, then just use the column name in your select statement and sort order.
I wanted to Group By and Order By the Date field but the Date field included the Time and I didn't want to include the Time in the grouping and sorting. So I converted the Date to Character and then converted the character back to Date to eliminate the Time and sort by date not by text. That grouped the data by the date and sorted by the date.
-- Projects initiated by Day.
select to_date(to_char(psd.PROJECTSTATUSDATE, 'mm/dd/yyyy'),'mm/dd/yyyy') as "Date", count(*)
from project pj, project_status_date psd
where PJ.PROJECTTOKEN = PSD.PROJECTTOKEN
and psd.PROJECTSTATUSDATE > '01-JAN-2001'
and PSD.PROJECTSTATUSCODE = 'BL'
group by to_date(to_char(psd.PROJECTSTATUSDATE, 'mm/dd/yyyy'),'mm/dd/yyyy')
order by to_date(to_char(psd.PROJECTSTATUSDATE, 'mm/dd/yyyy'),'mm/dd/yyyy')
Date Count
8/16/2013 102
8/19/2013 77
8/20/2013 257
8/21/2013 30
8/22/2013 173
8/23/2013 125
8/26/2013 41
8/27/2013 25
8/28/2013 14