IF statement doesn't allow Select statement to compare the given string - if-statement

Write a procedure (oracle plsql) to do any one of the following: (a) update the table course and set the fee of the input course name equal to fee of java course. (b) insert a new row for given input course and set the fee lowest of all courses available in the table. Condition is: do (a) if the input course name is already present in the table otherwise do (b) if the input course name is not in the table.
I am providing here the basic details of table:
create table course(cid number primary key, cname varchar2(100), duration number, fee number);
insert into course (CID, CNAME, DURATION, FEE)
values (101, 'java', 30, 13000);
insert into course (CID, CNAME, DURATION, FEE)
values (102, 'c', 20, 5000);
insert into course (CID, CNAME, DURATION, FEE)
values (104, 'oracle', 20, 20000);
insert into course (CID, CNAME, DURATION, FEE)
values (105, 'python', 20, 30000);
insert into course (CID, CNAME, DURATION, FEE)
values (106, 'sql', 20, 1000);
I tried the below code but i don't know how to compare the given name for each rows in the table inside IF statement. Please take a look in the code and help me.
create or replace procedure proc_CourseFeeUpdateTry(coursename in course.cname%type,
java_fee out number) is
n_fee number;
j_fee number;
begin
if course.cname = coursename then --i'm getting error here
select t.fee into j_fee from course t where t.cname = 'java';
java_fee := j_fee;
update course t set t.fee = java_fee where t.cname = coursename;
dbms_output.put_line('new course added');
else
dbms_output.put_line(sqlerrm || '-' || sqlcode);
select min(t.fee) into n_fee from course t;
java_fee := n_fee;
insert into course values (103, coursename, 40, java_fee);
end if;
commit;
end;

The error you get seems to flow from a major misconception of available table data to procedure. An IF statement has no problem allowing subsequent selects. The problem here is you referenced a table column (course.cname) without having previously select anything from the course table. Just because a table exists does not give access to the data within it, you must select before referencing column values. So before that IF at you need at least a select and since it's a procedure a Select .. into specifically.
Now a select into makes column values available if it exist but if not it throws NO_DATA_FOUND exception. We this fact to avoid that IF entirely. Further there are 2 instances where you use the structure:
select data_value into local variable;
output_variable = local_variable;
This is not necessary as you can just select directly into the output_variable.
The following contains 2 revisions to your procedure. The 1st leaving your code as is as much as possible. The 2nd revising the code to make use of all the above mentioned. I hope this helps you understand further.
The minimum necessary change requires you to select the course table prior to your IF statement and handle the no_data_found_error.
create or replace procedure proc_CourseFeeUpdateTry(coursename in course.cname%type,
java_fee out number) is
n_fee number;
j_fee number;
l_course_name course.cname%type;
begin
begin
select c.cname
into l_course_name
from course c
where c.cname = coursename;
exception
when no_data_found then
null;
end ;
if l_course_name = coursename then
select t.fee into j_fee from course t where t.cname = 'java';
java_fee := j_fee;
update course t set t.fee = java_fee where t.cname = coursename;
dbms_output.put_line('course fee updated'); --- course was not added just updted
else
dbms_output.put_line(sqlerrm || '-' || sqlcode);
select min(t.fee) into n_fee from course t;
java_fee := n_fee;
insert into course values (103, coursename, 40, java_fee); --- leave message
dbms_output.put_line('new course added');
end if;
commit;
end;
The second version uses the above mentions topics and restructures.
create or replace procedure proc_CourseFeeUpdateTry(coursename in course.cname%type,
java_fee out number) is
l_cid course.cid%type;
begin
select c.cid
into l_cid
from course c
where c.cname = coursename;
begin -- inner block to trap java not found
-- if we get here then we know that the implied test cource.cname = coursename is true
select t.fee into java_fee
from course t where t.cname = 'java';
update course t set t.fee = java_fee where cid = l_cid;
dbms_output.put_line( coursename || ' fee updated');
commit;
exception
when no_data_found then
raise_application_error( -20109, 'course name ''java'' is not in course table');
end ; -- inner block
exception
when no_data_found then
-- if we get here the we know that the course name does not exist
select min(t.fee) into java_fee from course t;
insert into course values (103, coursename, 40, java_fee);
commit;
end;
Notice the insert in both procedures. It hard codes id. As a result your procedure is able to add a row exactly 1 time. This is an extremely poor process. Either pass the new id as a parameter (still not good, but better), or redefine the table to auto generate the key. Depending on your version of Oracle look up sequences and insert triggers (prior to 12c) or Identity Columns (12c and later).

