Using Regex on a variable in a SQL function - sql

I'm working on a SQL developer function that standardizes street suffixes in a table of addresses. Originally I had a series of replace statements with regex like this:
select replace (ADDRESS, '(^|\s)(AVENUE)($|\s)', ' AVE ') into ADDRESS from dual;
But to try to decrease the length of the function, I changed it to a cursor which looks like this:
DECLARE
CURSOR CHANGE_SUFFIX
IS
SELECT *
FROM SUFFIX_LIST;
BEGIN
FOR SUFFIX_LIST
IN CHANGE_SUFFIX
LOOP
select regexp_replace (ADDRESS, SUFFIX_LIST.ORIG_SUFFIX, SUFFIX_LIST.CHANGED_SUFFIX) into ADDRESS from dual;
END LOOP;
END;
Where I have a table (SUFFIX_LIST) with a column of what should be changed (ORIG_SUFFIX) and what it's supposed to change into (CHANGED_SUFFIX).
The issue I'm having is that I can't find a way to apply the original regex to the replace statement in the cursor. I tried including it like
select regexp_replace (ADDRESS, (^|\s)SUFFIX_LIST.ORIG_SUFFIX($|\s), SUFFIX_LIST.CHANGED_SUFFIX) into ADDRESS from dual
But that gives an error (missing expression) and putting it in single quotes treats it like a string. Any help appreciated, please lmk if there are any questions.

You need the anchors to be strings, but then you need to concatenate them with the table values:
select regexp_replace (ADDRESS, '(^|\s)' || SUFFIX_LIST.ORIG_SUFFIX || '($|\s)',
SUFFIX_LIST.CHANGED_SUFFIX) into ADDRESS from dual;
Or you could do that concatenation in the cursor query. As ADDRESS is a PL/SQL variable you also don't need to select from dual to modify that; you can just assign it:
address := regexp_replace (ADDRESS, '(^|\s)' || SUFFIX_LIST.ORIG_SUFFIX || '($|\s)',
SUFFIX_LIST.CHANGED_SUFFIX);
You can do the same concatenation to add the spaces back around the CHANGED_SUFFIX values if the table values don't have those. (Presumably you trim them off at the end...)

Related

convert varchar value to array to varchar sql oracle [duplicate]

