Oracle creating N rows per value - sql

How can I modify the CTE below to INSERT rows into a new table T.
Based on the output below for table T I should have 4 rows for employee_id =1, 1 row for employee_id=3 and
2 rows for employee_id=7
CREATE TABLE T
(seq_num integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
employee_id NUMBER(6));
CREATE TABLE employees (
employee_id NUMBER(6)
);
INSERT INTO employees
(employee_id)
VALUES (1);
INSERT INTO employees
(employee_id)
VALUES (3);
INSERT INTO employees
(employee_id)
VALUES (7);
with rws as (
select level rn
from dual
connect by level <= 5
), emps as (
select /*+
materialize */e.*,
round (
dbms_random.value(1,5) ) n
from employees e
)
select employee_id, count (*) rw_count
from rws
join emps e
on rn <= n
group by employee_id
Order by employee_id;
EMPLOYEE_ID RW_COUNT
1 4
3 1
7 2

Just don't use group by. It's almost untouched your query, I only added insert and removed aggregation:
insert into t (employee_id)
with rws as ( select level rn from dual connect by level <= 5 ),
emps as ( select e.*, round (dbms_random.value(1,5) ) n from employees e )
select employee_id
from rws
join emps e on rn <= n
order by employee_id;
dbfiddle

Related

MERGE Statment insert into from select pl sql

How to create merge statment that insert into table from onther select statment
my example is :
MERGE INTO employees t
USING (SELECT :dept_id as dept_id FROM dual ) d
ON (t.dept_id = d.dept_id )
WHEN NOT MATCHED THEN
INSERT INTO employees (
ename,
fname )
SELECT
ename,
fname
FROM
trans_emps
where trans_id = :trans_id;
You can use INSERT INTO with a NOT EXISTS filter:
INSERT INTO employees (ename, fname)
SELECT ename, fname
FROM trans_emps
WHERE trans_id = :trans_id
AND NOT EXISTS( SELECT 1 FROM employees WHERE dept_id = :dept_id)
Or you can MERGE and move the SELECT .. FROM trans_emps into the USING clause:
MERGE INTO employees dst
USING (
SELECT ename, fname
FROM trans_emps
WHERE trans_id = :trans_id
) src
ON (dst.dept_id = :dept_id)
WHEN NOT MATCHED THEN
INSERT ( ename, fname )
VALUES ( src.ename, src.fname );
merge doesn't support such a syntax; insert clause can contain only values keyword (and list of values you're inserting into columns).
Switch to insert only (as your merge doesn't contain when matched clause anyway, so you aren't updating any rows in employees):
insert into employees (ename, fname)
select te.ename,
te.fname
from trans_emps te
where te.trans_id = :trans_id
and exists (select null
from employees a
where a.dept_id = :dept_id
);

Counting the hierarchy

