<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
		>
<channel>
	<title>Comments on: Finding Rows with Common Attributes &#8211; Roman to Find a Solution in Something New</title>
	<atom:link href="http://hoopercharles.wordpress.com/2011/07/01/finding-rows-with-common-attributes-roman-to-find-a-solution-in-something-new/feed/" rel="self" type="application/rss+xml" />
	<link>http://hoopercharles.wordpress.com/2011/07/01/finding-rows-with-common-attributes-roman-to-find-a-solution-in-something-new/</link>
	<description>Miscellaneous Random Oracle Topics: Stop, Think, ... Understand</description>
	<lastBuildDate>Mon, 13 May 2013 14:10:06 +0000</lastBuildDate>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
	<item>
		<title>By: Charles Hooper</title>
		<link>http://hoopercharles.wordpress.com/2011/07/01/finding-rows-with-common-attributes-roman-to-find-a-solution-in-something-new/#comment-3614</link>
		<dc:creator><![CDATA[Charles Hooper]]></dc:creator>
		<pubDate>Wed, 06 Jul 2011 14:56:33 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=5102#comment-3614</guid>
		<description><![CDATA[Jan,

That small change has fixed the SQL statement so that it produces the same output as the other methods.

---

Wow - a lot of different ways to solve this type of problem.  Fortunately, SQL is not an abbreviation of &quot;Standardized Query Language&quot; - it would have been much too boring to see 10 solutions that were all identical.  :-)]]></description>
		<content:encoded><![CDATA[<p>Jan,</p>
<p>That small change has fixed the SQL statement so that it produces the same output as the other methods.</p>
<p>&#8212;</p>
<p>Wow &#8211; a lot of different ways to solve this type of problem.  Fortunately, SQL is not an abbreviation of &#8220;Standardized Query Language&#8221; &#8211; it would have been much too boring to see 10 solutions that were all identical.  <img src='http://s0.wp.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> </p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Jan</title>
		<link>http://hoopercharles.wordpress.com/2011/07/01/finding-rows-with-common-attributes-roman-to-find-a-solution-in-something-new/#comment-3613</link>
		<dc:creator><![CDATA[Jan]]></dc:creator>
		<pubDate>Wed, 06 Jul 2011 14:21:45 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=5102#comment-3613</guid>
		<description><![CDATA[Thanks for your comments, I have fixed that issue with OUTER JOIN:
&lt;pre&gt;
SELECT DISTINCT c1
      FROM(
      SELECT * FROM(
         SELECT t1.*,a1.all_c1_cnt,
                COUNT(DISTINCT t1.c2) OVER (PARTITION BY t1.c1) AS matched_c1_cnt
              FROM(
                   SELECT c1,c2,
                     COUNT(DISTINCT c2) OVER (PARTITION BY c1) AS all_c1_cnt
                        FROM t1 WHERE c1=&#039;II&#039;) a1
                  RIGHT JOIN t1
                    ON(a1.c2=t1.c2
                       AND a1.c1 != t1.c1))
       WHERE matched_c1_cnt= all_c1_cnt)
         ORDER BY 1;
&lt;/pre&gt;]]></description>
		<content:encoded><![CDATA[<p>Thanks for your comments, I have fixed that issue with OUTER JOIN:</p>
<pre>
SELECT DISTINCT c1
      FROM(
      SELECT * FROM(
         SELECT t1.*,a1.all_c1_cnt,
                COUNT(DISTINCT t1.c2) OVER (PARTITION BY t1.c1) AS matched_c1_cnt
              FROM(
                   SELECT c1,c2,
                     COUNT(DISTINCT c2) OVER (PARTITION BY c1) AS all_c1_cnt
                        FROM t1 WHERE c1='II') a1
                  RIGHT JOIN t1
                    ON(a1.c2=t1.c2
                       AND a1.c1 != t1.c1))
       WHERE matched_c1_cnt= all_c1_cnt)
         ORDER BY 1;
</pre>
]]></content:encoded>
	</item>
	<item>
		<title>By: Charles Hooper</title>
		<link>http://hoopercharles.wordpress.com/2011/07/01/finding-rows-with-common-attributes-roman-to-find-a-solution-in-something-new/#comment-3612</link>
		<dc:creator><![CDATA[Charles Hooper]]></dc:creator>
		<pubDate>Wed, 06 Jul 2011 13:25:32 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=5102#comment-3612</guid>
		<description><![CDATA[Jan,

I modified your SQL statement to show a not equal to as !=  

If appears that the following line:
&lt;pre&gt;
COUNT(DISTINCT a1.c2) OVER (PARTITION BY a1.c1) AS matched_c1_cnt
&lt;/pre&gt;

May need to be changed to replace all a1. entries with t1., as shown below &lt;i&gt;(I receive 99 rows for the value of &#039;I&#039; without this change, and 9 rows with the change)&lt;/i&gt;:
&lt;pre&gt;
COUNT(DISTINCT t1.c2) OVER (PARTITION BY t1.c1) AS matched_c1_cnt
&lt;/pre&gt;

That is an interesting approach to solving the problem:
&lt;pre&gt;
SQL&gt; SELECT distinct c1
  2    FROM(
  3    SELECT * FROM(
  4       SELECT t1.*,a1.all_c1_cnt,
  5              COUNT(DISTINCT t1.c2) OVER (PARTITION BY t1.c1) AS matched_c1_cnt
  6            FROM(
  7                 SELECT c1,c2,
  8                   COUNT(DISTINCT c2) OVER (PARTITION BY c1) AS all_c1_cnt
  9                      FROM t1 WHERE c1=&#039;I&#039;) a1,
 10                 t1
 11           WHERE a1.c1 != t1.c1
 12             AND a1.c2=t1.c2)
 13      WHERE matched_c1_cnt= all_c1_cnt)
 14        ORDER BY 1;

C1
----------
LVII
LXXI
LXXIII
VI
X
XCIX
XV
XXIX
XXXI

9 rows selected.
&lt;/pre&gt;
AS can be seen by the above output, that worked.  

I think that there is a risk if all of the elements in the inner-most inline view are a subset of the elements in the outer inline-view (the inline view with != ).  This issue is because the equijoin in the WHERE clause will be applied before the COUNT(DISTINCT t1.c2) is calculated:
&lt;pre&gt;
SQL&gt; SELECT distinct c1
  2    FROM(
  3    SELECT * FROM(
  4       SELECT t1.*,a1.all_c1_cnt,
  5              COUNT(DISTINCT t1.c2) OVER (PARTITION BY t1.c1) AS matched_c1_cnt
  6            FROM(
  7                 SELECT c1,c2,
  8                   COUNT(DISTINCT c2) OVER (PARTITION BY c1) AS all_c1_cnt
  9                      FROM t1 WHERE c1=&#039;II&#039;) a1,
 10                 t1
 11           WHERE a1.c1 != t1.c1
 12             AND a1.c2=t1.c2)
 13      WHERE matched_c1_cnt= all_c1_cnt)
 14        ORDER BY 1;

C1
----------
C
I
III
IV
IX
LI
LII
LIV
LIX
LV
LVI
LVII
...
XXXI
XXXIV
XXXIX
XXXV
XXXVI
XXXVIII

84 rows selected.
&lt;/pre&gt;

Any suggestions for improvements to make certain that the one set of elements is not merely a subset of the other set of elements?]]></description>
		<content:encoded><![CDATA[<p>Jan,</p>
<p>I modified your SQL statement to show a not equal to as !=  </p>
<p>If appears that the following line:</p>
<pre>
COUNT(DISTINCT a1.c2) OVER (PARTITION BY a1.c1) AS matched_c1_cnt
</pre>
<p>May need to be changed to replace all a1. entries with t1., as shown below <i>(I receive 99 rows for the value of &#8216;I&#8217; without this change, and 9 rows with the change)</i>:</p>
<pre>
COUNT(DISTINCT t1.c2) OVER (PARTITION BY t1.c1) AS matched_c1_cnt
</pre>
<p>That is an interesting approach to solving the problem:</p>
<pre>
SQL&gt; SELECT distinct c1
  2    FROM(
  3    SELECT * FROM(
  4       SELECT t1.*,a1.all_c1_cnt,
  5              COUNT(DISTINCT t1.c2) OVER (PARTITION BY t1.c1) AS matched_c1_cnt
  6            FROM(
  7                 SELECT c1,c2,
  8                   COUNT(DISTINCT c2) OVER (PARTITION BY c1) AS all_c1_cnt
  9                      FROM t1 WHERE c1='I') a1,
 10                 t1
 11           WHERE a1.c1 != t1.c1
 12             AND a1.c2=t1.c2)
 13      WHERE matched_c1_cnt= all_c1_cnt)
 14        ORDER BY 1;

C1
----------
LVII
LXXI
LXXIII
VI
X
XCIX
XV
XXIX
XXXI

9 rows selected.
</pre>
<p>AS can be seen by the above output, that worked.  </p>
<p>I think that there is a risk if all of the elements in the inner-most inline view are a subset of the elements in the outer inline-view (the inline view with != ).  This issue is because the equijoin in the WHERE clause will be applied before the COUNT(DISTINCT t1.c2) is calculated:</p>
<pre>
SQL&gt; SELECT distinct c1
  2    FROM(
  3    SELECT * FROM(
  4       SELECT t1.*,a1.all_c1_cnt,
  5              COUNT(DISTINCT t1.c2) OVER (PARTITION BY t1.c1) AS matched_c1_cnt
  6            FROM(
  7                 SELECT c1,c2,
  8                   COUNT(DISTINCT c2) OVER (PARTITION BY c1) AS all_c1_cnt
  9                      FROM t1 WHERE c1='II') a1,
 10                 t1
 11           WHERE a1.c1 != t1.c1
 12             AND a1.c2=t1.c2)
 13      WHERE matched_c1_cnt= all_c1_cnt)
 14        ORDER BY 1;

C1
----------
C
I
III
IV
IX
LI
LII
LIV
LIX
LV
LVI
LVII
...
XXXI
XXXIV
XXXIX
XXXV
XXXVI
XXXVIII

84 rows selected.
</pre>
<p>Any suggestions for improvements to make certain that the one set of elements is not merely a subset of the other set of elements?</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Jan</title>
		<link>http://hoopercharles.wordpress.com/2011/07/01/finding-rows-with-common-attributes-roman-to-find-a-solution-in-something-new/#comment-3610</link>
		<dc:creator><![CDATA[Jan]]></dc:creator>
		<pubDate>Wed, 06 Jul 2011 11:52:47 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=5102#comment-3610</guid>
		<description><![CDATA[&lt;pre&gt;
SELECT distinct c1  
  FROM(
  SELECT * FROM(
     SELECT t1.*,a1.all_c1_cnt,
            COUNT(DISTINCT a1.c2) OVER (PARTITION BY a1.c1) AS matched_c1_cnt
          FROM(
               SELECT c1,c2,
                 COUNT(DISTINCT c2) OVER (PARTITION BY c1) AS all_c1_cnt
                    FROM t1 WHERE c1=&#039;I&#039;) a1, 
               t1
         WHERE a1.c1 != t1.c1           
           AND a1.c2=t1.c2)
    WHERE matched_c1_cnt= all_c1_cnt)
      ORDER BY 1;
&lt;/pre&gt;]]></description>
		<content:encoded><![CDATA[<pre>
SELECT distinct c1  
  FROM(
  SELECT * FROM(
     SELECT t1.*,a1.all_c1_cnt,
            COUNT(DISTINCT a1.c2) OVER (PARTITION BY a1.c1) AS matched_c1_cnt
          FROM(
               SELECT c1,c2,
                 COUNT(DISTINCT c2) OVER (PARTITION BY c1) AS all_c1_cnt
                    FROM t1 WHERE c1='I') a1, 
               t1
         WHERE a1.c1 != t1.c1           
           AND a1.c2=t1.c2)
    WHERE matched_c1_cnt= all_c1_cnt)
      ORDER BY 1;
</pre>
]]></content:encoded>
	</item>
	<item>
		<title>By: Charles Hooper</title>
		<link>http://hoopercharles.wordpress.com/2011/07/01/finding-rows-with-common-attributes-roman-to-find-a-solution-in-something-new/#comment-3605</link>
		<dc:creator><![CDATA[Charles Hooper]]></dc:creator>
		<pubDate>Tue, 05 Jul 2011 11:12:22 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=5102#comment-3605</guid>
		<description><![CDATA[Rob, 

Thank you for the link... I was having trouble with the function names until I saw the OTN thread linked in your blog article.  I believe that I was thinking of the COLLECT function, but the OTN thread also shows a couple of examples with the MODEL clause.  Since I was looking for a SQL only solution, the COLLECT function is probably not enough without the help of PL/SQL:
&lt;pre&gt;
SELECT
  C1,
  COLLECT(TO_CHAR(C2)) C2_LISTING
FROM
  T1
GROUP BY
  C1;

C1         C2_LISTING
---------- -------------------------------------------------------
C          SYSTPwdsOE9IeSjCswL2Sefy38w==(&#039;1&#039;, &#039;4&#039;, &#039;3&#039;, &#039;2&#039;)
I          SYSTPwdsOE9IeSjCswL2Sefy38w==(&#039;1&#039;, &#039;10&#039;, &#039;9&#039;, &#039;8&#039;, &#039;7&#039;, &#039;6&#039;, &#039;5&#039;, &#039;4&#039;, &#039;3&#039;, &#039;2&#039;)
II         SYSTPwdsOE9IeSjCswL2Sefy38w==(&#039;1&#039;, &#039;4&#039;, &#039;3&#039;, &#039;2&#039;)
III        SYSTPwdsOE9IeSjCswL2Sefy38w==(&#039;1&#039;, &#039;7&#039;, &#039;6&#039;, &#039;5&#039;, &#039;4&#039;, &#039;3&#039;, &#039;2&#039;)
IV         SYSTPwdsOE9IeSjCswL2Sefy38w==(&#039;1&#039;, &#039;9&#039;, &#039;8&#039;, &#039;7&#039;, &#039;6&#039;, &#039;5&#039;, &#039;4&#039;, &#039;3&#039;, &#039;2&#039;)
IX         SYSTPwdsOE9IeSjCswL2Sefy38w==(&#039;1&#039;, &#039;4&#039;, &#039;3&#039;, &#039;2&#039;)
L          SYSTPwdsOE9IeSjCswL2Sefy38w==(&#039;1&#039;, &#039;3&#039;, &#039;2&#039;)
LI         SYSTPwdsOE9IeSjCswL2Sefy38w==(&#039;1&#039;, &#039;5&#039;, &#039;4&#039;, &#039;3&#039;, &#039;2&#039;)
...
&lt;/pre&gt;
 
&lt;pre&gt;
SQL&gt; SELECT
  2    C1,
  3    SUBSTR(C2_LISTING,INSTR(C2_LISTING,&#039;==&#039;)+2)
  4  FROM
  5    (SELECT
  6      C1,
  7      COLLECT(TO_CHAR(C2)) C2_LISTING
  8    FROM
  9      T1
 10    GROUP BY
 11      C1);
  SUBSTR(C2_LISTING,INSTR(C2_LISTING,&#039;==&#039;)+2)
                          *
ERROR at line 3:
ORA-00932: inconsistent datatypes: expected CHAR got -
&lt;/pre&gt;

A search for other information also found:
If we allow PL/SQL, http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:2196162600402 shows the STRAGG and CONCAT_ALL custom PL/SQL routines.

This article also shows several methods: http://www.oracle-base.com/articles/misc/StringAggregationTechniques.php

There is a key difference in the OTN thread: you demonstrated timing the execution of each method to find the fastest solution, rather than just accepting the first solution that accomplishes the specified task.  I suspect that it might also be a good idea to determine the impact on the CPU - is a solution that is twice as fast, but uses 8 times as much CPU time a good solution - it might in some environments, but not in others (for instance in a multi-user system where several sessions may try to concurrently execute the same SQL statement).]]></description>
		<content:encoded><![CDATA[<p>Rob, </p>
<p>Thank you for the link&#8230; I was having trouble with the function names until I saw the OTN thread linked in your blog article.  I believe that I was thinking of the COLLECT function, but the OTN thread also shows a couple of examples with the MODEL clause.  Since I was looking for a SQL only solution, the COLLECT function is probably not enough without the help of PL/SQL:</p>
<pre>
SELECT
  C1,
  COLLECT(TO_CHAR(C2)) C2_LISTING
FROM
  T1
GROUP BY
  C1;

C1         C2_LISTING
---------- -------------------------------------------------------
C          SYSTPwdsOE9IeSjCswL2Sefy38w==('1', '4', '3', '2')
I          SYSTPwdsOE9IeSjCswL2Sefy38w==('1', '10', '9', '8', '7', '6', '5', '4', '3', '2')
II         SYSTPwdsOE9IeSjCswL2Sefy38w==('1', '4', '3', '2')
III        SYSTPwdsOE9IeSjCswL2Sefy38w==('1', '7', '6', '5', '4', '3', '2')
IV         SYSTPwdsOE9IeSjCswL2Sefy38w==('1', '9', '8', '7', '6', '5', '4', '3', '2')
IX         SYSTPwdsOE9IeSjCswL2Sefy38w==('1', '4', '3', '2')
L          SYSTPwdsOE9IeSjCswL2Sefy38w==('1', '3', '2')
LI         SYSTPwdsOE9IeSjCswL2Sefy38w==('1', '5', '4', '3', '2')
...
</pre>
<pre>
SQL&gt; SELECT
  2    C1,
  3    SUBSTR(C2_LISTING,INSTR(C2_LISTING,'==')+2)
  4  FROM
  5    (SELECT
  6      C1,
  7      COLLECT(TO_CHAR(C2)) C2_LISTING
  8    FROM
  9      T1
 10    GROUP BY
 11      C1);
  SUBSTR(C2_LISTING,INSTR(C2_LISTING,'==')+2)
                          *
ERROR at line 3:
ORA-00932: inconsistent datatypes: expected CHAR got -
</pre>
<p>A search for other information also found:<br />
If we allow PL/SQL, <a href="http://asktom.oracle.com/pls/asktom/f?p=100:11:0" rel="nofollow">http://asktom.oracle.com/pls/asktom/f?p=100:11:0</a>::::P11_QUESTION_ID:2196162600402 shows the STRAGG and CONCAT_ALL custom PL/SQL routines.</p>
<p>This article also shows several methods: <a href="http://www.oracle-base.com/articles/misc/StringAggregationTechniques.php" rel="nofollow">http://www.oracle-base.com/articles/misc/StringAggregationTechniques.php</a></p>
<p>There is a key difference in the OTN thread: you demonstrated timing the execution of each method to find the fastest solution, rather than just accepting the first solution that accomplishes the specified task.  I suspect that it might also be a good idea to determine the impact on the CPU &#8211; is a solution that is twice as fast, but uses 8 times as much CPU time a good solution &#8211; it might in some environments, but not in others (for instance in a multi-user system where several sessions may try to concurrently execute the same SQL statement).</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Rob van Wijk</title>
		<link>http://hoopercharles.wordpress.com/2011/07/01/finding-rows-with-common-attributes-roman-to-find-a-solution-in-something-new/#comment-3603</link>
		<dc:creator><![CDATA[Rob van Wijk]]></dc:creator>
		<pubDate>Tue, 05 Jul 2011 07:40:42 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=5102#comment-3603</guid>
		<description><![CDATA[PS: I forgot to mention: the 6 ways are not in that blogpost itself, but in the OTN-thread that I refer to in the link &quot;fastest solution&quot; ...]]></description>
		<content:encoded><![CDATA[<p>PS: I forgot to mention: the 6 ways are not in that blogpost itself, but in the OTN-thread that I refer to in the link &#8220;fastest solution&#8221; &#8230;</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Rob van Wijk</title>
		<link>http://hoopercharles.wordpress.com/2011/07/01/finding-rows-with-common-attributes-roman-to-find-a-solution-in-something-new/#comment-3602</link>
		<dc:creator><![CDATA[Rob van Wijk]]></dc:creator>
		<pubDate>Tue, 05 Jul 2011 07:22:49 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=5102#comment-3602</guid>
		<description><![CDATA[&gt; Wasn’t there another way to create a comma separated list prior to the introduction of LISTAGG?

Creating a comma separated list is essentially string aggregation. Prior to LISTAGG, I&#039;ve seen 6 different ways to do string aggregation. I mentioned them all in this blogpost: http://rwijk.blogspot.com/2008/05/string-aggregation-with-model-clause.html.
Your &quot;old method&quot; is one of them.

Regards,
Rob.]]></description>
		<content:encoded><![CDATA[<p>&gt; Wasn’t there another way to create a comma separated list prior to the introduction of LISTAGG?</p>
<p>Creating a comma separated list is essentially string aggregation. Prior to LISTAGG, I&#8217;ve seen 6 different ways to do string aggregation. I mentioned them all in this blogpost: <a href="http://rwijk.blogspot.com/2008/05/string-aggregation-with-model-clause.html" rel="nofollow">http://rwijk.blogspot.com/2008/05/string-aggregation-with-model-clause.html</a>.<br />
Your &#8220;old method&#8221; is one of them.</p>
<p>Regards,<br />
Rob.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Charles Hooper</title>
		<link>http://hoopercharles.wordpress.com/2011/07/01/finding-rows-with-common-attributes-roman-to-find-a-solution-in-something-new/#comment-3599</link>
		<dc:creator><![CDATA[Charles Hooper]]></dc:creator>
		<pubDate>Mon, 04 Jul 2011 23:19:06 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=5102#comment-3599</guid>
		<description><![CDATA[Rob,

Nice potential performance improvement.  The FIRST_VALUE function did not occur to me as a possibility.

---

A lot of creativity in the solutions found in this article.  Any other solutions?

Two more possible solutions:
#Start of solution 1: Retrieve the C2 value, its relative position, and the total number of elements:
&lt;pre&gt;
SELECT
  C1,
  C2,
  ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY C2) RN,
  COUNT(*) OVER  (PARTITION BY C1) CNT
FROM
  T1;
&lt;/pre&gt;

Copy the above twice into inline views.  The first inline view will retrieve the values of interest, the second inline view will retrieve all of the other values.  The WHERE clause makes certain that we have matching values and positions, and the HAVING clause makes certain that all elements are accounted for:
&lt;pre&gt;
SELECT
  V2.C1
FROM
  (SELECT
    C1,
    C2,
    ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY C2) RN,
    COUNT(*) OVER  (PARTITION BY C1) CNT
  FROM
    T1
  WHERE
    C1 = &#039;I&#039;) V1,
  (SELECT
    C1,
    C2,
    ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY C2) RN,
    COUNT(*) OVER  (PARTITION BY C1) CNT
  FROM
    T1
  WHERE
    C1 != &#039;I&#039;) V2
WHERE
  V1.C2=V2.C2
  AND V1.RN=V2.RN
  AND V1.CNT=V2.CNT
GROUP BY
  V2.C1
HAVING
  COUNT(*)=MAX(V1.CNT);
 
C1
---------------
XXIX
XCIX
XXXI
LXXIII
VI
XV
X
LXXI
LVII

9 rows selected.
&lt;/pre&gt;

II:
&lt;pre&gt;
C1
*---------------
XCIII
XXIII
XIII
XCVI
XC
LXXXV
C
LXXVII
VIII
IX
LXXV
LXXVIII
LXX

13 rows selected.
&lt;/pre&gt;

III:
&lt;pre&gt;
C1
---------------
XLVI
XXVIII
LXVI
XCVIII
XVII
LXXVI
LXXXVII
XLIII
LII
XXX

10 rows selected.
&lt;/pre&gt;


#Start of solution 2, use the *old* method (before LISTAGG was an option) to generate a comma separated list:
&lt;pre&gt;
SELECT
  C1,
  SUBSTR(SYS_CONNECT_BY_PATH(TO_CHAR(C2),&#039;,&#039;),2) C2_LISTING
FROM
  (SELECT
    C1,
    C2,
    ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY C2) RN
  FROM
    T1)
CONNECT BY
  PRIOR RN = RN-1
  AND PRIOR C1 = C1
START WITH
  RN = 1;
 
C1              C2_LISTING
--------------- --------------------
C               1
C               1,2
C               1,2,3
C               1,2,3,4
I               1
I               1,2
I               1,2,3
I               1,2,3,4
I               1,2,3,4,5
I               1,2,3,4,5,6
I               1,2,3,4,5,6,7
I               1,2,3,4,5,6,7,8
I               1,2,3,4,5,6,7,8,9
I               1,2,3,4,5,6,7,8,9,10
...
&lt;/pre&gt;

Use a MAX function to retrieve just the complete list for each of the C1 values:
&lt;pre&gt;
SELECT
  C1,
  MAX(C2_LISTING) C2_LISTING
FROM
  (SELECT
    C1,
    SUBSTR(SYS_CONNECT_BY_PATH(TO_CHAR(C2),&#039;,&#039;),2) C2_LISTING
  FROM
    (SELECT
      C1,
      C2,
      ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY C2) RN
    FROM
      T1)
  CONNECT BY
    PRIOR RN = RN-1
    AND PRIOR C1 = C1
  START WITH
    RN = 1)
GROUP BY
  C1
ORDER BY
  C1;
 
C1              C2_LISTING
--------------- --------------------
C               1,2,3,4
I               1,2,3,4,5,6,7,8,9,10
II              1,2,3,4
III             1,2,3,4,5,6,7
IV              1,2,3,4,5,6,7,8,9
IX              1,2,3,4
L               1,2,3
... 
XXXV            1,2,3,4,5,6,7,8,9
XXXVI           1,2,3,4,5
XXXVII          1,2,3
XXXVIII         1,2,3,4,5,6

100 rows selected.
&lt;/pre&gt;

Create two inline views using the above logic.  The first inline view will retrieve the values of interest, the second inline view will retrieve all of the other values.  The WHERE clause simply locates those C2_LISTING values that match the values of interest.
&lt;pre&gt;
SELECT
  V2.C1,
  V2.C2_LISTING
FROM
(SELECT
  C1,
  MAX(C2_LISTING) C2_LISTING
FROM
  (SELECT
    C1,
    SUBSTR(SYS_CONNECT_BY_PATH(TO_CHAR(C2),&#039;,&#039;),2) C2_LISTING
  FROM
    (SELECT
      C1,
      C2,
      ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY C2) RN
    FROM
      T1
    WHERE
      C1=&#039;I&#039;)
  CONNECT BY
    PRIOR RN = RN-1
    AND PRIOR C1 = C1
  START WITH
    RN = 1)
GROUP BY
  C1) V1,
(SELECT
  C1,
  MAX(C2_LISTING) C2_LISTING
FROM
  (SELECT
    C1,
    SUBSTR(SYS_CONNECT_BY_PATH(TO_CHAR(C2),&#039;,&#039;),2) C2_LISTING
  FROM
    (SELECT
      C1,
      C2,
      ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY C2) RN
    FROM
      T1
    WHERE
      C1 != &#039;I&#039;)
  CONNECT BY
    PRIOR RN = RN-1
    AND PRIOR C1 = C1
  START WITH
    RN = 1)
GROUP BY
  C1) V2
WHERE
  V1.C2_LISTING = V2.C2_LISTING
ORDER BY
  V2.C1;

C1              C2_LISTING
--------------- --------------------
LVII            1,2,3,4,5,6,7,8,9,10
LXXI            1,2,3,4,5,6,7,8,9,10
LXXIII          1,2,3,4,5,6,7,8,9,10
VI              1,2,3,4,5,6,7,8,9,10
X               1,2,3,4,5,6,7,8,9,10
XCIX            1,2,3,4,5,6,7,8,9,10
XV              1,2,3,4,5,6,7,8,9,10
XXIX            1,2,3,4,5,6,7,8,9,10
XXXI            1,2,3,4,5,6,7,8,9,10

9 rows selected.
&lt;/pre&gt;

II:
&lt;pre&gt;
C1              C2_LISTING
--------------- ----------
C               1,2,3,4
IX              1,2,3,4
LXX             1,2,3,4
LXXV            1,2,3,4
LXXVII          1,2,3,4
LXXVIII         1,2,3,4
LXXXV           1,2,3,4
VIII            1,2,3,4
XC              1,2,3,4
XCIII           1,2,3,4
XCVI            1,2,3,4
XIII            1,2,3,4
XXIII           1,2,3,4
&lt;/pre&gt;

III:
&lt;pre&gt;
C1              C2_LISTING
--------------- -------------
LII             1,2,3,4,5,6,7
LXVI            1,2,3,4,5,6,7
LXXVI           1,2,3,4,5,6,7
LXXXVII         1,2,3,4,5,6,7
XCVIII          1,2,3,4,5,6,7
XLIII           1,2,3,4,5,6,7
XLVI            1,2,3,4,5,6,7
XVII            1,2,3,4,5,6,7
XXVIII           1,2,3,4,5,6,7
XXX             1,2,3,4,5,6,7

10 rows selected.
&lt;/pre&gt;

There must be at least one more solution to this problem without resorting to PL/SQL.  Wasn&#039;t there another way to create a comma separated list prior to the introduction of LISTAGG?]]></description>
		<content:encoded><![CDATA[<p>Rob,</p>
<p>Nice potential performance improvement.  The FIRST_VALUE function did not occur to me as a possibility.</p>
<p>&#8212;</p>
<p>A lot of creativity in the solutions found in this article.  Any other solutions?</p>
<p>Two more possible solutions:<br />
#Start of solution 1: Retrieve the C2 value, its relative position, and the total number of elements:</p>
<pre>
SELECT
  C1,
  C2,
  ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY C2) RN,
  COUNT(*) OVER  (PARTITION BY C1) CNT
FROM
  T1;
</pre>
<p>Copy the above twice into inline views.  The first inline view will retrieve the values of interest, the second inline view will retrieve all of the other values.  The WHERE clause makes certain that we have matching values and positions, and the HAVING clause makes certain that all elements are accounted for:</p>
<pre>
SELECT
  V2.C1
FROM
  (SELECT
    C1,
    C2,
    ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY C2) RN,
    COUNT(*) OVER  (PARTITION BY C1) CNT
  FROM
    T1
  WHERE
    C1 = 'I') V1,
  (SELECT
    C1,
    C2,
    ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY C2) RN,
    COUNT(*) OVER  (PARTITION BY C1) CNT
  FROM
    T1
  WHERE
    C1 != 'I') V2
WHERE
  V1.C2=V2.C2
  AND V1.RN=V2.RN
  AND V1.CNT=V2.CNT
GROUP BY
  V2.C1
HAVING
  COUNT(*)=MAX(V1.CNT);
 
C1
---------------
XXIX
XCIX
XXXI
LXXIII
VI
XV
X
LXXI
LVII

9 rows selected.
</pre>
<p>II:</p>
<pre>
C1
*---------------
XCIII
XXIII
XIII
XCVI
XC
LXXXV
C
LXXVII
VIII
IX
LXXV
LXXVIII
LXX

13 rows selected.
</pre>
<p>III:</p>
<pre>
C1
---------------
XLVI
XXVIII
LXVI
XCVIII
XVII
LXXVI
LXXXVII
XLIII
LII
XXX

10 rows selected.
</pre>
<p>#Start of solution 2, use the *old* method (before LISTAGG was an option) to generate a comma separated list:</p>
<pre>
SELECT
  C1,
  SUBSTR(SYS_CONNECT_BY_PATH(TO_CHAR(C2),','),2) C2_LISTING
FROM
  (SELECT
    C1,
    C2,
    ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY C2) RN
  FROM
    T1)
CONNECT BY
  PRIOR RN = RN-1
  AND PRIOR C1 = C1
START WITH
  RN = 1;
 
C1              C2_LISTING
--------------- --------------------
C               1
C               1,2
C               1,2,3
C               1,2,3,4
I               1
I               1,2
I               1,2,3
I               1,2,3,4
I               1,2,3,4,5
I               1,2,3,4,5,6
I               1,2,3,4,5,6,7
I               1,2,3,4,5,6,7,8
I               1,2,3,4,5,6,7,8,9
I               1,2,3,4,5,6,7,8,9,10
...
</pre>
<p>Use a MAX function to retrieve just the complete list for each of the C1 values:</p>
<pre>
SELECT
  C1,
  MAX(C2_LISTING) C2_LISTING
FROM
  (SELECT
    C1,
    SUBSTR(SYS_CONNECT_BY_PATH(TO_CHAR(C2),','),2) C2_LISTING
  FROM
    (SELECT
      C1,
      C2,
      ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY C2) RN
    FROM
      T1)
  CONNECT BY
    PRIOR RN = RN-1
    AND PRIOR C1 = C1
  START WITH
    RN = 1)
GROUP BY
  C1
ORDER BY
  C1;
 
C1              C2_LISTING
--------------- --------------------
C               1,2,3,4
I               1,2,3,4,5,6,7,8,9,10
II              1,2,3,4
III             1,2,3,4,5,6,7
IV              1,2,3,4,5,6,7,8,9
IX              1,2,3,4
L               1,2,3
... 
XXXV            1,2,3,4,5,6,7,8,9
XXXVI           1,2,3,4,5
XXXVII          1,2,3
XXXVIII         1,2,3,4,5,6

100 rows selected.
</pre>
<p>Create two inline views using the above logic.  The first inline view will retrieve the values of interest, the second inline view will retrieve all of the other values.  The WHERE clause simply locates those C2_LISTING values that match the values of interest.</p>
<pre>
SELECT
  V2.C1,
  V2.C2_LISTING
FROM
(SELECT
  C1,
  MAX(C2_LISTING) C2_LISTING
FROM
  (SELECT
    C1,
    SUBSTR(SYS_CONNECT_BY_PATH(TO_CHAR(C2),','),2) C2_LISTING
  FROM
    (SELECT
      C1,
      C2,
      ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY C2) RN
    FROM
      T1
    WHERE
      C1='I')
  CONNECT BY
    PRIOR RN = RN-1
    AND PRIOR C1 = C1
  START WITH
    RN = 1)
GROUP BY
  C1) V1,
(SELECT
  C1,
  MAX(C2_LISTING) C2_LISTING
FROM
  (SELECT
    C1,
    SUBSTR(SYS_CONNECT_BY_PATH(TO_CHAR(C2),','),2) C2_LISTING
  FROM
    (SELECT
      C1,
      C2,
      ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY C2) RN
    FROM
      T1
    WHERE
      C1 != 'I')
  CONNECT BY
    PRIOR RN = RN-1
    AND PRIOR C1 = C1
  START WITH
    RN = 1)
GROUP BY
  C1) V2
WHERE
  V1.C2_LISTING = V2.C2_LISTING
ORDER BY
  V2.C1;

C1              C2_LISTING
--------------- --------------------
LVII            1,2,3,4,5,6,7,8,9,10
LXXI            1,2,3,4,5,6,7,8,9,10
LXXIII          1,2,3,4,5,6,7,8,9,10
VI              1,2,3,4,5,6,7,8,9,10
X               1,2,3,4,5,6,7,8,9,10
XCIX            1,2,3,4,5,6,7,8,9,10
XV              1,2,3,4,5,6,7,8,9,10
XXIX            1,2,3,4,5,6,7,8,9,10
XXXI            1,2,3,4,5,6,7,8,9,10

9 rows selected.
</pre>
<p>II:</p>
<pre>
C1              C2_LISTING
--------------- ----------
C               1,2,3,4
IX              1,2,3,4
LXX             1,2,3,4
LXXV            1,2,3,4
LXXVII          1,2,3,4
LXXVIII         1,2,3,4
LXXXV           1,2,3,4
VIII            1,2,3,4
XC              1,2,3,4
XCIII           1,2,3,4
XCVI            1,2,3,4
XIII            1,2,3,4
XXIII           1,2,3,4
</pre>
<p>III:</p>
<pre>
C1              C2_LISTING
--------------- -------------
LII             1,2,3,4,5,6,7
LXVI            1,2,3,4,5,6,7
LXXVI           1,2,3,4,5,6,7
LXXXVII         1,2,3,4,5,6,7
XCVIII          1,2,3,4,5,6,7
XLIII           1,2,3,4,5,6,7
XLVI            1,2,3,4,5,6,7
XVII            1,2,3,4,5,6,7
XXVIII           1,2,3,4,5,6,7
XXX             1,2,3,4,5,6,7

10 rows selected.
</pre>
<p>There must be at least one more solution to this problem without resorting to PL/SQL.  Wasn&#8217;t there another way to create a comma separated list prior to the introduction of LISTAGG?</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Rob van Wijk</title>
		<link>http://hoopercharles.wordpress.com/2011/07/01/finding-rows-with-common-attributes-roman-to-find-a-solution-in-something-new/#comment-3598</link>
		<dc:creator><![CDATA[Rob van Wijk]]></dc:creator>
		<pubDate>Mon, 04 Jul 2011 20:56:48 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=5102#comment-3598</guid>
		<description><![CDATA[Charles,

My solution would be to use listagg, combined with analytic functions to use only a single table scan, like this:

&lt;pre&gt;SQL&gt; exec dbms_stats.gather_table_stats(user,&#039;t1&#039;)

PL/SQL procedure successfully completed.

SQL&gt; var your_value varchar2(10)
SQL&gt; exec :your_value := &#039;I&#039;

PL/SQL procedure successfully completed.

SQL&gt; select *
  2    from t1
  3   where c1 = :your_value
  4  /

C1                      C2
--------------- ----------
I                        1
I                        2
I                        3
I                        4
I                        5
I                        6
I                        7
I                        8

8 rows selected.

SQL&gt; set serveroutput off
SQL&gt; alter session set statistics_level = all
  2  /

Session altered.

SQL&gt; select c1
  2       , c2_listing
  3    from ( select c1
  4                , listagg(to_char(c2), &#039;,&#039;) within group (order by c2) c2_listing
  5                , first_value(listagg(to_char(c2), &#039;,&#039;) within group (order by c2)) over (order by decode(c1,:your_value,1,2)) x
  6             from t1
  7            group by c1
  8         )
  9   where c2_listing = x
 10  /

C1              C2_LISTING
--------------- --------------------------------------------------
I               1,2,3,4,5,6,7,8
LXXX            1,2,3,4,5,6,7,8
LXXIX           1,2,3,4,5,6,7,8
IV              1,2,3,4,5,6,7,8
XXXVIII         1,2,3,4,5,6,7,8
XXXV            1,2,3,4,5,6,7,8
XLVIII          1,2,3,4,5,6,7,8
XVIII           1,2,3,4,5,6,7,8
XI              1,2,3,4,5,6,7,8

9 rows selected.

SQL&gt; select * from table(dbms_xplan.display_cursor(null,null,&#039;allstats last&#039;))
  2  /

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  8hhkguv0pcux7, child number 0
-------------------------------------
select c1      , c2_listing   from ( select c1               ,
listagg(to_char(c2), &#039;,&#039;) within group (order by c2) c2_listing
      , first_value(listagg(to_char(c2), &#039;,&#039;) within group (order by
c2)) over (order by decode(c1,:your_value,1,2)) x            from t1
       group by c1        )  where c2_listing = x

Plan hash value: 913157036

------------------------------------------------------------------------------------------------------------------
&#124; Id  &#124; Operation            &#124; Name &#124; Starts &#124; E-Rows &#124; A-Rows &#124;   A-Time   &#124; Buffers &#124;  OMem &#124;  1Mem &#124; Used-Mem &#124;
------------------------------------------------------------------------------------------------------------------
&#124;   0 &#124; SELECT STATEMENT     &#124;      &#124;      1 &#124;        &#124;      9 &#124;00:00:00.01 &#124;       3 &#124;       &#124;       &#124;          &#124;
&#124;*  1 &#124;  VIEW                &#124;      &#124;      1 &#124;    100 &#124;      9 &#124;00:00:00.01 &#124;       3 &#124;       &#124;       &#124;          &#124;
&#124;   2 &#124;   WINDOW SORT        &#124;      &#124;      1 &#124;    100 &#124;    100 &#124;00:00:00.01 &#124;       3 &#124; 13312 &#124; 13312 &#124;12288  (0)&#124;
&#124;   3 &#124;    SORT GROUP BY     &#124;      &#124;      1 &#124;    100 &#124;    100 &#124;00:00:00.01 &#124;       3 &#124; 46080 &#124; 46080 &#124;40960  (0)&#124;
&#124;   4 &#124;     TABLE ACCESS FULL&#124; T1   &#124;      1 &#124;    572 &#124;    572 &#124;00:00:00.01 &#124;       3 &#124;       &#124;       &#124;          &#124;
------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(&quot;C2_LISTING&quot;=&quot;X&quot;)


25 rows selected.&lt;/pre&gt;

Regards,
Rob.]]></description>
		<content:encoded><![CDATA[<p>Charles,</p>
<p>My solution would be to use listagg, combined with analytic functions to use only a single table scan, like this:</p>
<pre>SQL&gt; exec dbms_stats.gather_table_stats(user,'t1')

PL/SQL procedure successfully completed.

SQL&gt; var your_value varchar2(10)
SQL&gt; exec :your_value := 'I'

PL/SQL procedure successfully completed.

SQL&gt; select *
  2    from t1
  3   where c1 = :your_value
  4  /

C1                      C2
--------------- ----------
I                        1
I                        2
I                        3
I                        4
I                        5
I                        6
I                        7
I                        8

8 rows selected.

SQL&gt; set serveroutput off
SQL&gt; alter session set statistics_level = all
  2  /

Session altered.

SQL&gt; select c1
  2       , c2_listing
  3    from ( select c1
  4                , listagg(to_char(c2), ',') within group (order by c2) c2_listing
  5                , first_value(listagg(to_char(c2), ',') within group (order by c2)) over (order by decode(c1,:your_value,1,2)) x
  6             from t1
  7            group by c1
  8         )
  9   where c2_listing = x
 10  /

C1              C2_LISTING
--------------- --------------------------------------------------
I               1,2,3,4,5,6,7,8
LXXX            1,2,3,4,5,6,7,8
LXXIX           1,2,3,4,5,6,7,8
IV              1,2,3,4,5,6,7,8
XXXVIII         1,2,3,4,5,6,7,8
XXXV            1,2,3,4,5,6,7,8
XLVIII          1,2,3,4,5,6,7,8
XVIII           1,2,3,4,5,6,7,8
XI              1,2,3,4,5,6,7,8

9 rows selected.

SQL&gt; select * from table(dbms_xplan.display_cursor(null,null,'allstats last'))
  2  /

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  8hhkguv0pcux7, child number 0
-------------------------------------
select c1      , c2_listing   from ( select c1               ,
listagg(to_char(c2), ',') within group (order by c2) c2_listing
      , first_value(listagg(to_char(c2), ',') within group (order by
c2)) over (order by decode(c1,:your_value,1,2)) x            from t1
       group by c1        )  where c2_listing = x

Plan hash value: 913157036

------------------------------------------------------------------------------------------------------------------
| Id  | Operation            | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |      1 |        |      9 |00:00:00.01 |       3 |       |       |          |
|*  1 |  VIEW                |      |      1 |    100 |      9 |00:00:00.01 |       3 |       |       |          |
|   2 |   WINDOW SORT        |      |      1 |    100 |    100 |00:00:00.01 |       3 | 13312 | 13312 |12288  (0)|
|   3 |    SORT GROUP BY     |      |      1 |    100 |    100 |00:00:00.01 |       3 | 46080 | 46080 |40960  (0)|
|   4 |     TABLE ACCESS FULL| T1   |      1 |    572 |    572 |00:00:00.01 |       3 |       |       |          |
------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C2_LISTING"="X")


25 rows selected.</pre>
<p>Regards,<br />
Rob.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Charles Hooper</title>
		<link>http://hoopercharles.wordpress.com/2011/07/01/finding-rows-with-common-attributes-roman-to-find-a-solution-in-something-new/#comment-3597</link>
		<dc:creator><![CDATA[Charles Hooper]]></dc:creator>
		<pubDate>Mon, 04 Jul 2011 12:36:36 +0000</pubDate>
		<guid isPermaLink="false">http://hoopercharles.wordpress.com/?p=5102#comment-3597</guid>
		<description><![CDATA[Theory is helpful - thank you for explaining the cause of the error message.  I have tried (unsuccessfully) a couple of times in the past to do the same thing with table aliases.

(10.2.0.5 returns the same message)]]></description>
		<content:encoded><![CDATA[<p>Theory is helpful &#8211; thank you for explaining the cause of the error message.  I have tried (unsuccessfully) a couple of times in the past to do the same thing with table aliases.</p>
<p>(10.2.0.5 returns the same message)</p>
]]></content:encoded>
	</item>
</channel>
</rss>
