Select Duplicate records in Oracle - sql

I have a table below in Oracle
Table1
State | Product |other fields
CA | P1 | xxxx
OR | P1 | xxxx
OR | P1 | xxxx
OR | P1 | xxxx
WA | P1 | xxxx
VA | P2 | xxxx
My Output should be only select if State has been occurred more than once.
State | Product |other fields
OR | P1 | xxxx

If you just want to count duplicate states then:
SELECT DISTINCT
State,
Product,
Other_Fields
FROM (
SELECT t.*,
COUNT(1) OVER ( PARTITION BY State ) AS cnt
FROM Table1 t
)
WHERE cnt > 1;
If you want to consider duplicate rows (considering all fields) then:
SELECT *
FROM Table1
GROUP BY State, Product, Other_Fields
HAVING COUNT(1) > 1;

select state, product, column_3, column_4
from (
select state, product, column_3, column_4,
count(*) over (partition by state) as cnt
from the_table
) t
where cnt > 1;

You could use ROW_NUMBER analytic function.
For example,
SQL> WITH sample_data AS(
2 SELECT 'CA' State, 'P1' product FROM dual UNION ALL
3 SELECT 'OR', 'P1' product from dual union all
4 SELECT 'OR', 'P1' product FROM dual UNION ALL
5 SELECT 'OR', 'P1' product FROM dual UNION ALL
6 SELECT 'WA', 'P1' product FROM dual UNION ALL
7 SELECT 'VA', 'P2' product from dual
8 )
9 -- end of sample_data mimicking real table
10 SELECT distinct state,
11 product
12 FROM
13 (SELECT state,
14 product,
15 row_number() OVER(PARTITION BY state ORDER BY product) rn
16 FROM sample_data
17 )
18 WHERE rn >1;
ST PR
-- --
OR P1
SQL>

Related

Complex Oracle SQL query

I started learning SQL a few weeks back on my own and came upon an interesting problem that I can't seem to solve. Here's the database structure:
Material (name, price, origin_country)
Product (name, price)
Quantities (productName, materialName, quantity)
The query should show all data of the Material that's being used in the most Products that cost 20.000 or less.
This is what I have so far:
select max(count(m.name))
from Materials m
join Quantities q on (m.name=q.materialName)
join Product p on (p.name=q.productName)
where p.price < 20000
group by m.name
This, in theory, should show show the maximum number that a type of material appears in (and it indeed does so in practice). The problem is, I have no idea how to implement this in a way that it can show me the data from the Material table.
Join the tables together, filter out the products that are too expensive and then use the COUNT analytic function to find the number of products and ORDER BY that count in descending order and return the FIRST ROW WITH TIES:
SELECT M.*,
P.name AS ProductName,
p.Price,
Q.Quantity,
COUNT( DISTINCT p.ROWID ) OVER ( PARTITION BY m.ROWID ) AS num_products
FROM Material M
INNER JOIN Quantities Q
ON ( m.Name = Q.MaterialName )
INNER JOIN Product P
ON ( Q.ProductName = P.Name )
WHERE p.price <= 20000
ORDER BY num_products DESC
FETCH FIRST ROW WITH TIES;
(I used the ROWID pseudo-column in the count just in-case there were two products or materials with the same name then it would not group them together. If this is not going to be the case then you can just use p.Name and m.Name instead.)
Which, for the sample data:
CREATE TABLE Material( name, price, origin_country ) AS
SELECT 'M1', 1, 'Place1' FROM DUAL UNION ALL
SELECT 'M2', 2, 'Place2' FROM DUAL UNION ALL
SELECT 'M3', 3, 'Place3' FROM DUAL;
CREATE TABLE Product (name, price) AS
SELECT 'P123', 10 FROM DUAL UNION ALL
SELECT 'P13', 8 FROM DUAL UNION ALL
SELECT 'P223', 5 FROM DUAL;
CREATE TABLE Quantities (productName, materialName, quantity) AS
SELECT 'P123', 'M1', 1 FROM DUAL UNION ALL
SELECT 'P123', 'M2', 1 FROM DUAL UNION ALL
SELECT 'P123', 'M3', 1 FROM DUAL UNION ALL
SELECT 'P13', 'M1', 1 FROM DUAL UNION ALL
SELECT 'P13', 'M3', 1 FROM DUAL UNION ALL
SELECT 'P223', 'M2', 1 FROM DUAL UNION ALL
SELECT 'P223', 'M2', 1 FROM DUAL UNION ALL
SELECT 'P223', 'M3', 1 FROM DUAL;
Outputs:
NAME | PRICE | ORIGIN_COUNTRY | PRODUCTNAME | PRICE | QUANTITY | NUM_PRODUCTS
:--- | ----: | :------------- | :---------- | ----: | -------: | -----------:
M3 | 3 | Place3 | P123 | 10 | 1 | 3
M3 | 3 | Place3 | P223 | 5 | 1 | 3
M3 | 3 | Place3 | P13 | 8 | 1 | 3
db<>fiddle here

