Search code examples
oracle-databasecountgroup-byrefuser-defined-types

Define a method to group records by a field referenced from other tables


I have designed an Oracle database as below. My question is, how do I define a function in the BodyModel_TYPE to display no. of single deck buses in each body model and while that function has to accept a specific body model as input/parameter.

Diagram for the following database schema

The method I have coded is like:

ALTER TYPE BodyModel_TYPE
   ADD MEMBER FUNCTION cal_single_deck_bus RETURN int
   CASCADE;

CREATE OR REPLACE TYPE BODY BodyModel_TYPE AS
MEMBER FUNCTION cal_single_deck_bus RETURN int IS
N int;
BEGIN
SELECT COUNT(t.BUSID) INTO N FROM SINGLEDECKBUS_TABLE T WHERE DEREF(t.BODYMODEL) = self;
RETURN N;
END cal_single_deck_bus;
END;
/

It does not have an input and the returned result is not as expected though. The full sql code is as following:

-- Engine Model

create or replace type EngineModel_TYPE as object
(EngineModelID int,
EngineDescription varchar2(45),
EngineType varchar2(25),
EngineCapacity int);
/

create table EngineModel_TABLE of EngineModel_TYPE;

alter table EngineModel_TABLE
add constraint EngineModelID_PK primary key (EngineModelID);

-- Body Model

create or replace type BodyModel_TYPE as object
(ModelID int,
ModelName varchar2(45),
FloorType varchar2(5),
Manufacturer varchar2(45),
Length number(8,2),
Width number(8,2),
NoOfAxles int);
/

create table BodyModel_TABLE of BodyModel_TYPE;

alter table BodyModel_TABLE
add constraint BodyModelID_PK primary key (ModelID);

-- Bus 

create or replace type Bus_TYPE as object
(BusID int,
RegistrationNo VARCHAR2(10),
RegistrationExpireDate date,
EngineModel REF EngineModel_TYPE,
BodyModel REF BodyModel_TYPE) NOT FINAL;
/

-- Single Decker Bus

CREATE OR REPLACE TYPE SingleDeckBus_type UNDER Bus_type
(NoOfDoors INT,
SeatingCapacity int,
StandingCapacity int);
/
CREATE TABLE SingleDeckBus_table OF SingleDeckBus_type;

ALTER TABLE SingleDeckBus_table
ADD CONSTRAINT SingleDeckBus_BusId_pk PRIMARY KEY (BusId);

-- Double Decker bus

CREATE OR REPLACE TYPE DoubleDeckBus_type UNDER Bus_type
(LowerDeckSeatingCapacity int,
LowerDeckStandingCapacity int,
LuggageCapacity number(8,2),
UpperDeckSeatingCapacity int);
/
CREATE TABLE DoubleDeckBus_table OF DoubleDeckBus_type;

ALTER TABLE DoubleDeckBus_table
ADD CONSTRAINT DoubleDeckBus_BusId_pk PRIMARY KEY (BusId);

-- Accessory

CREATE OR REPLACE TYPE Accessory_type AS OBJECT
(AccessoryId INT,
AccessoryName VARCHAR2(45),
AccessoryDescription VARCHAR2(45),
InstallDate DATE,
SingleDeckBus REF SingleDeckBus_type,
DoubleDeckBus REF DoubleDeckBus_type);
/
CREATE TABLE Accessory_table OF Accessory_type;

ALTER TABLE Accessory_table
ADD CONSTRAINT AccessoryId_pk PRIMARY KEY(AccessoryId);


-- 2.   Insert record statements (3 records for each table that support testing of your queries) 

insert into EngineModel_TABLE values
(1, 'Description 1', 'Type 1', 10); 
insert into EngineModel_TABLE values
(2, 'Description 2', 'Type 2', 20);
insert into EngineModel_TABLE values
(3, 'Description 3', 'Type 3', 30);

insert into BodyModel_TABLE values
(1, 'name 1', 'Type1', 'manufac1', 10.1, 5.1, 10); 
insert into BodyModel_TABLE values
(2, 'name 2', 'Type2', 'manufac2', 10.2, 5.2, 20); 
insert into BodyModel_TABLE values
(3, 'name 3', 'Type3', 'manufac3', 10.3, 5.3, 30); 

