How to unescape % in LIKE clause - sql

I have my search patterns stored in database in patterns table. For example my table column name_pattern contains string 'Basic%'. I'd like to create dynamic search where search patterns will be fetched from name_pattern column.
So my SQL query should look something like:
SELECT *
FROM products
WHERE product_name LIKE name_pattern <-- somehow joined from patterns table
Seems that Oracle escapes % in my string but I want to take it unescapped in order my query to work like:
SELECT *
FROM products
WHERE product_name LIKE 'Basic%'
I found that my problem is with stable set of rows:
CREATE TABLE patterns(code CHAR(1),name_pattern VARCHAR2(20));
INSERT INTO patterns(code,name_pattern) VALUES('B','Basic%');
INSERT INTO patterns(code,name_pattern) VALUES('T','%thing');
CREATE TABLE products (id NUMBER,name VARCHAR2(20),code CHAR(1));
INSERT INTO products(id,name,found) VALUES(1,'Basic instinct',NULL);
INSERT INTO products(id,name,found) VALUES(2,'Basic thing',NULL);
INSERT INTO products(id,name,found) VALUES(3,'Super thing',NULL);
INSERT INTO products(id,name,found) VALUES(4,'Hyper instinct',NULL);
MERGE INTO products p USING
(
SELECT code,name_pattern FROM patterns
) s
ON (p.name LIKE s.name_pattern)
WHEN MATCHED THEN UPDATE SET p.code=s.code;
SELECT * FROM products;
If my search patterns were Basic% and Super% in patterns table then this MERGE will work, but if my search patterns are Basic% and %thing, the second product should be marked with both codes 'B' and 'T' and that causes the error:
ORA-30926: unable to get a stable set of rows in the source tables
So my problem is not in (un)escaping :-(, sorry

You don't have to (un)escape anything, I'd say.
SQL> with
2 patterns (name_pattern) as
3 (select 'Basic%' from dual union all
4 select '%foot%' from dual
5 ),
6 products (id, name) as
7 (select 1, 'Basic instinct' from dual union all
8 select 2, 'Visual Basic' from dual union all
9 select 3, 'Littlefoot' from dual union all
10 select 4, 'Happy feet' from dual
11 )
12 select b.id, b.name, a.name_pattern
13 from products b join patterns a on b.name like a.name_pattern;
ID NAME NAME_P
---------- -------------- ------
1 Basic instinct Basic%
3 Littlefoot %foot%
SQL>
Based on test case you provided: don't merge, update!
SQL> update products p set
2 p.found = 1
3 where exists (select null
4 from patterns o
5 where p.name like o.name_pattern
6 );
3 rows updated.
SQL> select * from products;
ID NAME FOUND
---------- -------------------- ----------
1 Basic instinct 1
2 Basic thing 1
3 Super thing 1
4 Hyper instinct 0
SQL>
After you changed your mind (again), it is still update. Though, you didn't explain which code you want to take when there's multiple match (for example, product 2 matches both "Basic%" and "%thing") so I took any of them, using the min function.
SQL> update products p set
2 p.code = (select min(o.code)
3 from patterns o
4 where p.name like o.name_pattern
5 );
4 rows updated.
SQL> select * from products;
ID NAME CODE
---------- -------------------- ----------
1 Basic instinct B
2 Basic thing B
3 Super thing T
4 Hyper instinct NULL
SQL>

Related

INSERT A Record when clicking a button depending on a if statement

hello everyone i want to make a save button in my oracle form ,and that form have many symptoms checkboxes that records a 1 when checked and I want a way to count those ones into a column called count_symptoms then if the number is larger than 3 the value for the result column would be like "positive" if not it would be "negative" and yes its a covid 19 project :)
, finally it will commit_form;
the table is like :
[ ID NAME FEVER COUGH HEADACHE.....NO_OF_SYMPTOMS RESULT
123. SCOTT 1. 1. 1. 3. POSITIVE ]
I tried this create table statement before but it didn't work so now I think if I did it with a button pl statement it might work
create table covid(ID NUMBER(7) , NAME VARCHAR2(32) ,DEPT VARCHAR2(16) , FEVER NUMBER(1) , COUGH NUMBER(1) ,
TIREDNESS NUMBER(1) , SHORT_BREATH NUMBER(1) ,SORE_THROAT NUMBER(1) ,CHEST_PAIN NUMBER(1) ,
LOSE_SENSES NUMBER(1),RUNNY_NOSE NUMBER(1), TEST_DATE DATE ,
SYMPTOMS NUMBER(16) ,
(CASE
WHEN SYMPTOMS >= 3 THEN 'POSITIVE' ELSE 'NEGATIVE'
END AS RESULT));
/
THANKS IN ADVANCE!
First of all, from my point of view, data model is wrong. There should be at least 3 tables to handle that: patients, symptoms, and [patient x symptom] which contains data about "active" symptoms for each patient. Something like this:
SQL> create table patient
2 (id_pat number primary key,
3 name varchar2(20) not null
4 );
Table created.
SQL> create table symptom
2 (id_sym number primary key,
3 name varchar2(20) not null
4 );
Table created.
SQL> create table patxsym
2 (id_pat number constraint fk_pxs_pat references patient,
3 id_sym number constraint fk_pxs_sym references symptom,
4 --
5 constraint pk_pxs primary key (id_pat, id_sym)
6 );
Table created.
Sample data:
SQL> insert into patient (id_pat, name)
2 select 1, 'Little' from dual union all
3 select 2, 'Foot' from dual;
2 rows created.
SQL> insert into symptom (id_sym, name)
2 select 1, 'fever' from dual union all
3 select 2, 'cough' from dual union all
4 select 3, 'headache' from dual union all
5 select 4, 'short breath' from dual union all
6 select 5, 'sore throat' from dual;
5 rows created.
Symptoms per patients: in Forms, patient would be a master block, while patxsym would be its detail block (tabular layout) so you'd add as many symptoms as necessary.
This option scales well. If new symptoms appear, you'd just add them into the symptom table and the form works with the newly added symptom.
Your option scales as a goat you'd like to teach how to fly, i.e. it doesn't scale at all. For any new symptom, you have
alter table and add a new column (for a new symptom)
run Forms Builder and edit the form by
adding new checboxes for new symptoms
modifying code that INSERTs INTO (as you have new column(s) and new checkbox(es))
modify any reports you might have written
And that's an endless nightmare. I wouldn't do it, if I were you.
So: sample data for patients who have some symptoms:
SQL> insert into patxsym (id_pat, id_sym)
2 select 1, 1 from dual union all
3 select 1, 4 from dual union all
4 select 1, 5 from dual union all
5 --
6 select 2, 2 from dual;
4 rows created.
Reporting:
Number of symptoms per patient:
SQL> select p.id_pat,
2 p.name patient_name,
3 count(*) no_of_symptoms,
4 case when count(*) >= 3 then 'positive'
5 else 'negative'
6 end result
7 from patient p join patxsym x on p.id_pat = x.id_pat
8 group by p.id_pat, p.name;
ID_PAT PATIENT_NAME NO_OF_SYMPTOMS RESULT
---------- -------------------- -------------- --------
1 Little 3 positive
2 Foot 1 negative
SQL>
Symptoms per patient:
SQL> select p.name patient_name,
2 s.name symptom,
3 case when x.id_sym is not null then 'Yes'
4 else 'No'
5 end patient_has_symptom
6 from patient p cross join symptom s
7 left join patxsym x on x.id_pat = p.id_pat
8 and x.id_sym = s.id_sym
9 order by p.name, s.name;
PATIENT_NAME SYMPTOM PAT
-------------------- -------------------- ---
Foot cough Yes
Foot fever No
Foot headache No
Foot short breath No
Foot sore throat No
Little cough No
Little fever Yes
Little headache No
Little short breath Yes
Little sore throat Yes
10 rows selected.
SQL>

