Oracle query for grouping - sql

I have a requirement for fetching records from a group which met some condition.Please find my input set of records below
ID FIRSTNAME SURNAME
123 E Mcilwham
123 Emma Mcilwham
123 Enda Mcilwham
321 Lion Mark
321 Lous Mark
342 L Isaac
342 L Isaac
455 Lewis hoting
455 L hoting
325 D Mark
In this record I need to do a group by based on ID and Surname.I need output in such a way that, for the same ID there should be at least one record whose length is 1 and for the same ID there should be other records whose length is greater that 1 and those record should start with the same letter as of the record whose length is 1.
In the above example ID:- 123,455 will cover the above said scenario.
Also I am not expecting any record in output where there is only record for that ID.(ID:- 325).
With the case of ID 342, this shouldn't come in the output as there are no records for this ID whose length is greater than 1.
Hope this is clear to every one. Please feel free to ask me if require more clarifications on this
Please find the query which I have used below, which is not giving me proper result as expected.
SELECT
C.ID,
C.FIRST_NAME,
C.SURNAME
FROM TABLE1 C
WHERE C.ID IN
(SELECT DISTINCT A.ID
FROM TABLE1 A ,
TABLE1 B
WHERE A.ID = B.ID
AND LENGTH(TRIM(A.FIRST_NAME)) <> LENGTH(TRIM(B.FIRST_NAME))
-- AND LENGTH(TRIM(B.FIRST_NAME)) <> LENGTH(TRIM(A.FIRST_NAME))
AND LENGTH(TRIM(A.FIRST_NAME)) = 1 OR LENGTH(TRIM(B.FIRST_NAME)) = 1
AND SUBSTR(TRIM(A.FIRST_NAME),1,1) = SUBSTR(TRIM(B.FIRST_NAME),1,1)
--AND SUBSTR(TRIM(B.FIRST_NAME),1,1) = SUBSTR(TRIM(A.FIRST_NAME),1,1)
AND TRIM(A.SURNAME) = TRIM(B.SURNAME)
)
ORDER BY 1

Use conditional aggregation over ID and surname:
select
id,
firstname,
surname
from
(
select
id,
firstname,
surname,
min(length(firstname)) over (partition by id, surname) as min_length,
max(length(firstname)) over (partition by id, surname) as max_length,
count(distinct substr(firstname, 1, 1))
over (partition by id, surname) as count_distinct_initials
from table1
)
where min_length = 1
and max_length > 1
and count_distinct_initials = 1
order by id, surname, firstname;

The following query satisfies all requirements that you specified for your specific data set. Depending on the actual data that you have to work with, you may need to be more specific.
with sample_data as(
select 123 as id, 'E' as firstname, 'Mcilwham' as surname from dual union all
select 123 as id, 'Emma' as firstname, 'Mcilwham' as surname from dual union all
select 123 as id, 'Enda' as firstname, 'Mcilwham' as surname from dual union all
select 321 as id, 'Lion' as firstname, 'Mark' as surname from dual union all
select 321 as id, 'Lous' as firstname, 'Mark' as surname from dual union all
select 342 as id, 'L' as firstname, 'Isaac' as surname from dual union all
select 342 as id, 'L' as firstname, 'Isaac' as surname from dual union all
select 455 as id, 'Lewis' as firstname, 'hoting' as surname from dual union all
select 455 as id, 'L' as firstname, 'hoting' as surname from dual union all
select 325 as id, 'D' as firstname, 'Mark' as surname from dual
)
select id
,surname
,listagg(firstname, ',') within group (order by firstname) as firstnames
,count(*) as nof_firstnames
from sample_data a
group
by id
,surname
having count(*) > 1 -- at least one record
and min(length(firstname)) = 1 -- at least one record has length = 1
and max(length(firstname)) > 1 -- at least one record has a length greater than 1
In this record I need to do a group by based on ID and Surname.I need
output in such a way that, for the same ID there should be at least
one record whose length is 1 and for the same ID there should be other
records whose length is greater that 1 and those record should start
with the same letter as of the record whose length is 1.
What would happen when there are multiple records for length = 1 and length > 1?For example Alexander does not start with 'R'. Bengt does not start with 'A' but there is a record 'B'. Cedric starts with 'C' but it doesn't match the 'A' record.
ID FIRSTNAME SURNAME
1 R Bahlsten
1 Ronnie Bahlsten
1 Alexander Bahlsten
2 A Andersson
2 Anna Andersson
2 B Andersson
2 Bengt Andersson
3 C Diggory
3 A Diggory
3 Cedric Diggory

Related

Write a Query to show Id, Name and No. of department?