insert into SingleDeckBus_table
SELECT 1, 'SA01', '1-Jan-2020', REF(E), REF(B), 1, 20, 10 from ENGINEMODEL_TABLE E, BODYMODEL_TABLE B 
where E.ENGINEMODELID = 1 AND B.MODELID = 1;
insert into SingleDeckBus_table
SELECT 2, 'SA02', '2-Jan-2020', REF(E), REF(B), 2, 25, 10 from ENGINEMODEL_TABLE E, BODYMODEL_TABLE B 
where E.ENGINEMODELID = 2 AND B.MODELID = 2;
insert into SingleDeckBus_table
SELECT 3, 'SA03', '3-Jan-2020', REF(E), REF(B), 3, 30, 10 from ENGINEMODEL_TABLE E, BODYMODEL_TABLE B 
where E.ENGINEMODELID = 3 AND B.MODELID = 3;
insert into SingleDeckBus_table
SELECT 55, 'SA03', '3-Jan-2020', REF(E), REF(B), 3, 30, 10 from ENGINEMODEL_TABLE E, BODYMODEL_TABLE B 
where E.ENGINEMODELID = 3 AND B.MODELID = 3;

insert into DOUBLEDECKBUS_TABLE
SELECT 4, 'SA04', '4-Jan-2020', REF(E), REF(B), 20, 10, 30.15, 20 from ENGINEMODEL_TABLE E, BODYMODEL_TABLE B 
where E.ENGINEMODELID = 1 AND B.MODELID = 1;
insert into DOUBLEDECKBUS_TABLE
SELECT 5, 'SA05', '5-Jan-2020', REF(E), REF(B), 25, 10, 35.15, 25 from ENGINEMODEL_TABLE E, BODYMODEL_TABLE B 
where E.ENGINEMODELID = 2 AND B.MODELID = 2;
insert into DOUBLEDECKBUS_TABLE
SELECT 6, 'SA06', '6-Jan-2020', REF(E), REF(B), 30, 10, 40.15, 30 from ENGINEMODEL_TABLE E, BODYMODEL_TABLE B 
where E.ENGINEMODELID = 3 AND B.MODELID = 3;

insert into ACCESSORY_TABLE
SELECT 1, 'Acc1', 'AccDesc1', '1-Mar-2017', REF(S), REF(D) from SINGLEDECKBUS_TABLE S, DOUBLEDECKBUS_TABLE D 
where s.BUSID = 1 AND d.BUSID = 4;
insert into ACCESSORY_TABLE
SELECT 2, 'Acc2', 'AccDesc2', '2-Mar-2017', REF(S), REF(D) from SINGLEDECKBUS_TABLE S, DOUBLEDECKBUS_TABLE D 
where s.BUSID = 2 AND d.BUSID = 5;
insert into ACCESSORY_TABLE
SELECT 3, 'Acc3', 'AccDesc3', '3-Mar-2017', REF(S), REF(D) from SINGLEDECKBUS_TABLE S, DOUBLEDECKBUS_TABLE D 
where s.BUSID = 3 AND d.BUSID = 6;

-- 3.   Create a method in the appropriate class to display how many single deck buses in each body model.

   ALTER TYPE BodyModel_TYPE
   ADD MEMBER FUNCTION cal_single_deck_bus RETURN int
   CASCADE;

CREATE OR REPLACE TYPE BODY BodyModel_TYPE AS
MEMBER FUNCTION cal_single_deck_bus RETURN int IS
N int;
BEGIN
SELECT COUNT(t.BUSID) INTO N FROM SINGLEDECKBUS_TABLE T WHERE DEREF(t.BODYMODEL) = self;
RETURN N;
END cal_single_deck_bus;
END;
/

commit;

