Update Query For ORACLE SQL DEVELOPER - sql

I have four columns
id
fisrt_name
last_name
city
101
A
B
C
303
A
B
C
207
A
B
C
55
X
Y
Z
67
X
Y
Z
200
X
Y
Z
Basically FN, LN and city are same but the ids are different for these same values. I want to UPDATE the Ids of the duplicate rows as same, shown in table below using oracle sql developer. The id can be update to either of the three but it should become same for all the three duplicate rows.
id
fisrt_name
last_name
city
101
A
B
C
101
A
B
C
101
A
B
C
55
X
Y
Z
55
X
Y
Z
55
X
Y
Z
I tried this
UPDATE TABLE T1 SET
T1.ID = ID
WHERE ROWID IN (SELECT ROWID FROM(
SELECT ROWID,ROW_NUMBER() OVER(PARTITION BY ID, CITY ORDER BY ADR_LINE_1) AS RN, ID, CITY
FROM TABLE
WHERE (first_name,last_name,city) IN (first_name,last_name,city
from table
group by first_name,last_name,city
having count(distinct id) >= 2)) WHERE RN = 1);
It updates the desired rows but with the same ids not with what I want.

Here's one way - join your table to the result of an aggregate query and update (group by fn, ln, city, filter out the groups with a single id, and select min(id) in the remaining groups, then use that to update).
Set up the test case:
create table my_table (id, first_name, last_name, city) as
select 101, 'A', 'B', 'C' from dual union all
select 303, 'A', 'B', 'C' from dual union all
select 207, 'A', 'B', 'C' from dual union all
select 55, 'X', 'Y', 'Z' from dual union all
select 67, 'X', 'Y', 'Z' from dual union all
select 200, 'X', 'Y', 'Z' from dual union all
select 333, 'D', 'F', 'G' from dual
;
Table MY_TABLE created.
Update:
update
( select t.id, g.min_id
from my_table t
inner join
( select min(id) as min_id, first_name, last_name, city
from my_table
group by first_name, last_name, city
having min(id) != max(id)
) g
using (first_name, last_name, city)
)
set id = min_id
where id != min_id
;
4 rows updated.
Check the result:
select * from my_table;
ID FIRST_NAME LAST_NAME CITY
---------- ---------- ---------- ----------
101 A B C
101 A B C
101 A B C
55 X Y Z
55 X Y Z
55 X Y Z
333 D F G
NOTE: If ID may be null, that will require a bit of additional handling (but, the column should be NOT NULL.... is it?)

One option would be using MIN() Analytic Function with grouping by repeating three columns (first_name, last_name, city) through a MERGE Statement such as
MERGE INTO tab t1
USING ( SELECT MIN(id) OVER (PARTITION BY first_name, last_name, city) AS new_id
FROM tab t ) t2
ON ( t1.rowid = t2.rowid )
WHEN MATCHED THEN UPDATE SET t1.id = t2.new_id
Demo

I would go for a correlated subquery:
update t1
set id = (
select min(id)
from mytable t1
where t1.first_name = t.first_name and t1.lastname = t.lastname and t1.city = t.city
)
where id > (
select min(id)
from mytable t1
where t1.first_name = t.first_name and t1.lastname = t.lastname and t1.city = t.city
)
This query would take advantage of an index on (firstname, lastname, city, id) - although updating the rows will require updating the index too...

Related

Find exactly equal rows in 2 tables, both in terms of value and number

