Remove duplicates with least row ids from Oracle - sql

I have a database table which looks like
ID
Book_no
Book_name
Book_category
ID1
1
B1
CB1
ID1
2
B1
CB1
ID1
3
B2
CB1
ID1
4
B2
CB1
ID1
5
B3
CB1
ID2
1
B1
CB2
ID2
2
B1
CB2
ID2
3
B2
CB2
And the expected result is like
ID
Book_No
Book_name
Book_category
ID1
2
B1
CB1
ID1
4
B2
CB1
ID1
5
B3
CB1
ID2
2
B1
CB2
ID2
3
B2
CB2
I want to delete duplicate records from table on the basis of ID, Book_name and Book_category. Below query deletes the duplicate records, but the result is not expected one. As I want to delete all the duplicate records except the highest Book_no. Want to maintain the highest Book_no and delete all other duplicates.

You can DELETE correlating on the ROWID pseudo-column:
DELETE FROM table_name
WHERE ROWID IN (
SELECT rid
FROM (
SELECT ROWID AS rid,
ROW_NUMBER() OVER (
PARTITION BY id, book_name, book_category
ORDER BY book_no DESC
) AS rn
FROM table_name
)
WHERE rn > 1
);
Which, for the sample data:
CREATE TABLE table_name (id, book_no, book_name, book_category) AS
SELECT 'ID1', 1, 'B1', 'CB1' FROM DUAL UNION ALL
SELECT 'ID1', 2, 'B1', 'CB1' FROM DUAL UNION ALL
SELECT 'ID1', 3, 'B2', 'CB1' FROM DUAL UNION ALL
SELECT 'ID1', 4, 'B2', 'CB1' FROM DUAL UNION ALL
SELECT 'ID1', 5, 'B3', 'CB1' FROM DUAL UNION ALL
SELECT 'ID2', 1, 'B1', 'CB2' FROM DUAL UNION ALL
SELECT 'ID2', 2, 'B1', 'CB2' FROM DUAL UNION ALL
SELECT 'ID2', 3, 'B2', 'CB2' FROM DUAL;
Then the remaining rows are:
SELECT * FROM table_name;
ID
BOOK_NO
BOOK_NAME
BOOK_CATEGORY
ID1
2
B1
CB1
ID1
4
B2
CB1
ID1
5
B3
CB1
ID2
2
B1
CB2
ID2
3
B2
CB2
sqlfiddle here

You can use lead() and filter where the name changes:
select t.*
from (select t.*,
lead(book_name) over (partition by id, book_category order by book_no) as next_book_name
from t
) t
where next_book_name is null or next_book_name <> book_name;

Assuming your table looks like this:
create table books (id, book_no, book_name, book_category) as
select 'ID1', 1, 'B1', 'CB1' from dual union all
select 'ID1', 2, 'B1', 'CB1' from dual union all
select 'ID1', 3, 'B2', 'CB1' from dual union all
select 'ID1', 4, 'B2', 'CB1' from dual union all
select 'ID1', 5, 'B3', 'CB1' from dual union all
select 'ID2', 1, 'B1', 'CB2' from dual union all
select 'ID2', 2, 'B1', 'CB2' from dual union all
select 'ID2', 3, 'B2', 'CB2' from dual
;
You can use a delete statement in which you compare each row to the ones you want to keep. The ones you want to keep have max(book_no) when grouped by the other columns. So:
delete from books
where (id, book_no, book_name, book_category) not in
(
select id, max(book_no), book_name, book_category
from books
group by id, book_name, book_category
)
;
This assumes that the columns are non-null; if you may have null in the table, you will need to rewrite this a little more carefully, with a not exists condition instead of not in, but the idea is the same.