I am trying to write a Query to show Id, Name and No. of department in given Table which are referring more than one department.
ID Name Department
-- ---- ----------
1 Sam HR
1 Sam FINANCE
2 Ron PAYROLL
3 Kia HR
3 Kia IT
Result :
ID Name Department
-- ---- ----------
1 Sam 2
3 Kia 2
I tried using group by id and using count(*), but query is giving error.
How can I do this?
Without seeing your query, a blind guess is that you wrongly wrote the GROUP BY clause (if you used it) and forgot to include the HAVING clause.
Anyway, something like this might be what you're looking for:
SQL> with test (id, name, department) as
2 (select 1, 'sam', 'hr' from dual union
3 select 1, 'sam', 'finance' from dual union
4 select 2, 'ron', 'payroll' from dual union
5 select 3, 'kia', 'hr' from dual union
6 select 3, 'kia', 'it' from dual
7 )
8 select id, name, count(*)
9 from test
10 group by id, name
11 having count(*) > 1
12 order by id;
ID NAM COUNT(*)
---------- --- ----------
1 sam 2
3 kia 2
SQL>
You were right about using count(). You need to group by other columns though and only count unique departments then filter on the number in having clause.
select id, name, count(distinct department) as no_of_department
from table
group by id, name
having count(distinct department) > 1
This can also be done using analytic functions like below:
select *
from (
select id, name, count(distinct department) over (partition by id, name) as no_of_department
from table
) t
where no_of_department > 1
You can use window function with subquery :
select distinct id, name, Noofdepartment
from (select t.*, count(*) over (partition by id,name) Noofdepartment
from table t
) t
where Noofdepartment > 1;
However, you can also use group by clause:
select id, name, count(*) as Noofdepartment
from table t
group by id, name
having count(*) > 1;

Oracle SQL - Identify sequential value ranges