Related

PL/SQL exception-handling

I have PL/SQL anonymous block to work with finding an employees based on their department_id. I wrote a procedure for that.
Code
CREATE OR REPLACE PROCEDURE find_employees (
p_dept_no IN NUMBER,
p_error_message OUT VARCHAR2)
AS
v_dept_no NUMBER;
dept_chk EXCEPTION;
CURSOR find_emp
IS
SELECT employee_id,
first_name,
last_name,
salary,
hire_date
FROM employees
WHERE department_id = p_dept_no;
BEGIN
-- Check if the department_id in departments table
IF Condition
THEN --Need to check the condition here
RAISE dept_chk;
END IF;
FOR i IN find_emp
LOOP
DBMS_OUTPUT.put_line (i.employee_id);
DBMS_OUTPUT.put_line (i.first_name);
DBMS_OUTPUT.put_line (i.last_name);
DBMS_OUTPUT.put_line (i.salary);
DBMS_OUTPUT.put_line (i.hire_date);
END LOOP;
EXCEPTION
WHEN dept_chk
THEN
p_error_message := 'Please enter valid department number';
END find_employees;
How to check if the department_id in departments table
Note:
On that procedure there is one input parameter p_dept_no as INPUT
and p_error_message is the output parameter.
I need to check the if the department_id is in in the departments table then automatically the records will show other wise it's showing an exception so there i need to check the condition how it's possible let me know Thanks in advance.
Given that you already scan the table, you can simply set a variable in within the loop and check its value outside.
For example:
CREATE OR REPLACE PROCEDURE find_employees(p_dept_no IN
NUMBER,p_error_message OUT VARCHAR2)
AS
v_dept_no NUMBER;
dept_chk EXCEPTION;
vEmployeeFound boolean := false; -- a boolean variable
CURSOR find_emp
IS
SELECT
employee_id, first_name, last_name, salary, hire_date
FROM
employees
WHERE
department_id = p_dept_no;
BEGIN
FOR i in find_emp
LOOP
dbms_output.put_line(i.employee_id);
dbms_output.put_line(i.first_name);
dbms_output.put_line(i.last_name);
dbms_output.put_line(i.salary);
dbms_output.put_line(i.hire_date);
vEmployeeFound := true; -- set the variable
END LOOP;
-- Check if the department_id in departments table
IF NOT vEmployeeFound THEN -- check the variable value
RAISE dept_chk;
END IF;
EXCEPTION
WHEN dept_chk THEN
p_error_message:='Please enter valid department number';
END find_employees;

Kettle database lookup case insensitive

I've a table "City" with more than 100k records.
The field "name" contains strings like "Roma", "La Valletta".
I receive a file with the city name, all in upper case as in "ROMA".
I need to get the id of the record that contains "Roma" when I search for "ROMA".
In SQL, I must do something like:
select id from city where upper(name) = upper(%name%)
How can I do this in kettle?
Note: if the city is not found, I use an Insert/update field to create it, so I must avoid duplicates generated by case-sensitive names.
You can make use of the String Operations steps in Pentaho Kettle. Set the Lower/Upper option to Y
Pass the city (name) from the City table to the String operations steps which will do the Upper case of your data stream i.e. city name. Join/lookup with the received file and get the required id.
More on String Operations step in pentaho wiki.
You can use a 'Database join' step. Here you can write the sql:
select id from city where upper(name) = upper(?)
and specify the city field name from the text file as parameter. With 'Number of rows to return' and 'Outer join?' you can control the join behaviour.
This solution doesn't work well with a large number of rows, as it will execute one query per row. In those cases Rishu's solution is better.
This is how I did:
First "Modified JavaScript value" step for create a query:
var queryDest="select coalesce( (select id as idcity from city where upper(name) = upper('"+replace(mycity,"'","\'\'")+"') and upper(cap) = upper('"+mycap+"') ), 0) as idcitydest";
Then I use this string as a query in a Dynamic SQL row.
After that,
IF idcitydest == 0 then
insert new city;
else
use the found record
This system make a query for file's row but it use few memory cache