You can use first_value for finding the required id and can remove others using delete.
SELECT DISTINCT a.*, FIRST_VALUE(a.book_name)
OVER (ORDER BY a.book_name DESC
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
AS "HIGHEST"
FROM ( select * from books group by id, Book_name, book_category ) a;
SELECT DISTINCT FIRST_VALUE(column_name)
OVER (ORDER BY column_name DESC
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
AS "HIGHEST"
FROM (select * from tables group by "column_names_to_be_grouped");

Related

Hiding whole objects when generating JSON form ORACLE tables

I would like to know is there any "elegant" way to hide whole generated object when one of columns is null?
For example if "tab_2.col_c" is null (because join doesn't return any values for "tab_2"), whole "tab_2" object is hidden, but when "tab_2.col_c" contains data then object "tab_2" is displayed.
tab_1:
|id|col_a|col_b|
|--|-----|-----|
|1 | a1 | b1 |
|2 | a2 | b2 |
tab_2:
|id|fk_id|col_c|col_d|
|--|-----|-----|-----|
|11| 2 | c1 | d1 |
|22| 2 | c2 | d2 |
Query:
select
json_object(
'tab_1_a' VALUE tab_1.col_a,
'tab_2_b' VALUE tab_1.col_b,
'tab_2' value json_object('c' value tab2.col_c,
'd' value tab2.col_d
)
)
from tab_1, tab2
where tab_1.id = tab_2.fk_id(+)
group by tab_1.col_a, tab_2.col_b
Output for "tab_1.id" = 1
{
"tab_1_a": "a1",
"tab_1_b": "b1",
"tab_2": {
"c": null,
"d": null
}
}
Desired output:
{
"tab_1_a": "a1",
"tab_1_b": "b1"
}
If you do not want the keys to be shown, you can use the ABSENT ON NULL clause of JSON_OBJECT_T. This will cause {} to still be returned though even if all of the keys do not contain a value.
WITH
tab_1 (id, col_a, col_b)
AS
(SELECT 1, 'a1', 'b1' FROM DUAL
UNION ALL
SELECT 2, 'a2', 'b2' FROM DUAL),
tab_2 (id,
fk_id,
col_c,
col_d)
AS
(SELECT 11, 2, 'c1', 'd1' FROM DUAL
UNION ALL
SELECT 22, 2, 'c2', 'd2' FROM DUAL)
SELECT tab_1.id,
json_object (
'tab_1_a' VALUE tab_1.col_a,
'tab_2_b' VALUE tab_1.col_b,
'tab_2' VALUE
json_object ('c' VALUE tab_2.col_c,
'd' VALUE tab_2.col_d
ABSENT ON NULL)) AS formatted_json
FROM tab_1 LEFT JOIN tab_2 ON tab_1.id = tab_2.fk_id
WHERE tab_1.id = 1;
ID FORMATTED_JSON
_____ _____________________________________________
1 {"tab_1_a":"a1","tab_2_b":"b1","tab_2":{}}
If you do not want {} returned, you can use a subquery. If no results are found then it will return null, but you can hide that as well using the ABSENT ON NULL clause again.
WITH
tab_1 (id, col_a, col_b)
AS
(SELECT 1, 'a1', 'b1' FROM DUAL
UNION ALL
SELECT 2, 'a2', 'b2' FROM DUAL),
tab_2 (id,
fk_id,
col_c,
col_d)
AS
(SELECT 11, 2, 'c1', 'd1' FROM DUAL
UNION ALL
SELECT 22, 2, 'c2', 'd2' FROM DUAL)
SELECT tab_1.id,
json_object (
'tab_1_a' VALUE tab_1.col_a,
'tab_2_b' VALUE tab_1.col_b,
'tab_2' VALUE
(SELECT json_object ('c' VALUE tab_2.col_c,
'd' VALUE tab_2.col_d
ABSENT ON NULL)
FROM tab_2
WHERE tab_2.fk_id = tab_1.id)
ABSENT ON NULL) AS formatted_json
FROM tab_1
WHERE tab_1.id = 1;
ID FORMATTED_JSON
_____ __________________________________
1 {"tab_1_a":"a1","tab_2_b":"b1"}

Group and exclude rows that had empty values aggregated - oracle sql

I have a oracle sql table that looks like so
"STUDENT_ID","FULL_NAME","SEMESTER_ID","STIP_ID"
"1","Liam Bottrill","1","1"
"1","Liam Bottrill","2","3"
"1","Liam Bottrill","3","2"
"1","Liam Bottrill","4","5"
"2","Maurits Smitham","1","6"
"2","Maurits Smitham","2",""
"2","Maurits Smitham","3","2"
"2","Maurits Smitham","4","6"
"43","Jackie Cotton","1",""
"43","Jackie Cotton","2",""
"43","Jackie Cotton","3",""
"43","Jackie Cotton","4",""
I want to group this table by "STUDENT_ID" and exclude from result any students that have any of "STIP_ID" rows empty
Im aiming for result like this:
"STUDENT_ID","FULL_NAME"
"1","Liam Bottrill"
Liam Bottrill should be displayed while Maurits Smitham and Jackie Cotton should be excluded from result
Can you please help me with such aggregate function?
Here is one way, using aggregation:
SELECT *
FROM yourTable
WHERE STUDENT_ID IN (
SELECT STUDENT_ID
FROM yourTable
GROUP BY STUDENT_ID
HAVING COUNT(CASE WHEN STIP_ID IS NULL THEN 1 END) = 0
);
Another way, using exists logic:
SELECT t1.*
FROM yourTable t1
WHERE NOT EXISTS (
SELECT 1
FROM yourTable t2
WHERE t2.STUDENT_ID = t1.STUDENT_ID AND
t2.STIP_ID IS NULL
);
You can group by the identifier and then use conditional aggregation to find the student where the count when STIP_ID is NULL (which, in Oracle, is the same as an empty string):
SELECT student_id,
MAX(full_name) AS full_name
FROM table_name
GROUP BY student_id
HAVING COUNT(CASE WHEN stip_id IS NULL THEN 1 END) = 0;
Which, for your sample data:
CREATE TABLE table_name (STUDENT_ID, FULL_NAME, SEMESTER_ID, STIP_ID) AS
SELECT 1, 'Liam Bottrill', 1, 1 FROM DUAL UNION ALL
SELECT 1, 'Liam Bottrill', 2, 3 FROM DUAL UNION ALL
SELECT 1, 'Liam Bottrill', 3, 2 FROM DUAL UNION ALL
SELECT 1, 'Liam Bottrill', 4, 5 FROM DUAL UNION ALL
SELECT 2, 'Maurits Smitham', 1, 6 FROM DUAL UNION ALL
SELECT 2, 'Maurits Smitham', 2, NULL FROM DUAL UNION ALL
SELECT 2, 'Maurits Smitham', 3, 2 FROM DUAL UNION ALL
SELECT 2, 'Maurits Smitham', 4, 6 FROM DUAL UNION ALL
SELECT 43, 'Jackie Cotton', 1, NULL FROM DUAL UNION ALL
SELECT 43, 'Jackie Cotton', 2, NULL FROM DUAL UNION ALL
SELECT 43, 'Jackie Cotton', 3, NULL FROM DUAL UNION ALL
SELECT 43, 'Jackie Cotton', 4, NULL FROM DUAL;
Outputs:
STUDENT_ID
FULL_NAME
1
Liam Bottrill
db<>fiddle here

Update Oracle SQL - table with values from duplicates

i hope that somebody could help. I need to update a table from a select with duplicates.
ID;CLASS;VALUE;NEW
1;a;a3;
1;b;s6;
1;c;b99;
2;a;s3;
2;b;r6;
2;c;b99;
3;a;s5;
4;a;r6;
4;b;a3;
Look at my example table, there is a colum NEW which i have to update. In the example the column NEW was filled manually.
Here is the goal (as shown in table col NEW):
1.find duplicates via ID (HAVING COUNT(*) >1 or something like that)
UPDATE TABLE SET NEW=
CLASS || '_' || VALUE
WHERE CLASS='a' or 'b'
Easy for you?
Thx in advance
The logic behind is not completely clear; this could be a way.
setup:
create table yourTable(id, class, value, new) as
(
select 1, 'a', 'a3', cast (null as varchar2(10)) from dual union all
select 1, 'b', 's6', null from dual union all
select 1, 'c', 'b99', null from dual union all
select 2, 'a', 's3', null from dual union all
select 2, 'b', 'r6', null from dual union all
select 2, 'c', 'b99', null from dual union all
select 3, 'a', 's5', null from dual union all
select 4, 'a', 'r6', null from dual union all
select 4, 'b', 'a3', null from dual
)
query:
merge into yourTable t1
using (
select listagg(value, '_') within group (order by class) as new,
id
from yourTable
where class in ('a', 'b')
group by id
having count(distinct class) = 2
) t2
on ( t1.id = t2.id
and t1.class in ('a', 'b')
)
when matched then
update set t1.new = t2.new
result:
SQL> select *
2 from yourTable;
ID C VAL NEW
---------- - --- ----------
1 a a3 a3_s6
1 b s6 a3_s6
1 c b99
2 a s3 s3_r6
2 b r6 s3_r6
2 c b99
3 a s5
4 a r6 r6_a3
4 b a3 r6_a3
9 rows selected.

How to mapping 2 table condition the first day

I have 2 table with columns, looking like this:
Table A
key_a, date_a
'k1', '2015-11-25'
'k2', '2015-11-10'
'k3', '2015-10-09'
Table B
id, key_b, date_b
1, 'k1', '2015-10-01'
2, 'k1', '2015-11-02'
3, 'k1', '2015-11-25'
4, 'k2', '2015-10-15'
5, 'k2', '2015-11-09'
6, 'k3', '2015-09-15'
7, 'k3', '2015-10-02'
8, 'k3', '2015-10-08'
I want to find row in table B have date_b is first with same key_b:
key_a, date_a, id, date_b
'k1', '2015-11-25', 1, 2015-10-01
'k2', '2015-11-10', 5, 2015-11-09
'k3', '2015-10-09', 6, 2015-09-15
How can I do this?
quick one - should work
SELECT
key_a, date_a, b.id AS id, b.date_b AS date_b
FROM table_a AS a
JOIN (
SELECT
id, key_b, date_b,
ROW_NUMBER() OVER(PARTITION BY key_b ORDER BY date_b) AS num
FROM table_b
) AS b
ON a.key_a = b.key_b
WHERE b.num = 1

Returning results based on combinations of rows [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
Hi I wonder if this is even possible in T-SQL.
The idea is to "not" return rows like row 1 and row 4 if the combination of rows 2 and 3 / rows 5 and 6 exist.
declare #table table
(
A int,
B char(2),
C char(2),
D char(2)
)
insert into #table
select 2, 'A1', 'B1', NULL -- row 1
union
select 2, 'A1', 'B1', 'C1' -- row 2 and row 3 is a combination (values are equal)
union
select 3, 'A1', 'B1', 'C1'
union
select 2, 'A2', 'B2', NULL -- row 4
union
select 2, 'A2', 'B2', 'C2' -- row 5 and row 6 is a combination (values are equal)
union
select 3, 'A2', 'B2', 'C2'
Anyone who wants to have a go at this?
Thank you
Best Regards
Steve
Try this
SELECT DISTINCT A,B,C,D FROM #table
WHERE
A IS NOT NULL AND
B IS NOT NULL AND
C IS NOT NULL AND
D IS NOT NULL
OR
SELECT DISTINCT A,B,C,D FROM #table
WHERE
D IS NOT NULL
something like this?
declare #table table
(
ID INT ,
A int,
B char(2),
C char(2),
D char(2)
)
insert into #table
select 1, 2, 'A1', 'B1', NULL -- row 1
union
select 2, 2, 'A1', 'B1', 'C1' -- row 2 and row 3 is a combination (values are equal)
union
select 3, 3, 'A1', 'B1', 'C1'
union
select 4, 2, 'A2', 'B2', NULL -- row 4
union
select 5, 2, 'A2', 'B2', 'C2' -- row 5 and row 6 is a combination (values are equal)
union
select 6, 3, 'A2', 'B2', 'C2'
SELECT *
FROM #table t
WHERE EXISTS(SELECT * FROM #table t1 WHERE t.id IN (t1.id + 1, t1.id + 2)
AND t.d IS NULL )
NOT EXISTS is the way to go:
SELECT A,B,C,D from #table t1
WHERE NOT EXISTS(
SELECT 1 FROM #table t2
WHERE t1.A < t2.A
AND COALESCE(t1.B,'')=COALESCE(t2.B,'')
AND COALESCE(t1.C,'')=COALESCE(t2.C,'')
AND COALESCE(t1.D,'')=COALESCE(t2.D,'')
)
Demo
The COALESCE-trick is to compare two null values. Otherwise null=null would return null and equal rows with null values would be included.
I think we can get this by filtering records separately using aggregation and ordering of tables. I believe, you want to eliminate those rows which are followed by rows having combinations...
If so, below one might work...
declare #table table
(
A int,
B char(2),
C char(2),
D char(2)
)
insert into #table
select 2, 'A1', 'B1', NULL -- row 1
union
select 2, 'A1', 'B1', 'C1' -- row 2 and row 3 is a combination (values are equal)
union
select 3, 'A1', 'B1', 'C1'
union
select 2, 'A2', 'B2', NULL -- row 4
union
select 2, 'A2', 'B2', 'C2' -- row 5 and row 6 is a combination (values are equal)
union
select 3, 'A2', 'B2', 'C2'
SELECT
T.*
FROM
#table T
INNER JOIN
(
SELECT
T1.*
FROM
(
SELECT
ROW_NUMBER() OVER(ORDER BY B,C,D) As RowNumber,
MIN(A) AS A,
B,
C,
D,
COUNT(1) Cnt
FROM #TABLE
GROUP BY B,C,D
) T1
LEFT JOIN
(
SELECT
ROW_NUMBER() OVER(ORDER BY B,C,D) As RowNumber,
MIN(A) AS A,
B,
C,
D,
COUNT(1) Cnt
FROM #TABLE
GROUP BY B,C,D
) T2 ON T2.RowNumber = T1.RowNumber + 1
WHERE ISNULL(T2.Cnt,0) <= 1
) NonPostDuplicateRows
ON T.B = NonPostDuplicateRows.B
AND T.C = NonPostDuplicateRows.C
AND T.D = NonPostDuplicateRows.D