Related
I have column with data such as '123456789012'
I want to divide each of each 3 chars from the data with a '/' in between so that the output will be like: "123/456/789/012"
I tried "SELECT TO_CHAR(DATA, '999/999/999/999') FROM TABLE 1" but it does not print out the output as what I wanted. Previously I did "SELECT TO_CHAR(DATA, '$999,999,999,999.99') FROM TABLE 1 and it printed out as "$123,456,789,012.00" so I thought I could do the same for other case as well, but I guess that's not the case.
There is also a case where I also want to put '#' in front of the data so the output will be something like this: #12345678901234. Can I use TO_CHAR for this problem too?
Is these possible? Because when I go through the documentation of oracle about TO_CHAR, it stated a few format that can be use for TO_CHAR function and the format that I want is not listed there.
Thank you in advance. :D
Here is one option with varchar2 datatype:
with test as (
select '123456789012' a from dual
)
select listagg(substr(a,(level-1)*3+1,3),'/') within group (order by rownum) num
from test
connect by level <=length(a)
or
with test as (
select '123456789012.23' a from dual
)
select '$'||listagg(substr((regexp_substr(a,'[0-9]{1,}')),(level-1)*3+1,3),',') within group (order by rownum)||regexp_substr(a,'[.][0-9]{1,}') num
from test
connect by level <=length(a)
output:
1st query
123/456/789/012
2nd query
$123,456,789,012.23
If you wants groups of three then you can use the group separator G, and specify the character to use:
SELECT TO_CHAR(DATA, 'FM999G999G999G999', 'NLS_NUMERIC_CHARACTERS=./') FROM TABLE_1
123/456/789/012
If you want a leading # then you can use the currency indicator L, and again specify the character to use:
SELECT TO_CHAR(DATA, 'FML999999999999', 'NLS_CURRENCY=#') FROM TABLE_1
#123456789012
Or combine both:
SELECT TO_CHAR(DATA, 'FML999G999G999G999', 'NLS_CURRENCY=# NLS_NUMERIC_CHARACTERS=./') FROM TABLE_1
#123/456/789/012
db<>fiddle
The data type is always a string; only the format changes.
I have a column in which numbers are stored as string because of the nature of the column where any kind of data type is expected like date, numbers, alpha numeric,
etc.
Now i need to check if the values in that column is in defined range or not here is sample data for testing
create table test (val varchar2(10));
insert into test values ('0');
insert into test values ('67');
insert into test values ('129');
insert into test values ('200');
insert into test values ('1');
Here expected range in which value should be is 0-128 if values are not in range then i need to filter them out for further processing.
For this i have written some queries but none of then is giving requires output.
select *
from test
where val not between '0' and '128';
select *
from test
to_number(val, '9') not between to_number('0', '9') and to_number('128', '9999');
select * from test where
to_number(val, '9') < TO_NUMBER('0', '9')
or
to_number(val, '999') > TO_NUMBER('128', '999')
;
These above queries are producing desired output !! :(
I ma using DB version --
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
Just leave the format out of to_number():
select *
from test
where to_number(val) not between to_number('0') and to_number('128');
The numeric format is needed for conversion to a character. If you pass it in to to_number(), then it expects a number of that format -- and you might get the number of digits wrong.
Or, better yet:
select *
from test
where to_number(val) not between 0 and 128;
Or, even better yet, change the column to contain a number rather than a string.
EDIT:
If the problem is that your value is not a number (which is quite different from your original question), then test for that. This is one situation where case is appropriate in the where clause (because case guarantees the order of evaluation of its arguments:
where (case when regexp_like(val, '[^-0-9]') then 'bad'
when cast(val as number) < 0 then 'bad'
when cast(val as number) > 128 then 'bad'
else 'good'
end) = 'bad'
#GordonLinoff's answer works with the sample data you've shown, but it will error with ORA-01722 "invalid number" if you have any values which do no represent numbers. Your sample data only has good values, but you said that for your real field "any kind of data type is expected like date, numbers, alpha numeric, etc."
You can get around that with a function that attempts to convert the stored string value to a number, and returns null if it gets that exception. A simple example:
create function safe_to_number (p_str varchar2) return number is
begin
return to_number(p_str);
exception
when value_error then
return null;
end;
/
You can then do
select *
from test
where safe_to_number(val) not between 0 and 128;
VAL
----------
129
200
Anything that can't be converted and causes an ORA-06502 value-error exception will be seen as null, which is neither between nor not between any values you supply.
If you need to check date ranges you can do something similar, but there are more errors possible, and you may have dates in multiple formats; you would need to declare exceptions and initialise them to known error numbersto catch the ones you expect to see. This isn't complete, but you could start with something like:
create function safe_to_date (p_str varchar2) return date is
l_formats sys.odcivarchar2list;
format_ex_1 exception;
format_ex_2 exception;
format_ex_3 exception;
format_ex_4 exception;
format_ex_5 exception;
pragma exception_init(format_ex_1, -1840);
pragma exception_init(format_ex_2, -1841);
pragma exception_init(format_ex_3, -1847);
pragma exception_init(format_ex_4, -1858);
pragma exception_init(format_ex_5, -1861);
-- add any others you might get
begin
-- define all expected formats
l_formats := sys.odcivarchar2list('YYYY-MM-DD', 'DD/MM/YYYY', 'DD-MON-RRRR'); -- add others
for i in 1..l_formats.count loop
begin
return to_date(p_str, l_formats(i));
exception
when format_ex_1 or format_ex_2 or format_ex_3 or format_ex_4 or format_ex_5 then
-- ignore the exception; carry on and try the next format
null;
end;
end loop;
-- did not match any expected formats
return null;
end;
/
select *
from test
where safe_to_date(val) not between date '2016-02-01' and date '2016-02-29';
Although I wouldn't normally use between for dates; if you don't have any with times specified then you'd get away with it here.
You could use when others to catch any exception without having to declare them all, but even for this that's potentially dangerous - if something is breaking in a way you don't expect you want to know about it, not hide it.
Of course, this is an object lesson in why you should store numeric data in NUMBER columns and dates in DATE or TIMESTAMP fields - trying to extract useful information when everything is stored as strings is messy, painful and inefficient.
I think the best approach you can try in this condition is use
TRANSLATE function to eliminate the alphanumeric characters. Once its
done all now is OLD school technique to check the data by using NOT
BETWEEN function Hope this helps.
SELECT B.NM
FROM
(SELECT a.nm
FROM
(SELECT '0' AS nm FROM dual
UNION
SELECT '1' AS nm FROM dual
UNION
SELECT '68' AS nm FROM dual
UNION
SELECT '129' AS nm FROM dual
UNION
SELECT '200' AS nm FROM dual
UNION
SELECT '125a' AS nm FROM dual
)a
WHERE TRANSLATE(a.nm, ' +-.0123456789', ' ') IS NULL
)b
WHERE b.nm NOT BETWEEN 1 AND 128;
I am a bit stuck on this to be honest, and I cant find a nice solution. No solution at all.
I have this table actor(id,Actor_Name) where the name is formatted as Actor-01 .. etc:
id Actor_Name
--------------------
01 Actor-01
02 Actor-02
03 Actor-03
What I need is, to increase the name of every one of these actors by one, using a procedure . Like this:
id Actor_Name
--------------------
01 Actor-02
02 Actor-02
03 Actor-04
Probably I need some kind of iteration , and I have tried a bit with cursors, but its mostly a disaster. If Anyone could provide some neat solution , or something I would be really happy!
I don't think you need a cursor. I'm not 100% sure about desired format, but something like
select CAST(REGEXP_SUBSTR('Actor-01','[[:digit:]]+')as int) from dual; returns 1 that can be incremented and stored (for instance UPDATE table1 set col1 = 'Actor-0' || CAST(REGEXP_SUBSTR(col1,'[[:digit:]]+')as int)+1 WHERE ....
Here are some concepts that may point toward a workable solution.
You can use INSTR to find the position of the minus sign.
SELECT INSTR('ACTOR-74','-') FROM DUAL
You can use SUBSTR to pull off the DIGITS
SELECT SUBSTR('ACTOR-74', 6) FROM DUAL
You can use TO_NUMBER to turn the digits into a number
SELECT TO_NUMBER('1234') FROM DUAL
You can use concatenation to build a replacement value
SELECT SUBSTR('ACTOR-74',1,6) ||
TO_CHAR(74 + 1, '09') FROM DUAL
Which yields a value that can be used in the update.
UPDATE TABLE
SET ACTOR_NAME = the-replacement-value
Try something like (not tested)
update actor set actor_name = 'Actor-' || lpad( to_char( id + 1), 2, '0' );
commit;
No need for a procedure.
merge into actor
using (
select id,
substr(actor_name, 1, instr(actor_name, '-')) as name,
to_number(substr(actor_name, instr(actor_name, '-') + 1)) as nr
from actor
) t on (t.id = actor.id)
when matched then update
set actor_name = t.name || to_char(nr + 1, 'FM09');
SQLFiddle example: http://sqlfiddle.com/#!4/93c02/1
Another approach:
update (select id
, replace( Actor_name
, substr(actor_name, -2)
, To_char(to_number(substr(actor_name, -2)) + 1, 'fm00')
) as New_Actor_name
, Actor_name
from actor
) a
set a.actor_name = a.new_actor_name;
SQLFiddle Demo
Using a stored procedure for such a simple task seems like overkill.
update ACTOR
set substr(ACTOR_NAME,1,length(ACTOR_NAME)-2)||to_number(substr(ACTOR_NAME,-2,2))+1
where substr(ACTOR_NAME,-2,2) between '00' and '99'
You may have been close using a cursor, but probably didn't know that cursors are READ-ONLY by default. To toggle this behavior, use the for update option in your cursor defintion:
http://www.techonthenet.com/oracle/cursors/for_update.php
(an additional explanation of the for update clause)
Then when looping through the cursor, you need to also use a clause which allows you to update the record last fetched by the cursor: where current of
http://www.techonthenet.com/oracle/cursors/current_of.php
("where current of" examples)
This should work with both anonymous pl/sql blocks or by a stored procedure definition.
I hope you can help - I'm strying to assign a date to a variable, and then call that variable in my select query. The code I'm posting is only part of what I'm strying to do, I will be calling that variable more than once.
I've tried to google for help, but I'm stuck on using the Select Into statement, as I've got so many selects already.
DECLARE
CurrMonth DATE := '27 may 2012'; -- Enter 27th of current month
BEGIN
SELECT
a.policynumber
,a.cifnumber
,a.phid
,a.policystartdate
,b.sistartdate
,c.dateofbirth
,'28/02/2013' AS TaxYearEnd
--Complete tax year end in the SELECT statement (once for tax year end and once for the age at tax year end)
,round ((months_between('28 feb 2013',c.dateofbirth)/12),8) AS AgeAtTaxYearEnd
,b.sifrequency AS CurrSIFrequ
,b.sivalue AS CurrentSIValue
,b.simode AS CurrentSIMode
,d.anniversarydate AS CurrentAnnDate
,d.anniversaryvalue AS CurrentAnnValue
,b.ruleeffectivedate
,b.sistatus AS CurrentSIStatus
,b.paymentbranchcode AS CurrSIBranchCode
,b.transferaccounttype AS CurrSIAccountType
,b.transferaccountnumber AS CurrSIAccountNo
,SUM(k.unitbalance) AS unitbalance
,a.latestrule
FROM fcislob.policytbl a
,fcislob.policysitbl b
,fcislob.unitholderdetailtbl c
,fcislob.policyanniversaryvaluetbl d
,fcislob.unitholderfundtbl k
WHERE a.policynumber = b.policynumber
AND a.policynumber = d.policynumber
AND b.policynumber = d.policynumber
AND a.phid = c.unitholderid
AND a.phid = k.unitholderid
AND c.unitholderid = k.unitholderid
AND a.ruleeffectivedate = b.ruleeffectivedate
AND a.ruleeffectivedate = d.ruleeffectivedate
AND b.ruleeffectivedate = d.ruleeffectivedate
AND a.latestrule <> 0
AND c.authrejectstatus = 'A'
AND a.phid LIKE 'AGLA%'
AND b.sistatus <> 'C'
AND k.unitbalance >0
AND b.transactiontype = '64'
AND b.sistartdate <= CurrMonth
AND b.sifrequency = 'M'
GROUP BY a.policynumber, a.cifnumber, a.phid, a.policystartdate, b.sistartdate , c.dateofbirth,b.sifrequency, b.sivalue, b.simode, d.anniversarydate, d.anniversaryvalue, b.ruleeffectivedate,
b.sistatus, b.paymentbranchcode, b.transferaccounttype, b.transferaccountnumber, b.policynumber, a.latestrule;
END;
You have a group by clause so you need to group by all collumns which aren't aggregated.
Are you sure you have only one record in the result ?
As #TonyAndrews said, you need the into clause. You need to declare a variable for every collumn and insert into it,
i.e.:
DECLARE
v_policynumber fcislob.policytbl.policynumber%TYPE;
v_cifnumber fcislob.policytbl.cifnumber%TYPE;
v_phid fcislob.policytbl.phid%TYPE;
-- and so on ...
v_sum number;
BEGIN
SELECT SUM(k.unitbalance), a.policynumber, a.cifnumber, a.phid -- and so on ...
INTO v_sum, v_policynumber, v_cifnumber, v_phid -- and so on ...
FROM fcislob.policytbl a -- and so on ...
GROUP BY a.policynumber, a.cifnumber, a.phid -- and so on ...
END;
The way you deal with dates is not "healthy", IMO it's better to use to_date and not realy on NLS parameters
If you are only using PL/SQL to be able persist the date value between several plain select statements, then it will complicate things a lot - switching to select into isn't straightforward if you just want to display the results of the query, particularly if there are multiple rows.
Since you mention you have many selects, I'm guessing you have them in a script file (example.sql) and are running them through SQL*Plus, like sqlplus user/password #example. If so you can keep your plain SQL statements and use positional parameters, substitution variables, or bind variables to track the date.
First option is if you want to pass the date on the command line, like sqlplus user/password #example 27-May-2012:
set verify off
select 'Supplied date is ' || to_date('&1', 'DD-Mon-RRRR') from dual;
This uses the first positional parameter, which is referenced as &1, and converts it to a date as needed in the query. The passed date has to be in the format expected by the to_date function, which in this case I've made DD-Mon-RRRR. Note that you have to enclose the variable in single quotes, otherwise (unless it's a number) Oracle will try to interpret it as a column name rather than a value. (The set verify off suppresses messages SQL*Plus shows by default whenever a substitution variable is used).
You can reference &1 as many times as you want in your script, but you may find it easer to redefine it with a meaningful name - particularly useful when you have multiple positional parameters - and then use that name in your queries.
define supplied_date = &1
select 'Supplied date is ' || to_date('&supplied_date', 'DD-Mon-RRRR') from dual;
If you don't want to pass the date from the command line, you can use a fixed value instead. I'm using a different default date format here, which allows me to use the date literal syntax or the to_date function.
define curr_date = '2012-05-31';
select 'Today is ' || date '&curr_date' from dual;
select 'Today is ' || to_date('&curr_date', 'YYYY-MM-DD') from dual;
You may want to derive the date value, using the result of one query in a later one. You can use the column ... new_value SQL*Plus command to do that; this defines a substitution variable curr_date with the string value from the today column (alias) from any future query, and you can then use it in the same way:
column today new_value curr_date
select to_char(sysdate, 'DD-Mon-YYYY') as today from dual;
select 'Today is ' || to_date('&curr_date', 'DD-Mon-YYYY') from dual;
You can also use bind variables, which you define with the var[iable] command, and set with exec:
var curr_date varchar2(10);
exec :curr_date := '2012-05-31';
select 'Today is ' || to_date(:curr_date, 'YYYY-MM-DD') from dual;
(exec is actually a wrapper around an anonymous PL/SQL block, so it means begin :curr_date := '2012-05-31'; end;, but you only really see that if there's an error). Note that it knows the bind variable is a string, so you don't enclose this in single quotes.
You can mix and match, so if you passed a date on the command line you could assign it to a bind variable with exec :supplied_date := '&1'; or to use current date exec :curr_date := to_char(sysdate, 'YYYY-MM-DD').
Many combinations possible, so you'll need to pick what suits what you're trying to do, and which you find simplest. Most, if not all, of these should work in SQL Developer too, but not sure about other clients.
I am trying to pass multiple values about 3000 values, to a BIND variable in Oracle SQL PLUS command prompt like..
SELECT JOB
FROM EMP
WHERE JOB IN :JOB -- bind variable value
I want to see my result, as all the values in EMP table on column JOB matching to that variable list has to be fetched out.
As its being production environment I can't create tables only I have grant on SELECT clause.
Need more information on how exactly it get executed when I run the same query from UNIX-SQL PLUS environment.
Will it prompt asking to enter the BIND variables values or can I refer to a file which has values as...
:JOB1 := 'MANAGER'
:JOB2 := 'CLERK'
:JOB3 := 'ACCOUNTANT'
Oracle bind variables are a one-to-one relationship, so you'd need one defined for each value you intend to include in the IN clause:
SELECT JOB
FROM EMP
WHERE JOB IN (:JOB1, :JOB2, :JOB3, ..., :JOB3000)
You need to also be aware that Oracle IN only supports a maximum of 1,000 values, or you'll get:
ORA-01795: maximum number of expressions in a list is 1000
The best alternative is to create a table (derived, temporary, actual, or view), and join to it to get the values you want. IE:
SELECT a.job
FROM EMP a
JOIN (SELECT :JOB1 AS col FROM DUAL
UNION ALL
SELECT :JOB2 FROM DUAL
UNION ALL
SELECT :JOB3 FROM DUAL
UNION ALL
...
UNION ALL
SELECT :JOB3000 FROM DUAL) b ON b.col = a.job
Our team just ran into this issue and this query is very clean to pass multiple state values. Each value is separated by comma only. I can pass all 52 states if required:
SELECT county_code,state_code FROM WMS__ASSET_COUNTY_STATE
WHERE STATE_CODE IN
(SELECT regexp_substr(:bindstateocde, '[^,]+', 1, LEVEL) token
FROM dual
CONNECT BY LEVEL <= length(:bindstateocde) - length(REPLACE(:bindstateocde, ',', '')) + 1) ;
Have a look at the Ugly-Delimited-String-Approach(tm).
That is, bind a string and convert it to a list in SQL. Ugly, that is.
One way to do it in 10g and up is with subquery factoring.
Assume :JOB is a comma-separated list of values. The following would work:
with job_list as
(select trim(substr(job_list,
instr(job_list, ',', 1, level) + 1,
instr(job_list, ',', 1, level + 1)
- instr (job_list, ',', 1, level) - 1
)
) as job
from (select
-- this is so it parses right
','|| :JOB ||',' job_list
from dual)
connect by level <= length(:JOB)
- length (replace (:JOB, ',', '') ) + 1
)
select * from emp
where job in (select * from job_list);
It's a bit ugly to read, yes, but it works, and Oracle's clever enough to do the parsing of the list of values once, not once per row, which is what you end up with otherwise. What it does under the covers is build a temporary table of the parsed values, which it then can join to the base table.
(I didn't come up with this on my own - original credit goes to an asktom question.)
:JOB is a bind variable which must be declared and populated before it can be used. The statements below demonstrate how to do that with SQL*Plus.
SQL> variable JOB varchar2(4000);
SQL> exec :JOB := '10, 20';
The first question I have to ask is this: where is this list of about 3000 values coming from? If it's coming from another table, then you can write something like the following:
SELECT JOB
FROM EMP
WHERE JOB IN (SELECT something FROM some_other_table WHERE ... )
For the rest of this answer, I'll assume it's not in the database anywhere.
In theory it's possible to do what you want. There are various ways to devise a query with a lot of bind variables in it. As an example, I'll write a script to query the all_objects data dictionary view using 3000 bind variables. I'm not going to write a SQL*Plus script with 3000 bind variables in it, so instead I wrote a Python script to generate this SQL*Plus script. Here it is:
ns = range(1, 9001, 3) # = 1, 4, 7, ..., 8998
# This gets rid of a lot of lines saying 'PL/SQL procedure successfully completed'.
print "SET FEEDBACK OFF;"
print
# Declare the bind variables and give them values.
for i, n in enumerate(ns):
print "VARIABLE X%04d NUMBER;" % i
print "EXEC :X%04d := %d;" % (i, n)
print
query = "SELECT object_name FROM all_objects WHERE"
# Break up the query into lines to avoid SQL*Plus' limit of 2500 characters per line.
chunk_size = 100
for i in range(0, len(ns), chunk_size):
query += "OR object_id IN (" + ",".join( ":X%04d" % j for j in range(i, i + chunk_size) ) + ")\n"
query = query.replace("WHEREOR", "WHERE") + ";\n"
print query
I was then able to run this script, redirect its output to a .sql file, and then run that .sql file in SQL*Plus.
You may notice above that I wrote 'In theory it's possible...'. I put the in theory clause there for a good reason. The query appears to be valid, and I don't know of a reason why it shouldn't execute. However, when I ran it on my Oracle instance (XE 11g Beta), I got the following output:
SQL> #genquery.sql
SELECT object_name FROM all_objects WHERE object_id IN (:X0000,:X0001,:X0002,:X0
003,:X0004,:X0005,:X0006,:X0007,:X0008,:X0009,:X0010,:X0011,:X0012,:X0013,:X0014
,:X0015,:X0016,:X0017,:X0018,:X0019,:X0020,:X0021,:X0022,:X0023,:X0024,:X0025,:X
0026,:X0027,:X0028,:X0029,:X0030,:X0031,:X0032,:X0033,:X0034,:X0035,:X0036,:X003
7,:X0038,:X0039,:X0040,:X0041,:X0042,:X0043,:X0044,:X0045,:X0046,:X0047,:X0048,:
X0049,:X0050,:X0051,:X0052,:X0053,:X0054,:X0055,:X0056,:X0057,:X0058,:X0059,:X00
60,:X0061,:X0062,:X0063,:X0064,:X0065,:X0066,:X0067,:X0068,:X0069,:X0070,:X0071,
:X0072,:X0073,:X0074,:X0075,:X0076,:X0077,:X0078,:X0079,:X0080,:X0081,:X0082,:X0
083,:X0084,:X0085,:X0086,:X0087,:X0088,:X0089,:X0090,:X0091,:X0092,:X0093,:X0094
,:X0095,:X0096,:X0097,:X0098,:X0099)
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel
Process ID: 556
Session ID: 137 Serial number: 29
The ORA-03113 error indicates that the server process crashed.
I tried several variations on this:
not using bind variables at all (i.e. putting the values in directly)
not using IN lists, i.e. writing SELECT ... FROM all_objects WHERE object_id=:X0000 OR object_id=:X0001 OR ...,
using OMG Ponies' approach,
using OMG Ponies' approach without using bind variables,
copying the data out of all_objects into a table, and querying that instead.
All of the above approaches caused an ORA-03113 error.
Of course, I don't know whether other editions of Oracle will suffer from these crashes (I don't have access to any other editions), but it doesn't bode well.
EDIT: You ask if you can achieve something like SELECT JOB FROM EMP WHERE JOB IN (:JOB). The short answer to that is no. SQL*Plus's usage message for the VARIABLE command is as follows:
Usage: VAR[IABLE] [ [ NUMBER | CHAR | CHAR (n [CHAR|BYTE]) |
VARCHAR2 (n [CHAR|BYTE]) | NCHAR | NCHAR (n) |
NVARCHAR2 (n) | CLOB | NCLOB | BLOB | BFILE
REFCURSOR | BINARY_FLOAT | BINARY_DOUBLE ] ]
All of the above types are single data values, with the exception of REFCURSOR, but SQL*Plus still seems to treat that as a single value. I can't find a way to query data returned in a REFCURSOR this way.
So in summary, what you're attempting to achieve is almost certainly impossible. I don't know what your ultimate aim is here, but I don't think you'll be able to do it using a single query in SQL*Plus.
While facing similar problem, I came up with this dirty solution:
select * from my_table where ',param_1,param_2,param_3,param_4,' LIKE '%,'||my_column||',%'