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

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
)

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

sql select values from another table if exist

Please help, need to select from table 1, but if entry with the same id exists in table2 should return name and last name from there otherwise values from table1
table1
id|name|lastname
1 | |
2 | |
3 | |
table2
id|name|lastname
3 | |
Tried this, but not working
SELECT ID, NAME, LASTNAME
FROM table1
WHERE EXISTS
(SELECT 1 FROM table2 WHERE table2.ID = table1.ID)
if entry with the same id exists in table2 should return name and last name from there otherwise values from table1
You want a LEFT OUTER JOIN and then to use COALESCE:
SELECT t1.id,
COALESCE( t2.name, t1.name ) AS name,
COALESCE( t2.lastname, t1.lastname ) AS last_name
FROM table1 t1
LEFT OUTER JOIN table2 t2
ON ( t1.id = t2.id )
Which, for your sample data:
CREATE TABLE table1 ( id, name, lastname ) AS
SELECT 1, 'Alice1', 'Abbot1' FROM DUAL UNION ALL
SELECT 2, 'Betty1', 'Baron1' FROM DUAL UNION ALL
SELECT 3, 'Carol1', 'Casey1' FROM DUAL;
CREATE TABLE table2 ( id, name, lastname ) AS
SELECT 3, 'Carol2', 'Casey2' FROM DUAL;
Outputs:
ID
NAME
LAST_NAME
3
Carol2
Casey2
2
Betty1
Baron1
1
Alice1
Abbot1
db<>fiddle here

Update Query For ORACLE SQL DEVELOPER

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...

Return default value for some IN condition not match

I think this shouldn't be really hard. I am writing an Oracle-SQL code to extract data from SQL:
select ID, Qty from TableOne where ID in ('A', 'B', 'C')
I want the database to show the result of the query if there is match for some items in the IN condition, and return a default value if there is no match for those items in the IN condition.
For example, I want the result to be:
+----+-----------+
| ID | Qty |
+----+-----------+
| A | 3 |
| A | 5 |
| B | 4 |
| C | Not Found |
+----+-----------+
Where there is no ID = C in the table TableOne.
Is there any easy way to code the result?
Thank you very much!
Use COALESCE, NVL or CASE with a LEFT OUTER JOIN and specify the ids in a sub-query factoring clause:
WITH ids_to_match( id ) AS (
SELECT 'A' FROM DUAL UNION ALL
SELECT 'B' FROM DUAL UNION ALL
SELECT 'C' FROM DUAL
)
select i.ID,
COALESCE( TO_CHAR(Qty), 'Not Found' ) AS Qty
from ids_to_match i
LEFT OUTER JOIN TableOne t
ON ( t.id = i.id )
or use a collection and a table collection expression:
select i.COLUMN_VALUE AS ID,
COALESCE( TO_CHAR(Qty), 'Not Found' ) AS Qty
from TABLE( SYS.ODCIVARCHAR2LIST( 'A', 'B', 'C' ) ) i
LEFT OUTER JOIN
TableOne t
ON ( t.id = i.COLUMN_VALUE )
You can use LEFT JOIN with UNION ALL :
WITH ALL_ID AS (
SELECT 'A' AS ID FROM DUAL UNION ALL
SELECT 'B' AS ID FROM DUAL UNION ALL
SELECT 'C' AS ID FROM DUAL
)
SELECT A.ID, t.Qty -- REPLACE NULL WITH NOT FOUND
FROM ALL_ID A ID LEFT JOIN
Table t
ON t.ID = A.ID;
if this works for you:
select t2.ID, case when t2.QTY is NULL then TO_CHAR('Not found') else t2.QTY end "QTY" from TableOnet1 t1 right join Tabletwo t2
on t1.ID = t2.ID where t2.ID in ('A', 'B', 'C')

Select No Rows If Any Row Meets A Condition?

How can I select no rows if any row in the result set meets a certain condition?
For instance:
Id|SomeColumn|Indicator
1 | test | Y
1 | test1 | Y
1 | test2 | X
2 | test1 | Y
2 | test2 | Y
3 | test1 | Y
Say I wanted to select all rows where Id = 1 unless there is a row with an indicator = X
Currently I am doing something like this
SELECT * FROM SOMETABLE WHERE ID = 1 AND INDICATOR = 'Y' AND ID NOT IN (SELECT ID WHERE INDICATOR = 'X')
But that feels really clunky and I feel like there could be a better way to be doing this. Is there or am I just being overly sensitive
Something like this ?
SELECT *
FROM SOMETABLE
WHERE ID = 1
AND NOT EXISTS (SELECT 1 FROM SOMETABLE WHERE INDICATOR = 'X')
or, if you want the X to discriminate only on the same id:
SELECT *
FROM SOMETABLE t1
WHERE t1.ID = 1
AND NOT EXISTS (SELECT 1 FROM SOMETABLE t2 WHERE t1.ID = t1.ID AND INDICATOR = 'X')
There are not too many options to do this. Another option is to use EXISTS.
SELECT *
FROM SOMETABLE s1
WHERE ID = 1 AND INDICATOR = 'Y'
AND NOT EXISTS (SELECT TOP 1 ID FROM SOMETABLE s2 WHERE s1.ID = s2.ID AND INDICATOR = 'X')
Another option, assuming that there's an enforced order in indicator column.
DECLARE #T TABLE
(
ID INT
, someColumn VARCHAR(5)
, Indicator CHAR(1)
)
INSERT INTO #T
( ID, someColumn, Indicator )
VALUES ( 1, 'test', 'Y' ),
( 1, 'test1', 'Y' ),
( 1, 'test2', 'X' ),
( 2, 'test1', 'Y' ),
( 2, 'test2', 'Y' ),
( 3, 'test1', 'Y' )
SELECT t.ID
, t.someColumn
, t.Indicator
FROM #T t
JOIN (SELECT ID
FROM #T t2
GROUP BY t2.ID
HAVING MIN(indicator) >= 'Y') q ON q.ID = t.ID
Not sure if it's any less clunky, but it may perform better since it's using positive exclusion rather than negative.