I have two Table, that both of them have 2 field (provinceid,cityid)
i want to find provinceid that have exactly the same cityid in this two table.
for example i have this tables:
table1:
provinceid
cityid
1
1
1
2
2
3
2
4
3
6
table2:
provinceid
cityid
1
1
1
5
2
3
2
4
3
6
3
7
i want a query that just return provinceid =2 and city id =3 and 4.
i try this query and it is right. but i want a better query:
select provinceid ,t1.cityid
from t1
left join t2 on t1=provinceid=t2.provinceid and t1.cityid=t2.cityid
where t2.provinceid is not null and t2.cityid is not null
and t1.provinceid not in (select provinceid
from t2
left join t1 on t1=provinceid=t2.provinceid and t1.cityid=t2.cityid
where t1.provinceid is not null and t1.cityid is not null)
thank you
Try this :
select t1.provinceid ,t1.cityid
from table1 t1 join table2 t2
on t1.provinceid=t2.provinceid
and t1.cityid=t2.cityid
and t1.provinceid in (
select distinct(t1.provinceid)
from
(select provinceid, count(provinceid) as cnt from table1 group by provinceid) as t1
cross join
(select provinceid ,count(provinceid) as cnt from table2 group by provinceid) as t2
where t1.cnt = t2.cnt);
Output:
provinceid
cityid
1
1
2
3
2
4
The simplest method for an exact match is to use string aggregation. The exact syntax varies by database, but in Standard SQL this looks like:
select t1.provinceid, t2.provinceid
from (select provinceid,
listagg(cityid, ',') within group (order by cityid) as cities
from t1
group by provinceid
) t1 join
(select provinceid,
listagg(cityid, ',') within group (order by cityid) as cities
from t2
group by provinceid
) t2
on t1.cities = t2.cities;
If you want the provinceids to be the same as well, just add t1.provinceid = t2.provinceid to the on clause.
Or, if you want the provinceids to be the same, you can use full join instead:
select provinceid
from t1 full join
t2
using (provinceid, cityid)
group by provinceid
having count(*) = count(t1.cityid) and count(*) = count(t2.cityid);
Besides match in provid and cityid, we are looking for exactly matching sets of records as well. There might be many different methods to this. I prefer to have string comparison for list of cities for each provide with addition to provide and cityid match clause to remove other sets of provide and cityid which are available in tables but not the exact row match.
WITH table1 AS(
SELECT 1 AS PROVID, 1 AS CITYID FROM DUAL UNION ALL
SELECT 1 AS PROVID, 2 AS CITYID FROM DUAL UNION ALL
SELECT 2 AS PROVID, 3 AS CITYID FROM DUAL UNION ALL
SELECT 2 AS PROVID, 4 AS CITYID FROM DUAL UNION ALL
SELECT 3 AS PROVID, 6 AS CITYID FROM DUAL
),
table2 AS (
SELECT 1 AS PROVID, 1 AS CITYID FROM DUAL UNION ALL
SELECT 1 AS PROVID, 5 AS CITYID FROM DUAL UNION ALL
SELECT 2 AS PROVID, 3 AS CITYID FROM DUAL UNION ALL
SELECT 2 AS PROVID, 4 AS CITYID FROM DUAL UNION ALL
SELECT 3 AS PROVID, 6 AS CITYID FROM DUAL UNION ALL
SELECT 3 AS PROVID, 7 AS CITYID FROM DUAL
),
listed_table1 AS (
SELECT
a.provid,
listagg(cityid,',') within GROUP (ORDER BY cityid) list_city
FROM table1 a
GROUP BY a.provid
),
listed_table2 AS (
SELECT
a.provid,
listagg(cityid,',') within GROUP (ORDER BY cityid) list_city
FROM table2 a
GROUP BY a.provid
)
SELECT
t1.provid, t1.cityid
FROM
(SELECT x.*, x1.list_city FROM table1 x, listed_table1 x1 WHERE x.provid = x1.provid) t1,
(SELECT y.*, y1.list_city FROM table2 y, listed_table2 y1 WHERE y.provid = y1.provid) t2
WHERE t1.provid = t2.provid AND t1.cityid = t2.cityid AND t1.list_city = t2.list_city
;
You can use (union ..)except (inner join..) to detect non-matches. Step by step
with u12 as (
select PROVID, CITYID from table1
union
select PROVID, CITYID from table2
),
c12 as (
select t1.PROVID, t2.CITYID
from table1 t1
join table2 t2 on t1.PROVID=t2.PROVID and t1.CITYID=t2.CITYID
),
nonMatch as (
select distinct PROVID
from (
select PROVID, CITYID from u12
except
select PROVID, CITYID from c12
) t
)
select *
from table1 t
where not exists (
select 1
from nonMatch n
where n.PROVID = t.PROVID);
If a number of doubles counts then count them first
with t1 as (
select PROVID, CITYID, count(*) n
from table1
group by PROVID, CITYID
),
t2 as (
select PROVID, CITYID, count(*) n
from table2
group by PROVID, CITYID
),
u12 as (
select PROVID, CITYID, n from t1
union
select PROVID, CITYID, n from t2
),
c12 as (
select t1.PROVID, t1.CITYID, t1.n
from t1
join t2 on t1.PROVID = t2.PROVID and t1.CITYID = t2.CITYID and t1.n = t2.n
),
nonMatch as (
select distinct PROVID
from (
select PROVID, CITYID, n from u12
except
select PROVID, CITYID, n from c12
) t
)
select *
from table1 t
where not exists (
select 1
from nonMatch n
where n.PROVID = t.PROVID)
db<>fiddle