Search through the data with a loop or nested loops in SAS

I am rather a beginner in SAS. I have the following problem. Given is a big data set (my_time) which I imported into SAS looking as follows
I want to implement the following algorithm
for every account look for a status and if it is equal to na then look for the same contract after one year (one year after it gets the status na) and put the information "my_date", "status" and "money" in three new columns "new_my_date", "new_status" and "new_money" like in
I need something like countifs in excel. I found loops in SAS like DO but not for the purpose to look through all rows.
I do not even know for which key word I have to look.
I would be grateful for any hint.
A simple method would be by sorting, then exploiting the special variable prefix first. and retain statement to get the desired result.
Step 1: Sort by account, date, and status
proc sort data=have;
by account my_date status;
run;
This will guarantee that your data is in the order that you need. Since we are looking only for year+1 after the status = 'na', anything that happens in-between that doesn't matter.
Step 2: Use a data step to remember the first year when na happens for that account
data want;
set have;
by account my_date status;
retain first_na_year first_na_account;
if(first.account) then call missing(first_na_year,first_na_account);
if(status IN('na', 'tna') ) then do;
first_na_year = year;
first_na_month = month;
first_na_account = account;
end;
if( year = first_na_year+1
AND first_na_month = month
AND account = first_na_account)
AND status NOT IN('na', 'tna') )
then do;
new_status = status ;
new_my_date = my_date;
new_money = money;
end;
if(cmiss(new_status, new_my_date, new_money) ) = 0;
drop first:;
run;
For each row, we compare three things:
Is the status not 'na'?
Is the year 1 year bigger than the last time it was 'na'?
Is this the same account we're comparing?
If all are true, then we want to create the three new variables.
What's happening:
SAS is inherently a looping language, so we do not need to use a do loop here. When SAS goes to a new row, it will clear all variables in the Program Data Vector (PDV) in preparation for filling them in with the new values in the row.
Since SAS the SAS data step only goes forwards and doesn't like to go backwards, we want it to remember the first time that na occurs for that account. retain tells SAS not to discard the value of a variable when it reads a new row.
When we are done doing our comparison and we've moved onto the next account, we reset these variables to missing. by group processing allows SAS to know exactly where the first and last occurrence of the account is in the dataset.
At the end, we output only if all 3 of the new variables are not missing. cmiss counts how many variables are not missing. Note that output is always implied before the run statement, so we simply need to use an "if without then" in this case.
The final statement, drop first:;, is a simple shortcut to remove any variables that start with the phrase first. This prevents them from being shown in the final dataset.

How to show other vaues in a selectlist in APEX

