0

Oracle - Compare a List to a List

oracle

Today I came across an issue where I needed to compare one list to another in Oracle before I could return the result set. One list is in a column of the table that I want to return. The other list is going to be passed in. I want the row returned if any value in the first list matches any value in the second. After doing a bit of searching, this is what I came up with.

I thought that it would be easier to work with the list if I could mutate it into it's own table. A friend of mine created a listToTable function in MS SQL Server a while back, so I knew there had to be an Oracle version out there. So I found this link , which I've modified slightly below.

/* you must first create a type for the returned table */
CREATE OR REPLACE TYPE split_table AS TABLE OF VARCHAR2(32767);

/* now we create the actual function */
CREATE OR REPLACE FUNCTION udf_split
    (
        nList VARCHAR2,
        nDelim VARCHAR2 := ','
    ) RETURN split_table PIPELINED
    IS
        wID PLS_INTEGER;
        wList VARCHAR2(32767) := nList;
        wValue VARCHAR2(32767);
    BEGIN
        LOOP
            wID := INSTR(wList,nDelim);
            IF wID > 0 THEN
                PIPE ROW(SUBSTR(wList, 1, wID - 1));
                wList := SUBSTR(wList, wID + LENGTH(nDelim));
            ELSE
                PIPE ROW(wList);
                EXIT;
            END IF;
        END LOOP;
    RETURN;
END udf_split;

From my understanding VARCHAR2(32767) is the largest varchar field you can have.

Now we have to create the package that we're going to call to get our results. This package will contain the procedure that will take in one list and compare it to another using the udf_split function twice.

/* first we create the package */
CREATE PACKAGE upk_test
AS
  TYPE cursor_type IS REF CURSOR;
  PROCEDURE getResults(
    pList IN VARCHAR2,
    pResults OUT cursor_type);
END upk_test;

/* then we create the package body */
CREATE OR REPLACE PACKAGE BODY upk_test
AS
  PROCEDURE getResults(
    pList IN VARCHAR2,
    pResults OUT cursor_type)
  IS
  BEGIN
    OPEN pResults FOR
      SELECT *
      FROM (
        SELECT a.*,
          CASE WHEN a.list_a IS NOT NULL THEN (
            SELECT COUNT(*)
            FROM TABLE(CAST(udf_split(a.list_a) AS split_table)) b
              INNER JOIN TABLE(CAST(udf_split(pList) AS split_table)) c
                ON b.column_value = c.column_value)
            ELSE 0
          END AS allowed
        FROM my_table a) d
      WHERE d.allowed > 0;
  END;
END upk_menu;

Now you can call the procedure for ColdFusion as such:

<cfstoredproc datasource="#myDSN#" procedure="upk_test.getResults">
   <cfprocparam cfsqltype="cf_sql_varchar" dbvarname="pList" value="#someList#" />
   <cfprocresult name="menu" />
</cfstoredproc>

The above takes in a list like "1,3,7" and compares it to the list column of a table like:

ID SOMETHING
LIST
1
do
2
2
go
3,6,8
3
say
7

Since rows 2 and 3 have a match to the input list, the package will return the following result set:

ID SOMETHING
LIST
2
go
3,6,8
3
say
7

What do you think? Is this a long way of going about what I'm trying to do? I would imagine that a good REGEXP would do the trick, but I'm not that good with REGEXP.

Anyone?

tags:
oracle
Bob Weston said:
 
Hey Rob!
I was able to get your problem figured out by making all the values in the list the same length by padding with zeros. Since my numbers table had values from 1-1000, I made them all 4 character strings. If your role/menu ID's are only 2 characters, you can change to two.

You could something like the following to convert the values in your table to a 4 char string:

UPDATE YOURTABLE
SET roleID = RIGHT('0000' + CONVERT(VARCHAR(4), roleID), 4)

Here's what my test table looked like:

id   something   list
1   do   0002
2   go   0003,0006,0008
3   say   0007
4   touch   0083,0093

Then, the query was simply:

SELECT    f.*
FROM dbo.tbl_numbers N, dbo.tbl_fro F
WHERE SUBSTRING(f.list, n.number, 4) in ('0001','0003','0009','0083')

Since the values in your condition would vary based on user input, I had to use a bit of dynamic SQL:

-- Setup Variables
DECLARE @INPUTSTRING VARCHAR(100)
DECLARE @TSQL VARCHAR(1000)

-- Quote each list value
SET    @INPUTSTRING = '0001,0003,0009,0083'
SET   @INPUTSTRING = '''' + REPLACE(@INPUTSTRING, ',', ''',''') + ''''

-- Build SQL Statement
SET @TSQL = 'SELECT    f.* FROM dbo.tbl_numbers N, dbo.tbl_fro F WHERE SUBSTRING(f.list, N.number, 4) in (' + @INPUTSTRING + ')'

-- Execute SQL
EXEC (@TSQL)
      
That returned:

id   something   list
2   go   0003,0006,0008
4   touch   0083,0093
      
Not sure how transferrable this is to Oracle, but hope it helps!
Thanks,

Bob Weston
 
posted 1121 days ago
Add Comment Reply to: this comment OR this thread
 
fro said:
 
Thanks Bob. I'll test this out tomorrow.
 
posted 1121 days ago
Add Comment Reply to: this comment OR this thread
 

Search