SQL: Select IDs which are not mapped to values in another column - sql

I have table with two columns like this:
ROLE_ID RESTRICTION_ID
--------- ---------------
2 null
2 15
2 null
13 12
13 null
13 13
555 null
555 null
555 null
91 10
91 12
91 null
I need to get:
ROLE_ID RESTRICTION_ID
--------- ---------------
555 null
555 null
555 null
Meaning , that i need all ROLE_ID's which are not connected to any RESTRICTION_ID.
If there is some number in the RESTRICTION_ID column for some ROLE_ID is want the ROLE_ID excluded from the select statement results.

SELECT t1.*
FROM
table t1
LEFT JOIN table t2
ON t1.ROLE_ID = t2.ROLE_ID
AND t2.RESTRICTION_ID IS NOT NULL
WHERE
t2.ROLE_ID IS NULL
Just because everyone else is showing the WHERE IN (SELECT ... answer here is a way to do it via a self join....
And just to show one way it can be done using EXISTS...
SELECT t1.*
FROM
table t1
WHERE
NOT EXISTS (SELECT *
FROM
table t2
WHERE
t1.ROLE_ID = t2.ROLE_ID
AND t2.RESTRICTION_ID IS NOT NULL)

Select the rows where resctriction_id is null
select * from my_table
where resctiction_id is null
and role_id not in (select role_id from my_table where resctiction_id is not null) ;

The following should get you started:
SELECT
role_id
, restriction_id
FROM Data
WHERE role_id NOT IN
(SELECT
role_id
FROM Data
GROUP BY role_id
HAVING COUNT(restriction_id) > 0
);
Please comment if and as this requires adjustment / further detail.

Select role_id, restriction_id
From Table
Where role_id in (Select ROLE_ID
From Table
Group by ROLE_ID
Having COUNT(RESTRICTION_ID)=0)
Better maybe...

SELECT ROLE_ID, RESTRICTION_ID
FROM <Your Table>
WHERE RESTRICTION_ID IS NULL
AND ROLE_ID NOT IN
(SELECT ROLE_ID FROM <Your Table> WHERE RESTRICTION_ID IS NOT NULL) sub

and becuase you have tagged SQL here is a Window function with common table expression that does the job nicely as well.
WITH cte AS (
SELECT
*
,COUNT(RESTRICTION_ID) OVER (PARTITION BY ROLE_ID) as CountOfRestrictionsIds
FROM
TableName
)
SELECT *
FROM
cte
WHERE
CountOfRestrictionsIds = 0

SELECT ROLE_ID, RESTRICTION_ID
FROM Table
WHERE
ROLE_ID NOT IN (SELECT ROLE_ID FROM Table WHERE RESTRICTION_ID IS NOT NULL)

Count them all
with t as (
-- sample data
select 2 as ROLE_ID ,null as RESTRICTION_ID from dual union all
select 2 ,15 from dual union all
select 2 ,null from dual union all
select 13 ,12 from dual union all
select 13 ,null from dual union all
select 13 ,13 from dual union all
select 555 ,null from dual union all
select 555 ,null from dual union all
select 555 ,null from dual union all
select 91 ,10 from dual union all
select 91 ,12 from dual union all
select 91 ,null from dual
)
select ROLE_ID,RESTRICTION_ID
from (
select t.*
,count(RESTRICTION_ID) over(partition by ROLE_ID) as cnt
from t
) x
where cnt=0;

Related

Oracle SQL Uniquely Update Duplicate Records

I have a STUDENT table and need to update the STUDENT_ID values by prefixing with the letter SS followed by STUDENT_ID value. For any duplicate STUDENT_ID records, I should prefix the duplicate records as SS1 SS2. Below is an example
Before Update:
NUM
STUDENT_ID
1
9234
2
9234
3
9234
4
3456
5
3456
6
789
7
956
After Update:
NUM
STUDENT_ID
1
SS9234
2
SS19234
3
SS29234
4
SS3456
5
SS13456
6
SS789
7
SS956
Below is the query for updating the STUDENT_ID for unique records.
update student set student_id = 'SS'||student_id ;
commit;
Need suggestion for updating the STUDENT_ID for duplicate records. There are around 1 million duplicate records in the table and total volume is around 40 million. Appreciate for any inputs for performance enhancement.
You can use a MERGE statement correlated on the ROWID pseudo-column and using the ROW_NUMBER() analytic function:
MERGE INTO table_name dst
USING (
SELECT ROWID as rid,
ROW_NUMBER() OVER (PARTITION BY student_id ORDER BY num) AS rn
FROM table_name
) src
ON (src.rid = dst.ROWID)
WHEN MATCHED THEN
UPDATE
SET student_id = 'SS' || CASE WHEN rn > 1 THEN rn - 1 END || dst.student_id;
Which, for the sample data:
CREATE TABLE table_name (NUM, STUDENT_ID) AS
SELECT 1, CAST('9234' AS VARCHAR2(20)) FROM DUAL UNION ALL
SELECT 2, '9234' FROM DUAL UNION ALL
SELECT 3, '9234' FROM DUAL UNION ALL
SELECT 4, '3456' FROM DUAL UNION ALL
SELECT 5, '3456' FROM DUAL UNION ALL
SELECT 6, '789' FROM DUAL UNION ALL
SELECT 7, '956' FROM DUAL;
Then after the MERGE the table contains:
NUM
STUDENT_ID
1
SS9234
2
SS19234
3
SS29234
4
SS3456
5
SS13456
6
SS789
7
SS956
fiddle
I'm sure there must be a better way, but this query can get the job done:
update t
set student_id = (
select new_student_id
from (
select x.*, 'SS' || case when rn = 1 then '' else '' || rn end
|| student_id as new_student_id
from (
select t.*, row_number() over(partition by student_id order by num) as rn
from t
) x
) y
where t.num = y.num
)
Result:
NUM STUDENT_ID
---- ----------
1 SS9234
2 SS29234
3 SS39234
4 SS3456
5 SS23456
6 SS789
7 SS956
See running example at db<>fiddle.
Maybe you could do it without updating!?
I would probably try to :
CREATE NEW_TABLE AS
SELECT [do the "update" here] FROM OLD_TABLE;
- add indexes on new table
- add constraints on new table
- add anything else you need on new table (foreign keys, grants...)
and then
DROP TABLE OLD_TABLE;
-- and
RENAME NEW_TABLE To OLD_TABLE;
SELECT with your sample data:
WITH
tbl as
(
Select 1 "NUM", 9234 "STUDENT_ID" From Dual Union All
Select 2 "NUM", 9234 "STUDENT_ID" From Dual Union All
Select 3 "NUM", 9234 "STUDENT_ID" From Dual Union All
Select 4 "NUM", 3456 "STUDENT_ID" From Dual Union All
Select 5 "NUM", 3456 "STUDENT_ID" From Dual Union All
Select 6 "NUM", 789 "STUDENT_ID" From Dual Union All
Select 7 "NUM", 956 "STUDENT_ID" From Dual
)
Select
NUM,
CASE WHEN Count(NUM) Over(Partition By STUDENT_ID) = 1 THEN 'SS' || STUDENT_ID
ELSE 'SS' || Replace(Sum(1) Over(Partition By STUDENT_ID Order By NUM) - 1, 0, '') || STUDENT_ID
END "STUDENT_ID"
From
tbl
Order By NUM
Result:
NUM
STUDENT_ID
1
SS9234
2
SS19234
3
SS29234
4
SS3456
5
SS13456
6
SS789
7
SS956

Is there a reason I am getting no rows selected for this NOT IN nested query

Query:
SELECT teamid
FROM team
WHERE teamid NOT IN (SELECT team_teamid FROM gymnast);
Tables:
TEAM table
Gymnast table
I have been trying to figure this out for a while, any information would be greatly appreciated. Thank you!
The fiddle for oracle
val NOT IN (some list) means the value does NOT match every entry in the list.
But if one entry in the list is null, value <> null can never be true for that entry, which means the entire NOT IN expression can never be true.
See the following examples:
WITH team (teamid) AS (
SELECT 1 FROM DUAL UNION
SELECT 2 FROM DUAL UNION
SELECT 3 FROM DUAL UNION
SELECT 4 FROM DUAL
)
, gymnast (team_teamid) AS (
SELECT 1 FROM DUAL UNION
SELECT 2 FROM DUAL UNION
SELECT 2 FROM DUAL UNION
SELECT 4 FROM DUAL
)
SELECT teamid FROM team WHERE teamid NOT IN (SELECT team_teamid FROM gymnast)
;
Result:
TEAMID
3
Case 2 with nulls in the list:
WITH team (teamid) AS (
SELECT 1 FROM DUAL UNION
SELECT 2 FROM DUAL UNION
SELECT 3 FROM DUAL UNION
SELECT 4 FROM DUAL
)
, gymnast (team_teamid) AS (
SELECT 1 FROM DUAL UNION
SELECT 2 FROM DUAL UNION
SELECT null FROM DUAL UNION
SELECT 4 FROM DUAL
)
SELECT teamid FROM team WHERE teamid NOT IN (SELECT team_teamid FROM gymnast)
;
Result:
TEAMID
No rows in result, due to the gymnast with a null team_teamid.

How do I find specific values in a table in Oracle?

Suppose my table is TEST_123 Which has the following records:
id | cid | result
------------------
1 | C-1 | TAM
2 | C-1 | TAM
3 | C-2 | RAM
4 | C-2 | TAM
5 | C-3 | SAM
6 | C-3 | SAM
Now I want such cid's which has only one type of result, so the answer should be C-1 AND C-3 but not C-2 since it has two different type of results. Need Oracle query for this?
You simple need to understand GROUP BY and HAVING clause.
The answer is as simple as
select cid
from TEST_123
group by cid
having count(distinct result) = 1
Note group by selects the distinct keys from CID; the having filters on condition valid for all the records in the group, in your case count(distinct result) = 1
Use exists, its a little bit tricky cause every group result should be same
select t1.* from TEST_123 t1 where exists(
select 1 from TEST_123 t2 where t2.cid=t1.cid
and t2.result=t1.result
group by t2.cid,t2.result
having count(*)=
(select count(*) from TEST_123 t3
where t3.cid=t2.cid)
)
Exmple
with TEST_123 as
(
select 1 as id , 'c-1' as cid , 'tam' as result from dual
union all
select 2 as id , 'c-1' as cid , 'tam' as result from dual
union all
select 3 as id , 'c-2' as cid , 'tam' as result from dual
union all
select 4 as id , 'c-2' as cid , 'ram' as result from dual
)
select distinct t1.cid from TEST_123 t1 where exists(
select 1 from TEST_123 t2 where t2.cid=t1.cid
and t2.result=t1.result
group by t2.cid,t2.result
having count(*)=
(select count(*) from TEST_123 t3
where t3.cid=t2.cid)
)
demo
Based on #zaynul's answer, here is another variation:
with TEST_123 as
(
select 1 as id , 'c-1' as cid , 'tam' as result from dual
union all
select 2 as id , 'c-1' as cid , 'tam' as result from dual
union all
select 3 as id , 'c-2' as cid , 'tam' as result from dual
union all
select 4 as id , 'c-2' as cid , 'ram' as result from dual
)
select * from test_123 where cid in (
select cid from test_123 group by cid having count(distinct result) = 1);
select t.cid from
(select cid, count(*) as count from table_1 group by cid, result) t
group by t.cid
having count(*)=1;
Should work for you
I would use NOT EXISTS :
SELECT t.*
FROM table t
WHERE NOT EXISTS (SELECT 1 FROM table t1 WHERE t1.cid = t.cid AND t1.result <> t.result);

Oracle sql group sum

I have table With ID,Sub_ID and value coloumns
ID SUB_ID Value
100 1 100
100 2 150
101 1 100
101 2 150
101 3 200
102 1 100
SUB ID can vary from 1..maxvalue( In this example it is 3). I need Sum of values for each Sub_ID. If SUB_ID is less than MAXVALUE for a particlaur ID then it should take MAX(SUB_ID) of each ID As shown below ( In this example for ID=100 for SUB_ID 3 it should take 150 i.e 2<3 so value=150))
SUB_ID SUM(values) Remarks
1 300 (100+100+100)
2 400 (150+150+100)
3 450 (150+200+100)
This can be easily done in PL/SQL . Can we use SQL for the same using Model Clause or any other options
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TableA ( ID, SUB_ID, Value ) AS
SELECT 100, 1, 100 FROM DUAL
UNION ALL SELECT 100, 2, 150 FROM DUAL
UNION ALL SELECT 101, 1, 100 FROM DUAL
UNION ALL SELECT 101, 2, 150 FROM DUAL
UNION ALL SELECT 101, 3, 200 FROM DUAL
UNION ALL SELECT 102, 1, 100 FROM DUAL
Query 1:
WITH sub_ids AS (
SELECT LEVEL AS sub_id
FROM DUAL
CONNECT BY LEVEL <= ( SELECT MAX( SUB_ID ) FROM TableA )
),
max_values AS (
SELECT ID,
MAX( VALUE ) AS max_value
FROM TableA
GROUP BY ID
)
SELECT s.SUB_ID,
SUM( COALESCE( a.VALUE, m.max_value ) ) AS total_value
FROM sub_ids s
CROSS JOIN
max_values m
LEFT OUTER JOIN
TableA a
ON ( s.SUB_ID = a.SUB_ID AND m.ID = a.ID )
GROUP BY
s.SUB_ID
Results:
| SUB_ID | TOTAL_VALUE |
|--------|-------------|
| 1 | 300 |
| 2 | 400 |
| 3 | 450 |
Try this
SELECT SUB_ID,SUM(values),
(SELECT DISTINCT SUBSTRING(
(
SELECT '+'+ CAST(values AS VARCHAR)
FROM table_Name AS T2
WHERE T2.SUB_ID = d.SUB_ID
FOR XML PATH ('')
),2,100000)[values]) as values
FROm table_Name d
GROUP BY SUB_ID
How about something like this:
select max_vals.sub_id, sum(nvl(table_vals.value,max_vals.max_value)) as sum_values
from (
select all_subs.sub_id, t1.id, max(t1.value) as max_value
from your_table t1
cross join (select sub_id from your_table) all_subs
group by all_subs.sub_id, t1.id
) max_vals
left outer join your_table table_vals
on max_vals.id = table_vals.id
and max_vals.sub_id = table_vals.sub_id
group by max_vals.sub_id;
The inner query gets you a list of all sub_id/id combinations and their fall-back values. The out query uses an nvl to use the table value if it exists and the fall-back value if it doesn't.