Here is my table:
ID Name Department
1 Michael Marketing
2 Alex Marketing
3 Tom Marketing
4 John Sales
5 Brad Marketing
6 Leo Marketing
7 Kevin Production
I am trying to find ID ranges where Department = 'Marketing':
Range From To
Range1 1 3
Range2 5 6
Any help would be appreciated.
This is easy to do with a technique called Tabibitosan.
What this technique does is compare the positions of each group's rows to the overall set of rows, in order to work out if rows in the same group are next to each other or not.
E.g., with your example data, this looks like:
WITH your_table AS (SELECT 1 ID, 'Michael' NAME, 'Marketing' department FROM dual UNION ALL
SELECT 2 ID, 'Alex' NAME, 'Marketing' department FROM dual UNION ALL
SELECT 3 ID, 'Tom' NAME, 'Marketing' department FROM dual UNION ALL
SELECT 4 ID, 'John' NAME, 'Sales' department FROM dual UNION ALL
SELECT 5 ID, 'Brad' NAME, 'Marketing' department FROM dual UNION ALL
SELECT 6 ID, 'Leo' NAME, 'Marketing' department FROM dual UNION ALL
SELECT 7 ID, 'Kevin' NAME, 'Production' department FROM dual)
-- end of mimicking your table with data in it. See the SQL below:
SELECT ID,
NAME,
department,
row_number() OVER (ORDER BY ID) overall_rn,
row_number() OVER (PARTITION BY department ORDER BY ID) department_rn,
row_number() OVER (ORDER BY ID) - row_number() OVER (PARTITION BY department ORDER BY ID) grp
FROM your_table;
ID NAME DEPARTMENT OVERALL_RN DEPARTMENT_RN GRP
---------- ------- ---------- ---------- ------------- ----------
1 Michael Marketing 1 1 0
2 Alex Marketing 2 2 0
3 Tom Marketing 3 3 0
4 John Sales 4 1 3
5 Brad Marketing 5 4 1
6 Leo Marketing 6 5 1
7 Kevin Production 7 1 6
Here, I've given all the rows across the entire set of data a row number in ascending id order (the overall_rn column), and I've given the rows in each department a row number (the department_rn column), again in ascending id order.
Now that I've done that, we can subtract one from the other (the grp column).
Notice how the number in the grp column remains the same for deparment rows that are next to each other, but it changes each time there's a gap.
E.g. for the Marketing department, rows 1-3 are next to each other and have grp = 0, but the 4th Marketing row is actually on the 5th row of the overall results set, so it now has a different grp number. Since the 5th marketing row is on the 6th row of the overall set, it has the same grp number as the 4th marketing row, so we know they're next to each other.
Once we have that grp information, it's a simple matter of doing an aggregate query grouping on both the department and our new grp column, using min and max to find the start and end ids:
WITH your_table AS (SELECT 1 ID, 'Michael' NAME, 'Marketing' department FROM dual UNION ALL
SELECT 2 ID, 'Alex' NAME, 'Marketing' department FROM dual UNION ALL
SELECT 3 ID, 'Tom' NAME, 'Marketing' department FROM dual UNION ALL
SELECT 4 ID, 'John' NAME, 'Sales' department FROM dual UNION ALL
SELECT 5 ID, 'Brad' NAME, 'Marketing' department FROM dual UNION ALL
SELECT 6 ID, 'Leo' NAME, 'Marketing' department FROM dual UNION ALL
SELECT 7 ID, 'Kevin' NAME, 'Production' department FROM dual)
-- end of mimicking your table with data in it. See the SQL below:
SELECT department,
MIN(ID) start_id,
MAX(ID) end_id
FROM (SELECT ID,
NAME,
department,
row_number() OVER (ORDER BY ID) - row_number() OVER (PARTITION BY department ORDER BY ID) grp
FROM your_table)
GROUP BY department, grp;
DEPARTMENT START_ID END_ID
---------- ---------- ----------
Marketing 1 3
Marketing 5 6
Sales 4 4
Production 7 7
N.B., I've assumed that gaps in the id columns aren't important (i.e. if there was no row for id = 6 (so Leo and Kevin's ids were 7 and 8 respectively), then Leo and Brad would still appear in the same group, with a start id = 5 and end id = 7.
If gaps in the id columns count as indicating a new group, then you could just use the id to label the overall set of rows (i.e. no need to caluclate the overall_rn; just use the id column instead).
That means your query would become:
WITH your_table AS (SELECT 1 ID, 'Michael' NAME, 'Marketing' department FROM dual UNION ALL
SELECT 2 ID, 'Alex' NAME, 'Marketing' department FROM dual UNION ALL
SELECT 3 ID, 'Tom' NAME, 'Marketing' department FROM dual UNION ALL
SELECT 4 ID, 'John' NAME, 'Sales' department FROM dual UNION ALL
SELECT 5 ID, 'Brad' NAME, 'Marketing' department FROM dual UNION ALL
SELECT 7 ID, 'Leo' NAME, 'Marketing' department FROM dual UNION ALL
SELECT 8 ID, 'Kevin' NAME, 'Production' department FROM dual)
-- end of mimicking your table with data in it. See the SQL below:
SELECT department,
MIN(ID) start_id,
MAX(ID) end_id
FROM (SELECT ID,
NAME,
department,
ID - row_number() OVER (PARTITION BY department ORDER BY ID) grp
FROM your_table)
GROUP BY department, grp;
DEPARTMENT START_ID END_ID
---------- ---------- ----------
Marketing 1 3
Sales 4 4
Marketing 5 5
Marketing 7 7
Production 8 8
I don't have the environment currently but you can try something like this
select * from tab1 where id in
(select min(id) from tab1 where Department = 'Marketing'
union
select max(id) from tab1 where Department = 'Marketing')

How can I select unique and duplicated rows exlude with different values

How can I fetch this table as expected in Oracle. I'm try to do this like below select but its not give me the right result. What I expect is fetch only the unique ones and exclude if these is a record like different values. Sorry for if asked before but I couldn't find it.
SELECT *
FROM ...
WHERE number IN ( SELECT name
FROM (SELECT *
FROM table
WHERE number IN ('Mel','Jose','Kim')
) ds
GROUP BY number
HAVING COUNT (*) = 1)
Current result:
number name
aaa Mel
asd Jose
fsa Jose
xdf Jose
zzz Kim
zzz Kim
Expected result:
aaa Mel
zzz Kim
You're close - I think you were just missing the distinct in the count in your having clause.
E.g.:
WITH your_table AS (SELECT 100 nmbr, 'Mel' NAME FROM dual UNION ALL
SELECT 112 nmbr, 'Jose' NAME FROM dual UNION ALL
SELECT 212 nmbr, 'Jose' NAME FROM dual UNION ALL
SELECT 313 nmbr, 'Jose' NAME FROM dual UNION ALL
SELECT 101 nmbr, 'Kim' NAME FROM dual UNION ALL
SELECT 101 nmbr, 'Kim' NAME FROM dual)
-- end of mimicking data in your table
-- you already have this table, so you would just need the below sql:
SELECT min(nmbr) nmbr,
NAME
FROM your_table
GROUP BY NAME
HAVING COUNT(DISTINCT nmbr) = 1;
NMBR NAME
---------- ----
101 Kim
100 Mel
Just to prove that it doesn't matter whether the nmbr column is of NUMBER or VARCHAR2 datatype:
WITH your_table AS (SELECT 'aaa' nmbr, 'Mel' NAME FROM dual UNION ALL
SELECT 'asd' nmbr, 'Jose' NAME FROM dual UNION ALL
SELECT 'fsa' nmbr, 'Jose' NAME FROM dual UNION ALL
SELECT 'xfd' nmbr, 'Jose' NAME FROM dual UNION ALL
SELECT 'zzz' nmbr, 'Kim' NAME FROM dual UNION ALL
SELECT 'zzz' nmbr, 'Kim' NAME FROM dual)
-- end of mimicking data in your table
-- you already have this table, so you would just need the below sql:
SELECT min(nmbr) nmbr,
NAME
FROM your_table
GROUP BY NAME
HAVING COUNT(DISTINCT nmbr) = 1;
NMBR NAME
---- ----
zzz Kim
aaa Mel
You can do by nested sql with the inner part eliminate repeating ones with respect to id & name, and in the outer part eliminate repeating ones with only name like in the following statement :
SELECT MAX(id),name
FROM (SELECT id,name FROM mytable GROUP BY id, name)
GROUP BY name
HAVING COUNT(1) = 1
ORDER BY MAX(id);
OUTPUT:
ID NAME
----- ------
100 Mel
101 Kim
D e m o 1
exactly the same sql works for your second case :
D e m o 2

Oracle select pivot query to put row adjacent to their counter part using generic column name

I have a table which each entry has a counter pair
Customer
Name Value
Bob 3
Bob 4
Sam 0
Sam 1
Joe 9
I want the following result
Customer
Name Value1 Value2
Bob 3 4
Sam 0 1
Joe 9
I have read this thread, Oracle query to put rows at odd number adjacent to even number, but I want to avoid using the MOD function instead possible using pivot instead.
You can't use the pivot statement here, if you have only two value for each name (it also works with dates, because we can use max and min for dates):
select name, min(value) value1, nullif(max(value), min(value)) value2
from customer_tables
group by name
If Bob, Sam and other have more that two value:
with t (Name, Value) as (
select 'Bob',3 from dual union all
select 'Bob',4 from dual union all
select 'Sam',0 from dual union all
select 'Sam',1 from dual union all
select 'Joe',9 from dual
), t1 (name, value, rn) as (
select name, value, ROW_NUMBER() OVER(partition by name order by value) from t
)
select * from t1
pivot XML (
max(value)
for rn in (ANY)
)
SQL> l
1 with t (Name, Value) as (
2 select 'Bob',3 from dual union all
3 select 'Bob',4 from dual union all
4 select 'Sam',0 from dual union all
5 select 'Sam',1 from dual union all
6 select 'Joe',9 from dual
7 ), t1 (name, value, rn) as (
8 select name, value, ROW_NUMBER() OVER(partition by name order by value) from t
9 )
10 select * from t1
11 pivot XML (
12 max(value)
13 for rn in (ANY)
14* )
SQL> /
NAM RN_XML
--- --------------------------------------------------------------------------------
Bob <PivotSet><item><column name = "RN">1</column><column name = "MAX(VALUE)">3</col
umn></item><item><column name = "RN">2</column><column name = "MAX(VALUE)">4</co
lumn></item></PivotSet>
Joe <PivotSet><item><column name = "RN">1</column><column name = "MAX(VALUE)">9</col
umn></item></PivotSet>
Sam <PivotSet><item><column name = "RN">1</column><column name = "MAX(VALUE)">0</col
umn></item><item><column name = "RN">2</column><column name = "MAX(VALUE)">1</co
lumn></item></PivotSet>
Read more about pivot here

SQL find nearest number

Say I have a table like the following (I'm on Oracle 10g btw)
NAME VALUE
------ ------
BOB 1
BOB 2
BOB 4
SUZY 1
SUZY 2
SUZY 3
How can I select all rows where value is closest to, but not greater than, a given number. For example if I want to find all the rows where value is closest to 3 I would get:
NAME VALUE
------ ------
BOB 2
SUZY 3
This seems like it should be simple... but I'm having no luck.
Thanks!
SELECT name, max(value)
FROM tbl
WHERE value <= 3
GROUP BY name
This works (SQLFiddle demo):
SELECT name, max(value)
FROM mytable
WHERE value <= 3
GROUP BY name
Based on hagensofts answer:
SELECT name, max(value)
FROM tbl
WHERE value <= 3 AND ROWNUM <=2
GROUP BY name
With ROWNUM you can limit the output rows, so if you want 2 row, then you can limit the rownum.
WITH v AS (
SELECT 'BOB' NAME, 1 value FROM dual
UNION ALL
SELECT 'BOB', 2 FROM dual
UNION ALL
SELECT 'BOB', 4 FROM dual
UNION ALL
SELECT 'SUZY', 1 FROM dual
UNION ALL
SELECT 'SUZY', 2 FROM dual
UNION ALL
SELECT 'SUZY', 3 FROM dual
)
SELECT *
FROM v
WHERE (name, value) IN (SELECT name, MAX(value)
FROM v
WHERE value <= :num
GROUP BY name)
;