How to insert values which are not present in the table?

I have a table with rows which are look like this:
| ID | NAME | LOCALE |
| x | name | en |
| x | name | ru |
| y | name1| en |
| y | name1| ru |
And so on. But some rows are present in just one locale. I need to insert missing rows, so for every ID and NAME there is 2 rows for 2 locales.
Assuming that each name would only ever have two locales present, then here is a straightforward option:
INSERT INTO yourTable (ID, NAME, LOCALE)
SELECT
ID,
NAME,
CASE WHEN LOCALE = 'en' THEN 'ru' ELSE 'en' END
FROM
(
SELECT ID, NAME, MAX(LOCALE) AS LOCALE
FROM yourTable
GROUP BY ID, NAME
HAVING COUNT(*) = 1
) t;
If you actually have more than two locales, then I think we would have to assume that there is some table containing all locales. The query for that case would be more complicated than what I wrote above.
If I understand well, you may need something like the following:
test case:
create table someTable(ID, NAME, LOCALE) as (
select 'x', 'name' ,'en' from dual union all
select 'x', 'name' ,'ru' from dual union all
select 'y', 'name1' ,'en' from dual union all
select 'y', 'name1' ,'ru' from dual union all
select 'z', 'ZZZZ' ,'ru' from dual
)
add missing rows:
merge into someTable s
using(
select *
from
(select 'en' LOCALE from dual union
select 'ru' LOCALE from dual
)
cross join
( select distinct ID, name from someTable)
) x
on (x.id = s.id and x.name = s.name and s.locale = x.locale)
when not matched then
insert values (x.id, x.name, x.locale)
The result:
ID NAME LOCALE
-- ----- ------
x name en
x name ru
y name1 en
y name1 ru
z ZZZZ ru
z ZZZZ en
Add missing name entries:
INSERT INTO <YourTable>
(ID, NAME, LOCALE)
(
SELECT t1. ID, 'name', t1.LOCALE
FROM <YourTable> t1
WHERE NOT EXISTS
(
SELECT t2.LOCALE
FROM <YourTable> t2
WHERE t2.NAME = 'name' AND t1.LOCALE = t2.LOCALE
)
)
Add missing name1 entries:
INSERT INTO <YourTable>
(ID, NAME, LOCALE)
(
SELECT t1.ID, 'name1', t1.LOCALE
FROM <YourTable> t1
WHERE NOT EXISTS
(
SELECT t2.LOCALE
FROM <YourTable> t2
WHERE t2.NAME = 'name1' AND t1.LOCALE = t2.LOCALE
)
)
If the strings name and name1 don't play a role, and you just need a second row for any locale whioch exist only once you can use:
INSERT INTO <YourTable>
(ID, NAME, LOCALE)
(
SELECT t1.ID, 'Placeholder for locale', t1.LOCALE
FROM <YourTable> t1
WHERE
(
SELECT COUNT(*)
FROM <YourTable> t2
WHERE t1.LOCALE = t2.LOCALE
) = 1
)