How to do select count(*) group by and select * at same time?

For example, I have table:
ID | Value
1 hi
1 yo
2 foo
2 bar
2 hehe
3 ha
6 gaga
I want my query to get ID, Value; meanwhile the returned set should be in the order of frequency count of each ID.
I tried the query below but don't know how to get the ID and Value column at the same time:
SELECT COUNT(*) FROM TABLE group by ID order by COUNT(*) desc;
The count number doesn't matter to me, I just need the data to be in such order.
Desire Result:
ID | Value
2 foo
2 bar
2 hehe
1 hi
1 yo
3 ha
6 gaga
As you can see because ID:2 appears most times(3 times), it's first on the list,
then ID:1(2 times) etc.
you can try this -
select id, value, count(*) over (partition by id) freq_count
from
(
select 2 as ID, 'foo' as value
from dual
union all
select 2, 'bar'
from dual
union all
select 2, 'hehe'
from dual
union all
select 1 , 'hi'
from dual
union all
select 1 , 'yo'
from dual
union all
select 3 , 'ha'
from dual
union all
select 6 , 'gaga'
from dual
)
order by 3 desc;
select t.id, t.value
from TABLE t
inner join
(
SELECT id, count(*) as cnt
FROM TABLE
group by ID
)
x on x.id = t.id
order by x.cnt desc
How about something like
SELECT t.ID,
t.Value,
c.Cnt
FROM TABLE t INNER JOIN
(
SELECT ID,
COUNT(*) Cnt
FROM TABLE
GROUP BY ID
) c ON t.ID = c.ID
ORDER BY c.Cnt DESC
SQL Fiddle DEMO
I see the question is already answered, but since the most obvious and most simple solution is missing, I'm posting it anyway. It doesn't use self joins nor subqueries:
SQL> create table t (id,value)
2 as
3 select 1, 'hi' from dual union all
4 select 1, 'yo' from dual union all
5 select 2, 'foo' from dual union all
6 select 2, 'bar' from dual union all
7 select 2, 'hehe' from dual union all
8 select 3, 'ha' from dual union all
9 select 6, 'gaga' from dual
10 /
Table created.
SQL> select id
2 , value
3 from t
4 order by count(*) over (partition by id) desc
5 /
ID VALU
---------- ----
2 bar
2 hehe
2 foo
1 yo
1 hi
6 gaga
3 ha
7 rows selected.