Using Insert/Update with 2 Tables - Oracle Apex

I'm still new to SQL and am having a hard time figuring out how to update and insert from one table into another table in Oracle Apex (uses SQL).
The two tables are named Temp and Table (example) and both have the same columns. Basically they are copies of each other but with different data. I would like to compare the ID field in Temp to the ID field in Table and if there is a row that matches that ID field in Temp, then to overwrite all the data on the row in Table with the data in the corresponding row in Temp.
Note: Table has 10 million rows of data, Temp has like 500.
if Temp.ID = Table.ID then
update
set
Table.ID = Temp.ID
Table.Address = Temp.Address
else
insert
(Table.ID,
Table.Address)
values
(Temp.ID,
Temp.Address
That is basically what I want done but not sure how to write it in SQL. Have seen a lot of different answers, but none really involving 2 tables and mostly for MySQL or SQL Server specific SQL that I am not sure also works with Oracle.
MERGE into TEST1
USING TEST2 on (TEST2.ID = TEST1.ID)
WHEN matched THEN UPDATE
SET TEST1.ID = TEST2.ID, TEST1.NAME = TEST2.NAME, TEST1.ADDRESS = TEST2.ADDRESS, TEST1.EMAIL = TEST2.EMAIL
WHEN not matched THEN INSERT (ID, NAME, ADDRESS, EMAIL) values (TEST2.ID, TEST2.NAME, TEST2.ADDRESS, TEST2.EMAIL);
I tried this but it is giving me the error:
ORA-38104: Columns referenced in the ON Clause cannot be updated: "TEST1"."ID"
UPDATE: Got it to work!
http://db-oriented.com/2013/09/20/the-merge-statement/
Was helpful with the answer to the error and I did not know that you could not have the IDs in the update clause, which makes total sense now. Thank you also to the respondent below for explaining the merge code to me. :)
Looks like a good candidate for MERGE. Have a look at the example.
Sample tables & data:
SQL> create table ttable (id number, address varchar2(20));
Table created.
SQL> create table temp (id number, address varchar2(20));
Table created.
SQL> insert into ttable
2 select 1, 'Address 1' from dual union all
3 select 2, 'Address 2' from dual union all
4 select 3, 'Address 3' from dual;
3 rows created.
SQL> insert into temp
2 select 1, 'New address 1' from dual union all
3 select 2, 'New address 2' from dual union all
4 select 4, 'New address 4' from dual;
3 rows created.
Merge & the result:
SQL> merge into ttable a
2 using temp e on (e.id = a.id)
3 when matched then update set a.address = e.address
4 when not matched then insert (id, address) values (e.id, e.address);
3 rows merged.
SQL> select * from ttable;
ID ADDRESS
---------- --------------------
1 New address 1
2 New address 2
3 Address 3
4 New address 4
SQL>