Select rows when a value appears multiple times

I have a table like this one:
+------+------+
| ID | Cust |
+------+------+
| 1 | A |
| 1 | A |
| 1 | B |
| 1 | B |
| 2 | A |
| 2 | A |
| 2 | A |
| 2 | B |
| 3 | A |
| 3 | B |
| 3 | B |
+------+------+
I would like to get the IDs that have at least two times A and two times B. So in my example, the query should return only the ID 1,
Thanks!
In MySQL:
SELECT id
FROM test
GROUP BY id
HAVING GROUP_CONCAT(cust ORDER BY cust SEPARATOR '') LIKE '%aa%bb%'
In Oracle
WITH cte AS ( SELECT id, LISTAGG(cust, '') WITHIN GROUP (ORDER BY cust) custs
FROM test
GROUP BY id )
SELECT id
FROM cte
WHERE custs LIKE '%aa%bb%'
I would just use two levels of aggregation:
select id
from (select id, cust, count(*) as cnt
from t
where cust in ('A', 'B')
group by id, cust
) ic
group by id
having count(*) = 2 and -- both customers are in the result set
min(cnt) >= 2 -- and there are at least two instances
This is one option; lines #1 - 13 represent sample data. Query you might be interested in begins at line #14.
SQL> with test (id, cust) as
2 (select 1, 'a' from dual union all
3 select 1, 'a' from dual union all
4 select 1, 'b' from dual union all
5 select 1, 'b' from dual union all
6 select 2, 'a' from dual union all
7 select 2, 'a' from dual union all
8 select 2, 'a' from dual union all
9 select 2, 'b' from dual union all
10 select 3, 'a' from dual union all
11 select 3, 'b' from dual union all
12 select 3, 'b' from dual
13 )
14 select id
15 from (select
16 id,
17 sum(case when cust = 'a' then 1 else 0 end) suma,
18 sum(case when cust = 'b' then 1 else 0 end) sumb
19 from test
20 group by id
21 )
22 where suma = 2
23 and sumb = 2;
ID
----------
1
SQL>
You can use group by and having for the relevant Cust ('A' , 'B')
And query twice (I chose to use with to avoid multiple selects and to cache it)
with more_than_2 as
(
select Id, Cust, count(*) c
from tab
where Cust in ('A', 'B')
group by Id, Cust
having count(*) >= 2
)
select *
from tab
where exists ( select 1 from more_than_2 where more_than_2.Id = tab.Id and more_than_2.Cust = 'A')
and exists ( select 1 from more_than_2 where more_than_2.Id = tab.Id and more_than_2.Cust = 'B')
What you want is a perfect candidate for match_recognize. Here you go:
select id_ as id from t
match_recognize
(
order by id, cust
measures id as id_
pattern (A {2, } B {2, })
define A as cust = 'A',
B as cust = 'B'
)
Output:
Regards,
Ranagal

Oracle SQL - Assign queue of customers to list of Employees