I am having trouble getting a block of pl/sql code to work. In the top of my procedure I get some data from my oracle apex application on what checkboxes are checked. Because the report that contains the checkboxes is generated dynamically I have to loop through the
APEX_APPLICATION.G_F01
list and generate a comma separated string which looks like this
v_list VARCHAR2(255) := (1,3,5,9,10);
I want to then query on that list later and place the v_list on an IN clause like so
SELECT * FROM users
WHERE user_id IN (v_list);
This of course throws an error. My question is what can I convert the v_list to in order to be able to insert it into a IN clause in a query within a pl/sql procedure?
If users is small and user_id doesn't contain commas, you could use:
SELECT * FROM users WHERE ',' || v_list || ',' LIKE '%,'||user_id||',%'
This query is not optimal though because it can't use indexes on user_id.
I advise you to use a pipelined function that returns a table of NUMBER that you can query directly. For example:
CREATE TYPE tab_number IS TABLE OF NUMBER;
/
CREATE OR REPLACE FUNCTION string_to_table_num(p VARCHAR2)
RETURN tab_number
PIPELINED IS
BEGIN
FOR cc IN (SELECT rtrim(regexp_substr(str, '[^,]*,', 1, level), ',') res
FROM (SELECT p || ',' str FROM dual)
CONNECT BY level <= length(str)
- length(replace(str, ',', ''))) LOOP
PIPE ROW(cc.res);
END LOOP;
END;
/
You would then be able to build queries such as:
SELECT *
FROM users
WHERE user_id IN (SELECT *
FROM TABLE(string_to_table_num('1,2,3,4,5'));
You can use XMLTABLE as follows
SELECT * FROM users
WHERE user_id IN (SELECT to_number(column_value) FROM XMLTABLE(v_list));
I have tried to find a solution for that too but never succeeded. You can build the query as a string and then run EXECUTE IMMEDIATE, see http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/dynamic.htm#i14500.
That said, it just occurred to me that the argument of an IN clause can be a sub-select:
SELECT * FROM users
WHERE user_id IN (SELECT something FROM somewhere)
so, is it possible to expose the checkbox values as a stored function? Then you might be able to do something like
SELECT * FROM users
WHERE user_id IN (SELECT my_package.checkbox_func FROM dual)
Personally, i like this approach:
with t as (select 'a,b,c,d,e' str from dual)
--
select val
from t, xmltable('/root/e/text()'
passing xmltype('<root><e>' || replace(t.str,',','</e><e>')|| '</e></root>')
columns val varchar2(10) path '/'
)
Which can be found among other examples in Thread: Split Comma Delimited String Oracle
If you feel like swamping in even more options, visit the OTN plsql forums.

LIKE on multiple values, all must match

I'm querying a legacy Oracle db to match a name using LIKE. It would be good if a multi-word pattern (could be 1 word, 3 words, 8 words, whatever) could be matched against names independently of word order. Eg, if the user searched for "rob jon", it would match
ROBERT JONES
JONATHON ADAM PROBE
but not match
ROB SMITH
PETER JONES
If the requirement was "at least one word should match" I could use REGEXP_LIKE(name, 'rob|jon", 'i').
But I can't work out how to make it match only when ALL words are found somewhere in the name. I could do this using Oracle Text, but I can't use that in this db. I also can't easily store the words in a table to join to either.
The query is currently static and uses bind variables, and so I would prefer not to add an extra LIKE predicate for each word in the search pattern.
Any ideas?
According to your question and the data presented in the post, I Used this table
create table name_pattern
(
name_1 varchar(200)
);
insert statements :
insert into name_pattern
select ( 'ROBERT JONES') from dual union all
select ('JONATHON ADAM PROBE') from dual union all
select ('ROB SMITH') from dual union all
select ('PETER JONES') from dual;
commit;
try this if it works for you if you are searching "rob" and "jon" separately.
select name from table_name where lower(name) like '%rob%' and lower(name) like '%jon%' ;
try this if you want to search "rob jon" combined and to get your expected result
select * from name_pattern where REGEXP_COUNT(name_1, 'rob|jon',1, 'i') =2;
You want your query to accept one bind variable containing a blank-separated list of name parts, e.g. 'rob jon'.
You can write a recursive query to get the single name parts from that string. Then use NOT EXISTS to only keep names for which not exists any mismatch.
with parameters as
(
select :namepartlist as namepartlist from dual
)
, nameparts (namepart, namepartlist, occurrence) as
(
select regexp_substr(namepartlist, '[^ ]+', 1, 1), namepartlist, 1
from parameters
union all
select regexp_substr(namepartlist, '[^ ]+', 1, occurrence + 1), namepartlist, occurrence + 1
from nameparts
where regexp_substr(namepartlist, '[^ ]+', 1, occurrence + 1) is not null
)
select *
from mytable
where not exists
(
select null
from nameparts
where lower(mytable.name) not like '%' || lower(nameparts.namepart) || '%'
)
order by name;
You can replace
where lower(mytable.name) not like '%' || lower(nameparts.namepart) || '%'
by
where not regexp_like(mytable.name, nameparts.namepart, 'i')
if you like that better.
Demo: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=5e48caaa20e9397afe65516504c62acd
if you prefer not to add an extra LIKE predicate for each word in the search pattern (the best solution IMHO) you can create a PL/SQL function to do the matching
FUNCTION PATTERN_MATCHES(pattern in varchar2, name in varchar2) returns number
is
Begin *pseudocode*
*convert pattern to lowercase*
*split pattern by spaces*
*for each part, Loop*
if NOT lower(name) like %part% then
return 0; --early exit
end;
End Loop;
return 1; --all parts have a match
end;
select * from employee where PATTERN_MATCHES(:pattern, name)=1
Then you can have a static parameterized query.
On the bright side, you'll be able to extend the behavior (add advanced search for example) without touching the front-end app.
PS: The function code is intentionally left for you to write. Ask if you need help with that.
PS2: returning "number" and not a boolean will help you with legacy database drivers. Feel free to use integer or a boolean if that works for you.
Shamelessy building on #Jfrd's solution ... is this any good ? (Performance will be shocking on large tables).
Basically, replace all spaces with '%' (not catered here for successive spaces).
var search_text varchar2(30)
exec :search_text := 'Rob Jon';
select name
from table_name
where lower(name) like REPLACE('%' || :search_text || '%', ' ', '%' ) ;

Can we use LIKE operator along with MEMBER OF operator in a stored procedure?

I have an array of data using which I select rows from a table. For that I use member of operator in where clause. I want to know if we can do that same but by using Like operator along with member of operator.
When my Array consists of{Delhi, Mumbai, Kolkata}
I select the rows which have these three values in their row.
This is how I do that:
select ...
Into...
From xyz where city member of array;
///Receiving the array from an in parameter of the stored procedure.
And it works perfectly fine.
But If my array has {Del, Mum, Kolk} //parts of the actual names
How do I use this array for the same purpose, maybe using Like operator.
Create or replace zz2(ar in array_collection, c out sys_refcursor)
Is
anotherabc tablename.city%type
Begin
Open c
For
Select ABC
Into anotherabc
From tablename where city member of ar;
End zz2;
I expect the output to have all the rows which have cities starting with the alphabet/characters present in the array. Using member of operator
Something like this?
Select ABC
Into anotherabc a
From tablename WHERE EXISTS
( select 1 FROM ( select column_value as city
FROM TABLE(ar) ) s where a.city like s.city||'%' )
There is no direct way to use LIKE with MEMBER OF.
If It is the protocol that your collection contains the first three characters of the city name then you can use substr() to match only the first three characters in MEMBER OF.
try the following thing:
DECLARE
TYPE t_tab IS TABLE OF varchar(3);
l_tab1 t_tab := t_tab('Del','Mom','Kol');
BEGIN
DBMS_OUTPUT.put('Is ''Delhi'' MEMBER OF l_tab1? ');
IF SUBSTR('Delhi',1,3) MEMBER OF l_tab1 THEN -- note the use of SUBSTR here
DBMS_OUTPUT.put_line('TRUE');
ELSE
DBMS_OUTPUT.put_line('FALSE');
END IF;
END;
/
db<>fiddle demo
Cheers!!

INSERT with dynamic column names

I have column names stored in variable colls, next I execute code:
DO $$
DECLARE
v_name text := quote_ident('colls');
BEGIN
EXECUTE 'insert into table1 select '|| colls ||' from table2 ';
-- EXECUTE 'insert into table1 select '|| v_name ||' from table2 ';
END$$;
I have got error: column "colls" does not exist. Program used colls as name not as variable. What am I doing wrong?
I have found similar example in documentation:
https://www.postgresql.org/docs/8.1/static/plpgsql-statements.html#PLPGSQL-STATEMENTS-EXECUTING-DYN
I have column names stored in variable colls
No, you don't. You have a variable v_name - which holds a single word: 'colls'. About variables in SQL:
User defined variables in PostgreSQL
Read the chapters Identifiers and Key Words and Constants in the manual.
And if you had multiple column names in a single variable, you could not use quote_ident() like that. It would escape the whole string as a single identifier.
I guess the basic misunderstanding is this: 'colls' is a string constant, not a variable. There are no other variables in a DO statement than the ones you declare in the DECLARE section. You might be looking for a function that takes a variable number of column names as parameter(s) ...
CREATE OR REPLACE FUNCTION f_insert_these_columns(VARIADIC _cols text[])
RETURNS void AS
$func$
BEGIN
EXECUTE (
SELECT 'INSERT INTO table1 SELECT '
|| string_agg(quote_ident(col), ', ')
|| ' FROM table2'
FROM unnest(_cols) col
);
END
$func$ LANGUAGE plpgsql;
Call:
SELECT f_insert_these_columns('abd', 'NeW Deal'); -- column names case sensitive!
SELECT f_insert_these_columns(VARIADIC '{abd, NeW Deal}'); -- column names case sensitive!
Note how I unnest the array of column names and escape them one by one.
A VARIADIC parameter should be perfect for your use case. You can either pass a list of column names or an array.
Either way, be vary of SQL injection.
Related, with more explanation:
Pass multiple values in single parameter
Table name as a PostgreSQL function parameter

Table variable in PostgreSQL without execute command

I would like to use variable for table name in my sql script below. Is possible to do that in different way than i show you below ? I mean without EXECUTE command? My script select data from one table and move to the another one. I want to avoid escaping quotes in my scripts.
DO $proc$
DECLARE
v_table_name VARCHAR(100) := 'selected_customers';
BEGIN
EXECUTE 'INSERT INTO ' || v_table_name || '(name, surname, address)
SELECT name, surname, address FROM customers
WHERE name ILIKE ''mon%''';
END;
$proc$;
SQL strictly segregates code and data. To convert variables into code, you need dynamic SQL. The only other way would be to use at least two round trips to the server and concatenate code in your client, but there is really no point in doing that.
Whenever you turn data into code, be wary of possible SQL injection.
"To avoid escaping quotes", there are a number of possibilities. format() (Postgres 9.1+) can take care of identifiers and literals for you.
And use dollar-quoting to make your life easier - just like you do already for the body of the DO statement.
Your example:
DO
$proc$
DECLARE
v_table_name text := 'selected_customers';
BEGIN
EXECUTE format($$
INSERT INTO %I
(name, surname, address)
SELECT name, surname, address
FROM customers
WHERE name ILIKE 'mon%'$$
,v_table_name text);
END
$proc$;
There are more options:
Define table and column names as arguments in a plpgsql function?