ORACLE: Creating unique values without using sequence while multi-inserts

There is a parent table (MAIN_TABLE) and need to do multi-inserts into a child table(CHILD_TABLE)
for various types (TYPE_1/TYPE_2/TYPE_3 etc).
That is for single parent record I will have three childs if there are three types.
WITH MAIN_TABLE AS
(
SELECT 100 AS ID,'RICK' AS NAME,5 AS LINE FROM DUAL
UNION ALL
SELECT 101 AS ID,'TOM' AS NAME,6 AS LINE FROM DUAL
)
SELECT * FROM MAIN_TABLE;
ID NAME LINE
---------- -------------------- ----------
100 RICK 5
101 TOM 6
2 rows selected.
Need to insert parent record into the child table where for the line column it has to be populated in the increasing order.
INSERT ALL
INTO CHILD_TABLE(ID, NAME, LINE, TYPE)
VALUES (ID, NAME, LINE, 'TYPE_1')
INTO CHILD_TABLE(ID, NAME, LINE, TYPE)
VALUES (ID, NAME, LINE, 'TYPE_2')
INTO CHILD_TABLE(ID, NAME, LINE, TYPE)
VALUES (ID, NAME, LINE, 'TYPE_3')
INTO CHILD_TABLE(ID, NAME, LINE, TYPE)
VALUES (ID, NAME, LINE, 'TYPE_4')
SELECT ID, NAME, LINE
FROM MAIN_TABLE;
SQL> SELECT * FROM CHILD_TABLE
100 RICK 5 TYPE_1
101 TOM 6 TYPE_1
100 RICK 5 TYPE_2
101 TOM 6 TYPE_2
100 RICK 5 TYPE_3
101 TOM 6 TYPE_3
100 RICK 5 TYPE_4
101 TOM 6 TYPE_4
8 rows selected.
Here, instead of 5,6 as the line, I need to have 5,6,7,8,9,10,11,12.
How to go about this?
Note: 1. I cannot create sequence 2. No multiple separate inserts statements if possible.
I certainly hope this is a homework assignment:
INSERT INTO CHILD_TABLE
SELECT m.ID,
m.NAME,
o.MIN_LINE + ROWNUM - 1 AS LINE,
t.TYPE
FROM MAIN_TABLE m
CROSS JOIN (SELECT MIN(LINE) AS MIN_LINE FROM MAIN_TABLE) o
CROSS JOIN (SELECT 'TYPE_1' AS TYPE FROM DUAL UNION ALL
SELECT 'TYPE_2' AS TYPE FROM DUAL UNION ALL
SELECT 'TYPE_3' AS TYPE FROM DUAL UNION ALL
SELECT 'TYPE_4' AS TYPE FROM DUAL) t;
dbfiddle here
I would strongly recommend using an Oracle sequence whenever its possible to. If you still feel the strong need for not using a sequence then try building pseudo-sequence tables and write some PLSQL functions to perform the inserts by choosing the available sequence + 1.
Or you could do something like
creating a variable for your current max value and try to use ROW_NUMBER() OVER (ORDER BY column_name)+#your_var where #your_var= SELECT MAX(column_name) FROM child

How do you find a missing number in a table field starting from a parameter and incrementing sequentially?

