oracle sorting with text as number - sorting

My NLS settings:
NLS_SORT POLISH
NLS_COMP BINARY
Simple test query:
select * from (
select '11117' as x from dual
union
select '12988' as x from dual
union
select '14659' as x from dual
union
select '1532' as x from dual
union
select '18017' as x from dual
) order by x;
Actual result:
x
-----
11117
12988
14659
1532
18017
Desired result:
x
-----
1532
11117
12988
14659
18017
Question:
Is there a NLS setting that will help me achive desired result? I know I can do order by to_number(x) or, even better, order by lpad(x, 5), but it's not good in this case - I need a system-wide solution that won't require query change.
What I tried:
order by nlssort(x, 'nls_sort=binary');
alter session set nls_sort='binary';

Maybe a somewhat odd solution, but ...
Rename your TABLE_SOMETHING to TABLE_OTHER.
Create a view TABLE_SOMETHING on top of the TABLE_OTHER which will TO_NUMBER() the troubled string column of yours to the same column name.
If the table is, application-wide, being modified, then create INSTEAD OF triggers over the view TABLE_SOMETHING.
Recompile possibly invalidated packages.
Won't be particularly performant when it comes to modifying large quantities of records, won't allow for truncating the table, but it might solve the ordering problem.

Related

oracle forms 12. how to display leading zero of decimal numbers

I wish to display decimal numbers (from a query) into text items.
if I set
:TXT_ITEM := '0,000123456789'
it works. But, if :TXT_ITEM is bound to a numeric table field, value is displayed as ,000123456789.
I'm trying to force format number on several triggers (post-change, when-new-record-instance, post-text...), unsuccessfully. On other hand, setting format_mask would force my DB value to a given number of decimal digits.
How can I get leading zero to be displayed?
See if any of these two options help.
Sample data:
SQL> create table test as
2 (select 12.34 col from dual union all
3 select 0.1234003 from dual union all
4 select -13.43432203 from dual union all
5 select 0.00012345221 from dual union all
6 select -0.002412428238234821 from dual
7 );
Table created.
SQL> desc test;
Name Null? Type
----------------------------------------- -------- ----------------------------
COL NUMBER
SQL> select col,
2 regexp_replace(col, '^(-?)([.,])', '\10\2') result1,
3 rtrim(to_char(col, 'fm90D999999999999999999999'), '.') result2
4 from test;
COL RESULT1 RESULT2
---------- ------------------------- -------------------------
12,34 12,34 12,34
,1234003 0,1234003 0,1234003
-13,434322 -13,43432203 -13,43432203
,000123452 0,00012345221 0,00012345221
-,00241243 -0,002412428238234821 -0,002412428238234821
SQL>
How would use it/them in Forms? Exactly like that - you'd e.g.
select regexp_replace(col, '^(-?)([.,])', '\10\2')
into :block.text_item
from your_table
where some_condition;
Seems the concerned numeric data in the table is of type
NUMBER(13,12)
in this case it's enough to set TXT_ITEMs Format Mask attribute within the Data part of Property Palette as
0D000000000000
with precision of 13 and scale of 12 values.
Considering the scale part is fixed, you can add more zeroes before D character depending on your column's precision value such as two zeroes before D are kept for NUMBER(14,12) or three zeroes for NUMBER(15,12).

Convert VARCHAR to NUMBER with decimal on Oracle

I'm having some trouble to convert VARCHAR2 informations, like:
-111.21
11.11
-51.146610399175472
to NUMBER. I want to store these numbers on a NUMBER(19,16) column. Mostly of these values are coordinates (latitude and longitude).
I already tried different commands with different values:
select cast('-111.21' as NUMBER) from dual
select cast('-111.21' as decimal) from dual
select cast('111.21' as decimal) from dual
select to_number('-1.1') from dual
select to_decimal('-1.1') from dual
But I always receive the error:
The specified number was invalid
This SQL:
select to_number('-134.33','099.99') from dual;
Works, but any change on the number (like change to '-34.33') return the same error.
What I'm doing wrong here? Obviously I'm missing something here but I can't figure out what.
I found the problem. I need to pass a mask as parameter to the to_number function. Like '999.999999999999999'
So:
select to_number('90.79493','999.999999999999999') from dual;
select to_number('90.146610399175472','999.999900000000000') from dual;
select to_number('90.34234324','999.999999999999999') from dual;
works for different size of numbers.

