Search code examples
sqloracle-databaseoracle11gora-00907

Oracle error - ORA-00907: missing right parenthesis


I'm scratching my head over this Oracle error. The following query works perfectly:

SELECT
   V.PROJECTID,
   (SELECT WM_CONCAT(DISTINCT NAME) 
   FROM TPM_TRAININGPLAN JOIN TPM_DELIVERYMETHODS USING (METHODID) 
   WHERE PROJECTID=V.PROJECTID 
   AND VERSIONID=V.VERSIONID) as Methods
FROM TPM_PROJECTVERSION V

However, I want to return my concantonated list in alphabetical order because I'm picky like that. You'd think I would do:

SELECT
   V.PROJECTID,
   (SELECT WM_CONCAT(DISTINCT NAME) 
     FROM TPM_TRAININGPLAN JOIN TPM_DELIVERYMETHODS USING (METHODID) 
     WHERE PROJECTID=V.PROJECTID 
     AND VERSIONID=V.VERSIONID ORDER BY NAME) as Methods
FROM TPM_PROJECTVERSION V

However, when I try this I get the error:

[Error] Script lines: 15-19 ------------------------
ORA-00907: missing right parenthesis

I can run the query in its own SELECT statement, like so:

SELECT WM_CONCAT(DISTINCT NAME) 
FROM TPM_TRAININGPLAN JOIN TPM_DELIVERYMETHODS USING (METHODID) 
WHERE PROJECTID=240 
AND VERSIONID=1 
ORDER BY NAME

And it runs fine. My parenthesis are more balanced than a Cirque du Soleil troop. Why the error?


Solution

  • I think the aggregated WM_CONCAT function isn't affected by ORDER BY.

    Therefor, as promised, a custom aggregation that sorts the results. Can be used in earlier versions too.

    -- A string table type to hold the values to concat. I limited it to 4000 because 
    -- of trouble with the driver I use. You should be able to change it to 32767, or
    -- whatever is VARCHAR2's max size.
    CREATE OR REPLACE TYPE TT_STRING as table of varchar2(4000);
    
    -- An aggregate type for the concatenation. It uses the string table to 
    -- hold all values, and sorts it when you're done aggregating.
    CREATE OR REPLACE TYPE AT_CONCATSORTED as object
    (
      V_ITEMS TT_STRING,
    
      static function ODCIAggregateInitialize(
        P_CONTEXT in out AT_CONCATSORTED)
      return number,
    
      member function ODCIAggregateIterate(
        self  in out AT_CONCATSORTED,
        P_VALUE in     varchar2)
      return number,
    
      member function ODCIAggregateTerminate(
        self     in  AT_CONCATSORTED,
        P_RESULT out varchar2,
        P_FLAGS  in  number)
      return number,
    
      member function ODCIAggregateMerge(
        self      in out AT_CONCATSORTED,
        P_CONTEXT in     AT_CONCATSORTED)
      return number
    );
    
    
    create or replace type body AT_CONCATSORTED is
    
    static function ODCIAggregateInitialize(
      P_CONTEXT in out AT_CONCATSORTED)
    return number
    is
    begin
      if P_CONTEXT is null then
        P_CONTEXT := AT_CONCATSORTED(TT_STRING(''));
      else
        P_CONTEXT.V_ITEMS.delete;
      end if;
    
      return ODCIConst.Success;
    end;
    
    member function ODCIAggregateIterate(
      self    in out AT_CONCATSORTED,
      P_VALUE in     varchar2)
    return number
    is
    begin
      self.V_ITEMS.extend;
      self.V_ITEMS(self.V_ITEMS.last) := P_VALUE;
    
      return ODCIConst.Success;
    end;
    
    member function ODCIAggregateTerminate(
      self     in  AT_CONCATSORTED,
      P_RESULT out varchar2,
      P_FLAGS  in  number)
    return number
    is
      V_SORTEDITEMS TT_STRING;
    begin
      select
        cast(multiset(select
                        *
                      from
                        table(self.V_ITEMS)
                      order by
                        1) as TT_STRING)
      into
        V_SORTEDITEMS
      from
        dual;
    
      for i in V_SORTEDITEMS.first..V_SORTEDITEMS.last loop
        P_RESULT := P_RESULT || V_SORTEDITEMS(i);
        if i < V_SORTEDITEMS.last - 1 then
          P_RESULT := P_RESULT || ', ';
        end if;
      end loop;
    
      return ODCIConst.Success;
    end;
    
    member function ODCIAggregateMerge(
      self      in out AT_CONCATSORTED,
      P_CONTEXT in     AT_CONCATSORTED)
    return number
    is
    begin
      for i in P_CONTEXT.V_ITEMS.first..P_CONTEXT.V_ITEMS.last loop
        self.V_ITEMS.extend;
        self.V_ITEMS(self.V_ITEMS.last) := P_CONTEXT.V_ITEMS(i);
      end loop;
    
      return ODCIConst.Success;
    end;
    
    end;
    
    -- The actual concat function
    create or replace function CONCATSORTED (input varchar2) return varchar2
    aggregate using AT_CONCATSORTED;
    

    Now your query could look something like this:

    SELECT
      V.PROJECTID,
      CONCATSORTED(DISTINCT NAME) as NAMES
    FROM 
      TPM_TRAININGPLAN JOIN TPM_DELIVERYMETHODS USING (METHODID) 
    WHERE 
      PROJECTID=V.PROJECTID 
      AND VERSIONID=V.VERSIONID) as Methods
    FROM 
      TPM_PROJECTVERSION V