Is this decision correct? PL/SQL - database

Can anyone help me with the following task:
Create a procedure SectionCount(instructor_ID) that, according to the instructor's ID, displays the numbers of those of its sections with the largest number of students enrolled. Display appropriate messages if an instructor with such an ID does not exist or if there are no sections to lead. Add a block to handle the necessary exceptions.
my solution is the following..whether it is correct:
CREATE OR REPLACE FUNCTION your_function_name(i_student_id NUMBER)
RETURN NUMBER AS v_sections_count NUMBER;
BEGIN
SELECT COUNT(SECTION_ID) into v_sections_count FROM ENROLLMENT WHERE STUDENT_ID = i_student_id;
IF v_sections_count > 3 THEN
RETURN -1;
ELSE
RETURN v_sections_count;
END IF;
EXCEPTION WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An error occured.');
RETURN -2;
END;

A suggestion or two, if I may.
always use table's alias when referencing columns. In your case, that's only one table but - often you have to "fix" a query and add yet another table, and then you don't know which column belongs to which table and have to add aliases anyway.
try to use only one RETURN per function (two, if there's an exception handling section - as in your function) because you might do something wrong in code logic and RETURN (i.e. terminate further execution) although you didn't actually want to do that. Instead, add a new variable which will be used to "calculate" the return value (such as retval in code I posted); then return its value
DBMS_OUTPUT.PUT_LINE is OK for debugging; nobody else will ever see it, because end users won't call that function from SQL*Plus (or other tools which are capable of displaying that message). For example, it (DBMS_OUTPUT.PUT_LINE) won't raise error in Oracle Apex or Oracle Forms, but you won't see anything.
Also, add SQLERRM which will actually show you which error happened. Info "an error occurred" isn't very descriptive
So:
CREATE OR REPLACE FUNCTION your_function_name (i_student_id NUMBER)
RETURN NUMBER
AS
v_sections_count NUMBER;
retval NUMBER;
BEGIN
SELECT COUNT(e.section_id)
INTO v_sections_count
FROM enrollment e
WHERE e.student_id = i_student_id;
retval := CASE WHEN v_sections_count > 3 then -1
ELSE v_sections_count
END;
RETURN retval;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An error occured: ' || SQLERRM);
retval := -2;
RETURN retval;
END your_function_name ;

Related

Ada - try to call entry of element of array of protected type, if busy - try next element

I'm working on a multithreading project and i have one array of tasks, let's call it A and one array of protected, let's call it B.
Now every element of A at some point will want to access entry from one of the elements of B. Is there a way to iterate quickly through B's and find accesible one?
I was browsing some resources avalaible online and found only
select
call entry;
or
delay;
which won't do the job.
Is there a way to do that that i'm not aware of?
Thanks in advance!
EDIT
So i got really excited about that select else statements and tried it out, unfortunately it still doesnt work, if I try it this way - using Mr. Simon Wright's code sample to simulate effect i need, the
else
part of code never gets executed, they (the taskArray elements) all think they can acces Call_Me and get stuck in the queue.
How can i solve this issue? I also tried using 'Count thing, but it somehow always ended up being 0.
with Ada.Text_IO; use Ada.Text_IO;
procedure Array_Of_POs is
protected type PO is
entry Call_Me;
private
Callable : Boolean := True; --changed
end PO;
protected body PO is
entry Call_Me when Callable is
begin
Callable := False; --added
Put_Line("Doin stuff");
delay 1.0;
Callable := True;
end Call_Me;
end PO;
POs : array (1 .. 3) of PO;
Success : Boolean := False;
task type Test; --added
task body Test is
begin
for J in POs'Range loop -- '
select
POs (J).Call_Me;
Success := True;
else
Put_Line("I never get displayed!"); --added
end select;
end loop;
Put_Line ((if Success
then "succeeded, oh dear"
else "didn't succeed, good"));
end Test;
testArray : array (1..3) of Test;
end Array_Of_POs;
You could try finding an entry E whose Count attribute (E’Count, ARM 9.9(5)) is zero; but this is going to result in race conditions, since another task could sneak in between your checking the count and your making the call. And see the note at (7), ibid.
You need a conditional entry call, as in ARM 9.7.3.
It would look something like
with Ada.Text_IO; use Ada.Text_IO;
procedure Array_Of_POs is
protected type PO is
entry Call_Me;
private
Callable : Boolean := False;
end PO;
protected body PO is
entry Call_Me when Callable is
begin
null;
end Call_Me;
end PO;
POs : array (1 .. 3) of PO;
Success : Boolean := False;
begin
for J in POs'Range loop -- '
select
POs (J).Call_Me;
Success := True;
else
null;
end select;
end loop;
Put_Line ((if Success
then "succeeded, oh dear"
else "didn't succeed, good"));
end Array_Of_POs;
In your case, I suspect this construct would be in an outer loop, so you’d need to do something to avoid busy-waiting (on the lines of putting in a delay if not Success, perhaps).
EDIT
This is a response to your edited question.
ARM 9.5.1(8) states that your PO.Call_Me contains a bounded error. In the next paragraph,
If the bounded error is detected, Program_Error is raised. If not detected, the bounded error might result in deadlock or a (nested) protected action on the same target object.
At least GNAT hasn’t done either of those.
The issue is that the body of the entry is executed in the context (thread) of the caller. So what happens is
Test calls POs(1).Call_Me
The call is accepted
Callable is set False
Put_Line and delay are called (still in the context of Test)
Callable is set True
POs(1).Call_Me returns to Test
...
The approach you’ve adopted might work if instead of using an array of POs you used an array of tasks.

Query to fetch data between two characters in informix

I have a value in informix which is like this :
value AMOUNT: <15000000.00> USD
I need to fetch 15000000.00 afrom the above.
I am using this query to fetch the data between <> as workaround
select substring (value[15,40]
from 1 for length (value[15,40]) -5 )
from tablename p where value like 'AMOUNT%';
But, this is not generic as the lenght may vary.
Please help me with a generic query for this, fetch the data between <>.
The database I am using is Informix version 9.4.
It's a diabolical problem, created by whoever chose to break one of the fundamental rules of database design: that the content of a column should be a single, indivisible value.
The best solution would be to modify the table to contain a value_descr = "AMOUNT", a value = 15000000.00, and a value_type = "USD", and ensure that the incoming data is stored in that fashion. Easier said than done, I know.
Failing that, you'll have to write a UDR that parses the string and returns the numeric portion of it. This would be feasible in SPL, but probably very slow. Something along the lines of:
CREATE PROCEDURE extract_value (inp VARCHAR(255)) RETURNING DECIMAL;
DEFINE s SMALLINT;
DEFINE l SMALLINT;
DEFINE i SMALLINT;
FOR i = 1 TO LENGTH(inp)
IF SUBSTR(inp, i, 1) = "<" THEN
LET s = i + 1;
ELIF SUBSTR(inp, i, 1) = ">" THEN
LET l = i - s - 1;
RETURN SUBSTR(inp, s, l)::DECIMAL;
END IF;
END FOR;
RETURN NULL::DECIMAL; -- could not parse out number
END PROCEDURE;
... which you would execute thus:
SELECT extract_value(p.value)
FROM tablename AS p
WHERE p.value LIKE 'AMOUNT%'
NB: that procedure compiles and produces output in my limited testing on version 11.5. There is no validation done to ensure the string between the <> parses as a number. I don't have an instance of 9.4 handy, but I haven't used any features not available in 9.4 TTBOMK.

Passing Array to Oracle Function

I am passing an array to a PL/SQL package function. I am doing this to use this array in a query inside the function which has IN clause.
My declaration of package looks like :
create or replace
PACKAGE selected_pkg IS
TYPE NUM_ARRAY IS TABLE OF NUMBER;
FUNCTION get_selected_kml(
in_layer IN NUMBER,
in_id IN NUMBER,
in_feature_ids IN selected_pkg.NUM_ARRAY,
in_lx IN NUMBER,
in_ly IN NUMBER,
in_ux IN NUMBER,
in_uy IN NUMBER
)
RETURN CLOB;
END selected_pkg;
In my PL/SQL function I am firing a query like following
select a.id, a.geom from Table_FIELD a where a.id in (select * from table (in_feature_ids)) and sdo_filter(A.GEOM,mdsys.sdo_geometry(2003,4326,NULL,mdsys.sdo_elem_info_array(1,1003,3), mdsys.sdo_ordinate_array(0,57,2.8,59)),'querytype= window') ='TRUE'
The same query runs fine if I run it from anonymous block like
CREATE TYPE num_arr1 IS TABLE OF NUMBER;
declare
myarray num_arr1 := num_arr1(23466,13396,14596);
BEGIN
FOR i IN (select a.id, a.geom from Table_FIELD a where a.id in (select * from table (myarray)) and sdo_filter(A.GEOM,mdsys.sdo_geometry(2003,4326,NULL,mdsys.sdo_elem_info_array(1,1003,3), mdsys.sdo_ordinate_array(0,57,2.8,59)),'querytype= window') ='TRUE'
loop
dbms_output.put_line(i.id);
end loop;
end;
If I try to run it by calling function as below
--Running function from passing array for IDs
declare
result CLOB;
myarray selected_pkg.num_array := selected_pkg.num_array(23466,13396,14596);
begin
result:=SELECTED_PKG.get_selected_kml(3, 19, myarray, 0.0,57.0,2.8,59);
end;
I am getting error
ORA-00904: "IN_FEATURE_IDS": invalid identifier
Could someone please help me understand the cause of it?
Thanks,
Alan
You cannot query a type declared in plsql in a sql query, as the sql engine doesn't recognise it.
Your first example works because you have declared the type numarr1 in the database, whereas the type selected_pkg.num_array is declared in a package.
Good summary here
I can't quite recreate the error you're getting; the anonymous block doesn't refer to in_feature_ids, and the package ought to only report that if it doesn't recognise it on compilation rather than at runtime - unless you're using dynamic SQL. Without being able to see the function body I'm not sure how that's happening.
But you can't use a PL/SQL-defined type in an SQL statement. At some point the table(in_feature_ids) will error; I'm getting an ORA-21700 when I tried it, which is a new one for me, I'd expect ORA-22905. Whatever the error, you have to use a type defined at schema level, not within the package, so this will work (skipping the spatial stuff for brevity):
CREATE TYPE num_array IS TABLE OF NUMBER;
/
CREATE OR REPLACE PACKAGE selected_pkg IS
FUNCTION get_selected_kml(
in_layer IN NUMBER,
in_id IN NUMBER,
in_feature_ids IN NUM_ARRAY,
in_lx IN NUMBER,
in_ly IN NUMBER,
in_ux IN NUMBER,
in_uy IN NUMBER
) RETURN CLOB;
END selected_pkg;
/
CREATE OR REPLACE PACKAGE BODY selected_pkg IS
FUNCTION get_selected_kml(
in_layer IN NUMBER,
in_id IN NUMBER,
in_feature_ids IN NUM_ARRAY,
in_lx IN NUMBER,
in_ly IN NUMBER,
in_ux IN NUMBER,
in_uy IN NUMBER
) RETURN CLOB IS
BEGIN
FOR i IN (select * from table(in_feature_ids)) LOOP
DBMS_OUTPUT.PUT_LINE(i.column_value);
END LOOP;
RETURN null;
END get_selected_kml;
END selected_pkg;
/
... and calling that also using the schema-level type:
set serveroutput on
declare
result CLOB;
myarray num_array := num_array(23466,13396,14596);
begin
result:=SELECTED_PKG.get_selected_kml(3, 19, myarray, 0.0,57.0,2.8,59);
end;
/
23466
13396
14596
PL/SQL procedure successfully completed.
Also note that you have to use exactly the same type, not just one that looks the same, as discussed in a recent question. You wouldn't be able to call your function with a variable of num_arr1 type, for example; they look the same on the surface but to Oracle they are different and incompatible.

Problems with writing to a MS Access Database (Delphi)

I'm trying to write bits of code to a Microsoft access database from Delphi. I'm getting data from a TStringGrid. The first column has the ItemID, and the 2nd column has the Quantity. I'd like it to loop through the TStringGrid and save each row as a reperate row in my database and also save the Order ID with it on every column (The order ID stays the same for each order so that doesn't need to change) .
I'm getting an error when running which says
"Project Heatmat.exe raised an exception class EVarientInvalidArgError with message 'Invalid Argument'. Process Stopped."
I can't figure out why it's giving me this error, and as you can probably see i'm not very good at coding yet. Any help would be appreciated!
Thank you.
procedure TCreateNewOrder.btnSaveClick(Sender: TObject);
var
intNumber, count : integer;
begin
Count:= 0;
if messagedlg ('Are you sure?', mtWarning, [mbyes, mbno], 0) = mryes then
begin
with HeatmatConnection.HeatmatDatabase do
begin
intNumber:= TBLOrder.RecordCount;
TBLOrder.Append;
TBLOrder['CustomerID']:= CompanyName.ItemIndex+1;
TBLOrder['OrderID']:= intNumber +1;
for count:= 1 to StringGrid1.RowCount-1 do
begin
TBLOrderedItem.Append;
TBLOrderedItem['OrderID']:= intNumber+1;
TBLOrderedItem['ItemID']:= StringGrid1.Cells[1, count];
TBLOrderedItem['Quantity']:= StringGrid1.Cells[2, count];
TBLOrderedItem.Post;
end;
end;
end;
end;
TStringGrid cells are strings. trying to assign a string directly to a numeric field will raise an Exception.
So a good practice is to assign values to database fields via AsString, AsInteger, AsBoolean etc... this will make the correct conversion.
In your code use:
TBLOrderedItem.FieldByName('ItemID').AsString := StringGrid1.Cells[1, count];
The same is true for Quantity.
To assign an Integer value use:
TBLOrderedItem.FieldByName('OrderID').AsInteger := intNumber + 1;
BTW, you are forgetting TBLOrder.Post i.e:
....
TBLOrder.Append;
TBLOrder.FieldByName('CustomerID').AsInteger := CompanyName.ItemIndex + 1;
TBLOrder.FieldByName('OrderID').AsInteger := intNumber + 1;
TBLOrder.Post;
...
Finally, I would also suggest to rename TBLOrder to tblOrder so that it's name wont imply that it is a Type.

Equivalent plpgsql trigger in C

I've a PostgreSQL 9.0 server and I'm using heritage on some tables, for this reason I have to simulate foreign keys through triggers like this:
CREATE OR REPLACE FUNCTION othertable_before_update_trigger()
RETURNS trigger AS
$BODY$
DECLARE
sql VARCHAR;
rows SMALLINT;
BEGIN
IF (NEW.parenttable_id IS DISTINCT FROM OLD.parenttable_id) THEN
sql := 'SELECT id '
|| 'FROM parentTable '
|| 'WHERE id = ' || NEW.parenttable_id || ';';
BEGIN
EXECUTE sql;
GET DIAGNOSTICS rows = ROW_COUNT;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'Error when I try find in parentTable the id %. SQL: %. ERROR: %',
NEW.parenttable_id,sql,SQLERRM;
END;
IF rows = 0 THEN
RAISE EXCEPTION 'Not found a row in parentTable with id %. SQL: %.',NEW.parenttable_id,sql;
END IF;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
But due to performance I try to create a equivalent trigger in C code:
#include "postgres.h"
#include "executor/spi.h" /* this is what you need to work with SPI */
#include "commands/trigger.h" /* ... and triggers */
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
extern Datum othertable_before_update_trigger(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(othertable_before_update_trigger);
Datum
othertable_before_update_trigger(PG_FUNCTION_ARGS) {
TriggerData *trigdata = (TriggerData *) fcinfo->context;
TupleDesc tupdesc;
HeapTuple rettuple;
bool isnull;
int ret, i;
/* make sure it's called as a trigger at all */
if (!CALLED_AS_TRIGGER(fcinfo))
elog(ERROR, "othertable_before_update_trigger: not called by trigger manager");
/* tuple to return to executor */
if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
rettuple = trigdata->tg_newtuple;
else
rettuple = trigdata->tg_trigtuple;
tupdesc = trigdata->tg_relation->rd_att;
/* connect to SPI manager */
if ((ret = SPI_connect()) < 0)
elog(ERROR, "othertable_before_update_trigger (fired %s): SPI_connect returned %d", "before", ret);
[A]
[B]
return PointerGetDatum(rettuple);
}
I need fill the code in:
[A]: get the previous and new values for parenttable_id. With:
int32 att = DatumGetInt32(heap_getattr(rettuple, 1, tupdesc, &isnull));
or
int32 att = DatumGetInt32(SPI_getbinval(rettuple, tupdesc, 1, &isnull));
I can get only the old value of parenttable_id but not the new value. Even if I try to use the column name instead of their number with:
GetAttributeByName (rettuple->t_data, "parenttable_id", &isnull);
Getting error: record type has not been registered
[B]: execute the query SELECT id FROM parentTable WHERE id = NEW.parenttable_id
I found the function SPI_execute_with_args, but I haven't found examples of this for my case.
Thanks in advance.
This does not strike me as the sort of trigger that will benefit from moving to C. You can take advantage of a lot of caching of plans in pl/pgsql and this is likely to help more than moving to C will speed things up. Additionally there are two big performance red flags here that strike me as worth fixing.
The first is that EXCEPTION blocks have significant performance costs. All you are doing here is reporting an exception in more friendly terms. You would do better to just remove it if performance is an issue.
The second is your EXECUTE which means that the query plan will never be cached. You really should change this to a straight query.
When you combine this with the possibility of a C-language trigger causing crashes or worse in the back-end, I think you will be putting a lot of effort into rewriting the trigger for fewer performance gains than you could get by rewriting it in pl/pgsql.

Resources