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||',%'
Related
I'm looking for a function that would sort chars in varchar2 alphabetically.
Is there something built-in into oracle that I can use or I need to create custom in PL/SQL ?
From an answer at http://forums.oracle.com/forums/thread.jspa?messageID=1791550 this might work, but don't have 10g to test on...
SELECT MIN(permutations)
FROM (SELECT REPLACE (SYS_CONNECT_BY_PATH (n, ','), ',') permutations
FROM (SELECT LEVEL l, SUBSTR ('&col', LEVEL, 1) n
FROM DUAL
CONNECT BY LEVEL <= LENGTH ('&col')) yourtable
CONNECT BY NOCYCLE l != PRIOR l)
WHERE LENGTH (permutations) = LENGTH ('&col')
In the example col is defined in SQL*Plus, but if you make this a function you can pass it in, or could rework it to take a table column directly I suppose.
I'd take that as a start point rather than a solution; the original question was about anagrams so it's designed to find all permutations, so something similar but simplified might be possible. I suspect this doesn't scale very well for large values.
So eventually I went PL/SQL route, because after searching for some time I realized that there is no build-in function that I can use.
Here is what I came up with. Its based on the future of associative array which is that Oracle keeps the keys in sorted order.
create or replace function sort_chars(p_string in varchar2) return varchar deterministic
as
rv varchar2(4000);
ch varchar2(1);
type vcArray is table of varchar(4000) index by varchar2(1);
sorted vcArray;
key varchar2(1);
begin
for i in 1 .. length(p_string)
loop
ch := substr(p_string, i, 1);
if (sorted.exists(ch))
then
sorted(ch) := sorted(ch) || ch;
else
sorted(ch) := ch;
end if;
end loop;
rv := '';
key := sorted.FIRST;
WHILE key IS NOT NULL LOOP
rv := rv || sorted(key);
key := sorted.NEXT(key);
END LOOP;
return rv;
end;
Simple performance test:
set timing on;
create table test_sort_fn as
select t1.object_name || rownum as test from user_objects t1, user_objects t2;
select count(distinct test) from test_sort_fn;
select count (*) from (select sort_chars(test) from test_sort_fn);
Table created.
Elapsed: 00:00:01.32
COUNT(DISTINCTTEST)
-------------------
384400
1 row selected.
Elapsed: 00:00:00.57
COUNT(*)
----------
384400
1 row selected.
Elapsed: 00:00:00.06
You could use the following query:
select listagg(letter)
within group (order by UPPER(letter), ASCII(letter) DESC)
from
(
select regexp_substr('gfedcbaGFEDCBA', '.', level) as letter from dual
connect by regexp_substr('gfedcbaGFEDCBA', '.', level) is not null
);
The subquery splits the string into records (single character each) using regexp_substr, and the outer query merges the records into one string using listagg, after sorting them.
Here you should be careful, because alphabetical sorting depends on your database configuration, as Cine pointed.
In the above example the letters are sorted ascending "alphabetically" and descending by ascii code, which - in my case - results in "aAbBcCdDeEfFgG".
The result in your case may be different.
You may also sort the letters using nlssort - it would give you better control of the sorting order, as you would get independent of your database configuration.
select listagg(letter)
within group (order by nlssort(letter, 'nls_sort=german')
from
(
select regexp_substr('gfedcbaGFEDCBA', '.', level) as letter from dual
connect by regexp_substr('gfedcbaGFEDCBA', '.', level) is not null
);
The query above would give you also "aAbBcCdDeEfFgG", but if you changed "german" to "spanish", you would get "AaBbCcDdEeFfGg" instead.
You should remember that there is no common agreement what "alphabetically" means. It all depends on which country it is, and who is looking at your data and what context it is in.
For instance in DK, there are a large number of different sortings of a,aa,b,c,æ,ø,å
per the alphabet: a,aa,b,c,æ,ø,å
for some dictionary: a,aa,å,b,c,æ,ø
for other dictionaries: a,b,c,æ,ø,aa,å
per Microsoft standard: a,b,c,æ,ø,aa,å
check out http://www.siao2.com/2006/04/27/584439.aspx for more info. Which also happens to be a great blog for issues as these.
Assuming you don't mind having the characters returned 1 per row:
select substr(str, r, 1) X from (
select 'CAB' str,
rownum r
from dual connect by level <= 4000
) where r <= length(str) order by X;
X
=
A
B
C
For people using Oracle 10g, select listagg within group won't work. The accepted answer does work, but it generates every possible permutation of the input string, which results in terrible performance - my Oracle database struggles with an input string only 10 characters long.
Here is another alternative working for Oracle 10g. It's similar to Jeffrey Kemp's answer, only the result isn't splitted into rows:
select replace(wm_concat(ch), ',', '') from (
select substr('CAB', level, 1) ch from dual
connect by level <= length('CAB')
order by ch
);
-- output: 'ABC'
wm_concat simply concatenates records from different rows into a single string, using commas as separator (that's why we are also doing a replace later).
Please note that, if your input string had commas, they will be lost. Also, wm_concat is an undocumented feature, and according to this answer it has been removed in Oracle 12c. Use it only if you're stuck with 10g and don't have a better option (such as listagg, if you can use 11g instead).
From Oracle 12, you can use:
SELECT *
FROM table_name t
CROSS JOIN LATERAL (
SELECT LISTAGG(SUBSTR(t.value, LEVEL, 1), NULL) WITHIN GROUP (
ORDER BY SUBSTR(t.value, LEVEL, 1)
) AS ordered_value
FROM DUAL
CONNECT BY LEVEL <= LENGTH(t.value)
)
Which, for the sample data:
CREATE TABLE table_name (value) AS
SELECT 'ZYX' FROM DUAL UNION ALL
SELECT 'HELLO world' FROM DUAL UNION ALL
SELECT 'aæøåbcæøåaæøå' FROM DUAL;
Outputs:
VALUE
ORDERED_VALUE
ZYX
XYZ
HELLO world
EHLLOdlorw
aæøåbcæøåaæøå
aabcåååæææøøø
If you want to change how the letters are sorted then use NLSSORT:
SELECT *
FROM table_name t
CROSS JOIN LATERAL (
SELECT LISTAGG(SUBSTR(t.value, LEVEL, 1), NULL) WITHIN GROUP (
ORDER BY NLSSORT(SUBSTR(t.value, LEVEL, 1), 'NLS_SORT = BINARY_AI')
) AS ordered_value
FROM DUAL
CONNECT BY LEVEL <= LENGTH(t.value)
)
Outputs:
VALUE
ORDERED_VALUE
ZYX
XYZ
HELLO world
dEHLLlOorw
aæøåbcæøåaæøå
aaåååbcøøøæææ
db<>fiddle here
I am creating a stored procedure in Oracle where I am required to mask employee ID in the SELECT statement.
I would like that the same masking number be applied to the all the rows with the same Employee ID.
EMP_ID MASK_ID
------ -------
212 USER9293
443 USER6474
212 USER9293
Currently in my MSSQL SELECT I accomplish this by:
CONCAT(‘USER’, CONVERT(NVARCHAR(6), (1.0 + FLOOR(250000 * RAND(CONVERT(VARBINARY, EMP_ID))))))
I would like to accomplish a similar solution in Oracle. I believe a SEED using DBMS_RANDOM would probably get me close but I am not sure how to pass the column in as it return an error every time.
Any tips?
Often people ask for "random" when they don't actually need that. It seems that "hash" should work fine for your problem.
If you want to append a four-digit number (between 1 and 9999, perhaps with leading zeros) to 'USER', you could do something like what I show below. If you want numbers between 1000 and 9999, you can adjust the math yourself.
Note that you may still give a seed (as the third argument to ora_hash) if you need that for some reason. The default seed is 0, so this is deterministic even if you don't specify the seed.
with
inputs (emp_id) as (
select 212 from dual union all
select 443 from dual union all
select 212 from dual union all
select 400 from dual
)
select emp_id,
'USER' || to_char(1 + ora_hash(emp_id, 9998), 'fm0000') as mask_id
from inputs
;
EMP_ID MASK_ID
------ ---------
212 USER8297
443 USER5176
212 USER8297
400 USER2606
1. Use utl_raw.cast_to_raw to convert EMP_ID to binary
2. You must use the plsql block to use DBMS_RANDOM.SEED.
add function my_random_emplID to generate a random number with binary type
create function my_random_emplID(p_seed in number default null)
return number is
begin
if p_seed is not null then
dbms_random.seed(utl_raw.cast_to_raw(p_seed));
end if;
return dbms_random.value;
end;
/
get mask_id
SELECT
CONCAT('USER',CAST(1.0 + FLOOR(250000 * my_random_emplID(1452)) as varchar(6)))
FROM
dual;
demo in db<>fiddle
=# select row(0, 1) ;
row
-------
(0,1)
(1 row)
How to get 0 within the same query? I figured the below sort of working but is there any simple way?
=# select json_agg(row(0, 1))->0->'f1' ;
?column?
----------
0
(1 row)
No luck with array-like syntax [0].
Thanks!
Your row type is anonymous and therefore you cannot access its elements easily. What you can do is create a TYPE and then cast your anonymous row to that type and access the elements defined in the type:
CREATE TYPE my_row AS (
x integer,
y integer
);
SELECT (row(0,1)::my_row).x;
Like Craig Ringer commented in your question, you should avoid producing anonymous rows to begin with, if you can help it, and type whatever data you use in your data model and queries.
If you just want the first element from any row, convert the row to JSON and select f1...
SELECT row_to_json(row(0,1))->'f1'
Or, if you are always going to have two integers or a strict structure, you can create a temporary table (or type) and a function that selects the first column.
CREATE TABLE tmptable(f1 int, f2 int);
CREATE FUNCTION gettmpf1(tmptable) RETURNS int AS 'SELECT $1.f1' LANGUAGE SQL;
SELECT gettmpf1(ROW(0,1));
Resources:
https://www.postgresql.org/docs/9.2/static/functions-json.html
https://www.postgresql.org/docs/9.2/static/sql-expressions.html
The json solution is very elegant. Just for fun, this is a solution using regexp (much uglier):
WITH r AS (SELECT row('quotes, "commas",
and a line break".',null,null,'"fourth,field"')::text AS r)
--WITH r AS (SELECT row('',null,null,'')::text AS r)
--WITH r AS (SELECT row(0,1)::text AS r)
SELECT CASE WHEN r.r ~ '^\("",' THEN ''
WHEN r.r ~ '^\("' THEN regexp_replace(regexp_replace(regexp_replace(right(r.r, -2), '""', '\"', 'g'), '([^\\])",.*', '\1'), '\\"', '"', 'g')
ELSE (regexp_matches(right(r.r, -1), '^[^,]*'))[1] END
FROM r
When converting a row to text, PostgreSQL uses quoted CSV formatting. I couldn't find any tools for importing quoted CSV into an array, so the above is a crude text manipulation via mostly regular expressions. Maybe someone will find this useful!
With Postgresql 13+, you can just reference individual elements in the row with .fN notation. For your example:
select (row(0, 1)).f1; --> returns 0.
See https://www.postgresql.org/docs/13/sql-expressions.html#SQL-SYNTAX-ROW-CONSTRUCTORS
I was using a sql query in jdbc
SELECT COUNT (COST_CENTER) AS count FROM IMDB1_FINANCE_BUDGET where COST_CENTER='object name'
this is working fine but i have some test objects like '0654603 ? SSG Accounting with a single quote in beginning now query will be
SELECT COUNT (COST_CENTER) AS count FROM IMDB1_FINANCE_BUDGET where COST_CENTER=''0654603 ? SSG '
now it is throwing exception
java.sql.SQLException: ORA-00933: SQL command not properly ended
how to handle such type of objects
The best way to deal with single-quotations in such cases is to use Quoting string literal technique
For example, q'['SCOTT]'
SQL> WITH DATA AS(
2 SELECT '''SCOTT' nm FROM dual
3 )
4 SELECT * FROM DATA WHERE nm = q'['SCOTT]';
NM
------
'SCOTT
SQL>
Of course, the traditional way to enclose it with single-quotes '''SCOTT' would still work -
SQL> WITH DATA AS(
2 SELECT '''SCOTT' nm FROM dual
3 )
4 SELECT * FROM DATA WHERE nm = '''SCOTT';
NM
------
'SCOTT
SQL>
Heyho,
I've gotta write a Procedure which Inserts a resultset from a select-statement into a table.
A co-worker of mine did something similar before to copy values from one table to another. His statement looks like this:
CREATE OR REPLACE PROCEDURE Co-Worker(
pId IN INT
)
AS
BEGIN
INSERT INTO Table1_PROCESSED
SELECT * FROM Table1
WHERE ID = pId;
DELETE FROM Table1
WHERE ID = pId;
END Co-Worker;
/
The two tables mentioned here got the same structure (in fact table1_processed is just a copy of table 1).
So I thought like "Hey! I get a resultset from my select-satement too! So why I just don't adjust it a bit do to the same!"
So I created my Table like this:
MyTable:
TIMEID (number) | NAME (varchar2 - 128)
-----------------------------------
VALUE | VALUE
VALUE | VALUE
VALUE | VALUE
and my Procedure like this:
CREATE OR REPLACE procedure MyProcedure(
pdate in date,
pJobtype in number default 3,
pTasktype in number default 4,
pJobstatus in number default 1,
pTaskstatus in number default 4
)
AS
pformateddate date;
BEGIN
Select to_date(to_char(to_date(pdate, 'DD.MM.YYYY HH24:MI:SS'), 'DD.MM.YYYY'), 'DD.MM.YYYY')
into pformateddate
from dual;
Insert into MyTable (TIMEID, NAME)
Select Function_GETTIMEID(to_date(st, 'DD.MM.YYYY HH24')) TIMEID
,to_char(ext) NAME
from(
Select to_char(arch_job.exec_start, 'DD.MM.YYYY HH24') st
,file.name ext
, count(file.id) cnt
from
arch_task_file
left join file on arch_task_file.File_ID = file.ID
left join arch_task on arch_task_file.Task_ID = arch_task.ID
left join arch_job on arch_task.Job_ID = arch_job.ID
where
arch_job.exec_start > pformateddate
and arch_job.exec_end <pformateddate + 1
and arch_job.jobtype_id = pJobtype
and arch_job.jobstatus_id = pJobstatus
and arch_task.Tasktype_ID = pTasktype
and arch_task.Taskstatus_ID = pTaskstatus
group by
file.name,
to_char(arch_job.exec_start, 'DD.MM.YYYY HH24'
)
);
End MyProcedure;
/
the Result for the large Select-Statement ALONE looks like this:
TIMEID | NAME
-----------------------------------
VALUE | VALUE
VALUE | VALUE
VALUE | VALUE
But If I execute this procedure and give it a dummydate (sysdate - 12 or a date like '16.07.2010 10:32:50') my Toad-gives my a message "Procedure completed" my table stays empty...!
But as said before the large Select-Statement gives results so there shouldn't be a try to insert an empty resultset...! Can anyone tell me why my procedure ain't work?
Thx for every useful answer. =)
Greetz!
P.S.:
The
Select to_date(to_char(to_date(pdate, 'DD.MM.YYYY HH24:MI:SS'), 'DD.MM.YYYY'), 'DD.MM.YYYY')
into pformateddate
from dual;
is required to shorten the pDate-value! i tested it, so that works too and you can ignore it in the whole logic. It's just here to give you a complete picture of the situation!
This is a very common pattern in SQL forums. The pattern is the OP says
"I run this SQL in my TOAD worksheet
(or whatever) and it works. But when
I include it in a different context -
such as a stored procedure - it
doesn't work. What gives?"
What gives is that the two statements are not the same. Somewhere there is a mis-transcription. Perhaps a join has been omitted or an extra one added. The most likely source of errors is the replacement of literals in the worksheet with parameters in the stored procedure.
Obviously I cannot tell you where the difference lies. All I can do is urge you to closely inspect the two SQL statements and figure out the discrepancy.
If you really cannot find any difference then you will need to debug your code. The quickest way to start is with the Devil's Debugger. After the insert statement add this line:
dbms_output.put_line('Rows inserted = '||to_char(sql%rowcount));
You'll need to enable DBMS_OUTPUT in TOAD; there's a tab for it somewhere. This will at least tell you whether the query really is returning zero rows or your procedure is inserting rows and you're not seeing them for some reason. Those are two different problems.
You would need to COMMIT in the Toad session where you ran this procedure before you could see that data in the table in any other session, such as the table browser. Did you remember to do that?
Not really relevant to your problem but this
Select to_date(to_char(to_date(pdate, 'DD.MM.YYYY HH24:MI:SS'), 'DD.MM.YYYY'), 'DD.MM.YYYY')
into pformateddate
from dual;
is just a long winded way of removing the time element from the passed parameter. This does the same thing:
Select trunc(pdate)
into pformateddate
from dual;
Or indeed as Tony points out, a straightforward assignment:
pformateddate := trunc(pdate);