I need to distribute list of employees to customers.
For example:
Table 1: List of Employees: A, B & C
Table 2: List of Customers: 1, 2, 3, 4, 5, 6, 7, 8, 9
The needed result:
|------------|------------|
| Customers | Employees |
|------------|------------|
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | A |
| 5 | B |
| 6 | C |
| 7 | A |
| 8 | B |
| 9 | C |
|------------|------------|
You can use ROW_NUMBER() to assign a number on the fly to the employees, and the MOD() to do the rolling join. For example:
select
c.id,
e.name
from (
select t.*,
row_number() over(order by name) as rn
from employees t
) e
join customers c on e.rn =
mod(rn, (select count(*) from customers)) + 1
Number all rows, then use a modulo function for the join:
with e as
(
select employee, row_number() over (order by employee) as rn
from employees
)
, c as
(
select customer, row_number() over (order by customer) as rn
from customers
)
select c.customer, e.employee
from c
join e on e.rn - 1 = mod(c.rn - 1, (select count(*) from e))
order by c.customer;
Demo: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=43a6ca7469dff023d5513fa209e33ea7
You can use MOD function for this purpose. Try below code.
CREATE TABLE EMP
AS
SELECT 'A' AS EMP FROM DUAL
UNION ALL
SELECT 'B' AS EMP FROM DUAL
UNION ALL
SELECT 'C' AS EMP FROM DUAL;
CREATE TABLE CUST
AS
SELECT '1' AS CUST FROM DUAL
UNION ALL
SELECT '2' AS CUST FROM DUAL
UNION ALL
SELECT '3' AS CUST FROM DUAL
UNION ALL
SELECT '4' AS CUST FROM DUAL
UNION ALL
SELECT '5' AS CUST FROM DUAL
UNION ALL
SELECT '6' AS CUST FROM DUAL
UNION ALL
SELECT '7' AS CUST FROM DUAL
UNION ALL
SELECT '8' AS CUST FROM DUAL
UNION ALL
SELECT '9' AS CUST FROM DUAL;
SELECT CUST, EMP
FROM (SELECT ROW_NUMBER () OVER (PARTITION BY 1 ORDER BY EMP) AS ID, EMP
FROM EMP) EMP
INNER JOIN CUST ON MOD (TO_NUMBER (CUST.CUST) - 1, 3) = EMP.ID - 1
ORDER BY 1;

Use SUM function in oracle

I have a table in Oracle which contains :
id | month | payment | rev
----------------------------
A | 1 | 10 | 0
A | 2 | 20 | 0
A | 2 | 30 | 1
A | 3 | 40 | 0
A | 4 | 50 | 0
A | 4 | 60 | 1
A | 4 | 70 | 2
I want to calculate the payment column (SUM(payment)). For (id=A month=2) and (id=A month=4), I just want to take the greatest value from REV column. So that the sum is (10+30+40+70)=150. How to do it?
You can also use below.
select id,sum(payment) as value
from
(
select id,month,max(payment) from table1
group by id,month
)
group by id
Edit: for checking greatest rev value
select id,sum(payment) as value
from (
select id,month,rev,payment ,row_number() over (partition by id,month order by rev desc) as rno from table1
) where rno=1
group by id
This presupposes you don't have more than one value per rev. If that's not the case, then you probably want a row_number analytic instead of max.
with latest as (
select
id, month, payment, rev,
max (rev) over (partition by id, month) as max_rev
from table1
)
select sum (payment)
from latest
where rev = max_rev
Or there's this, if I've understood the requirement right:
with demo as (
select 'A'as id, 1 as month, 10 as payment, 0 as rev from dual
union all select 'A',2,20,0 from dual
union all select 'A',2,30,1 from dual
union all select 'A',3,40,0 from dual
union all select 'A',4,50,0 from dual
union all select 'A',4,60,1 from dual
union all select 'A',4,70,2 from dual
)
select sum(payment) keep (dense_rank last order by rev)
from demo;
You can check the breakdown by including the key columns:
with demo as (
select 'A'as id, 1 as month, 10 as payment, 0 as rev from dual
union all select 'A',2,20,0 from dual
union all select 'A',2,30,1 from dual
union all select 'A',3,40,0 from dual
union all select 'A',4,50,0 from dual
union all select 'A',4,60,1 from dual
union all select 'A',4,70,2 from dual
)
select id, month, max(rev)
, sum(payment) keep (dense_rank last order by rev)
from demo
group by id, month;
select sum(payment) from tableName where id='A' and month=2 OR month=4 order by payment asc;

How to group data according the condition and sequentially to number these groups?

