Re: Help with Lists in one column

From: Charles Hooper <hooperc2000_at_yahoo.com>
Date: Mon, 3 Mar 2008 09:07:40 -0800 (PST)
Message-ID: <4a1cb43f-57b0-443c-9b0f-f50de8557d39@y77g2000hsy.googlegroups.com>


On Mar 3, 10:36 am, Paul <paulwragg2..._at_hotmail.com> wrote:
> Hi,
>
> I have a problem whereby I need to be able to obtain data from a
> column that stores multiple list values (comma separated) using one
> SQL Statement. An example is here:
>
> DDL:
>
> CREATE TABLE TESTTBL
> (ID             NUMBER          NOT NULL,
> COMMENT01       VARCHAR2(4000)  NULL);
>
> INSERT INTO TESTTBL(ID, COMMENT01)
> VALUES(1,'Item1,Item2,Item3');
>
> INSERT INTO TESTTBL(ID, COMMENT01)
> VALUES(2,'Item1,Item2,Item3');
>
> INSERT INTO TESTTBL(ID, COMMENT01)
> VALUES(3,'Item1,Item2,Item3');
>
> COMMIT;
>
> The results I need to obtain are:
>
> ID      COMMENT01
> ----------------------------------------------------------
> 1       Item1
> 1       Item2
> 1       Item3
> 2       Item1
> 2       Item2
> 2       Item3
> 3       Item1
> 3       Item2
> 3       Item3
>
> This needs to be linked in to a main query, for example:
>
> SELECT ID, <OTHER COLUMNS>, COMMENT01
> FROM TESTTBL...
>
> but I need to ensure that there is a separate row for each textual
> item in the COMMENT01 field. By the way, there are 10 of these COMMENT
> fields.
>
> I appreciate this is bad DB design storing the list items this way but
> this is a legacy issue that we need a way of getting around so any
> ideas would be appreciated.
>
> In addtion, I need this for Oracle 9, 10 (and 11), and SQL Server 2000
> and 2005 and so a 'pure SQL' solution would be great. If not any other
> help is fine.
>
> Thanks in advance!

Thanks for posting the DDL and DML.

You will probably find that the there is no universal way to do this that works on all database platforms.
One method that will work on versions of Oracle that support regular expressions (note that this is a bit sloppy as it requires a DISTINCT clause):
SELECT
  T.ID,
  SUBSTR(W.WORDS,1,20) WORDS
FROM
  TESTTBL T,
  (SELECT DISTINCT
    ID,
    REGEXP_SUBSTR(COMMENT01,'\w+',1,LEVEL) WORDS   FROM
    TESTTBL W
  CONNECT BY
    REGEXP_SUBSTR(COMMENT01,'\w+',1,LEVEL) IS NOT NULL) W WHERE
  T.ID=W.ID
ORDER BY
  T.ID,
  W.WORDS;         ID WORDS
---------- -----

         1 Item1
         1 Item2
         1 Item3
         2 Item1
         2 Item2
         2 Item3
         3 Item1
         3 Item2
         3 Item3

The inline view uses REGEXP_SUBSTR and Oracle's CONNECT BY syntax to break apart each word into a separate row.

Let's look at another way that will work with older versions of Oracle (as well as Oracle 11g). First, let's see if we can place each word in a separate column. In this example, I am only displaying the first and third keywords:
SELECT
  ID,
  SUBSTR(DECODE(SPACE_1,0,NULL,SUBSTR(NEW_KEYWORD,1,SPACE_1-1)),1,20) SUB_KEYWORD1,
  SUBSTR(DECODE(SPACE_3,0,NULL,SUBSTR(NEW_KEYWORD,SPACE_2+1,SPACE_3- SPACE_2-1)),1,20) SUB_KEYWORD3
FROM
  (SELECT
    ID,

    TRIM(COMMENT01)||',' NEW_KEYWORD,
    INSTR(TRIM(COMMENT01)||',',',',1,1) SPACE_1,
    INSTR(TRIM(COMMENT01)||',',',',1,2) SPACE_2,
    INSTR(TRIM(COMMENT01)||',',',',1,3) SPACE_3,
    INSTR(TRIM(COMMENT01)||',',',',1,4) SPACE_4,
    INSTR(TRIM(COMMENT01)||',',',',1,5) SPACE_5,
    INSTR(TRIM(COMMENT01)||',',',',1,6) SPACE_6,
    INSTR(TRIM(COMMENT01)||',',',',1,7) SPACE_7,
    INSTR(TRIM(COMMENT01)||',',',',1,8) SPACE_8,
    INSTR(TRIM(COMMENT01)||',',',',1,9) SPACE_9,
    INSTR(TRIM(COMMENT01)||',',',',1,10) SPACE_10,
    INSTR(TRIM(COMMENT01)||',',',',1,11) SPACE_11,
    INSTR(TRIM(COMMENT01)||',',',',1,12) SPACE_12,
    INSTR(TRIM(COMMENT01)||',',',',1,13) SPACE_13
  FROM
    TESTTBL); ID SUB_KEYWORD1 SUB_KEYWORD3
-- -------------------- ------------
 1 Item1                Item3
 2 Item1                Item3
 3 Item1                Item3

But we want each word on a separate row, so we need some sort of a counter to cycle through the words to accomplish this: SELECT
  ROWNUM RN
FROM
  DUAL
CONNECT BY
  LEVEL<=13;

        RN


         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13

Now, let's create a Cartesian join between the table and the counter, manually expanding the formulas for SPACE_1, SPACE_2, SPACE_3 as needed from the previous test SQL statement: SELECT
  ID,
 SUBSTR(DECODE(
  INSTR(TRIM(COMMENT01)||',',',',1,RN) ,0,NULL,   SUBSTR(COMMENT01||',',
    DECODE(RN,1,0,INSTR(TRIM(COMMENT01)||',',',',1,RN-1))+1,     INSTR(TRIM(COMMENT01)||',',',',1,RN)

  • DECODE(RN,1,0,INSTR(TRIM(COMMENT01)||',',',',1,RN-1)) -1)) ,1,20) WORDS FROM TESTTBL, (SELECT ROWNUM RN FROM DUAL CONNECT BY LEVEL<=13);

The problem that we have now is that NULL words are being included, so we slide the above into an inline view to filter out the NULLs: SELECT
  ID,
  WORDS
FROM
(SELECT
  ID,
 SUBSTR(DECODE(
  INSTR(TRIM(COMMENT01)||',',',',1,RN) ,0,NULL,   SUBSTR(COMMENT01||',',
    DECODE(RN,1,0,INSTR(TRIM(COMMENT01)||',',',',1,RN-1))+1,     INSTR(TRIM(COMMENT01)||',',',',1,RN)

  • DECODE(RN,1,0,INSTR(TRIM(COMMENT01)||',',',',1,RN-1)) -1)) ,1,20) WORDS FROM TESTTBL, (SELECT ROWNUM RN FROM DUAL CONNECT BY LEVEL<=13)) WHERE WORDS IS NOT NULL ORDER BY ID, WORDS;
        ID WORDS
---------- -----
         1 Item1
         1 Item2
         1 Item3
         2 Item1
         2 Item2
         2 Item3
         3 Item1
         3 Item2
         3 Item3

I am not sure how you might implement this on SQL Server.

Charles Hooper
IT Manager/Oracle DBA
K&M Machine-Fabricating, Inc. Received on Mon Mar 03 2008 - 11:07:40 CST

Original text of this message