ORACLE get rows with condition value equals something but not equals to anything else

I have rows that look like .
OrderNo OrderStatus SomeOtherColumn
A 1
A 1
A 3
B 1 X
B 1 Y
C 2
C 3
D 2
I want to return all orders that have only one possible value of orderstatus. For e.g Here order B has only order status 1 SO result should be
B 1 X
B 1 Y
Notes:
Rows can be duplicated with same order status. For e.g. B here.
I am interested in the order having a very peculiar status for e.g. 1 here and not having any other status. So if B had a status of 3 at any point of time it is disqualified.
You can use not exists:
select t.*
from t
where not exists (select 1
from t t2
where t.orderno = t2.orderno and t.OrderStatus = t2.OrderStatus
);
If you just want the orders where this is true, you can use group by and having:
select orderno
from t
group by orderno
having min(OrderStatus) = max(OrderStatus);
If you only want a status of 1 then add max(OrderStatus) = 1 to the having clause.
Here is one way to do it. It does not handle the case where the status can be NULL; if that is possible, you will need to explain how you want it handled.
SQL> create table test_data ( orderno, status, othercol ) as (
2 select 'A', 1, null from dual union all
3 select 'A', 1, null from dual union all
4 select 'A', 3, null from dual union all
5 select 'B', 1, 'X' from dual union all
6 select 'B', 1, 'Y' from dual union all
7 select 'C', 2, null from dual union all
8 select 'C', 3, null from dual union all
9 select 'D', 2, null from dual
10 );
Table created.
SQL> variable input_status number
SQL> exec :input_status := 1
PL/SQL procedure successfully completed.
SQL> column orderno format a8
SQL> column othercol format a8
SQL> select orderno, status, othercol
2 from (
3 select t.*, count(distinct status) over (partition by orderno) as cnt
4 from test_data t
5 )
6 where status = :input_status
7 and cnt = 1
8 ;
ORDERNO STATUS OTHERCOL
-------- ---------- --------
B 1 X
B 1 Y
One way to handle NULL status (if that may happen), if in that case the orderno should be rejected (not included in the output), is to define the cnt differently:
count(case when status != :input_status or status is null then 1 end)
over (partition by orderno) as cnt
and in the outer query change the WHERE clause to a single condition,
where cnt = 0
Count distinct OrderStatus partitioned by OrderNo and show only rows where number equals one:
select OrderNo, OrderStatus, SomeOtherColumn
from ( select t.*, count(distinct orderstatus) over (partition by orderno) cnt
from t )
where cnt = 1
SQLFiddle demo
Just wanted to add something to Gordon's answer, using a stats function:
select orderno
from t
group by orderno
having variance(orderstatus) = 0;

ORACLE SQL JOINS

