Home » SQL & PL/SQL » SQL & PL/SQL » SQL query  () 1 Vote
SQL query [message #115468] Mon, 11 April 2005 16:58 Go to next message
Hina
Messages: 51
Registered: April 2004
Member
Table description
employee_id, role_type
001 PRESIDENT
002 ANALYST
003 CLERK
001 DIRECTOR

How to write a query, so it will display the following results from above mentioned table

results :-
employee_id role_type
001 PRESIDENT/DIRECTOR
002 ANALYST
003 CLERK


Re: SQL query [message #115469 is a reply to message #115468] Mon, 11 April 2005 17:59 Go to previous messageGo to next message
sverch
Messages: 582
Registered: December 2000
Senior Member
Source - Barbara:
http://www.orafaq.com/msgboard/sql/messages/30023.htm

SELECT x.employee_id,
MAX(SUBSTR(SYS_CONNECT_BY_PATH(TO_CHAR(x.role_type),'/'),2))
KEEP(DENSE_RANK LAST ORDER BY x.curr)as_string
FROM (SELECT t.employee_id,
t.role_type,ROW_NUMBER()OVER (PARTITION BY t.employee_id ORDER BY t.role_type)curr,
ROW_NUMBER()OVER (PARTITION BY t.employee_id ORDER BY t.role_type) - 1 prev
FROM t3 t) x
GROUP BY x.employee_id
START WITH x.curr = 1
CONNECT BY x.prev = PRIOR x.curr
AND x.employee_id = PRIOR x.employee_id;

EMPLOYEE_ID AS_STRING
001 DIRECTOR/PRESIDENT
002 ANALYST
003 CLERK
Re: SQL query [message #115470 is a reply to message #115468] Mon, 11 April 2005 18:28 Go to previous messageGo to next message
Hina
Messages: 51
Registered: April 2004
Member
Thanks a lot, I appreciate that.

Could you tell me, how is it works.
I am trying to understand, but its lot of good stuff.

Also, if in future I have to group by some other description like 'President/Clerk' where I have to make changes


Thank you again
Re: SQL query [message #115471 is a reply to message #115469] Mon, 11 April 2005 18:52 Go to previous messageGo to next message
Barbara Boehmer
Messages: 9090
Registered: November 2002
Location: California, USA
Senior Member
The link provided in sverch's response was to a post by Art Metzer, that demonstrates the various methods nicely. Just to clarify where the various methods originated from, as far as I know, and provide further examples and links, and clarification:

You can use Tom Kyte's stragg function (I changed the delimiter from the original ',' to '/' to meet your requirements):

scott@ORA92> SELECT employee_id,
  2  	    stragg (role_type) AS role_types
  3  FROM   employee
  4  GROUP  BY employee_id
  5  /

EMPLOYEE_ID ROLE_TYPES
----------- ------------------------------
001         PRESIDENT/DIRECTOR
002         ANALYST
003         SET


You can use James Padfield's concat_all function, which is a modification of Tom Kyte's stragg function, such that it allows you to select the delimiter as a parameter of an object, rather than hard-coding it in the function:

scott@ORA92> SELECT employee_id,
  2  	    concat_all (concat_expr (role_type, '/')) AS role_types
  3  FROM   employee
  4  GROUP  BY employee_id
  5  /

EMPLOYEE_ID ROLE_TYPES
----------- ------------------------------
001         PRESIDENT/DIRECTOR
002         ANALYST
003         SET


You can use the following method which was made popular on this forum by William Robertson, who informed me that he found that, after developing the method on his own, he found that the same thing had been previously posted on Tom Kyte's site by a reader (Mikito Hari I think):

scott@ORA92> COLUMN role_types FORMAT A30
scott@ORA92> SELECT employee_id,
  2  	    MAX (SUBSTR (SYS_CONNECT_BY_PATH (role_type, '/'), 2)) AS role_types
  3  FROM   (SELECT employee_id, role_type,
  4  		    ROW_NUMBER () OVER (PARTITION BY employee_id ORDER BY role_type) AS curr,
  5  		    ROW_NUMBER () OVER (PARTITION BY employee_id ORDER BY role_type) - 1 AS prev
  6  	     FROM   employee)
  7  START  WITH curr = 1
  8  CONNECT BY PRIOR curr = prev AND PRIOR employee_id = employee_id
  9  GROUP  BY employee_id
 10  /

EMPLOYEE_ID ROLE_TYPES
----------- ------------------------------
001         DIRECTOR/PRESIDENT
002         ANALYST
003         SET


If you are using a version prior to 9i, you can use the following old generic method by Tom Kyte:

scott@ORA92> create or replace
  2  function transpose( p_key_name in varchar2,
  3  			 p_key_val  in varchar2,
  4  			 p_other_col_name in varchar2,
  5  			 p_tname     in varchar2,
  6  			 p_sep	     in varchar2)
  7  return varchar2
  8  as
  9  	 type rc is ref cursor;
 10  	 l_str	  varchar2(4000);
 11  	 l_sep	  varchar2(1);
 12  	 l_val	  varchar2(4000);
 13  
 14  	 l_cur	  rc;
 15  begin
 16  
 17  	 open l_cur for 'select '||p_other_col_name||'
 18  			   from '|| p_tname || '
 19  			  where ' || p_key_name || ' = :x '
 20  		     using p_key_val;
 21  
 22  	 loop
 23  	     fetch l_cur into l_val;
 24  	     exit when l_cur%notfound;
 25  	     l_str := l_str || l_sep || l_val;
 26  	     l_sep := p_sep;
 27  	 end loop;
 28  	 close l_cur;
 29  
 30  	 return l_str;
 31  end;
 32  /

Function created.

scott@ORA92> SELECT employee_id,
  2  	    transpose ('employee_id', employee_id, 'role_type', 'employee', '/')
  3  	      AS role_types
  4  FROM   employee
  5  GROUP  BY employee_id
  6  /

EMPLOYEE_ID ROLE_TYPES
----------- ------------------------------
001         PRESIDENT/DIRECTOR
002         ANALYST
003         SET


You can create a function specific to your individual situation. This method has been around so long that I do not know who first thought of it or posted it:

scott@ORA92> CREATE OR REPLACE FUNCTION your_func
  2    (p_employee_id IN employee.employee_id%TYPE)
  3    RETURN		 VARCHAR2
  4  AS
  5    v_role_types	 VARCHAR2(4000);
  6  BEGIN
  7    FOR rec IN
  8  	 (SELECT role_type
  9  	  FROM	 employee
 10  	  WHERE  employee_id = p_employee_id
 11  	  ORDER  BY role_type)
 12    LOOP
 13  	 v_role_types := v_role_types || '/' || rec.role_type;
 14    END LOOP;
 15    RETURN LTRIM (v_role_types, '/');
 16  END your_func;
 17  /

Function created.

scott@ORA92> SHOW ERRORS
No errors.
scott@ORA92> COLUMN role_types FORMAT A30
scott@ORA92> SELECT employee_id,
  2  	    your_func (employee_id) AS role_types
  3  FROM   employee
  4  GROUP  BY employee_id
  5  /

EMPLOYEE_ID ROLE_TYPES
----------- ------------------------------
001         DIRECTOR/PRESIDENT
002         ANALYST
003         SET


William Robertson has put together a rather nice demonstration and explanation of the method that he made popular here and links to various other methods:

http://www.williamrobertson.pwp.blueyonder.co.uk/reframe.html?documents/one_row.html

Additionally, this same question was posted slightly differently on the PL/SQL Pipelines, where the impression was given that it was the information, rather than the format that was important, and that only the duplicates were sought, and William Robertson suggested the following method:

scott@ORA92> SELECT * FROM employee
  2  WHERE  employee_id IN
  3  	    ( SELECT employee_id
  4  	      FROM   employee
  5  	      GROUP BY employee_id
  6  	      HAVING COUNT(*) > 1 )
  7  /

EMPLOYEE_ID ROLE_TYPE
----------- ---------------
001         PRESIDENT
001         DIRECTOR



Re: SQL query [message #115472 is a reply to message #115470] Mon, 11 April 2005 19:00 Go to previous messageGo to next message
Barbara Boehmer
Messages: 9090
Registered: November 2002
Location: California, USA
Senior Member
You would just add a where condition to any of the previously provided queries, for example, if you wanted to limit the result set to those with role_type of 'PRESIDENT' or 'DIRECTOR':

scott@ORA92> SELECT * FROM employee
  2  WHERE  employee_id IN
  3  	    ( SELECT employee_id
  4  	      FROM   employee
  5  	      GROUP BY employee_id
  6  	      HAVING COUNT(*) > 1 )
  7  AND    role_type IN ('PRESIDENT', 'DIRECTOR')
  8  /

EMPLOYEE_ID ROLE_TYPE
----------- ---------------
001         PRESIDENT
001         DIRECTOR


scott@ORA92> SELECT employee_id,
  2  	    stragg (role_type) AS role_types
  3  FROM   employee
  4  WHERE  role_type IN ('PRESIDENT', 'DIRECTOR')
  5  GROUP  BY employee_id
  6  /

EMPLOYEE_ID ROLE_TYPES
----------- ------------------------------
001         PRESIDENT/DIRECTOR


scott@ORA92> SELECT employee_id,
  2  	    concat_all (concat_expr (role_type, '/')) AS role_types
  3  FROM   employee
  4  WHERE  role_type IN ('PRESIDENT', 'DIRECTOR')
  5  GROUP  BY employee_id
  6  /

EMPLOYEE_ID ROLE_TYPES
----------- ------------------------------
001         PRESIDENT/DIRECTOR


scott@ORA92> COLUMN role_types FORMAT A30
scott@ORA92> SELECT employee_id,
  2  	    MAX (SUBSTR (SYS_CONNECT_BY_PATH (role_type, '/'), 2)) AS role_types
  3  FROM   (SELECT employee_id, role_type,
  4  		    ROW_NUMBER () OVER (PARTITION BY employee_id ORDER BY role_type) AS curr,
  5  		    ROW_NUMBER () OVER (PARTITION BY employee_id ORDER BY role_type) - 1 AS prev
  6  	     FROM   employee)
  7  WHERE  role_type IN ('PRESIDENT', 'DIRECTOR')
  8  START  WITH curr = 1
  9  CONNECT BY PRIOR curr = prev AND PRIOR employee_id = employee_id
 10  GROUP  BY employee_id
 11  /

EMPLOYEE_ID ROLE_TYPES
----------- ------------------------------
001         DIRECTOR/PRESIDENT


scott@ORA92> SELECT employee_id,
  2  	    transpose ('employee_id', employee_id, 'role_type', 'employee', '/')
  3  	      AS role_types
  4  FROM   employee
  5  WHERE  role_type IN ('PRESIDENT', 'DIRECTOR')
  6  GROUP  BY employee_id
  7  /

EMPLOYEE_ID ROLE_TYPES
----------- ------------------------------
001         PRESIDENT/DIRECTOR


scott@ORA92> COLUMN role_types FORMAT A30
scott@ORA92> SELECT employee_id,
  2  	    your_func (employee_id) AS role_types
  3  FROM   employee
  4  WHERE  role_type IN ('PRESIDENT', 'DIRECTOR')
  5  GROUP  BY employee_id
  6  /

EMPLOYEE_ID ROLE_TYPES
----------- ------------------------------
001         DIRECTOR/PRESIDENT





Re: SQL query [message #115474 is a reply to message #115468] Mon, 11 April 2005 19:27 Go to previous messageGo to next message
Hina
Messages: 51
Registered: April 2004
Member
its really nice, thanks
Re: SQL query [message #115475 is a reply to message #115468] Mon, 11 April 2005 19:38 Go to previous messageGo to next message
Hina
Messages: 51
Registered: April 2004
Member
I am getting this error when I am trying to execute following query.

ORA-00904: "STRAGG": invalid identifier

SELECT employee_id,
stragg (role_type) AS role_types
FROM cibc_person_role
WHERE role_type IN ('PRESIDENT', 'DIRECTOR')
GROUP BY employee_id
/
Re: SQL query [message #115492 is a reply to message #115475] Mon, 11 April 2005 21:35 Go to previous message
Barbara Boehmer
Messages: 9090
Registered: November 2002
Location: California, USA
Senior Member
You are getting the error because you did not read the responses carefully, click on the links provided, copy the code, and run it, to create the stragg function, so you have no stragg function. You have to run all of the code below to create the stragg function, then you can use it:

create or replace type string_agg_type as object
(
   total varchar2(4000),

   static function
        ODCIAggregateInitialize(sctx IN OUT string_agg_type )
        return number,

   member function
        ODCIAggregateIterate(self IN OUT string_agg_type ,
                             value IN varchar2 )
        return number,

   member function
        ODCIAggregateTerminate(self IN string_agg_type,
                               returnValue OUT  varchar2,
                               flags IN number)
        return number,

   member function
        ODCIAggregateMerge(self IN OUT string_agg_type,
                           ctx2 IN string_agg_type)
        return number
);
/
create or replace type body string_agg_type
is

static function ODCIAggregateInitialize(sctx IN OUT string_agg_type)
return number
is
begin
    sctx := string_agg_type( null );
    return ODCIConst.Success;
end;

member function ODCIAggregateIterate(self IN OUT string_agg_type,
                                     value IN varchar2 )
return number
is
begin
    self.total := self.total 
    || '/' 
    || value;
    return ODCIConst.Success;
end;

member function ODCIAggregateTerminate(self IN string_agg_type,
                                       returnValue OUT varchar2,
                                       flags IN number)
return number
is
begin
    returnValue := ltrim(self.total,'/');
    return ODCIConst.Success;
end;

member function ODCIAggregateMerge(self IN OUT string_agg_type,
                                   ctx2 IN string_agg_type)
return number
is
begin
    self.total := self.total || ctx2.total;
    return ODCIConst.Success;
end;


end;
/
CREATE or replace
FUNCTION stragg(input varchar2 )
RETURN varchar2
PARALLEL_ENABLE AGGREGATE USING string_agg_type;
/

Previous Topic: Count difference
Next Topic: Passing Table Name to Procedure to Transformations
Goto Forum:
  


Current Time: Thu Apr 25 14:59:30 CDT 2024