I'm attempting to replicate the functionality of an Excel workbook in Oracle. One of the formulae on the worksheet uses CHIDIST, which "Returns the one-tailed probability of the chi-squared distribution". I can't see a similar function in Oracle anywhere! Am I going to have to write my own CHIDIST function?
Here's a link to Excel's documentation on CHIDIST: http://office.microsoft.com/en-gb/excel-help/chidist-HP005209010.aspx
I've already worked out the Chi Square and Degrees of Freedom.
Thanks
If you're using CHIDIST to do a chi-squared test, the STATS_CROSSTAB function might be a different means to the same end. It will calculate the chi-squared value, significance, or degrees of freedom for two sets of paired observations.
Right tail probability... nice...
In oracle you can embedded java code into stored procedures and functions. The best way is to
use proper java class and call it from oracle's function. It is not that hard:
http://docs.oracle.com/cd/B19306_01/java.102/b14187/chfive.htm
http://jwork.org/scavis/api/doc.php/umontreal/iro/lecuyer/probdist/ChiDist.html
That is all I can do for you:
DECLARE
l_value_to_evaluate NUMBER (10, 3) := 18.307; /* X; Value at which you want to evaluate the distribution */
l_degrees_freedom NUMBER := 10; /* Degrees of freedom */
l_number NUMBER;
FUNCTION is_number(str_in IN VARCHAR2) RETURN NUMBER
IS
n NUMBER;
BEGIN
n := TO_NUMBER(str_in);
RETURN 1;
EXCEPTION
WHEN VALUE_ERROR THEN
RETURN 0;
END;
BEGIN
-- If either argument is nonnumeric, CHIDIST returns the #VALUE! error value.
l_number := is_number(l_value_to_evaluate);
l_number := is_number(l_degrees_freedom);
-- If x is negative, CHIDIST returns the #NUM! error value.
IF SIGN(l_value_to_evaluate) = -1 THEN
RAISE_APPLICATION_ERROR(-20998, '#NUM!');
END IF;
-- If degrees_freedom is not an integer, it is truncated.
l_degrees_freedom := TRUNC(l_degrees_freedom);
-- If degrees_freedom < 1 or degrees_freedom > 10^10, CHIDIST returns the #NUM! error value.
IF l_degrees_freedom < 1
OR l_degrees_freedom > POWER(10, 10) THEN
RAISE_APPLICATION_ERROR(-20997, '#NUM!');
END IF;
-- CHIDIST is calculated as CHIDIST = P(X>x), where X is a χ2 random variable.
/* Here the integral's implementation */
EXCEPTION
WHEN VALUE_ERROR THEN
RAISE_APPLICATION_ERROR(-20999, '#VALUE!');
END;
Related
I am trying to use a condition elsif where something like decode needs to be used so that the condition is true and insertion is made. I am doing this in a procedure and condition is like
elsif ((v_DIVIDEND/Divisor)-1 < ABS(0.2)) then
insert into table(Divisor,b,c) values(Dividend,y,z);
It works fine when the divisor is not zero but when the divisor is zero it fails. I want to rule out zeroes in divisor using like another nested if condition within elsif or something like decode. I tried another if but syntax seemed to be wrong. Using Decode says it can only be used in SQL statement. Any suggestions, please...
you can also handle the "divisor equal to zero" exception and made the insert if the error occurs, see samples code in handling "divisor equal to zero" exception,
Sample 1: This sample defines a divisor_equal_to_zero exception
DECLARE
divisor_equal_to_zero EXCEPTION;
PRAGMA EXCEPTION_INIT(divisor_equal_to_zero, -1476);
v_divisor NUMBER := 0;
v_quotient NUMBER;
BEGIN
v_quotient := 1/v_divisor;
DBMS_OUTPUT.PUT_LINE('Print A: '||v_quotient);
EXCEPTION
WHEN divisor_equal_to_zero THEN
v_divisor := 1;
v_quotient := 1/v_divisor;
DBMS_OUTPUT.PUT_LINE('Print B: '||v_quotient);
--you can put the insert statement here
END;
/
Sample 2: This sample use the pre-defined ZERO_DIVIDE exception
DECLARE
v_divisor NUMBER := 0;
v_quotient NUMBER;
BEGIN
v_quotient := 1/v_divisor;
DBMS_OUTPUT.PUT_LINE('Print A: '||v_quotient);
EXCEPTION
WHEN ZERO_DIVIDE THEN
v_divisor := 1;
v_quotient := 1/v_divisor;
DBMS_OUTPUT.PUT_LINE('Print B: '||v_quotient);
--you can put the insert statement here
END;
/
Well, depending on what you want to get as a result when divisor = 0 (i.e. something large or something small), you can use something like this:
For small result (divide by 1E99):
case when divisor = 0 then 1E99 else divisor end
For large result (divide by 1E-99)
case when divisor = 0 then 1E-99 else divisor end
As of you being unable to use DECODE: that's correct, it can be used ONLY within the SELECT statement. It means that you should rewrite your code and put everything into SELECT (which is probably a bad idea). So - try CASE.
Maybe you could add a condition like this to check if Divisor is zero.
elsif ( Divisor !=0 ) and ((v_DIVIDEND/Divisor)-1 < ABS(0.2))
THEN
insert into table(Divisor,b,c) values(Dividend,y,z)
elsif Divisor = 0
THEN
insert into table(Divisor,b,c) values(?,?,?);
You can try nullif:
declare
divisor constant integer := 0;
result number;
begin
result := 100 / nullif(divisor,0);
end;
Here divisor is replaced with null if it has the value 0, giving the result as null.
Using PL/SQL, it is possible to call a stored function from within that same function. This can be demonstrated with the following example:
CREATE OR REPLACE FUNCTION factorial(x in number)
RETURN number
IS
f number;
BEGIN
IF x = 0 THEN
f := 1;
ELSE
f := x * factorial(x-1);
END IF;
RETURN f;
END;
/
DECLARE
num number;
factorial number;
BEGIN
num := #
factorial := factorial(num);
dbms_output.put_line(' The factorial of '|| num || ' is ' || factorial);
END;
/
Can this be done using PL/SQL stored procedures as well?
Yes, you can write a procedure that calls itself recursively in PL/SQL. Here is an example - implementing the factorial.
With that said, don't ever write a procedure (or a function like yours) without error handling. If you don't understand why, change 5 to 5.3 in the anonymous block below, and you'll see why.
CODE window:
create or replace procedure fact ( x in number, x_fact out number )
as
begin
if x = 0 then x_fact := 1;
else fact(x-1, x_fact);
x_fact := x * x_fact;
end if;
end;
/
set serveroutput on
declare
z number;
begin
fact(5, z);
dbms_output.put_line(z);
end;
/
SCRIPT OUTPUT window (matching each "result" to the corresponding part of the code left as an exercise):
Procedure FACT compiled
PL/SQL procedure successfully completed.
120
You can certainly call PL/SQL functions recursively (with all the usual warnings about the dangers of doing so in any language!).
You are, however, going to run into trouble if you name a local variable the same as your function. You will, for example, get this error when you try to execute the block:
PLS-00222: no function with name 'FACTORIAL' exists in this scope
I am trying to create a way to find the exponent of a number (in this case the base is 4 and the exponent 2 so the answer should be 16) using a procedure without using the POW Function or any built in functions to find the exponent. Eventually I would like to take input numbers from the user.
set serveroutput on;
CREATE OR REPLACE PROCEDURE Exponent(base number, exponent number) as
answer number;
BEGIN
base := 4;
exponent := 2;
LOOP
IF exponent > 1 THEN
answer := base * base;
END IF;
END LOOP;
dbms_output.put_line('Answer is: ' || answer);
END;
/
Error(7,25): PLS-00103: "expression 'BASE' cannot be used as an assignment target" and "expression 'EXPONENT' cannot be used as an assignment target"
Any ideas on how to solve the error and/or better ways of getting the exponent without using built-in functions like POW?
In your procedure base and exponent are input parameters and can't be changed. You've got a couple of options:
1) copy the parameters to variables internal to the procedure and manipulate those internal values, or
2) change the parameters to be input/output parameters so you can change them.
Examples:
1)
CREATE OR REPLACE PROCEDURE Exponent(pin_base number, pin_exponent number) as
base number := pin_base;
exponent number := pin_exponent;
answer number;
BEGIN
base := 4;
exponent := 2;
LOOP
IF exponent > 1 THEN
answer := base * base;
END IF;
END LOOP;
dbms_output.put_line('Answer is: ' || answer);
END;
2)
CREATE OR REPLACE PROCEDURE Exponent(base IN OUT number,
exponent IN OUT number) as
answer number;
BEGIN
base := 4;
exponent := 2;
LOOP
IF exponent > 1 THEN
answer := base * base;
END IF;
END LOOP;
dbms_output.put_line('Answer is: ' || answer);
END;
The best thing is that whatever Oracle provides as inbuilt functionality that serves the purpose in a best possible. (Almost all the times better then customized codes) Try to use EXP function. I have tried to make customized code per my understanding. Hope this helps.
CREATE OR REPLACE
FUNCTION EXP_DUMMY(
BASE_IN IN NUMBER,
EXPO_IN IN NUMBER)
RETURN PLS_INTEGER
AS
lv PLS_INTEGER:=1;
BEGIN
FOR I IN
(SELECT base_in COL1 FROM DUAL CONNECT BY level < expo_in+1
)
LOOP
lv:=lv*i.col1;
END LOOP;
RETURN
CASE
WHEN EXPO_IN = 0 THEN
1
ELSE
lv
END;
END;
SELECT EXP_DUMMY(2,4) FROM DUAL;
Is there a way to determine the maximum possible value of a pls_integer either by a language predefined constant or a function? I can find the maximum on the internet (2^31 - 1 = 2,147,483,647), but I don't want to hard code it.
Cheers :)
I don't think this is possible. Why? Because it is not needed - PLS_INTEGER's maximal value is due to its maximal size - 4 bytes (and it is a signed datatype).
What is more, as stated in documentation about PL/SQL datatypes, PLS_INTEGER is actually a BINARY_INTEGER. Look at the definition of PLS_INTEGER in the Oracle's STANDARD package:
subtype pls_integer is binary_integer;
And then take a look at the definition of BINARY_INTEGER:
subtype BINARY_INTEGER is INTEGER range '-2147483647'..2147483647;
Nowhere in the STANDARD package header can you find a constant which holds the maximal value of those datatypes.
I don't think there is any constant that you can use; however, if it is so vital not to hard code any values, you can calculate the maximum value with the method given below.
This solution is based on the assumption that the maximum value would be in the form of 2^b-1 where b is the number of bits.
This is the function you can use:
CREATE FUNCTION MAX_PLS_INTEGER_SIZE RETURN PLS_INTEGER AS
p PLS_INTEGER;
b NUMBER;
BEGIN
b := 0;
WHILE TRUE LOOP
BEGIN
p := POWER(2, b)-1;
b := b + 1;
EXCEPTION WHEN OTHERS THEN
EXIT;
END;
END LOOP;
RETURN p;
end;
After you create the function, you can test it:
SELECT MAX_PLS_INTEGER_SIZE FROM DUAL;
Result:
MAX_PLS_INTEGER_SIZE
--------------------
2147483647
so simple, if I create my function as CREATE OR REPLACE FUNCTION MD5_ENCODE it will run smoothly, but if it stays anonymously within the SQL-Plus block as PL/SQL --> "may not be a function" error.
What is this Oracle "feature" again?
DECLARE
FUNCTION MD5_ENCODE(CLEARTEXT IN VARCHAR2) RETURN VARCHAR2 IS
CHK VARCHAR2(16);
HEX VARCHAR2(32);
I INTEGER;
C INTEGER;
H INTEGER;
BEGIN
IF CLEARTEXT IS NULL THEN
RETURN '';
ELSE
CHK := DBMS_OBFUSCATION_TOOLKIT.MD5(INPUT_STRING
=> CLEARTEXT);
FOR I IN 1 .. 16 LOOP
C := ASCII(SUBSTR(CHK, I, 1));
H := TRUNC(C / 16);
IF H >= 10 THEN
HEX := HEX || CHR(H + 55);
ELSE
HEX := HEX || CHR(H + 48);
END IF;
H := MOD(C, 16);
IF H >= 10 THEN
HEX := HEX || CHR(H + 55);
ELSE
HEX := HEX || CHR(H + 48);
END IF;
END LOOP;
RETURN HEX;
END IF;
END;
BEGIN
UPDATE ADDRESSES_T SET STREET = MD5ENCODE(STREET) ;
-- etc...
END
http://forums.oracle.com/forums/thread.jspa?threadID=245112
There are a number of things it could be.
My #1 candidate is, we can only use functions in SQL statements that are public i.e. declared in a package spec. This is the case even for SQL statements executed within the same Package Body. The reson is that SQL statements are executed by a different engine which can only see publicly declared functions.
Long story short, function must be defined in a package, like CREATE OR REPLACE FUNCTION MD5ENCODE(IN_TEXT IN VARCHAR2) RETURN VARCHAR2 IS ...
In order to call a function within a SQL statement, it needs to exist as a function object within the database. The SQL statement is parsed and executed separately from your PL/SQL code, so it doesn't have access to the locally defined function.
If you really don't want to create the function object, you could use a cursor to loop over the rows in the table, execute the function in a PL/SQL statement, and update each row as you go.
Your first problem is you have a typo.
FUNCTION MD5_ENCODE(CLEARTEXT IN
VARCHAR2) RETURN VARCHAR2 IS
versus
UPDATE ADDRESSES_T SET STREET =
MD5ENCODE(STREET) ;
You are missing the underscore in the function call in the update statement.
The next error you will encounter is :
PLS-00231: function 'MD5_ENCODE' may not be used in SQL
So you can simply assign the function results to a variable and use it in the Update statement.
As it mentioned above comments, this could be useful.
DECLARE
V NUMBER := 0;
FUNCTION GET_SQ(A NUMBER) RETURN NUMBER AS
BEGIN
RETURN A * A;
END;
BEGIN
V := GET_SQ(5);
--DBMS_OUTPUT.PUT_LINE(V);
UPDATE MYTABLE A SET A.XCOL = V;
END;