I have the two tables:
TABLE1:
id name values
1 john AB
2 marry CD
3 sreya YG
TABLE2:
pid country values
45 india JKABHJ
46 usa YURRRCD
47 uk YGHJJKLJL
output
name values country
john AB india
marry CD usa
sreya YG uk
I want to join these two tables on the common columns values, but the other table columns contain extra data. How to overcome this?
table2 column "values" contains data matching to table1 "values"
values
AB
CD
YG
values
JKABHJ
YURRRCD
YGHJJKLJL
You can use like operator in query for matching values in table1 and table2.
For this query:
WITH table1 as (
select 1 as id, 'john' as name, 'AB' as value from dual union all
select 2 as id, 'marry' as name, 'CD' as value from dual union all
select 3 as id, 'sreya' as name, 'YG' as value from dual
),
table2 as (
select 45 as id, 'india' as country, 'JKABHJ' as value from dual union all
select 46 as id, 'usa' as country, 'YURRRCD' as value from dual union all
select 47 as id, 'uk' as country, 'YGHJJKLJL' as value from dual
)
select a.name, a.value, b.country
from table1 a
join table2 b on b.value like '%'||a.value||'%';
Output:
NAME VALUE COUNTRY
john AB india
marry CD usa
sreya YG uk
But I would recommend you to change a structure to make it more efficient. For example, by adding new table table2_values with column id referenced to table2.id and split values:
WITH table1 as (
select 1 as id, 'john' as name, 'AB' as value from dual union all
select 2 as id, 'marry' as name, 'CD' as value from dual union all
select 3 as id, 'sreya' as name, 'YG' as value from dual
),
table2 as (
select 45 as id, 'india' as country from dual union all
select 46 as id, 'usa' as country from dual union all
select 47 as id, 'uk' as country from dual
),
table2_values as (
select 45 as id, 'JK' as value from dual union all
select 45 as id, 'AB' as value from dual union all
select 45 as id, 'HJ' as value from dual union all
select 46 as id, 'YU' as value from dual union all
select 46 as id, 'RRR' as value from dual union all
select 46 as id, 'CD' as value from dual union all
select 47 as id, 'YG' as value from dual union all
select 47 as id, 'HJ' as value from dual
)
select a.name, a.value, c.country
from table1 a
join table2_values b on b.value = a.value
join table2 c on c.id = b.id;
You should use like operator while joining the two tables.
As below
SELECT *
FROM TABLE1
JOIN TABLE2
ON TABLE1.values like CONCAT('%',TABLE2.values,'%')

How to select row based on existance of value in other column

I realise the title to this question may be vague but I am not sure how to phrase it. I have the following table:
i_id option p_id
---- ------ ----
1 A 4
1 B 8
1 C 6
2 B 3
2 C 5
3 A 7
3 B 3
4 E 11
How do I select a row based on the value of the option column for each unique i_id: if 'C' exists, select the row, else select row with 'B' else with 'A' so that result set is:
i_id option p_id
---- ------ ----
1 C 6
2 C 5
3 B 3
select i_id, option, p_id
from (
select
i_id,
option,
p_id,
row_number() over (partition by i_id order by case option when 'C' then 0 when 'B' then 1 when 'A' then 2 end) takeme
from thetable
where option in ('A', 'B', 'C')
) foo
where takeme = 1
This will give you the values ordered by C, B, A, while removing any i_id record that does not have one of these values.
WITH ranked AS
(
SELECT i_id, [option], p_id
, ROW_NUMBER() OVER (PARTITION BY i_id ORDER BY CASE [option]
WHEN 'C' THEN 1
WHEN 'B' THEN 2
WHEN 'A' THEN 3
ELSE 4
END) AS rowNumber
FROM yourTable
WHERE [option] IN ('A', 'B', 'C')
)
SELECT r.i_id, r.[option], r.p_id
FROM ranked AS r
WHERE r.rowNumber = 1
create table t2 (
id int,
options varchar(1),
pid int
)
insert into t2 values(1, 'A', 4)
insert into t2 values(1, 'B', 8)
insert into t2 values(1, 'C', 6)
insert into t2 values(1, 'E', 7)
select t2.* from t2,
(select id, MAX(options) as op from t2
where options <> 'E'
group by id) t
where t2.id = t.id and t2.options = t.op
Well, I would suggest that this problem can be made easier if you can assign a numeric "score" to each letter, such that "better" letters have higher scores. Then you can use MAX to find, for each group, the row with the highest "score" for the option. Since 'A' < 'B' < 'C', we could cheat here and use option as the score, and thus:
SELECT t1.i_id, t1.option, t1.p_id
FROM thetable t1
INNER JOIN (SELECT t2.i_id, MAX(option)
FROM thetable t2
GROUP BY t2.i_id) AS maximums
ON t1.i_id = maximums.i_id
WHERE option != 'D'
This assumes that {i_id, option} is a natural key of the table (i.e., that no two rows will have the same combination of values for those two columns; or, alternatively, that you have an uniqueness constraint on that pair of columns).