I am using Oracle APEX to build a interactive report. There is a field in my database called method which should contain either A or B. In the edit page, I want to show a list containing A and B so that users can choose from those two.
I set the type of the item to SelectList and since I need to add the other value to the list, in the List of Values area, I set the type to PL/SQL Function Body returning SQL Query and the code is as follows:
Begin
select TEST_METHOD into method from table_test
where ROWID = :P2_ROWID;
IF ('Live' = method) THEN
return select 'Screenshots' from dual;
END IF;
return select 'Live' from dual;
End;
However, I got the following error:
ORA-06550: line 5, column 10: PLS-00103: Encountered the symbol "SELECT" when expecting one of the following: ( - + ; case mod new not null continue avg count current exists max min prior sql stddev sum variance execute forall merge time timestamp interval date pipe
I am new to plsql and APEX, I know the code looks wired but I don't know what's wrong. I am also wondering if there is any other way to achieve my goal? Thanks!

What's the right pattern for unique data in columns?

I've a table [File] that has the following schema
CREATE TABLE [dbo].[File]
(
[FileID] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](256) NOT NULL,
CONSTRAINT [PK_File] PRIMARY KEY CLUSTERED
(
[FileID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
The idea is that the FileID is used as the key for the table and the Name is the fully qualified path that represents a file.
What I've been trying to do is create a Stored Procedure that will check to see if the Name is already in use if so then use that record else create a new record.
But when I stress test the code with many threads executing the stored procedure at once I get different errors.
This version of the code will create a deadlock and throw a deadlock exception on the client.
CREATE PROCEDURE [dbo].[File_Create]
#Name varchar(256)
AS
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION xact_File_Create
SET XACT_ABORT ON
SET NOCOUNT ON
DECLARE #FileID int
SELECT #FileID = [FileID] FROM [dbo].[File] WHERE [Name] = #Name
IF ##ROWCOUNT=0
BEGIN
INSERT INTO [dbo].[File]([Name])
VALUES (#Name)
SELECT #FileID = [FileID] FROM [dbo].[File] WHERE [Name] = #Name
END
SELECT * FROM [dbo].[File]
WHERE [FileID] = #FileID
COMMIT TRANSACTION xact_File_Create
GO
This version of the code I end up getting rows with the same data in the Name column.
CREATE PROCEDURE [dbo].[File_Create]
#Name varchar(256)
AS
BEGIN TRANSACTION xact_File_Create
SET NOCOUNT ON
DECLARE #FileID int
SELECT #FileID = [FileID] FROM [dbo].[File] WHERE [Name] = #Name
IF ##ROWCOUNT=0
BEGIN
INSERT INTO [dbo].[File]([Name])
VALUES (#Name)
SELECT #FileID = [FileID] FROM [dbo].[File] WHERE [Name] = #Name
END
SELECT * FROM [dbo].[File]
WHERE [FileID] = #FileID
COMMIT TRANSACTION xact_File_Create
GO
I'm wondering what the right way to do this type of action is? In general this is a pattern I'd like to use where the column data is unique in either a single column or multiple columns and another column is used as the key.
Thanks
If you are searching heavily on the Name field, you will probably want it indexed (as unique, and maybe even clustered if this is the primary search field). As you don't use the #FileID from the first select, I would just select count(*) from file where Name = #Name and see if it is greater than zero (this will prevent SQL from retaining any locks on the table from the search phase, as no columns are selected).
You are on the right course with the SERIALIZABLE level, as your action will impact subsequent queries success or failure with the Name being present. The reason the version without that set causes duplicates is that two selects ran concurrently and found there was no record, so both went ahead with the inserts (which creates the duplicate).
The deadlock with the prior version is most likely due to the lack of an index making the search process take a long time. When you load the server down in a SERIALIZABLE transaction, everything else will have to wait for the operation to complete. The index should make the operation fast, but only testing will indicate if it is fast enough. Note that you can respond to the failed transaction by resubmitting: in real world situations hopefully the load will be transient.
EDIT: By making your table indexed, but not using SERIALIZABLE, you end up with three cases:
Name is found, ID is captured and used. Common
Name is not found, inserts as expected. Common
Name is not found, insert fails because another exact match was posted within milliseconds of the first. Very Rare
I would expect this last case to be truly exceptional, so using an exception to capture this very rare case would be preferable to engaging SERIALIZABLE, which has serious performance consequences.
If you do really have an expectation that it will be common to have posts within milliseconds of one another of the same new name, then use a SERIALIZABLE transaction in conjunction with the index. It will be slower in the general case, but faster when these posts are found.
First, create a unique index on the Name column. Then from your client code first check if the Name exists by selecting the FileID and putting the Name in the where clause - if it does, use the FileID. If not, insert a new one.
Using the Exists function might clean things up a little.
if (Exists(select * from table_name where column_name = #param)
begin
//use existing file name
end
else
//use new file name