Oracle PIVOT with COUNT() missing data - oracle

I am counting events from a history table and want to pivot on the month the events occur. Base query is something like this:
SELECT TO_CHAR(Date_Entered, 'MONTH') AS Month, Userid FROM Customer_Order_History
The full query that implements the PIVOT then looks like:
SELECT * FROM
(SELECT TO_CHAR(Date_Entered, 'MONTH') AS Month, Userid FROM Customer_Order_History)
PIVOT
(
COUNT(*) AS Events
FOR Month
IN ('JANUARY' AS Jan, 'FEBRUARY' AS Feb, 'MARCH' AS Mar, 'APRIL' AS Apr,
'MAY' AS May, 'JUNE' AS Jun, 'JULY' AS Jul, 'AUGUST' AS Aug,
'SEPTEMBER' AS Sep, 'OCTOBER' AS Oct, 'NOVEMBER' AS Nov, 'DECEMBER' AS Dec)
)
This query is valid and runs fine. Except in the results, where I should have had events to count for July, August, and September, everything is zeroes except September.
The problem was with the literal values in the IN() clause of the PIVOT. I come from a FoxPro background where the length of a string data column is not variable, so "JULY" is not the same as "JULY " (JULY plus five spaces). On a hunch, then, I changed the literals in the IN() to all be nine characters in length (the longest a month name can be), and July and August began reporting counts.
Of course, the better way to do this is to use a more consistent format for the month, such as just using the first three characters or using the month index. But the above-described behavior seems very odd to me. After all, if I compared a VARCHAR(100) with a VARCHAR(2000), and they were both "JULY", in a WHERE clause or a JOIN, they would be considered equal. I am used to padding strings to a certain width when comparing in FoxPro -- not in Oracle.
Has anyone else seen this in PIVOTs, and is there some larger concept I am missing in Oracle where the size of a VARCHAR column suddenly starts to matter (other scenarios I should be aware of)?

This isn't about pivoting, or about general string comparisons; it's about how month names are produced. From the documentation:
The character elements MONTH, MON, DAY, and DY are padded with trailing blanks to the width of the longest full month name, the longest abbreviated month name, the longest full date name, or the longest abbreviated day name, respectively, among valid names determined by the values of NLS_DATE_LANGUAGE and NLS_CALENDAR parameters. For example, when NLS_DATE_LANGUAGE is AMERICAN and NLS_CALENDAR is GREGORIAN (the default), the largest element for MONTH is SEPTEMBER, so all values of the MONTH format element are padded to nine display characters. ...
... which is exactly what you are seeing. The comparison with your literals in the pivot clause is still using nonpadded character semantics - but the strings produced by to_char() are padded to nine characters and your literals are not, so they don't match.
If you change your base query to add the FM format modifier then it won't pad the names:
TO_CHAR(Date_Entered, 'FMMONTH')
Since your pivot clause is relying on the names being in English, you might also want to force that to be used, overriding the session NLS settings of whoever runs the code:
TO_CHAR(Date_Entered, 'FMMONTH', 'NLS_DATE_LANGUAGE=ENGLISH')
which will always give you 'MAY', 'JUNE', etc. with no padding; or
TO_CHAR(Date_Entered, 'FMMonth', 'NLS_DATE_LANGUAGE=ENGLISH')
which will always give you 'May', 'June', etc. still with no padding, but will then look a bit less shouty in the pivot list.

Related

Single Month Digit Date Format issue in Oracle

Am getting the below issue when am using 'mon-d-yyyy' to convert date to char, as i need a single day digit for values from 1 to 9 days in a month.
When i use the 'mon-d-yyyy' format, am losing out on 5 days and getting a wrong date. Any help on this would be great.
select to_char(sysdate-22,'mon-d-yyyy') from dual;--aug-2-2017
select to_char(sysdate-22,'mon-dd-yyyy') from dual;--aug-07-2017
select sysdate-22 from dual;--07-AUG-17 11.06.43
In Oracle date formats, d gets the day of week. The 2 in your output means monday, not august the 2nd.
Try using Fill Mode as Format Model Modifier
select to_char(sysdate-22,'mon-fmdd-yyyy') from dual;
One option might be to piece together the date output you want:
SELECT
TO_CHAR(sysdate-22, 'mon-') ||
TRIM(LEADING '0' FROM TO_CHAR(sysdate-22, 'dd-')) ||
TO_CHAR(sysdate-22, 'yyyy')
FROM dual;
The middle term involving TRIM strips off the leading zeroes, if present, from the date.
Output:
Demo here:
Rextester
SQL>SELECT TO_CHAR(TO_DATE('29-AUG-2017','DD-MON-YYYY') - 22,'"WEEKDAY :"D, MON-FMDD-YYYY') "Before22Days" FROM DUAL;
D- Gives you a numeric weekday(2nd weekday in a week) on AUG-07-2017.
DD-Gives a Numeric Month Day i.e,07th
FMDD-Gives 7th
Before22Days
----------------------
WEEKDAY :2, AUG-7-2017

Oracle DB: calculate age from strings