Solution

  • Your existing method function seems to get the right result, based on the inserts you showed:

    SELECT b.ModelID, b.ModelName, b.cal_single_deck_bus() AS single_deck_bus_count
    FROM BodyModel_TABLE b;
    
       MODELID MODELNAME            SINGLE_DECK_BUS_COUNT
    ---------- -------------------- ---------------------
             1 name 1                                   1
             2 name 2                                   1
             3 name 3                                   2
    

    That seems a reasonable way to do this. But if your assignment specifies that the function has to accept a body model - which I don't think the wording of your question 3 does, unless you've left part of it out - then you probably want a static function, not a member function:

    -- remove your existing member function first
    ALTER TYPE BodyModel_TYPE
    DROP MEMBER FUNCTION cal_single_deck_bus RETURN int
    CASCADE;
    
    ALTER TYPE BodyModel_TYPE
    ADD STATIC FUNCTION cal_single_deck_bus (p_bodymodel REF BodyModel_TYPE)
    RETURN int CASCADE;
    
    CREATE OR REPLACE TYPE BODY BodyModel_TYPE AS
    STATIC FUNCTION cal_single_deck_bus (p_bodymodel REF BodyModel_TYPE)
    RETURN int IS
      N int;
    BEGIN
      SELECT COUNT(t.BUSID) INTO N
      FROM SINGLEDECKBUS_TABLE T
      WHERE t.BODYMODEL = p_bodymodel;
    
      RETURN N;
    END cal_single_deck_bus;
    END;
    /
    

    Then you can pass a specific reference from any of the related tables to the static function, or get the count for all body type for instance, e.g.:

    SELECT b.ModelID, b.ModelName,
      BodyModel_TYPE.cal_single_deck_bus(ref(b)) AS single_deck_bus_count
    FROM BodyModel_TABLE b;
    
       MODELID MODELNAME            SINGLE_DECK_BUS_COUNT
    ---------- -------------------- ---------------------
             1 name 1                                   1
             2 name 2                                   1
             3 name 3                                   2
    

    The BodyModel_TYPE.cal_single_deck_bus(ref(b)) is a static call, as it's against the type itself rather than an instance of that type; compare with the earlier b.cal_single_deck_bus() call. In both cases b is an instance of the object type.

    You could also get the reference from elsewhere though, e.g. in a rather contrived way:

    SELECT d.BusID, d.UpperDeckSeatingCapacity,
      deref(d.BodyModel).ModelID as ModelID,
      deref(d.BodyModel).ModelName as ModelName,
      BodyModel_TYPE.cal_single_deck_bus(d.BodyModel) AS single_deck_bus_count
    FROM DoubleDeckBus_TABLE d
    WHERE d.RegistrationNo = 'SA06';
    
         BUSID UPPERDECKSEATINGCAPACITY    MODELID MODELNAME            SINGLE_DECK_BUS_COUNT
    ---------- ------------------------ ---------- -------------------- ---------------------
             6                       30          3 name 3                                   2
    

    If you want to pass a model ID in, rather than an object reference, you will need to join the body model and single-decker bus tables together inside the static function:

    -- remove your existing member function first
    ALTER TYPE BodyModel_TYPE
    DROP STATIC FUNCTION cal_single_deck_bus (p_bodymodel REF BodyModel_TYPE) RETURN int
    CASCADE;
    
    ALTER TYPE BodyModel_TYPE
    ADD STATIC FUNCTION cal_single_deck_bus (p_modelid int)
    RETURN int CASCADE;
    
    CREATE OR REPLACE TYPE BODY BodyModel_TYPE AS
    STATIC FUNCTION cal_single_deck_bus (p_modelid int)
    RETURN int IS
      N int;
    BEGIN
      SELECT COUNT(sd.BUSID) INTO N
      FROM BODYMODEL_TABLE b
      JOIN SINGLEDECKBUS_TABLE sd
      ON sd.BODYMODEL = REF(b)
      WHERE b.ModelID = p_modelid;
    
      RETURN N;
    END cal_single_deck_bus;
    END;
    /
    

    Then you can call it as:

    SELECT BodyModel_TYPE.cal_single_deck_bus(3) AS single_deck_bus_count FROM dual;
    
    SINGLE_DECK_BUS_COUNT
    ---------------------
                        2
    

    or if you want to show related info as well:

    SELECT b.ModelID, b.ModelName,
      BodyModel_TYPE.cal_single_deck_bus(b.ModelID) AS single_deck_bus_count
    FROM BodyModel_TABLE b;
    
       MODELID MODELNAME            SINGLE_DECK_BUS_COUNT
    ---------- -------------------- ---------------------
             1 name 1                                   1
             2 name 2                                   1
             3 name 3                                   2