Let's say I have an sql server table:
NumberTaken CompanyName
2 Fred 3 Fred 4 Fred 6 Fred 7 Fred 8 Fred 11 Fred
I need an efficient way to pass in a parameter [StartingNumber] and to count from [StartingNumber] sequentially until I find a number that is missing.
For example notice that 1, 5, 9 and 10 are missing from the table.
If I supplied the parameter [StartingNumber] = 1, it would check to see if 1 exists, if it does it would check to see if 2 exists and so on and so forth so 1 would be returned here.
If [StartNumber] = 6 the function would return 9.
In c# pseudo code it would basically be:
int ctr = [StartingNumber]
while([SELECT NumberTaken FROM tblNumbers Where NumberTaken = ctr] != null)
ctr++;
return ctr;
The problem with that code is that is seems really inefficient if there are thousands of numbers in the table. Also, I can write it in c# code or in a stored procedure whichever is more efficient.
Thanks for the help
Fine, if this question isn't going to be closed, I may as well Copy and paste my answer from the other one:
I called my table Blank, and used the following:
declare #StartOffset int = 2
; With Missing as (
select #StartOffset as N where not exists(select * from Blank where ID = #StartOffset)
), Sequence as (
select #StartOffset as N from Blank where ID = #StartOffset
union all
select b.ID from Blank b inner join Sequence s on b.ID = s.N + 1
)
select COALESCE((select N from Missing),(select MAX(N)+1 from Sequence))
You basically have two cases - either your starting value is missing (so the Missing CTE will contain one row), or it's present, so you count forwards using a recursive CTE (Sequence), and take the max from that and add 1
Tables:
create table Blank (
ID int not null,
Name varchar(20) not null
)
insert into Blank(ID,Name)
select 2 ,'Fred' union all
select 3 ,'Fred' union all
select 4 ,'Fred' union all
select 6 ,'Fred' union all
select 7 ,'Fred' union all
select 8 ,'Fred' union all
select 11 ,'Fred'
go
I would create a temp table containing all numbers from StartingNumber to EndNumber and LEFT JOIN to it to receive the list of rows not contained in the temp table.
If NumberTaken is indexed you could do it with a join on the same table:
select T.NumberTaken -1 as MISSING_NUMBER
from myTable T
left outer join myTable T1
on T.NumberTaken= T1.NumberTaken+1
where T1.NumberTaken is null and t.NumberTaken >= STARTING_NUMBER
order by T.NumberTaken
EDIT
Edited to get 1 too
1> select 1+ID as ID from #b as b
where not exists (select 1 from #b where ID = 1+b.ID)
2> go
ID
-----------
5
9
12
Take max(1+ID) and/or add your starting value to the where clause, depending on what you actually want.

How do I select rows in table (A) sharing the same foreign key (itemId) where multiple rows in table have the values in table B

Sorry about the title, not sure how to describe without example. I trying to implement faceting of attributes in SQL Server 2008.
I have 2 tables. itemAttributes and facetParameters
Assume the following values in itemAttributes
id, itemId, name, value
---------------------------------------
1 1 keywords example1
2 1 keywords example2
3 2 color red
4 2 keywords example1
5 2 keywords example2
6 3 keywords example2
7 3 color red
8 3 color blue
Assume the following values in facetParameters
name value
----------------------
keywords example1
color red
I need to retrieve the (optional: distinct) itemIds where a given itemId has rows that contain all the values in facetParameters.
e.g. given the rows in facetParameters the query should return itemId 2. At the moment I would be using this in a CTE however given that they do not support a number of features I can work around this if there is no solution that works inside a CTE.
I have done a fair bit of sql over the years but this one has really stumped me and the shame is I keep thinking the answer must be simple.
You could join both tables, and use a having clause to ensure that all items match:
select ia.itemid
from #itemAttributes ia
inner join #facetParameters fp
on ia.name = fp.name
and ia.value = fp.value
group by ia.itemid
having count(distinct fp.name) =
(
select count(*) from #facetParameters
)
The count in the having clause assumes that the name uniquely identifies a row in the facetParameters table. If it doesn't, add an identity column to facetParameters, and use count(distinct id_column) instead of count(distinct fp.name).
Here's code to create the data set in the question:
declare #itemAttributes table (id int, itemId int,
name varchar(max), value varchar(max))
insert into #itemAttributes
select 1,1,'keywords','example1'
union all select 2,1,'keywords','example2'
union all select 3,2,'color','red'
union all select 4,2,'keywords','example1'
union all select 5,2,'keywords','example2'
union all select 6,3,'keywords','example2'
union all select 7,3,'color','red'
union all select 8,3,'color','blue'
declare #facetParameters table (name varchar(max), value varchar(max))
insert into #facetParameters
select 'keywords','example1'
union all select 'color','red'