Oracle. CAST COLLECT for date datatype

Consider the types:
CREATE OR REPLACE TYPE date_array AS TABLE OF DATE;
CREATE OR REPLACE TYPE number_array AS TABLE OF NUMBER;
CREATE OR REPLACE TYPE char_array AS TABLE OF VARCHAR2(80);
Queries:
WITH q AS
(SELECT LEVEL ID,
TRUNC(SYSDATE) + LEVEL MyDate,
to_char(LEVEL) STRING
FROM dual
CONNECT BY LEVEL < 5)
SELECT CAST(COLLECT(ID) AS number_array)
FROM q;
return collection of numbers
WITH q AS
(SELECT LEVEL ID,
TRUNC(SYSDATE) + LEVEL MyDate,
to_char(LEVEL) STRING
FROM dual
CONNECT BY LEVEL < 5)
SELECT CAST(COLLECT(STRING) AS char_array)
FROM q;
return collection of strings
WITH q AS
(SELECT LEVEL ID,
TRUNC(SYSDATE) + LEVEL MyDate,
to_char(LEVEL) STRING
FROM dual
CONNECT BY LEVEL < 5)
SELECT CAST(COLLECT(MyDate) AS date_array)
FROM q
return error invalid datatype.
Can anyone explain why the Date datatype behaves differently?
Here are my findings... it seems you are facing a bug caused by the fact that calculated dates seem to have a different internal representation from "database" dates. I did find a workaround, so keep on reading.
On my oracle dev installation (Oracle 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production) I am experiencing your same problem.
BUT... If I create a physical table containing your test data:
create table test_data as
SELECT LEVEL ID,
TRUNC(SYSDATE) + LEVEL MyDate,
to_char(LEVEL) STRING
FROM dual
CONNECT BY LEVEL < 5
and then run the "cast collect" operator on this physical table, it works as expected:
-- this one works perfectly
SELECT CAST(COLLECT(MyDate) AS date_array) from test_data
but all these examples still do NOT work:
-- here I just added 1 .. and it doesn't work
SELECT CAST(COLLECT(MyDate + 1) AS date_array)
from test_data
-- here I am extracting sysdate, instead of a physical column... and it doesn't work
SELECT CAST(COLLECT(sysdate) AS date_array)
from test_data
It feels like oracle doesn't think that calculated dates are the same thing of physical dates
So, I tried to "persuade" oracle that the data I am providing is actually a normal DATE value, using an explicit cast... and EUREKA! this does the job correctly:
WITH q AS
(SELECT LEVEL ID,
-- this apparently unnecessary cast does the trick
CAST( TRUNC(SYSDATE) + LEVEL AS DATE) MyDate,
to_char(LEVEL) STRING
FROM dual
CONNECT BY LEVEL < 5)
SELECT CAST(COLLECT(MyDate) AS date_array)
FROM q
Yes... but why??
It really seems that these two values are not exactly the same thing, even if the values we see are actually the same:
select sysdate, cast (sysdate as date) from dual
So I dug in the internal representation of the two values applying the "dump" function to both of them:
select dump(sysdate), dump(cast (sysdate as date)) from dual
and these are the results I got:
DUMP(SYSDATE ) -> Typ=13 Len=8: 226,7,11,9,19,20,47,0
DUMP(CAST(SYSDATEASDATE) as DUAL) -> Typ=12 Len=7: 120,118,11,9,20,21,48
Internally they look like two totally different data types! one is type 12 and the other is type 13... and they have different length and representation.
Anyway I discovered something more.. it seems someone else has noticed this: https://community.oracle.com/thread/4122627
The question has an answer pointing to this document: http://psoug.org/reference/datatypes.html
which contains a lengthy note about dates... an excerpt of it reads:
"What happened? Is the information above incorrect or does the DUMP()
function not handle DATE values? No, you have to look at the "Typ="
values to understand why we are seeing these results. ". The datatype
returned is 13 and not 12, the external DATE datatype. This occurs
because we rely on the TO_DATE function! External datatype 13 is an
internal c-structure whose length varies depending on how the
c-compiler represents the structure. Note that the "Len=" value is 8
and not 7. Type 13 is not a part of the published 3GL interfaces for
Oracle and is used for date calculations mainly within PL/SQL
operations. Note that the same result can be seen when DUMPing the
value SYSDATE."
Anyway, I repeat: I think this is a bug, but at least I found a workaround: use an explicit cast to DATE.