I have next data:
with t as
(select 1 as id, '1324345' as amount, 7821 as code
from dual
union all
select 2 as id, 'current' as amount, 2210 as code
from dual
union all
select 3 as id, 'link' as amount, 2210 as code
from dual
union all
select 4 as id, '56236400' as amount, 6740 as code
from dual
union all
select 5 as id, '45562330' as amount, 5578 as code
from dual
union all
select 6 as id, '34875930' as amount, 5828 as code
from dual
union all
select 7 as id, 'current' as amount, 8520 as code
from dual
union all
select 8 as id, 'link' as amount, 8520 as code
from dual
union all
select 9 as id, '6731347060' as amount, 4740 as code
from dual
union all
select 10 as id, '346008600' as amount, 6575 as code
from dual)
select * from t
and I want to get the following:
with t as
(select 1 as id, '1324345' as amount, 7821 as code, 1 as group_id
from dual
union all
select 2 as id, 'current' as amount, 2210 as code, 2 as group_id
from dual
union all
select 3 as id, 'link' as amount, 2210 as code, 2 as group_id
from dual
union all
select 4 as id, '56236400' as amount, 6740 as code, 3 as group_id
from dual
union all
select 5 as id, '45562330' as amount, 5578 as code, 3 as group_id
from dual
union all
select 6 as id, '34875930' as amount, 5828 as code, 3 as group_id
from dual
union all
select 7 as id, 'current' as amount, 8520 as code, 4 as group_id
from dual
union all
select 8 as id, 'link' as amount, 8520 as code, 4 as group_id
from dual
union all
select 9 as id, '6731347060' as amount, 4740 as code, 5 as group_id
from dual
union all
select 10 as id, '346008600' as amount, 6575 as code, 5 as group_id
from dual)
select * from t
The condition is the value of "amount" field. It may be number or text.
UPD: Expected result:
id | amount | code | group_id
---------------------------------------------
1 | 1324345 | 7821 | 1
---------------------------------------------
2 | current | 2210 | 2
---------------------------------------------
3 | link | 2210 | 2
---------------------------------------------
4 | 56236400 | 6740 | 3
---------------------------------------------
5 | 45562330 | 5578 | 3
---------------------------------------------
6 | 34875930 | 5828 | 3
---------------------------------------------
7 | current | 8520 | 4
---------------------------------------------
8 | link | 8520 | 4
---------------------------------------------
9 | 6731347060 | 4740 | 5
---------------------------------------------
10 | 346008600 | 6575 | 5
---------------------------------------------
EDIT: best solution:
with tmain as
(select t.*,
decode(isnumeric(Amount),
lag(isnumeric(Amount)) over(order by id),
null,
1) lg
from t
order by id)
select id, amount, code, count(lg) over(order by id) group_id from tmain
Where isnumeric function is (based on #valexhome answer):
CREATE OR REPLACE FUNCTION ISNUMERIC (Str IN CHAR) RETURN NUMBER AS
TMP int;
BEGIN
if Str is null then
return(null);
end if;
TMP:=TO_NUMBER(Str);
RETURN (1);
EXCEPTION
WHEN OTHERS THEN
RETURN (0);
END;
Here is function ISNUMERIC to define before a query run:
CREATE OR REPLACE FUNCTION ISNUMERIC (Str IN CHAR) RETURN NUMBER AS
TMP int;
BEGIN
if Str is null then
return(null);
end if;
--if input null return NULL
TMP:=TO_NUMBER(Str);
RETURN (1);
EXCEPTION
WHEN OTHERS THEN
RETURN (0);
END;
And here is the query:
select id, amount, code,
(
select count(id)
from t Tab
where tab.id<=t.id
and
isnumeric(Amount)<>nvl(isnumeric((select Amount from t d1 where d1.id=(select max(d.id) from t d where (d.id<Tab.id)))),isnumeric(Amount)-1)
) Group_id
from t order by id
I found another solution:
with tmain as
(select t.*,
decode(isnumeric(Amount),
lag(isnumeric(Amount)) over(order by id),
null,
1) lg
from t
order by id)
select id, amount, code, count(lg) over(order by id) group_id from tmain
It works fine on large amounts of data.