I need to calculate age for the people and their birthdates are saved in varchar2 like 19900130, and some people don't have their birthdates recorded and default value is 00000000.
Here is my code:
SELECT
e.id_number,
e.birth_dt,
(CASE WHEN SUBSTR(e.birth_dt, 1, 4) = '0000' THEN 0
WHEN SUBSTR(e.birth_dt, 1, 4) <> '0000' THEN
ROUND(MONTHS_BETWEEN(SYSDATE, TO_DATE(e.birth_dt, 'YYYYMMDD')) / 12)
ELSE -1
END) age
FROM employee e
The error is:
ORA-01843: Not a valid month
Is anything wrong? I couldn't figure out.
Is anything wrong? Yes, your case is not covering the possibility that some strings have something other than 01, 02, ..., 12 in character positions 5 and 6. Likewise, your case is not covering the possibility that the day of the month character positions are something other than 01, 02, ... 31 (and the covering of the possibility that a day of the month is not valid for a particular month).
If I were you, I'd add a proper date column, modify the app to populate both columns, fix the app so that it stops putting bad data into the table, decide what to do with the bad data, modify the app to stop populating the varchar2 column, and then drop the varchar2 column.

how to create Packed numeric style for current date in oracle

Specify the day, month, and year as three INTEGER values with no separators between them, using these rules:
The day and month components must have two digits. When the day or month is less than 10, it must be preceded by a zero.
For any year, the year component can have four digits (for example, 1997). For years in the range 1950 to 2049, the year component can, alternatively, have two digits (50 represents 1950, and so on).
You cannot use any separators between the date components.
Examples: '240497' or '04241997'
I'm not sure what's the question but you can format a date with to_char
select to_char(sysdate,'ddmmyyyy') as d
from dual;
OUPUT
D
--------
07092016
If you want to insert into a table
insert into t1 (field1)
values (to_char(sysdate,'ddmmyyyy'));

SQL Query in Oracle showing less records than those satisfying criterion

I need to check whether a user inputted Month-Year combination falls in between from_dt and to_dt column of the table.My Query is As follows:
SELECT emp_code,
CASE WHEN TO_CHAR(TO_DATE(from_dt),'MM') = TO_CHAR(TO_DATE((1||'/'||:for_mon||'/'|| :for_year),'DD/MM/YYYY'),'MM')
THEN LAST_DAY(TO_DATE((1||'/'||:for_mon||'/'|| :for_year),'DD/MM/YYYY')) - TO_DATE(FROM_DT) +1
WHEN TO_CHAR(TO_DATE(from_dt),'DD/MM/YYYY')<=
to_char(TO_DATE((1||'/'||:for_mon||'/'||
:for_year),'DD/MM/YYYY'),'DD/MM/YYYY')
AND TO_CHAR(TO_DATE(to_dt),'DD/MM/YYYY')>=to_char(LAST_DAY(TO_DATE((1||'/'||:for_mon||'/'||
:for_year),'DD/MM/YYYY')),'DD/MM/YYYY')
Then LAST_DAY(TO_DATE((1||'/'||:for_mon||'/'|| :for_year),'DD/MM/YYYY'))
-TO_DATE((1||'/'||:for_mon||'/'|| :for_year),'DD/MM/YYYY')+1
ELSE 0
END AS Leave
FROM TestTable
But this query shows only one employee's leave count for the given month-year combo while other employees' leave count also should be shown as their from_dt and to_dt also falls in the same month-year.
Moreover its is showing no exceptions.
I've tried setting values of NLS_COMP and NLS _SORT too, but with no gain.
When comparing strings, you use lexicographically comparison. That is, you are using the dictionary order. Depending your date string format, the lexicography order might or might not be the same as the "natural" date order:
TO_CHAR(TO_DATE(from_dt),'DD/MM/YYYY')
<=to_char(TO_DATE((1||'/'||:for_mon||'/'||:for_year),'DD/MM/YYYY'),'DD/MM/YYYY')
For example, the following table is lexicographically ordered using the DD/MM/YYYY format:
TO_CHAR(DATA,'DD/MM/YYYY')
01/12/2014
14/10/2013
24/11/2014
As you can see, such format does not preserve "natural" date order, and so is not suitable for date comparison. On the other hand, the YYYY/MM/DD is:
TO_CHAR(DATA,'YYYY/MM/DD')
2013/10/14
2014/11/24
2014/12/01
Either switch to that date format, or better, get rid of those date to string comparison and use plain date arithmetics (take a look at EXTRACT(MONTH FROM ...) for example)

How to remove leading zeroes from day and month values in Oracle, when parsing to string using to_char function?

I want to retrieve a date without the leading zeroes in front of the day and month values in a select statement. If I execute the following query
select to_char(sysdate, 'dd.mm.yyyy') from dual;
I will get 21.03.2014 as a result. Moreover, if today was, for example, 7th of March, 2014, I would get 07.03.2014. How can I get rid of these leading zeroes?
select to_char(sysdate,'DD.MM.YY') -- Without Fill Mode
, to_char(sysdate-20,'fmDD.fmMM.YY') -- With Fill Mode, 20 days ago
from dual;
Returns
21.03.14 | 1.3.14
FM Fill mode.
In a datetime format element of a TO_CHAR function, this modifier suppresses blanks in subsequent character elements (such as MONTH) and suppresses leading zeroes for subsequent number elements (such as MI) in a date format model. Without FM, the result of a character element is always right padded with blanks to a fixed length, and leading zeroes are always returned for a number element. With FM, which suppresses blank padding, the length of the return value may vary.
Try this:
select to_char(to_date('20-oct-2000'),'fmmm/dd/fmrr') from dual
The above query will remove the leading zeroes from day and month values in Oracle.
This is quite far from being elegant but it works:
select to_number(to_char(sysdate,'dd')) || '.' || to_number(to_char(sysdate,'mm')) || '.' || to_number(to_char(sysdate,'yyyy')) from dual
Basically, you would convert to number each part of the date

Resources