Week-number calculation - pascal

I have a problem with code which used to calculate the week number from date.
The application is Report Builder.
The language is: Report Application Programming (RAP)The RAP language is identical to Object Pascal.
My written code works for the years 2017 and 2018.
But it doesn`t work for 2019 and 2020.
Does someone have an Idea what I´m doing wrong here?
if (AVX['P_DELDATE'] >= 42737) and (AVX['P_DELDATE'] <= 43100) {= Jahr 2017} then begin
Value:= (AVX['P_DELDATE'] - 1 - (42737-1) + 7-((AVX['P_DELDATE'] -1 - (42737-1) ) mod 7))/7;
end else begin
if (AVX['P_DELDATE'] >= 43101) and (AVX['P_DELDATE'] <= 43465) {= Jahr 2018} then begin
Value:= (AVX['P_DELDATE'] - 1 - (43101-1) + 7-((AVX['P_DELDATE'] -1 - (43101-1) ) mod 7))/7;
end;
end else begin
if (AVX['P_DELDATE'] >= 43466) and (AVX['P_DELDATE'] <= 43830) {= Jahr 2019} then begin
Value:= (AVX['P_DELDATE'] - 1 - (43466-1) + 7-((AVX['P_DELDATE'] -1 - (43466-1) ) mod 7))/7;
end;
end else begin
if (AVX['P_DELDATE'] >= 43831) and (AVX['P_DELDATE'] <= 44196) {= Jahr 2020} then begin
Value:= (AVX['P_DELDATE'] - 1 - (43831-1) + 7-((AVX['P_DELDATE'] -1 - (43831-1) ) mod 7))/7;
end;
end;
end;

You don't say why you are asking about doing the Week number calculation
in RAP code. Apart from the fact that I can find no definition of the AVX
function in the RAP documentation, attempting to calculate the week number
using AVX in RAP is obviously error-prone, not self-documenting and is an all but undebuggable way to do it.
Evidently, your function(?, or maybe it is a report field) AVX returns a numeric value whose integer part corresponds to the integer part of a TDateTime value. In the example project
below - which I have included to show a much easier and less error prone way
of dealing with the problem, I included a function which (I hope) matches your RAP function, as follows:
function WeekNumber(Day : Integer) : Double;
begin
if (Day >= 42737) and (Day <= 43100) {= Jahr 2017} then begin
result:= (Day - 1 - (42737-1) + 7-((Day -1 - (42737-1) ) mod 7))/7;
end
else begin
if (Day >= 43101) and (Day <= 43465) {= Jahr 2018} then begin
result:= (Day - 1 - (43101-1) + 7-((Day -1 - (43101-1) ) mod 7))/7;
end
else begin
if (Day >= 43466) and (Day <= 43830) {= Jahr 2019} then begin
result:= (Day - 1 - (43466-1) + 7-((Day -1 - (43466-1) ) mod 7))/7;
end
else begin
if (Day >= 43831) and (Day <= 44196) {= Jahr 2020} then begin
result:= (Day - 1 - (43831-1) + 7-((Day -1 - (43831-1) ) mod 7))/7;
end;
end;
end;
end;
end;
(Btw, in doing this conversion, I had to fix a number of syntax error in your RAP code which you'll soon find if you try the conversion yourself.)
Then, I added a procedure to test this function:
procedure TForm1.TestWeekNumber;
var
Date : TDateTime;
i : integer;
procedure TestInner(Date : TDateTime);
var
WN1,
WN2 : Double;
S,
SError : String;
begin
WN1 := WeekOf(Date);
WN2 := WeekNumber(Trunc(Date));
if WN1 = WN2 then
SError := ''
else
SError := '***';
S := Format('Date: %s WeekOf: %g WeekNumber: %g %s',[ DateTimeToStr(Date), WN1, WN2, SError]);
if SError <> '' then
Memo1.Lines.Add(S);
end;
begin
Date := StrToDateTime('01/01/2017');
Memo1.Lines.BeginUpdate;
try
for i := 0 to 1999 do
TestInner(Date + i);
finally
Memo1.Lines.EndUpdate;
end;
end;
This compares your WeekNumber with the standard WeekOf function in DateUtils.Pas, which returns the week number as defined by the ISO 8601 standard, of a TDateTime input value and writes the values to a TMemo when they produce different results. The following is an extract from the output:
Date: 01/01/2017 WeekOf: 52 WeekNumber: 1.13989900694441E-307 ***
Date: 31/12/2018 WeekOf: 1 WeekNumber: 53 ***
Date: 07/01/2019 WeekOf: 2 WeekNumber: 1 ***
Date: 14/01/2019 WeekOf: 3 WeekNumber: 2 ***
Date: 21/01/2019 WeekOf: 4 WeekNumber: 3 ***
Date: 28/01/2019 WeekOf: 5 WeekNumber: 4 ***
Date: 04/02/2019 WeekOf: 6 WeekNumber: 5 ***
Date: 11/02/2019 WeekOf: 7 WeekNumber: 6 ***
[...]
Date: 06/01/2020 WeekOf: 2 WeekNumber: 1 ***
Date: 07/01/2020 WeekOf: 2 WeekNumber: 1 ***
Date: 13/01/2020 WeekOf: 3 WeekNumber: 2 ***
Date: 14/01/2020 WeekOf: 3 WeekNumber: 2 ***
Date: 20/01/2020 WeekOf: 4 WeekNumber: 3 ***
Date: 21/01/2020 WeekOf: 4 WeekNumber: 3 ***
Date: 27/01/2020 WeekOf: 5 WeekNumber: 4 ***
Date: 28/01/2020 WeekOf: 5 WeekNumber: 4 ***
Date: 03/02/2020 WeekOf: 6 WeekNumber: 5 ***
Date: 04/02/2020 WeekOf: 6 WeekNumber: 5 ***
Date: 10/02/2020 WeekOf: 7 WeekNumber: 6 ***
As you can see, things start going wrong at the end of 2018 and, after that,
the erroneous values seem to suggest an "off by one" kind of error. I leave
you to investigate and fix this problem yourself.
The reason I haven't looked into it any further myself is that it seems to me
that the way you are doing it is messy and avoidably complicated. It is virtually trivial to add week number
support to your RBuilder reports by adding a calculated field with the weeknumber
value to the dataset which feeds the report. Code to do that is shown below.
Alternatively, you could use ReportBuilder's facility to add a WeekNumber
function to the RAP run-time language that can take any datetime value (e.g.
a value from any dataset datetime field). See e.g. Extending RAP - RAP Pass-Through Functions at p213 of the ReportBuilder Developer's guide available
here: Report Builder Developer's Guide. What is described
there is probably the most flexible method of surfacing a Delphi function
in a report, though ISTR there are others.
Code (excluding RBuilder components for simplicity)
type
TForm1 = class(TForm)
CDS1: TClientDataSet;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
procedure FormCreate(Sender: TObject);
procedure CDS1CalcFields(DataSet: TDataSet);
private
procedure CreateDataSetFields;
protected
public
end;
[...]
implementation
uses
DateUtils;
procedure TForm1.FormCreate(Sender: TObject);
var
i : Integer;
begin
CreateDataSetFields; // see below
CDS1.CreateDataSet;
// Insert one row per day for current year
for i := 1 to 365 do begin
CDS1.InsertRecord([i, i + Now - DayOfTheYear(Now)]);
end;
CDS1.First;
end;
procedure TForm1.CDS1CalcFields(DataSet: TDataSet);
begin
// This sets the WeekNo calculated field to the value returned by the DateUtils.WeekOf function
DataSet.FieldByName('WeekNo').AsInteger := WeekOf(DataSet.FieldByName('Date').AsDateTime);
end;
procedure TForm1.CreateDataSetFields;
var
Field : TField;
begin
Field := TIntegerField.Create(Self);
Field.FieldKind := fkData;
Field.FieldName := 'ID';
Field.Name := 'ID';
Field.DataSet := CDS1;
Field := TDateTimeField.Create(Self);
Field.FieldKind := fkData;
Field.FieldName := 'Date';
Field.Name := 'Date';
Field.DataSet := CDS1;
Field := TIntegerField.Create(Self);
Field.FieldKind := fkInternalCalc;
Field.FieldName := 'WeekNo';
Field.Name := 'WeekNo';
Field.DataSet := CDS1;
end;

Related

Package compilation fails with ORA-00927: missing equal sign

I am creating a package and I don't understand why the error occurs.
My package:
create or replace PACKAGE test_action AS
subj CHAR default '';
count_student INTEGER default 0;
FUNCTION count_positive(subject CHAR) RETURN INTEGER;
PROCEDURE number_requests;
END test_action;
Package body:
CREATE OR REPLACE PACKAGE BODY test_action AS
FUNCTION count_positive(sub CHAR) RETURN INTEGER
AS
BEGIN
count_student := 0;
subj := sub;
SELECT COUNT(*) INTO count_student
FROM D8_EXAMS E JOIN D8_SUBJECT S
ON E.subj_id = S.subj_id
WHERE E.mark > 3 AND S.subj_name = subject
GROUP BY S.subj_name;
number_requests;
return count_student;
END count_positive;
PROCEDURE number_requests AS
BEGIN
INSERT INTO package_table (subject,counts,callCount)
VALUES (subj,count_student,1);
exception
when dup_val_on_index then
update t
set callCount := callCount + 1,
set counts := count_student
where subject = subj;
END number_requests;
END test_action;
and then I get an error
if I try to add assignment to variables before the function description, then a new error occurs
Because - as error says - you're missing equal sign (it is just =, not :=), and there's only one set keyword per update:
update t
set callCount = callCount + 1,
counts = count_student
where subject = subj;
Apart from that:
function declaration in package body must match the one in package specification
in exception, you're updating table t; shouldn't that be package_table?
select count(*) has group by clause; to me, it smells like possible point to raise too_many_rows error you didn't handle
This, at least, compiles: package spec:
SQL> CREATE OR REPLACE PACKAGE test_action
2 AS
3 subj CHAR DEFAULT '';
4 count_student INTEGER DEFAULT 0;
5
6 FUNCTION count_positive (sub CHAR)
7 RETURN INTEGER;
8
9 PROCEDURE number_requests;
10 END test_action;
11 /
Package created.
Package body:
SQL> CREATE OR REPLACE PACKAGE BODY test_action
2 AS
3 FUNCTION count_positive (sub CHAR)
4 RETURN INTEGER
5 AS
6 BEGIN
7 count_student := 0;
8 subj := sub;
9
10 SELECT COUNT (*)
11 INTO count_student
12 FROM D8_EXAMS E JOIN D8_SUBJECT S ON E.subj_id = S.subj_id
13 WHERE E.mark > 3
14 AND S.subj_name = sub
15 GROUP BY S.subj_name;
16
17 number_requests;
18 RETURN count_student;
19 END count_positive;
20
21 PROCEDURE number_requests
22 AS
23 BEGIN
24 INSERT INTO package_table (subject, counts, callCount)
25 VALUES (subj, count_student, 1);
26 EXCEPTION
27 WHEN DUP_VAL_ON_INDEX
28 THEN
29 UPDATE package_table
30 SET callCount = callCount + 1, counts = count_student
31 WHERE subject = subj;
32 END number_requests;
33 END test_action;
34 /
Package body created.
SQL>

Program Throwing Unknown Error: EConvertError

I am trying to create a program that takes a users input and checks if its valid as a date (years 2000 - 2099). when I was bug testing the code I put in an enter as the input twice in a row, the first time I entered an enter, no error was thrown but the second time the console flashes a message and crashes. I took a screenshot as it crashed and this is the error it showed
An unhandled exception occured at $0040CDE1
EConvertError : "" is an invalid integer
My best guess is that the error was caused by one of my StrToInt due to the EConvertError but it unclear to me as to why it only throws it on the second run through and not the first. I suspect that the error occurs on either line, 85, 98, 111, or 195.
When I initially wrote this program a was a beginner to Pascal so I apologise in advance for any sloppy code.
below is all of the code if you would like to run it for yourselves.
program DateVerifierAssignment;
uses crt, sysutils;
var
userInputArray: array[0..7] of string;
len, i, day, month, year: integer;
date, userInput, stringDay, stringMonth, stringYear: string;
errorCatch: boolean;
//DECLARING FUNCTIONS & PROCEDURES
procedure TitleScreen();
begin
//prints a coloured wall of text to give information like name and instructions
WriteLn(' o-------------------------o');
Write(' | ');TextColor(White);Write('Date Validation Program');TextColor(Cyan);WriteLn(' |');
WriteLn(' o-------------------------o');
WriteLn();
WriteLn(' o-------------------------o');
Write(' | ');TextColor(White);Write('Instructions');TextColor(Cyan);WriteLn(' |');
WriteLn(' o-------------------------o');
Write(' | ');TextColor(White);Write('Enter a date and the');TextColor(Cyan);WriteLn(' |');
Write(' | ');TextColor(White);Write('program will verify it');TextColor(Cyan);WriteLn(' |');
Write(' | ');TextColor(White);Write('and convert it to its');TextColor(Cyan);WriteLn(' |');
Write(' | ');TextColor(White);Write('long form');TextColor(Cyan);WriteLn(' |');
WriteLn(' o-------------------------o');
WriteLn();
TextColor(White);
Write(' press any key to begin');
//waits for user input then clears the screen and returns the text color to white
ReadKey();
ClrScr;
end;
function DateVerification(var userInput: string): boolean;
var
errorLimit : boolean;
bounds: integer;
begin
errorLimit := True;
//stores the length of the string as a variable
len := Length(userInput);
//checks to see if entry is 8 chracters long and displays an error message and returns user to input screen if it doesn't fit
if (len <> 8) and (errorLimit = True) then
begin
ClrScr();
TextColor(Red);
WriteLn('input was not the right length (8 characters)');
TextColor(White);
Write('make sure date fits format ');
TextColor(LightGreen);
WriteLn('dd/mm/yy');
TextColor(White);
Readkey();
ClrScr();
errorLimit := false;
Dateverification := false;
end;
//spits each character into its own spot in an array
for i := 1 to len do
userInputArray[i - 1] := userInput[i];
//tests if every slot in the array where a slash should be is a slash
for i := 0 to len-1 do
begin
if (userInputArray[2] <> '/') or (userInputArray[5] <> '/') and (errorLimit = true) then
begin
ClrScr();
TextColor(Red);
WriteLn('input did not have slashes in correct locations ');
TextColor(LightGreen);
WriteLn('dd/mm/yy');
TextColor(White);
Readkey();
ClrScr();
errorLimit := false;
Dateverification := false;
end;
end;
year := ((StrToInt(userInputArray[6]))*10) + StrToInt(userInputArray[7]);
if (year < 1) or (year > 99) and (errorLimit = true) then
begin
ClrScr();
TextColor(Red);
WriteLn('year was not from 0 to 99');
TextColor(White);
Readkey();
ClrScr();
errorLimit := false;
Dateverification := false;
end;
month := ((StrToInt(userInputArray[3]))*10) + StrToInt(userInputArray[4]);
if (month < 1) or (month > 12) and (errorLimit = true) then
begin
ClrScr();
TextColor(Red);
WriteLn('month was not from 1 to 12');
TextColor(White);
Readkey();
ClrScr();
errorLimit := false;
Dateverification := false;
end;
day := ((StrToInt(userInputArray[0]))*10) + StrToInt(userInputArray[1]);
if (month = 4) or (month = 6) or (month = 9) or (month = 11) then
bounds := 30;
if (month = 2) then
begin
if (IsLeapYear(year) = true) then
bounds := 29
else
bounds := 28;
end
else
bounds := 31;
if (day < 1) or (day > bounds) and (errorLimit = true) then
begin
ClrScr();
TextColor(Red);
WriteLn('day was not from 1 to days in month');
TextColor(White);
Readkey();
ClrScr();
errorLimit := false;
Dateverification := false;
end;
if (errorLimit = true) then
DateVerification := True;
end;
function IsLeapYear(var year: integer): boolean;
//simple function to determine if a year is a leap year on the gregorian calender
begin
if (year mod 4) = 0 then
if (year mod 100 <> 0) then
if (year mod 400 = 0) then
IsLeapYear := true
else
IsLeapYear := false
else
IsLeapyear := false
else
IsLeapyear := false
end;
procedure DateToAlpha(var userInput: string);
begin
end;
//MAIN PROGRAM
begin
//preparing for while loop later in code and changes text colour to cyan
errorCatch := true;
TextColor(Cyan);
//Displays a title screen and instruction about how to use(stored in a procedure to help with readability)
TitleScreen();
//begins a loop so that if an error is to occur the program can easily ask for a new date and try again
while (errorCatch = true) do
begin
//sets error catch to 0 so that if there a no errors the program will not loop
errorCatch := false;
//displays information on how to input a date as well as an example with different colours for better readability
Write('Enter a date in the format');TextColor(LightGreen); WriteLn(' dd/mm/yy');TextColor(White);
Write('e.g. ');TextColor(LightGreen);WriteLn(' 09/07/20'#13#10);TextColor(White);
//takes date user inputs and stores it
ReadLn(userInput);
//calls the date verification function to see if the date is valid
//(refer to dateVerification at the top of the program for more info and comments)
if (DateVerification(userInput) = false) then
errorCatch := true;
len := Length(userInput);
for i := 1 to len do
userInputArray[i - 1] := userInput[i];
year := ((StrToInt(userInputArray[6]))*10) + StrToInt(userInputArray[7]);
readKey();
end;
end.
Ordinarily on SO we don't provide fully-coded answers to qs which are obviously coursework.
In this case I thought I'd make an exception because it was obvious that you are making
unnecessarily heavy weather of this, and the way you've coded it is likely to actually get in the way
of debugging it and getting it working correctly.
So, the example below shows a very simple,
clean way to validate a date in the 21st century supplied in dd/mm/yyyy format. It
shows the correct order of processing and validation steps; if it detects an error
in any step, it says what the problem is and halts. If execution drops through to the end,
the supplied date string must be valid.
I have deliberately left out any loops
or prettifying code, because the important thing to get right first is a clean and concise
coding of the processing and validation steps. If you want to use loops, fine, but the way you've used
e.g. errorCatch, I bet that if you come back to your code in 6 months, you won't remember how
it's supposed to work. You don't need any complicated flags or loops for this task - the user simply has to be able to type exactly eight characters (followed by [Enter]) and the code will tell then whether it's valid or not.
Note I've used the standard Copy function to separate out the various parts of the
date inputted. Btw, your declaration
userInputArray: array[0..7] of string;
is just plain wrong. If you want an array of a certain number of characters, fine, but that would
just needlessly complicate handling and processing what the user types. It's far
simpler, and therefore less error-prone to accept a single string of 8 characters
and just deal with that. But in any case, to permit the string to include the / characters after the day and month digits, the length of the stringg should be 10, not eight, to allow for those and four year digits.
program DateVerification;
uses
SysUtils;
var
sUserInput,
sDay,
sMonth,
sYear : String;
iDay,
iMonth,
iYear,
iDays: Integer;
bIsLeapYear : Boolean;
function DaysInMonth(iMonth, iYear : Integer) : Integer;
begin
Result := -1; // you supply the code for this taking into account leap year for February
end;
begin
writeln('Please enter a date between 01/01/2000 and 31/12/2099 in the format shown');
readln(sUserInput);
if Length(sUserInput) <> 10 then begin
writeln('Input is wrong length.');
Halt;
end;
if (sUserInput[3] <> '/') or (sUserInput[6] <> '/') then begin
writeln('Input is incorrectly delimited.');
Halt;
end;
sDay := Copy(sUserInput, 1, 2);
sMonth := Copy(sUserInput, 4, 2);
sYear := Copy(sUserInput, 7, 4);
iYear := StrToInt(sYear);
if (iYear < 2000) or (iYear > 2099) then begin
writeln('Invalid year : ', sYear);
Halt;
end;
bIsLeapYear := IsLeapYear(iYear); // you supply the code for IsLeapYear
iMonth := StrToInt(sMonth);
if (iMonth < 1) or (iMonth > 12) then begin
writeln('Invalid month : ', sMonth);
Halt;
end;
iDay := StrToInt(sDay);
if (iDay < 1) or (iDay > DaysInMonth(iMonth, iYear)) then begin
// You need to supply the DaysInMoth function, which of course
// needs to account for February in a leap year
writeln('Invalid day of month: ', sMonth);
Halt;
end;
writeln(sUserInput, ' is a valid date.');
readln;
end.

Error while creating and executing a function

I have a table as follows:
SCREENING_ID PLAN_ID THEATRE_ID SCREENING_DATE SCREENING_START_HH24 SCREENING_START_MM60
1 1 3 01.06.2015 00:00:00 9 0
2 1 3 01.06.2015 00:00:00 11 30
3 1 3 01.06.2015 00:00:00 14 0
4 1 4 01.06.2015 00:00:00 14 0
I have to check whether a particular time slot is available or not.This function should contain the following input parameters: plan_id_p, theatre_id_p, screening_date_p, screening_start_hh24_p, screening_start_mm60_p. It should return 1 if having a time slot otherwise return 0.
My code:
CREATE OR REPLACE FUNCTION Func_is_time_available
(plan_id_p IN NUMBER,
theatre_id_p IN OUT NUMBER,
screening_date_p IN OUT DATE,
screening_start_hh24_p IN OUT NUMBER,
screening_start_mm60_p IN OUT NUMBER)
RETURN NUMBER
AS
return_val NUMBER;
CURSOR cr2 IS
SELECT plan_id,
THEATRE_ID,
SCREENING_DATE,
SCREENING_START_HH24,
SCREENING_START_MM60
FROM screening s
WHERE plan_id = plan_id_p and
theatre_id=theatre_id_p and
SCREENING_DATE=SCREENING_DATE_P and
SCREENING_START_HH24=SCREENING_START_HH24_P and
SCREENING_START_MM60=SCREENING_START_MM60_P;
BEGIN
OPEN cr2;
FETCH cr2 INTO plan_id_p,
THEATRE_ID_P,
SCREENING_DATE_P,
SCREENING_START_HH24_P,
SCREENING_START_MM60_P;
IF cr2%NOTFOUND THEN
return_val := 1;
ELSE
return_val := 0;
END IF;
CLOSE cr2;
RETURN return_val;
END;
And the execution part:
DECLARE
v_result NUMBER;
BEGIN v_result:=Func_is_time_available(plan_id_p=>1,
theatre_id_p=>3,
screening_date_p=>'1/JUN/2015',
screening_start_hh24_p=>9,
screening_start_mm60_p=>0);
END;
But this is giving error as:
ORA-06550: line 18, column 44:
PLS-00363: expression '<expression>' cannot be used as an assignment target
Can anyone explain what my mistake is here?
I am using oracle.
I have made changes to your code refer below
CREATE OR REPLACE FUNCTION Func_is_time_available
(plan_id_p IN NUMBER,
theatre_id_p IN NUMBER,
screening_date_p IN DATE,
screening_start_hh24_p IN NUMBER,
screening_start_mm60_p IN NUMBER)
RETURN NUMBER
AS
return_val NUMBER;
var1 number;
begin
SELECT count(*) into var1
FROM screening s
WHERE plan_id = plan_id_p and
theatre_id=theatre_id_p and
SCREENING_DATE=SCREENING_DATE_P and
SCREENING_START_HH24=SCREENING_START_HH24_P and
SCREENING_START_MM60=SCREENING_START_MM60_P;
if var1 >= 1 then -- count can be 1 or > 1
return_val:= 1;
else return_val:=0;
end if;
end;
Make all parameters as IN instead of IN OUT and try.

IS OF TYPE generates an exception

I read about IS OF TYPE and I expected that it should return TRUE, FALSE or NULL.
I have two object types:
CREATE TYPE o1 AS OBJECT ( id NUMBER );
/
CREATE TYPE o2 AS OBJECT ( id NUMBER );
/
When I run the code below, everything is OK.
DECLARE
type1 o1;
BEGIN
type1 := o1(id=>1);
if (type1 IS OF (o1)) then
DBMS_OUTPUT.PUT_LINE('type1 is o1');
END if;
END;
/
But when I try to run:
DECLARE
type1 o1;
BEGIN
type1 := o1(id=>1);
if (type1 IS OF (o2)) then
DBMS_OUTPUT.PUT_LINE('type1 is o1');
END if;
END;
/
I received the following exceptions
Error report:
ORA-06550: line 6, column 21:
PLS-00382: expression is of wrong type
ORA-06550: line 6, column 4:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
In the documentation there isn't clear explanation, should I catch exception if something is of the wrong type? Or, should I expect false in the IF condition?
If you have declared your variable as O1 type then you can use is of [type] condition to test only whether your variable is of o1 type or is of o1's subtype. Here is an example(variables must be instantiated):
-- base type
SQL> create or replace type o1 as object(
2 prop number
3 )not final;
4 /
Type created
-- O1's subtype
SQL> create or replace type o2 under o1(
2 prop1 number
3 );
4 /
-- test if the l_o1 is of O1 type
SQL> declare
2 l_o1 o1;
3 begin
4 l_o1 := o1(prop=>1);
5 if l_o1 is of (o1)
6 then
7 dbms_output.put_line('Yes');
8 else
9 dbms_output.put_line('No');
10 end if;
11 end;
12 /
Yes
PL/SQL procedure successfully completed
-- test if the l_o1 is of O2 type
SQL> declare
2 l_o1 o1;
3 begin
4 l_o1 := o1(prop=>1);
5 if l_o1 is of (o2)
6 then
7 dbms_output.put_line('Yes');
8 else
9 dbms_output.put_line('No');
10 end if;
11 end;
12 /
No
PL/SQL procedure successfully completed
-- test if the l_o2 is of O2 type
SQL> declare
2 l_o2 o2;
3 begin
4 l_o2 := o2(prop=>1, prop1 => 1);
5 if l_o2 is of (o2)
6 then
7 dbms_output.put_line('Yes');
8 else
9 dbms_output.put_line('No');
10 end if;
11 end;
12 /
Yes
PL/SQL procedure successfully completed
Update:
Take a look at this to get more information about is of[type]. Usually data type of a variable is known at compile time, but if you have to deal with dynamic typing you may look at anydata(object data type). Here is a simple example:
SQL> declare
2 l_o1 o1;
3
4 -- Here is a procedure(for the sake of simplicity has not
5 -- been written as a schema object)
6 -- that doesn't not know
7 -- variable of what dada type will be passed in
8 -- at compile time;
9 procedure DoSomething(p_var anydata)
10 is
11 begin
12 case p_var.gettypename
13 when 'HR.O1'
14 then dbms_output.put_line('O1 data type. Do something');
15 when 'HR.O2'
16 then dbms_output.put_line('O2 data type. Do something');
17 else
18 dbms_output.put_line('Unknown data type');
19 end case;
20 end;
21
22 begin
23 l_o1 := o1(prop => 1);
24 DoSomething(anydata.ConvertObject(l_o1));
25 end;
26 /
O1 data type. Do something
PL/SQL procedure successfully completed

IP address stored as decimal - PL/SQL to display as dotted quad

We have an Oracle database that contains IP addresses stored as decimal integers - this is incredibly painful when manipulating the data by hand instead of via the web interface, yet hand manipulation is really handy as the network guys continually ask us to do strange things that the web interface authors did not anticipate.
Could someone provide me with the PL/SQL or other method to display these decimal IPs as dotted decimal i.e. 123.123.123.123 format?
I.e. I'd like to be able to run a query such as :
select hostname, inttoip(ip_address) from host;
and have the inttoip() procedure display ip_address as 203.30.237.2 instead of as 3407801602.
Ideally I'd like a procedure which provides the inverse function too, e.g.
insert into host (hostname,ip_address) values ('some-hostname', iptoint('203.30.237.2'));
I have perl to do this, but my PL/SQL/Oracle knowledge is not good enough to port it into PL/SQL.
Alternatively a way to run the perl as the procedural language within the oracle context analogous to the following in postgres:
CREATE FUNCTION perl_func (integer) RETURNS integer AS $$
<some perl>
$$ LANGUAGE plperl;
Would be great - if possible - probably even better as I could then do lots of procedural stuff within Oracle in a language I am familiar with.
This is the function you need:
create or replace
function inttoip(ip_address integer) return varchar2
deterministic
is
begin
return to_char(mod(trunc(ip_address/256/256/256),256))
||'.'||to_char(mod(trunc(ip_address/256/256),256))
||'.'||to_char(mod(trunc(ip_address/256),256))
||'.'||to_char(mod(ip_address,256));
end;
(Comments about making function deterministic and using to_char taken on board - thanks).
In Oracle 11G you could make the formatted IP address a virtual column on the host table:
alter table host
add formatted_ip_address varchar2(15)
generated always as
( to_char(mod(trunc(ip_address/256/256/256),256))
||'.'||to_char(mod(trunc(ip_address/256/256),256))
||'.'||to_char(mod(trunc(ip_address/256),256))
||'.'||to_char(mod(ip_address,256))
) virtual;
This column could then be indexed for queries if required.
Your query becomes:
select hostname, formatted_ip_address from host;
CREATE OR REPLACE
FUNCTION inttoip(ip_address IN INTEGER) RETURN VARCHAR2 IS
v8 VARCHAR2(8);
BEGIN
-- 1. convert the integer into hexadecimal representation
v8 := TO_CHAR(ip_address, 'FMXXXXXXXX');
-- 2. convert each XX portion back into decimal
RETURN to_number(substr(v8,1,2),'XX')
|| '.' || to_number(substr(v8,3,2),'XX')
|| '.' || to_number(substr(v8,5,2),'XX')
|| '.' || to_number(substr(v8,7,2),'XX');
END inttoip;
CREATE OR REPLACE
FUNCTION iptoint(ip_string IN VARCHAR2) RETURN INTEGER IS
d1 INTEGER;
d2 INTEGER;
d3 INTEGER;
q1 VARCHAR2(3);
q2 VARCHAR2(3);
q3 VARCHAR2(3);
q4 VARCHAR2(3);
v8 VARCHAR2(8);
BEGIN
-- 1. parse the input, e.g. '203.30.237.2'
d1 := INSTR(ip_string,'.'); -- first dot
d2 := INSTR(ip_string,'.',1,2); -- second dot
d3 := INSTR(ip_string,'.',1,3); -- third dot
q1 := SUBSTR(ip_string, 1, d1 - 1); -- e.g. '203'
q2 := SUBSTR(ip_string, d1 + 1, d2 - d1 - 1); -- e.g. '30'
q3 := SUBSTR(ip_string, d2 + 1, d3 - d2 - 1); -- e.g. '237'
q4 := SUBSTR(ip_string, d3 + 1); -- e.g. '2'
-- 2. convert to a hexadecimal string
v8 := LPAD(TO_CHAR(TO_NUMBER(q1),'FMXX'),2,'0')
|| LPAD(TO_CHAR(TO_NUMBER(q2),'FMXX'),2,'0')
|| LPAD(TO_CHAR(TO_NUMBER(q3),'FMXX'),2,'0')
|| LPAD(TO_CHAR(TO_NUMBER(q4),'FMXX'),2,'0');
-- 3. convert to a decimal number
RETURN TO_NUMBER(v8, 'FMXXXXXXXX');
END iptoint;
-- INET ATON en INET NTOA and helper function GET TOKEN
CREATE OR REPLACE function inet_ntoa (ip integer) return varchar2
is
ip1 integer;
ip2 integer;
ip3 integer;
ip4 integer;
ipi integer := ip;
begin
ip1 := floor(ipi/power(2,24));
ipi := ipi - (ip1*power(2,24));
ip2 := floor(ipi/power(2,16));
ipi := ipi - (ip2*power(2,16));
ip3 := floor(ipi/power(2,8));
ipi := ipi - (ip3*power(2,8));
ip4 := ipi;
return ip1||'.'||ip2||'.'||ip3||'.'||ip4;
end;
/
CREATE OR REPLACE FUNCTION get_token (the_list VARCHAR2,the_index NUMBER, delim VARCHAR2 := '.') RETURN VARCHAR2
IS
start_pos INTEGER;
end_pos INTEGER;
BEGIN
IF the_index = 1 THEN
start_pos := 1;
ELSE
start_pos := INSTR (the_list, delim, 1, the_index - 1);
IF start_pos = 0 THEN
RETURN NULL;
ELSE
start_pos := start_pos + LENGTH (delim);
END IF;
END IF;
end_pos := INSTR (the_list, delim, start_pos, 1);
IF end_pos = 0 THEN
RETURN SUBSTR (the_list, start_pos);
ELSE
RETURN SUBSTR (the_list, start_pos, end_pos - start_pos);
END IF;
END get_token;
/
CREATE OR REPLACE function inet_aton (ip varchar2) return integer
is
invalid_ip_adres exception;
pragma exception_init(invalid_ip_adres,-6502);
ipi integer;
begin
ipi := get_token(ip,4)
+(get_token(ip,3)*power(2,8))
+(get_token(ip,2)*power(2,16))
+(get_token(ip,1)*power(2,24));
return ipi;
exception
when invalid_ip_adres
then
return null;
end;
/

Resources