I have a table with a hierarchy:
CREATE TABLE mng
(
id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR(50) NOT NULL,
manager_id INTEGER
);
INSERT INTO mng (id, name, manager_id) VALUES (1, 'Lola', NULL);
INSERT INTO mng (id, name, manager_id) VALUES (2, 'Bella', NULL);
INSERT INTO mng (id, name, manager_id) VALUES (3, 'Lo', 1);
INSERT INTO mng (id, name, manager_id) VALUES (4, 'Ann', 2);
INSERT INTO mng (id, name, manager_id) VALUES (5, 'Ki', 3);
INSERT INTO mng (id, name, manager_id) VALUES (6, 'Qo', 5);
I need to print all top managers (where manager_id is NULL) with their subordinates count (including all levels)
id | cnt
--------+---------
1 | 3
2 | 1
upd
was trying something like this:
WITH DirectReports(ManagerID, Employee) AS
(
SELECT id, 0 AS Employee
FROM mng
WHERE manager_id IS NULL
UNION ALL
SELECT e.id, Employee + 1
FROM mng AS e
INNER JOIN DirectReports AS d
ON e.manager_id = d.ManagerID
)
SELECT ManagerID, Employee
FROM DirectReports
ORDER BY ManagerID;
got level number, but how to get count?
The issue with your original code is you lose track of the "top manager". You can, however, retain this within the CTE, and simply have it listed for every employee:
WITH DirectReports(id, TopMgr, ManagerID, Employee) AS
(
SELECT id, id, NULL, 0 AS Employee
FROM mng
WHERE manager_id IS NULL
UNION ALL
SELECT e.id, d.TopMgr, e.manager_id, 1 AS Employee
FROM mng AS e
INNER JOIN DirectReports AS d
ON e.manager_id = d.id
)
SELECT TopMgr, SUM(Employee)
FROM DirectReports
GROUP BY TopMgr
ORDER BY TopMgr
Output:
TopMgr EmployeeCount
1 3
2 1
First you can get the employees under manager, top level manager. Later, you can count the employees under top level manager.
;WITH DirectReports(EmployeeId, ManagerID, TopMostManagerId) AS
(
SELECT id, Null, id
FROM #mng
WHERE manager_id IS NULL
UNION ALL
SELECT e.id, e.manager_Id, d.TopMostManagerId
FROM #mng AS e
INNER JOIN DirectReports AS d
ON e.manager_id = d.EmployeeId
)
SELECT TopMostManagerId, COUNT(EmployeeId) as CountOfEmployees
FROM DirectReports
WHERE ManagerID is not null -- excludes toplevel manager
group by TopMostManagerId
TopMostManagerId
CountOfEmployees
1
3
2
1

Random records for a row

I have the following table strvals() with data. I'm looking to randomly choose 2 rows from strval() table and populate table S1 in a loop.
I want something like this
Create table s1(id primary key,strval1, strval2) as
select level,random_rec(strvals), random_rec(strvals)
from dual
connect by level<=10;
The caveat is the column strval1 has to be different THEN strval2 for each row.
Valid output
1, 'AAAA', 'BBBB'
2, 'CCCC', 'BBBB'
3, 'CCCC', 'AAAA'
Not valid
1, 'AAAA', 'AAAA'
Create table strvals(
strval varchar2(4),
constraint pk_strval primary key (strval)
);
insert into strvals
values(
'AAAA'
);
insert into strvals
values(
'BBBB'
);
insert into strvals
values(
'CCCC'
);
Getting a random string from a table can be tricky. One method is to use correlated subqueries -- the correlation clause ensures that the subquery is not "optimized" to run only once.
So, here is one method:
select id, strval,
(select s2.strval
from strvals s2
where s2.strval <> x.strval and x.id > 0
order by dbms_random.random fetch first 1 row only
) as strval2
from (select id,
(select strval
from strvals
where x.id > 0
order by dbms_random.random fetch first 1 row only
) as strval
from (select level as id
from dual
connect by level < 25
) x
) x;
And here is a db<>fiddle.
To reduce a number of slow dbms_random.value in case of many rows, I would suggest to use this technique:
with
rand as (select row_number()over(order by dbms_random.value()) n,strval from strvals)
,cnt as (select count(*) m from rand)
,generator as (select level id, ceil(dbms_random.value()*cnt.m) rnd from cnt connect by level<=10)
select generator.id, rand.strval
from generator
join rand on rand.n=generator.rnd;
As you can see, I sort the table strvals once by dbms_random.value (rand view), so each row has own random integer number, then I count them to get maximum N. And then I calculate random N for each generated row so we can join rand view using hash join.
Update: for 2 strvals: DBFiddle
with
rand as (select row_number()over(order by dbms_random.value()) n,strval from strvals)
,cnt as (select count(*) m from rand)
,generator as (
select
level id,
ceil(dbms_random.value()*cnt.m) rnd1,
ceil(dbms_random.value()*cnt.m) rnd2
from cnt connect by level<=10
)
select
generator.id,
rnd1.strval,
rnd2.strval
from generator
join rand rnd1 on rnd1.n=generator.rnd1
join rand rnd2 on rnd2.n=generator.rnd2;
Update2: now with 2 strvals: DBFiddle
with
rand as (
select
row_number()over(order by dbms_random.value()) n
,s1.strval strval1
,s2.strval strval2
from strvals s1
join strvals s2
on s1.strval!=s2.strval
)
,cnt as (select count(*) m from rand)
,generator as (
select
level id,
ceil(dbms_random.value()*cnt.m) rnd
from cnt connect by level<=10
)
select
generator.id,
rand.strval1,
rand.strval2
from generator
join rand on rand.n=generator.rnd
order by 1
;

