The question given was to write a PL/SQL block to print the details of the customers whose total order quantity is greater than 200 where the Customer table had ID number(5) primary key, Name varchar2(20), Contact_No varchar2(10) and the Order table had Order_Id number(5) primary Key, Quantity number(4) not null, C_id number(5) references Customer(ID).
If no record is found "No Records Found" is to be printed out.
This is the Code I wrote:
SET SERVEROUTPUT ON;
begin
dbms_output.put_line('Customer_Id ' || 'Customer_Name '|| 'Customer_Phone');
for cur_d in (select o.C_ID,total AS sum(o.QUANTITY) from Orders o group by o.C_ID) loop
from Customers c
where c.ID = cur_d.C_ID and cur_d.total > 200;
dbms_output.put_line(c.ID || c.Name || c.Contact_No);
end loop;
end;
/
The error I faced was -
for cur_d in (select o.C_ID,total AS sum(o.QUANTITY) from Orders o group by o.C_ID) loop
ERROR at line 2:
ORA-06550: line 2, column 41:
PL/SQL: ORA-00923: FROM keyword not found where expected
ORA-06550: line 2, column 15:
PL/SQL: SQL Statement ignored
ORA-06550: line 3, column 1:
PLS-00103: Encountered the symbol "FROM" when expecting one of the following:
( begin case declare exit for goto if loop mod null pragma
raise return select update while with '<'an identifier'>'
'<'a double-quoted delimited-identifier'> ')
Even after you correct the substantial that have been pointed out your procedure will still fail. You indicate that if no record is found printing a message to that effect. That in itself is ambiguous. Does that mean for the no records in the data table or no records for a given customer? Either way you have no code to produce such a message. How do you expect it to be written. Finally, SQL is set based processing so you need to start thinking in terms of sets instead of loops. The following reduces the db access to a single query; the loop is only to print results.
begin
dbms_output.put_line('Customer_Id ' || 'Customer_Name '|| 'Customer_Phone' || 'Total Orders');
for cur_d in (
with order_totals as
( select c_id, sum (quantity) order_total
from orders
group by c_id
having sum(quantity) > 200
)
select c.id, c.name, c.contact_no
, case when o.c_id is null
then 'No Records Found'
else to_char(o.order_total)
end order_total
from customers c
left join order_totals o
on c.id = o.c_id
order by c.id
)
loop
dbms_output.put_line(cur_d.ID || cur_d.Name || cur_d.Contact_No || cur_d.order_total);
end loop;
end;
The results are jammed together just as you initially had them. You need to workout their presentation.
There is a "select into" part missing between "for .. loop" and "from" parts. It has to be like this in order to work
for ..... loop
select some_column -- <-- this line is missing
into some_variable -- <-- this line is missing too
from ..........
This is the kind of issue where formatting your code will make the problem obvious. If you always start a new line and indent after for xxx in ( and also place the closing bracket on its own line, and include some gaps between commands, you'll get this, which is clearly wrong:
begin
dbms_output.put_line('Customer_Id ' || 'Customer_Name '|| 'Customer_Phone');
for cur_d in (
select o.c_id, total as sum(o.quantity)
from orders o
group by o.c_id
)
loop
from customers c
where c.id = cur_d.c_id
and cur_d.total > 200;
dbms_output.put_line(c.id || c.name || c.contact_no);
end loop;
end;
The first statement inside the loop seems to be missing something, as ekochergin mentioned.
total as sum(o.quantity) is backwards as Turo mentioned.
If you want id, name and contact_no to be printed in columns, you should look at lpad and rpad for formatting them. Just concatenating them together will produce something unreadable.
The dbms_output inside the loop refers to c.id, c.name and c.contact_no, but the record is called cur_d, not c.
Also cur_d is a slightly confusing name for a record (it's not a cursor). I always use r for cursor records unless there is some other r involved that it could be confused with.
I am currently using the following code to pivot a table and it works perfectly. Now I want to replace any null values with 'No Data' after it is summed but I am getting errors, so I think I am placing the case statement in the wrong place.
This works:
SELECT *
FROM (SELECT PROV_NO, DATA_YEAR, DATA_MONTH, MEASURE_ID, CASES
FROM pivot_test_2)
PIVOT (SUM(CASES) FOR (MEASURE_ID) IN ('MORT_30_AMI', 'MORT_30_HF', 'MORT_30_PN'))
order by PROV_NO, DATA_YEAR, DATA_MONTH;
but this does not
SELECT *
FROM (SELECT PROV_NO, DATA_YEAR, DATA_MONTH, MEASURE_ID, CASES
FROM pivot_test_2)
PIVOT (SUM(CASES) FOR (MEASURE_ID) IN ('MORT_30_AMI', 'MORT_30_HF', 'MORT_30_PN'))
case when MORT_30_HF is null then 'No Data' else MORT_30_HF end
order by PROV_NO, DATA_YEAR, DATA_MONTH;
I get "ORA-00933: SQL command not properly ended" as the error. I'm trying to place ";" around but the error is still the same. I am currently in Oracle 11g and using Golden as my scripting/retrieval software.
You can move the CASE statement to the SELECT statement and handle the NULL values there. Better yet, use COALESCE. But unfortunately you have to do this for each item in the SELECT list:
SELECT
--Must manually reference each column.
COALESCE(TO_CHAR(MORT_30_AMI), 'No Data') MORT_30_AMI,
COALESCE(TO_CHAR(MORT_30_HF), 'No Data') MORT_30_HF,
COALESCE(TO_CHAR(MORT_30_PN), 'No Data') MORT_30_PN,
PROV_NO, DATA_YEAR, DATA_MONTH
FROM (SELECT PROV_NO, DATA_YEAR, DATA_MONTH, MEASURE_ID, CASES
FROM pivot_test_2)
PIVOT
(
SUM(CASES)
FOR (MEASURE_ID) IN
--Use aliases to make the columns easier to use.
('MORT_30_AMI' MORT_30_AMI, 'MORT_30_HF' MORT_30_HF, 'MORT_30_PN' MORT_30_PN))
ORDER BY PROV_NO, DATA_YEAR, DATA_MONTH;
A Simpler Version That Doesn't Work
Ideally you would be able to replace this part of the code:
SUM(CASES)
With this:
COALESCE(TO_CHAR(SUM(CASES)), 'No data')
Then you wouldn't need to handle each column separately. But there doesn't appear to be a way to automatically apply a non-aggregate function to the results of a PIVOT. Using the above code generates this error message:
ORA-56902: expect aggregate function inside pivot operation
Sample Schema
create table pivot_test_2
(
PROV_NO CHAR(6),
DATA_YEAR NUMBER(4),
DATA_MONTH Number(2),
MEASURE_ID VARCHAR2(250),
CASES NUMBER
);
insert into pivot_test_2
select 'A', 2000, 1, 'MORT_30_AMI', 1 from dual union all
select 'A', 2000, 1, 'MORT_30_AMI', 1 from dual union all
select 'A', 2000, 1, 'MORT_30_HF', 2 from dual union all
select 'A', 2000, 1, 'MORT_30_HF', 2 from dual;
Thanks everyone, with help from you all, I was able to cob this together and it works.
SELECT PROV_NO, DATA_YEAR, DATA_MONTH,
case when MORT_30_AMI is null then 'No Data' else to_char(MORT_30_AMI) end as MORT_30_AMI,
case when MORT_30_HF is null then 'No Data' else to_char(MORT_30_HF) end as MORT_30_HF,
case when MORT_30_PN is null then 'No Data' else to_char(MORT_30_PN) end as MORT_30_PN
FROM pivot_test_2
PIVOT (SUM(CASES) FOR (MEASURE_ID) IN ('MORT_30_AMI' as MORT_30_AMI,'MORT_30_HF' as MORT_30_HF, 'MORT_30_PN' as MORT_30_PN))
order by PROV_NO, DATA_YEAR, DATA_MONTH;
Use DECODE keyword for the query defined fields (from the list of pivot defined fields) and then replace NULL with whatever value you need. i.e., instead of NULL replace it with 'No Data'. I believe that should solve this issue.
I'm trying to do this query (in oracle) but I have some problems:
SELECT CASE
WHEN deptno = '10' THEN scott.seq.nextval
|| 'next10'
WHEN deptno = '20' THEN scott.seqnextval
|| 'next20'
WHEN deptno = '30' THEN scott.seq.currval
|| 'curr'
END col_1
FROM scott.emp;
I'm getting this results:
COL_1
----------------------------------------------
191next20
192curr
193curr
194next20
195curr
196curr
197next10
198next20
199next10
200curr
201next20
202curr
203next20
204next10
205next20
206next10
207next10
And this is what I think they should be:
COL_1
----------------------------------------------
191next20
192curr
193curr
194next20
194curr
194curr
197next10
198next20
199next10
199curr
201next20
201curr
203next20
204next10
205next20
206next10
207next10
So, why i get the next value of the sequence also when I should have the current value and not only when the case selects the next value?
Yeah, this could be done with a plsql script but I can't.
Thanks you!
Nextval and currval are not functions, but are "Sequence Pseudocolumns".
"Within a single SQL statement containing a reference to NEXTVAL, Oracle increments the sequence once: For each row returned by the outer query block of a SELECT statement. Such a query block can appear in the following places. ..." (emphasis added) [Oracle Database SQL Language Reference, "How to Use Sequence Values"]
In other words, seq.nextval is not a function with a side affect, but a pseudocolumn that has a particular value per row. Once there is a single reference to seq.nextval, the value increments for every row, whether or not the value is used. The outcome OP is seeing is a peculiar to sequences, not case expressions. For example, same thing with decode:
SQL> select decode(deptno
2 , 10, seq.nextval || 'next10'
3 , 20, seq.nextval || 'next20'
4 , 30, seq.currval || 'curr30')
5 from emp;
DECODE(DEPTNO,10,SEQ.NEXTVAL||'NEXT10',20,SEQ.
----------------------------------------------
35next20
36curr30
37curr30
38next20
39curr30
40curr30
41next10
42next20
43next10
44curr30
45next20
46curr30
47next20
48next10
Interesting. Per the Oracle docs:
The statements in a WHEN clause can modify the database and call
non-deterministic functions. There is no fall-through mechanism as in
the C switch statement
Notice it doesn't say the statements in the "true" WHEN clause. So even if the when statement is false, the nextval will fire:
select
case when 1=0 then 'next ' || seq_id.nextval
when 1=1 then 'curr ' || seq_id.currval
end col1
from dual;
I must admit this is different than I expected.
EDIT:
See answer from ShannonSeverance
I am developing an application in Oracle APEX. I have a string with user id's that is comma deliminated which looks like this,
45,4932,20,19
This string is stored as
:P5_USER_ID_LIST
I want a query that will find all users that are within this list my query looks like this
SELECT * FROM users u WHERE u.user_id IN (:P5_USER_ID_LIST);
I keep getting an Oracle error: Invalid number. If I however hard code the string into the query it works. Like this:
SELECT * FROM users u WHERE u.user_id IN (45,4932,20,19);
Anyone know why this might be an issue?
A bind variable binds a value, in this case the string '45,4932,20,19'. You could use dynamic SQL and concatenation as suggested by Randy, but you would need to be very careful that the user is not able to modify this value, otherwise you have a SQL Injection issue.
A safer route would be to put the IDs into an Apex collection in a PL/SQL process:
declare
array apex_application_global.vc_arr2;
begin
array := apex_util.string_to_table (:P5_USER_ID_LIST, ',');
apex_collection.create_or_truncate_collection ('P5_ID_COLL');
apex_collection.add_members ('P5_ID_COLL', array);
end;
Then change your query to:
SELECT * FROM users u WHERE u.user_id IN
(SELECT c001 FROM apex_collections
WHERE collection_name = 'P5_ID_COLL')
An easier solution is to use instr:
SELECT * FROM users u
WHERE instr(',' || :P5_USER_ID_LIST ||',' ,',' || u.user_id|| ',', 1) !=0;
tricks:
',' || :P5_USER_ID_LIST ||','
to make your string ,45,4932,20,19,
',' || u.user_id|| ','
to have i.e. ,32, and avoid to select the 32 being in ,4932,
I have faced this situation several times and here is what i've used:
SELECT *
FROM users u
WHERE ','||to_char(:P5_USER_ID_LIST)||',' like '%,'||to_char(u.user_id)||',%'
ive used the like operator but you must be a little carefull of one aspect here: your item P5_USER_ID_LIST must be ",45,4932,20,19," so that like will compare with an exact number "',45,'".
When using it like this, the select will not mistake lets say : 5 with 15, 155, 55.
Try it out and let me know how it goes;)
Cheers ,
Alex
Create a native query rather than using "createQuery/createNamedQuery"
The reason this is an issue is that you cannot just bind an in list the way you want, and just about everyone makes this mistake at least once as they are learning Oracle (and probably SQL!).
When you bind the string '32,64,128', it effectively becomes a query like:
select ...
from t
where t.c1 in ('32,64,128')
To Oracle this is totally different to:
select ...
from t
where t.c1 in (32,64,128)
The first example has a single string value in the in list and the second has a 3 numbers in the in list. The reason you get an invalid number error is because Oracle attempts to cast the string '32,64,128' into a number, which it cannot do due to the commas in the string.
A variation of this "how do I bind an in list" question has come up on here quite a few times recently.
Generically, and without resorting to any PLSQL, worrying about SQL Injection or not binding the query correctly, you can use this trick:
with bound_inlist
as
(
select
substr(txt,
instr (txt, ',', 1, level ) + 1,
instr (txt, ',', 1, level+1) - instr (txt, ',', 1, level) -1 )
as token
from (select ','||:txt||',' txt from dual)
connect by level <= length(:txt)-length(replace(:txt,',',''))+1
)
select *
from bound_inlist a, users u
where a.token = u.id;
If possible the best idea may be to not store your user ids in csv! Put them in a table or failing that an array etc. You cannot bind a csv field as a number.
Please dont use: WHERE ','||to_char(:P5_USER_ID_LIST)||',' like '%,'||to_char(u.user_id)||',%' because you'll force a full table scan although with the users table you may not have that many so the impact will be low but against other tables in an enterprise environment this is a problem.
EDIT: I have put together a script to demonstrate the differences between the regex method and the wildcard like method. Not only is regex faster but it's also a lot more robust.
-- Create table
create table CSV_TEST
(
NUM NUMBER not null,
STR VARCHAR2(20)
);
create sequence csv_test_seq;
begin
for j in 1..10 loop
for i in 1..500000 loop
insert into csv_test( num, str ) values ( csv_test_seq.nextval, to_char( csv_test_seq.nextval ));
end loop;
commit;
end loop;
end;
/
-- Create/Recreate primary, unique and foreign key constraints
alter table CSV_TEST
add constraint CSV_TEST_PK primary key (NUM)
using index ;
alter table CSV_TEST
add constraint CSV_TEST_FK unique (STR)
using index;
select sysdate from dual;
select *
from csv_test t
where t.num in ( Select Regexp_Substr('100001, 100002, 100003 , 100004, 100005','[^,]+', 1, Level) From Dual
Connect By Regexp_Substr('100001, 100002,100003, 100004, 100005', '[^,]+', 1, Level) Is Not Null);
select sysdate from dual;
select *
from csv_test t
where ('%,' || '100001,100002, 100003, 100004 ,100005' || ',%') like '%,' || num || ',%';
select sysdate from dual;
select *
from csv_test t
where t.num in ( Select Regexp_Substr('100001, 100002, 100003 , 100004, 100005','[^,]+', 1, Level) From Dual
Connect By Regexp_Substr('100001, 100002,100003, 100004, 100005', '[^,]+', 1, Level) Is Not Null);
select sysdate from dual;
select *
from csv_test t
where ('%,' || '100001,100002, 100003, 100004 ,100005' || ',%') like '%,' || num || ',%';
select sysdate from dual;
drop table csv_test;
drop sequence csv_test_seq;
Solution from Tony Andrews works for me. The process should be added to "Page processing" >> "After submit">> "Processes".
As you are Storing User Ids as String so You can Easily match String Using Like as Below
SELECT * FROM users u WHERE u.user_id LIKE '%'||(:P5_USER_ID_LIST)||'%'
For Example
:P5_USER_ID_LIST = 45,4932,20,19
Your Query Surely Will return Any of 1 User Id which Matches to Users table
This Will Surely Resolve Your Issue , Enjoy
you will need to run this as dynamic SQL.
create the entire string, then run it dynamically.
I have two rows that have a varchar column that are different according to a Java .equals(). I can't easily change or debug the Java code that's running against this particular database but I do have access to do queries directly against the database using SQLDeveloper. The fields look the same to me (they are street addresses with two lines separated by some new line or carriage feed/new line combo).
Is there a way to see all of the hidden characters as the result of a query?I'd like to avoid having to use the ascii() function with substr() on each of the rows to figure out which hidden character is different.
I'd also accept some query that shows me which character is the first difference between the two fields.
Try
select dump(column_name) from table
More information is in the documentation.
As for finding the position where the character differs, this might give you an idea:
create table tq84_compare (
id number,
col varchar2(20)
);
insert into tq84_compare values (1, 'hello world');
insert into tq84_compare values (2, 'hello' || chr(9) || 'world');
with c as (
select
(select col from tq84_compare where id = 1) col1,
(select col from tq84_compare where id = 2) col2
from
dual
),
l as (
select
level l from dual
start with 1=1
connect by level < (select length(c.col1) from c)
)
select
max(l.l) + 1position
from c,l
where substr(c.col1,1,l.l) = substr(c.col2,1,l.l);
SELECT DUMP('€ÁÑ', 1016)
FROM DUAL
... will print something like:
Typ=96 Len=3 CharacterSet=WE8MSWIN1252: 80,c1,d1