Replacing/Converting 1 to A with Oracle/PLSQL

Firstly, I greatly appreciate any feedback that anyone can offer. I am using Oracle SQL Developer, Version 4.0.2.15, Build 15.21.
I know and understand that many, many similar questions have been asked, as I've searched around on stackoverflow as well as the rest of the internet. However, the corresponding answers are either too vague or too extravagant, and attempt to do things that are way over my head and not what I am trying to accomplish. I am extremely new to SQL and haven't seriously done any coding since I did Java about 12 years ago. So please understand that something simple to you, is not so simple and obvious to me.
My bare-bones endstate that I am shooting for is taking a pre-existing Oracle Table Column, which is called 'service_level', that has parameters of 1-3, and making them A-C (where A=1, B=2, C=3). The reason for this is that I have an ArcGIS gdB featureclass that has a corresponding column, called 'MaintServi', with the parameters of A-C. I am going to join them using ArcToolbox once I have converted/replaced the 1-3 to A-C, and have exported them from Oracle into an Arc gdB as another table. The reason being is that the featureclass (obviously) has geometry, but this particular Oracle table does not.
From what I have gathered I know (or think) I will need to use something like:
chr(ord('a') + 3)
^ Where I will need to use/call upon the chr/ord functions. However, due to my inexperience, I cannot think of how to properly call this without getting an error. Below is what I have for my query thus far (but without chr/ord). I just need to figure out how to correctly insert it into my query to achieve the desired results.
SELECT v_wv_wp_crew.*,
Substr(v_wv_wp_crew.winter_supp_id, 1, 6) AS CostCenter,
Substr(v_wv_wp_crew.winter_supp_id, 8, 11) AS Crew_Supp_ID
FROM v_wv_wp_crew
WHERE crew_on_road >= '13-FEB-12'
AND ( operation = 2
OR operation = 3 );
Thanks again and hopefully I have complied with the posting rules of stackoverflow.
# Mark J. Bobak -
When implementing his ideas I get either this (Like I said, i'm not sure how to insert it properly without receiving an error)
SELECT v_wv_wp_crew.*,
Substr(v_wv_wp_crew.winter_supp_id, 1, 6) AS CostCenter,
Substr(v_wv_wp_crew.winter_supp_id, 8, 11) AS Crew_Supp_ID
FROM v_wv_wp_crew
WHERE crew_on_road >= '13-FEB-12'
AND ( operation = 2
OR operation = 3 )
UNION ALL
WITH service_level as (select 1 service_level from dual
union all
select 2 service_level from dual union all
select 3 service_level from dual)
select decode(service_level,1,'A',2,'B',3,'C') from service_level;
I receive the following error:
*ORA-32034: unsupported use of WITH clause
32034. 00000 - "unsupported use of WITH clause"
*Cause: Inproper use of WITH clause because one of the following two reasons
1. nesting of WITH clause within WITH clause not supported yet
2. For a set query, WITH clause can't be specified for a branch.
3. WITH clause can't sepecified within parentheses.
Action: correct query and retry
Error at Line: 14 Column: 25
Or I receive an output of only 3 rows (A, B, C) if I run the query separately - sorry I don't have enough reputation to post the image yet.
You can use the DECODE() function. Something like this should work:
with list_of_digits as (select 1 col_a from dual
union all
select 2 col_a from dual
union all
select 3 col_a from dual
union all
select 4 col_a from dual)
select decode(col_a,1,'A',2,'B',3,'C','Other') from list_of_digits;
Using your query, try this:
WITH service_level as (select 1 service_level from dual
union all
select 2 service_level from dual union all
select 3 service_level from dual)
select decode(service_level,1,'A',2,'B',3,'C') from service_level
union all
SELECT v_wv_wp_crew.*,
Substr(v_wv_wp_crew.winter_supp_id, 1, 6) AS CostCenter,
Substr(v_wv_wp_crew.winter_supp_id, 8, 11) AS Crew_Supp_ID
FROM v_wv_wp_crew
WHERE crew_on_road >= '13-FEB-12'
AND ( operation = 2
OR operation = 3 );
ord isn't an Oracle function. The equivalent Oracle function is ASCII. However, even substituting in the correct function, I don't see how that gets you what you want.
It seems most likely that you just want to add a column (I'd use case to translate the values):
SELECT v_wv_wp_crew.*,
Substr(v_wv_wp_crew.winter_supp_id, 1, 6) AS CostCenter,
Substr(v_wv_wp_crew.winter_supp_id, 8, 11) AS Crew_Supp_ID,
case service_level
when '1' then 'a'
when '2' then 'b'
when '3' then 'c'
end as service_level_alpha
FROM v_wv_wp_crew
WHERE crew_on_road >= '13-FEB-12'
AND ( operation = 2
OR operation = 3 );
If you want to return this column as service_level, then you'll need to return the full list of columns instead of using the asterisk.
Since this is a straight-forward character swap, you could use translate to really streamline the operation: translate(service_level,'123','abc'). However, I vastly prefer case over either decode or translate for readability

Find next id from varchar in Oracle

I have a row that is a varchar(50) that has a unique constraint and i would like to get the next unique number for an new insert but with a given prefix.
My rows could look like this:
ID (varchar)
00010001
00010002
00010003
00080001
So if I would like to get the next unqiue number from the prefix "0001" it would be "00010004" but if I would want it for the prefix "0008" it would be "00080002".
There will be more then 1 millon entries in this table. Is there a way with Oracle 11 to perform this kind of operation that is fairly fast?
I know that this setup is totaly insane but this is what I have to work with. I cant create any new tables etc.
You can search for the max value of the specified prefix and increment it:
SQL> WITH DATA AS (
2 SELECT '00010001' id FROM DUAL UNION ALL
3 SELECT '00010002' id FROM DUAL UNION ALL
4 SELECT '00010003' id FROM DUAL UNION ALL
5 SELECT '00080001' id FROM DUAL
6 )
7 SELECT :prefix || to_char(MAX(to_number(substr(id, 5)))+1, 'fm0000') nextval
8 FROM DATA
9 WHERE ID LIKE :prefix || '%';
NEXTVAL
---------
00010004
I'm sure you're aware that this is an inefficient method to generate a primary key. Furthermore it won't play nicely in a multi-user environment and thus won't scale. Concurrent inserts will wait then fail since there is a UNIQUE constraint on the column.
If the prefix is always the same length, you can reduce the workload somewhat: you could create a specialized index that would find the max value in a minimum number of steps:
CREATE INDEX ix_fetch_max ON your_table (substr(id, 1, 4),
substr(id, 5) DESC);
Then the following query could use the index and will stop at the first row retrieved:
SELECT id
FROM (SELECT substr(id, 1, 4) || substr(id, 5) id
FROM your_table
WHERE substr(id, 1, 4) = :prefix
ORDER BY substr(id, 5) DESC)
WHERE rownum = 1
If you need to do simultaneous inserts with the same prefix, I suggest you use DBMS_LOCK to request a lock on the specified newID. If the call fails because someone is already inserting this value, try with newID+1. Although this involves more work than traditional sequence, at least your inserts won't wait on each others (potentially leading to deadlocks).
This is a very unsatisfactory situation for you. As other posters have pointed out - if you don't use sequences then you will almost certainly have concurrency issues. I mentioned in a comment the possibility that you live with big gaps. This is the simplest solution but you will run out of numbers after 9999 inserts.
Perhaps an alternative would be to create a separate sequence for each prefix. This would only really be practical if the number of prefixes is fairly low but it could be done.
ps - your requirement that > 1000000 records should be possible may, in fact, mean you have no choice but to redesign the database.
SELECT to_char(to_number(max(id)) + 1, '00000000')
FROM mytable
WHERE id LIKE '0001%'
SQLFiddle demo here http://sqlfiddle.com/#!4/4f543/5/0

Resources