Max sal to print base on manager_id

I have this table:
id mgr_id sal
1 5 5000
2 5 6000
3 6 7000
4 6 8000
I expect this output:
id mgr_id sal MaX_sal
1 5 5000 6000
2 5 6000 6000
3 6 7000 8000
4 6 8000 8000
Based on mgr_id select max sal and print in front of id.
Thanks in advance.
Possible would be a subselect inside the select (although a bit slow as all subselects are):
select id, mgr_id, sal, (select max(sal) from mytable n2
where n2.mgr_id = mytable.mgr_id) as max_sal from mytable
Create a subtable grouping by mrg_id to get the max Sal per mrg_id and then left join it with the principal table on mrg_id -- Try this :
declare #table table (id int,mrg_id int,sal decimal(10,0))
insert into #table
select 1, 5, 5000 union all
select 2, 5, 6000 union all
select 3, 6, 7000 union all
select 4, 6, 8000
/***** change #table with your table name *****/
SELECT
t.id,
t.mrg_id,
t.sal,
t1.max_sal
FROM #table t
LEFT JOIN
(SELECT
mrg_id,
MAX(sal) max_sal
FROM #table
group by
mrg_id) t1
on t.mrg_id=t1.mrg_id
This would work even in SQL Server 6.5
SELECT id, mgr_id, sal,
(
SELECT MAX(sal)
FROM employees mgr
WHERE mgr.mgr_id = emp.mgr_id
) AS max_sal
FROM
employees emp
This works on SQL Server 2005:
WITH max_sals AS
(
SELECT mgr_id, MAX(sal) AS max_sal
FROM employees
GROUP BY mgr_id
)
SELECT emp.*, max_sals.max_sal
FROM
employees emp
LEFT JOIN max_sals
ON emp.mgr_id = max_sals.mgr_id
and this using partiton:
SELECT emp.*, MAX(emp.sal) OVER (PARTITION BY emp.mgr_id) AS max_sal
FROM employees emp
Sample data and table:
CREATE TABLE employees
(
id int,
mgr_id int,
sal int
)
INSERT INTO employees VALUES
(1,5,5000),
(2,5,6000),
(3,6,7000),
(4,6,8000)
With SQL Server 2012 simply use this query:
SELECT d.id, d.mgr_id, d.sal, MAX(sal) over(partition by mgr_id)
FROM #data d
You can use this query with SQL Server <2012:
SELECT d.id, d.mgr_id, d.sal, m.mx
FROM #data d
INNER JOIN (
SELECT mgr_id, mx = MAX(sal) FROM #data
GROUP BY mgr_id
) m
ON m.mgr_id = d.mgr_id;
Another option with SQL Server >= 2005:
SELECT d.id, d.mgr_id, d.sal, m.mx
FROM #data d
CROSS APPLY (SELECT mx = MAX(sal) FROM #data m WHERE m.mgr_id = d.mgr_id) m(mx)
Sample data:
Declare #data table([id] int, [mgr_id] int, [sal] int);
INSERT INTO #data([id], [mgr_id], [sal])
VALUES
(1, 5, 5000),
(2, 5, 6000),
(3, 6, 7000),
(4, 6, 8000)
;
Try Cross Apply,
SELECT id,
mgr_id,
sal,
Max_sal
FROM #your_table A
CROSS APPLY (SELECT Max(SAL) AS Max_sal
FROM #your_table B
WHERE B.mgr_id = A.mgr_id)cs

how to get second highest salary department wise without using analytical functions?

Suppose we have 3 employees in each department.we have total 3 departments . Below is the sample source table
Emp deptno salary
A 10 1000
B 10 2000
C 10 3000
D 20 7000
E 20 9000
F 20 8000
G 30 17000
H 30 15000
I 30 30000
Output
B 10 2000
F 20 8000
G 30 17000
With using analytic function dense_rank we can achive the second highest salary dept wise.
Can we achieve this without using ANY analytic function ???
Is Max() is also analytic function ??
It is a pain, but you can do it. The following query gets the second highest salary:
select t.deptno, max(t.salary) as maxs
from table t
where t.salary < (select max(salary)
from table t2
where t2.deptno = t.deptno
)
group by t.deptno;
You can then use this to get the employee:
select t.*
from table t join
(select t.deptno, max(t.salary) as maxs
from table t
where t.salary < (select max(salary)
from table t2
where t2.deptno = t.deptno
)
group by t.deptno
) tt
on t.deptno = tt.deptno and t.salary = tt.maxs;
Create table and insert dummy data
CREATE TABLE #Employee
(
Id Int,
Name NVARCHAR(10),
Sal int,
deptId int
)
INSERT INTO #Employee VALUES
(1, 'Ashish',1000,1),
(2,'Gayle',3000,1),
(3, 'Salman',2000,2),
(4,'Prem',44000,2)
Query to get result
;WITH cteRowNum AS (
SELECT *,
DENSE_RANK() OVER(PARTITION BY deptId ORDER BY Sal DESC) AS RowNum
FROM #Employee
)
SELECT *
FROM cteRowNum
WHERE RowNum = 2;
This will give you 2nd highest salary in each department:
SELECT a.Emp, a.deptno, a.salary
FROM Emp a
WHERE 1 = (SELECT COUNT(DISTINCT salary)
FROM Emp b
WHERE b.salary > a.salary AND a.deptno = b.deptno)
group by a.deptno
Solution using Correlated Subquery:
SELECT * FROM emp e1 WHERE 2 = (SELECT COUNT(DISTINCT sal)
FROM emp e2
WHERE e1.sal <= e2.sal
AND e1.deptno = e2.deptno
);
Quite straightforward and declarative, but slow
select
t1.*
from
#tmp t1
inner join #tmp h1 on h1.dept = t1.dept and h1.emp <> t1.emp
left outer join #tmp h2 on h2.dept = h1.dept and h2.salary > h1.salary
left outer join #tmp t2 on t1.dept = t2.dept and t2.salary > t1.salary and t2.emp <> h1.emp
where
t2.emp is null and h2.emp is null
You can find 2nd highest salary something like this:
select max(a.Salary),a.Deptno from Employee a join (select MAX(salary) salary
from Employee group by Deptno) b on a.Salary < b.salary group by a.Deptno
And no MAX() is not an analytic function.
Reference
On MySQL this how you can get second highest salary, given table name is salaries:
By Nested Queries: (where you can change offset 0/1/2 for first, second and third place respectively)
select
*
from
salaries as t1
where
t1.salary = (select
salary
from
salaries
where
salaries.deptno = t1.deptno ORDER by salary desc limit 1 offset 1);
or might be by creating rank: (where you can change rank= 1/2/3 for first, second and third place respectively)
SET #prev_value = NULL;
SET #rank_count = 0;
select * from
(SELECT
s.*,
CASE
WHEN #prev_value = deptno THEN #rank_count := #rank_count + 1
WHEN #prev_value := deptno THEN #rank_count := 1
ELSE #rank_count := 1
END as rank
FROM salaries s
ORDER BY deptno, salary desc) as t
having t.rank = 2;
SQL Query:
select TOP 2 max(salary),Emp from EMployee where deptno='your_detpno'
Very simple logic.
Please try:
SELECT dept as dd, ( SELECT ee.salary FROM `employees` as ee WHERE ee.dept=dd
ORDER BY ee.salary DESC LIMIT 1,1 ) as sndHigh
FROM `employees`
WHERE 1
GROUP BY dept
select min(salary),deptno from
(SELECT distinct top 2 salary,deptno from table ORDER BY salary DESC)
as a
group by deptno
CREATE TABLE Employee
([Name] varchar(1), [Dept] varchar(1), [Salary] int)
;
INSERT INTO Employee
([Name], [Dept], [Salary])
VALUES
('a', 'M', 20),
('b', 'M', 25),
('c', 'M', 30),
('d', 'C', 44),
('e', 'C', 45),
('f', 'C', 46),
('g', 'H', 20)