Oracle SQL - Identify sequential value ranges - sql

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')

Related

Get duplicate employee count department wise in single sql

ID Name dep_id
1 A 1
2 B 2
3 A 1
4 A 2
5 B 2
6 A 2
I think you want to have such a SQL
with tab( ID, Name, dep_id) as
(
select 1,'A',1 union all
select 2,'B',2 union all
select 3,'A',1 union all
select 4,'A',2 union all
select 5,'B',2 union all
select 6,'A',2
)
select name,
count(dep_id) as dept_count
from tab t
group by name
having count(name)>1;
NAME DEPT_COUNT
---- ----------
A 4
B 2
Due to you last edit( which you wanted to add to this answer ), consider grouping also by dept_id :
with tab( ID, Name, dep_id) as
(
select 1,'A',1 union all
select 2,'B',2 union all
select 3,'A',1 union all
select 4,'A',2 union all
select 5,'B',2 union all
select 6,'A',2
)
select name, dept_id,
count(dept_id) as dept_count
from tab t
group by name, dept_id
having count(name)>1;
NAME DEPT_ID DEPT_COUNT
---- ------ ----------
A 2 2
A 1 2
B 2 2

Seat arrangement using Oracle SQL for examination

I was trying to arrange seat for an examination from the below dataset.
and the output dataset would be like the below(alternate department student one after another)
I am unable to get the desire output. Please help me on that. I am using the Oracle 11g express edition.
http://sqlfiddle.com/#!4/510071/1
Using ROW_NUMBER analytic function, create sort order for each department; then select values sorted by that number.
For example:
SQL> with test (roll_no, name, department) as
2 (select 1, 'anik', 'cse' from dual union all
3 select 2, 'sudipto', 'cse' from dual union all
4 select 3, 'injamam', 'cse' from dual union all
5 select 8, 'sajukta', 'ece' from dual union all
6 select 9, 'gourab', 'ece' from dual union all
7 select 10, 'soumenn', 'ece' from dual),
8 inter as
9 (select roll_no, name, department,
10 row_number() over (partition by department order by roll_no) rn
11 from test
12 )
13 select roll_no, name, department
14 from inter
15 order by rn, department;
ROLL_NO NAME DEP
---------- ------- ---
1 anik cse
8 sajukta ece
2 sudipto cse
9 gourab ece
3 injamam cse
10 soumenn ece
6 rows selected.
SQL>
You seem to want them interleaved. If so, use row_number() in the order by:
select s.*
from student s
order by row_number() over (partition by "department" order by "roll_no"),
"department";
Here is the SQL Fiddle.
Note: Don't wrap column names in double quotes. That means that the case of the identifier matters -- and just makes queries harder to write.

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 query for grouping

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

Oracle SQL to generate interleaved SQL results

Hi i am looking for a way to write a SQL statement which will come out with the following results :-
Lets say we have Dept & Emp ID i would like to generate like records from Dept 3 for the first two rows then followed by Dept 2 with one row then continue Dept 3 and so on :
DEPT EMPID
----- ------
3 1
3 2
2 3
3 7
3 8
2 9
Thank You.
You could use something like this
SELECT
DEPT,
EMPID
FROM (
SELECT
*,
ceil((row_number() OVER (PARTITION BY dept ORDER BY EMPID ))/ 2::numeric(5,2)) AS multiple_row_dept,
row_number() OVER (PARTITION BY dept ORDER BY EMPID ) AS single_row_dept
FROM
test_data2
) sub_query
ORDER BY
CASE
WHEN DEPT = 2 THEN single_row_dept
ELSE multiple_row_dept
END,
DEPT DESC,
EMPID
single_row_dept specifics which dept should appear only once, in this case its DEPT 2 followed by multiple other departments
First order a table by empid in a subquery,
then calculate a remainder of rowids divided by 3,
then depending on a result of calculation, return 2 or 3, using case expression,
like this
SELECT
CASE REMAINDER( rownum, 3 )
WHEN 0 THEN 2
ELSE 3
END As DeptId,
empid
FROM (
SELECT empid
FROM table1
ORDER BY empid
)
demo: http://sqlfiddle.com/#!4/bd1bb/3
The following code has some limitations:
1) There are only 2 DepId's in the source table
2) Ratio between records having different DepId is exactly 2:1
3) Ordering in one place of the query should be changed depending whether DeptId having more records is naturally greater then the other or not
with t as (
select 3 dept_id, 1 emp_id from dual
union all
select 3, 2 from dual
union all
select 3, 7 from dual
union all
select 3, 8 from dual
union all
select 2, 3 from dual
union all
select 2, 9 from dual)
select dept_id, emp_id
from (select dept_id,
emp_id,
dense_rank() over(partition by dept_id order by in_num + mod(in_num, out_num)) ord
from (select dept_id,
emp_id,
row_number() over(partition by dept_id order by emp_id) in_num,
/*in the following ORDER BY change to DESC or ASC depending on which dept_id has more records*/
dense_rank() over(order by dept_id) out_num
from t))
order by ord, dept_id desc;
DEPT_ID EMP_ID
---------- ----------
3 1
3 2
2 3
3 8
3 7
2 9