SQL Employees Searching for Others in Same Department - sql

I have a business rule that employees cannot purchase items from employees of the same department. I have two tables. one is the list of employees and their IDs:
Emp_ID Emp_Name Dept_ID
1 John 1
2 Bob 1
3 Susie 2
4 Jack 3
5 Jill 3
And a table of the employee ID and the employee ID they purchased from:
Emp_ID Bought_From_Emp_ID
1 2
2 3
4 5
5 1
My expected output would be to have the the employee id (or name) of both employees if one purchased an item from the same department:
Emp_ID Bought_From_Emp_ID Same_Dept_ID
1 2 1 --John and Bob are in Same Department (1)
4 5 3 --Jack and Jill are in Same Department (3)
How would I do this for millions of records? I have a feeling that this is very simple in the long run, but my mind hasn't shifted towards the solution yet.
I am using Teradata, but can use MSSQL if there are any SQL-specific answers

DECLARE #emp TABLE
(
emp_id INT,
emp_name VARCHAR(20),
dept_id INT
);
INSERT INTO #emp
(
emp_id,
emp_name,
dept_id
)
VALUES
--Emp_ID Emp_Name Dept_ID
(1, 'John ', 1),
(2, 'Bob', 1),
(3, 'Susie', 2),
(4, 'Jack', 3),
(5, 'Jill', 3);
DECLARE #purch TABLE
(
emp_id INT,
Bought_From_Emp_ID INT
);
INSERT INTO #purch
(
emp_id,
Bought_From_Emp_ID
)
VALUES
(1, 2),
(2, 3),
(4, 5),
(5, 1);
SELECT e.emp_id,
e1.emp_id AS Bought_From_Emp_ID,
e.dept_id AS Same_Dept_ID
FROM #purch p
JOIN #emp e
ON p.emp_id = e.emp_id
JOIN #emp e1
ON p.Bought_From_Emp_ID = e1.emp_id
AND e.dept_id = e1.dept_id
WHERE e1.emp_id <> e.emp_id;

Try this query
SELECT purch.emp_id AS EmpID,
purch.bought_from_emp_id AS BoughtFrom,
T1.dept_id AS department
FROM purch
INNER JOIN emp T1
ON T1.emp_id = purch.emp_id
INNER JOIN emp T2
ON T2.emp_id = purch.bought_from_emp_id
WHERE t1.dept_id = t2.dept_id
Output
+-------+------------+------------+
| EmpID | BoughtFrom | department |
+-------+------------+------------+
| 1 | 2 | 1 |
| 4 | 5 | 3 |
+-------+------------+------------+
Demo: http://www.sqlfiddle.com/#!18/22746/1/0

Related

Line Number and attaching single employee

SQL Server 2000 so no ROW_NUMBER available ....
I need to attach employees to the free lines.
I have a dataset 1 that tells me the free lines per country and region combo.
Table A – available line numbers to use:
Country Region Line Number Employee
---------------------------------------------------
A 1 1 Null
A 1 2 Null
A 2 1 Null
Table B – what employees are available to fill missing line numbers:
Country Region Employee
----------------------------------------
A 1 Dave Smith
A 1 Johnny Cash
A 1 Peter Seller
A 2 David Donald
So required output is
Table C - attaching a single employee to each combo of country, region, line number:
Country Region Line Number Employee
-------------------------------------------------------------
A 1 1 Dave Smith
A 1 2 Johnny Cash
A 2 1 David Donald
I tried a lot of joins, including self joins, and cross joins in SQL Server 2000, but can't get the desired output.
This is my last attempt:
Select
A.Country, A.Region, A.Line Number,
B.Employee
From
Table_A A
Inner Join
Table_B B On A.Country = B.Country and A.Region = B.Region
You need an additional join key after country and region for the assignment. For this, you can use row_number():
select a.*, b.employee
from (select a.*,
row_number() over (partition by country, region order by linenumber) as seqnum
from table_a a
) a join
(select b.*
row_number() over (partition by country, region order by (select null) ) as seqnum
from b
) b
on b.country = a.country and b.region = a.region and b.seqnum = a.seqnum
Just pulling together all of the suggestions, answers, and comments.
--Setting up the tables as given:
CREATE TABLE #e (
Country char(1),
Region int,
LineNumber int,
Employee varchar(50));
INSERT #e
VALUES ('A', 1, 1,NULL)
,('A',1,2,NULL)
,('A',2,1,NULL);
CREATE TABLE #r (
Country char(1),
Region int,
Employee varchar(50));
INSERT #r
VALUES
('A', 1, 'Dave Smith')
,('A', 1, 'Johnny Cash')
,('A', 1, 'Peter Sellers')
,('A', 2, 'David Donald');
--Creating a temporary table with
--a line number to join on.
CREATE TABLE #T(
LineNumber int,
Country char(1),
Region int,
Employee varchar(50));
--Populate the temporary table
--with the line number data.
INSERT INTO #T
(
LineNumber,
Country,
Region,
Employee
)
SELECT
(SELECT
COUNT(*) AS Line
FROM #r AS R2
WHERE R2.Employee <= #r.Employee
AND R2.Region = #r.Region
) AS LineNumber,
Country,
Region,
Employee
FROM #r;
--Set up the final output.
SELECT
A.Country,
A.Region,
A.LineNumber,
B.Employee
FROM
#e A
INNER JOIN
#T B
ON A.Country = B.Country
AND A.Region = B.Region
AND A.LineNumber = B.LineNumber
ORDER BY
A.Country,
A.Region,
A.LineNumber;
--Clean up.
DROP TABLE #r;
DROP TABLE #T;
DROP TABLE #e;
Results:
+---------+--------+------------+--------------+
| Country | Region | LineNumber | Employee |
+---------+--------+------------+--------------+
| A | 1 | 1 | Dave Smith |
| A | 1 | 2 | Johnny Cash |
| A | 2 | 1 | David Donald |
+---------+--------+------------+--------------+

How to write case for empty record in sql

I am having two table
Employee 1
Emp_Id Name Old_Id
111 Hemant 1
222 Sachin 2
Employee
Emp_ID Name Temp_Name
1 Hemant NULL
2 Sachin NULL
3 Vinod NULL
4 Nitin 1
5 Ajit 2
6 Suraj 3
I want to replace Temp_Name in Employee table by its new value available in Employee1 table.
Employee.Temp_Name is Employee1.Old_Id
Condition
1 : if Temp_Name is not present in Employee1.Old_id column it should be retained.
2 : Without creating any function.
The final output should be :
Employee
Emp_ID Name Short_Name
1 Hemant NULL
2 Sachin NULL
3 Vinod NULL
4 Nitin 111
5 Ajit 222
6 Suraj 3
I tried this
Update [Employee]
set
Temp_Name = (
select CASE Emp_ID
WHEN NULL THEN [Employee].Temp_Name
ELSE Emp_ID
END AS Emp_ID
from [Employee1] e1
where e1.old_id = [Employee].Temp_Name
)
But it gives me output :
Employee
Emp_ID Name Temp_Name
1 Hemant NULL
2 Sachin NULL
3 Vinod NULL
4 Nitin 111
5 Ajit 222
6 Suraj NULL
You can use LEFT JOIN with a combination of CASE expression.
SQL Fiddle
SELECT
e.Emp_Id,
e.Name,
Short_Name =
CASE
WHEN e1.Old_Id IS NOT NULL THEN e1.Emp_Id
ELSE e.Short_Name
END
FROM Employee e
LEFT JOIN Employee1 e1
ON e1.Old_Id = e.Short_Name
UPDATE statement
UPDATE e
SET e.Short_Name = e1.Emp_Id
FROM Employee e
LEFT JOIN Employee1 e1
ON e1.Old_Id = e.Short_Name
WHERE e1.Old_Id IS NOT NULL
EDIT: This is for sql server.
Here's the code I have with inserting the sample data:
begin tran
create table Employee1
(
Emp_Id int,
Name varchar(50),
Old_Id int
);
create table Employee
(
Emp_Id int,
Name varchar(50),
Short_Name int NULL
);
insert into Employee1
values(111, 'Hemant', 1);
insert into Employee1
values(222, 'Sachin', 2);
insert into Employee
values(1, 'Hemant', NULL);
insert into Employee
values(2, 'Sachin', NULL);
insert into Employee
values(3, 'Vinod', NULL);
insert into Employee
values(4, 'Nitin', 1);
insert into Employee
values(5, 'Ajit', 2);
insert into Employee
values(6, 'Suraj', 3);
update emp
set
emp.Short_Name = CASE
WHEN emp1.Emp_Id IS NULL THEN emp.Short_Name
ELSE emp1.Emp_Id
END
from
Employee emp
left join Employee1 emp1
on (emp.Short_Name = emp1.Old_Id)
select * From Employee
drop table Employee1
drop table Employee
rollback
You had to ensure you left join your tables.

SQL OVER (Partiton by) - Handle nulls

I have a following scenario:
Table Employees:
First Name | Last Name | Department | Salary
-----------|-----------|------------|---------
John | Doe | Finance | 20
John | Doe | R&D | 20
John | null | Finance | 20
John | long | Finance | 20
and I want 1 row for each (First Name,Last Name),
unless we have a null in the last name, and then i want just 1 row with (First Name,null)
for the above example the result is:
First Name | Last Name | Department | Salary
-----------|-----------|------------|---------
John | null | Finance | 20
but if i didn't have that record then the result should have been:
First Name | Last Name | Department | Salary
-----------|-----------|------------|---------
John | Doe | R&D | 20
John | long | Finance | 20
I guess the answer involves some Partition By-s, but I'm not sure where.
Right now I came to this:
SELECT FirstName,LastName, DEPARTMENT,Salary,RK FROM
(
select * from
SELECT EXT.*,
ROW_NUMBER() OVER(PARTITION BY EXT.FirstName,EXT.LastName
ORDER BY rownum ASC) AS RK
FROM Employees EXT
)
WHERE RK = 1 ;
Thanks !
Your problem is in the PARTITION clause. You want every first name where there is a surname unless at least one surname with that first name is NULL, in which case you want only those first names that have a NULL surname.
The answer here is to use RANK() instead of ROW_NUMBER(). RANK() does not create a consecutive list; instead rows with equal values get the same rank.
select firstname, lastname, department, salary, rk
from ( select a.*
, rank() over ( partition by firstname
order by case when lastname is null then 0
else 1
end
) as rnk
from employees a
)
where rnk = 1
This works by making the existence of a surname relevant rather than the surname itself.
Two more points:
You had a nested select without parenthesis. This won't work.
There's no point ordering by ROWNUM. By definition rownum returns rows in the order returned by the statement, which means the rows will always be in the order of the ROWNUM.
something like this:
SQL> create table person
2 (
3 fname varchar2(10),
4 lname varchar2(10),
5 dept varchar2(10),
6 sal number
7 );
Table created.
SQL> insert into person values ('John', 'Doe', 'Finance', 20);
1 row created.
SQL> insert into person values ('John', 'Doe', 'R&D', 20);
1 row created.
SQL> insert into person values ('John', '', 'Finance', 20);
1 row created.
SQL> insert into person values ('John', 'Long', 'Finance', 20);
1 row created.
SQL> insert into person values ('Paul', 'Doe', 'R&D', 30);
1 row created.
SQL> insert into person values ('Paul', 'Doe', 'Finance', 30);
1 row created.
SQL> insert into person values ('Paul', 'Long', 'Finance', 30);
1 row created.
SQL> select fname, lname, dept, sal
2 from (select fname, lname, dept, sal,has_null,
3 row_number() over(partition by fname,
4 case when has_null = 'N' then lname else null end
5 order by lname desc nulls first) rn
6 from (select fname, lname,
7 nvl(max(case when lname is null then 'Y'
8 end) over(partition by fname), 'N') has_null, dept, sal
9 from person))
10 where rn = 1;
FNAME LNAME DEPT SAL
---------- ---------- ---------- ----------
John Finance 20
Paul Doe R&D 30
Paul Long Finance 30
That query does the (same) trick, but preforms better.
SELECT fname,
lname,
dept,
sal
FROM (SELECT fname,
lname,
dept,
sal,
First_value(lname)
OVER(
partition BY fname
ORDER BY lname nulls first) null_domain,
Row_number()
OVER (
partition BY fname, lname
ORDER BY fname) r
FROM person)
WHERE ( ( null_domain IS NULL
AND lname IS NULL )
OR null_domain IS NOT NULL )
AND r = 1;

Display the Employee Name (Boss) and number of Employee (Subordinates) in SQL

I have a table emp having foll data:
EmpID EmpName MgrID
100 King NULL
101 Smith 100
102 Shine 100
103 Racy 102
Now i want to Display the Employee Name (Boss) and number of Employee (Subordinates) something like this
BOSS SUBORDINATES
BLAKE 5
CLARK 1
FORD 1
JONES 2
KING 3
SCOTT 1
Please guide how to go about querying this table in SQL Server 2008.
Attempted query:
select e.first_name as ename,m.first_name as mname from employees e,employees m where e.manager_id=m.employee_id
Start by self-joining on EmpID=MgrID
Group by MgrID and EmpName
Select EmpName and count(*)
Translating this to SQL is mechanical:
SELECT b.EmpName, COUNT(*)
FROM Employee e
JOIN Employee b ON b.EmpID=e.MgrID
GROUP BY b.EmpID, b.EmpName
CREATE TABLE test (
EmpID INT,
EmpName VARCHAR(100),
MgrID INT)
INSERT INTO test VALUES (100, 'King', NULL),
(101, 'Smith', 100),
(102, 'Shine', 100),
(103, 'Racy', 102)
SELECT t1.EmpName AS Boss,
COUNT(*) AS Subordinates
FROM test AS t1 INNER JOIN test AS t2 ON t1.EmpID = t2.MgrID
GROUP BY t1.EmpName

How do I select a row from nearly duplicate rows based on a field value?

If I have rows with this data:
ID |Name |ContractType|
---|------------|------------|
1 |Aaron Shatz | 6-month |
2 |Jim Smith |12-month |
3 |Jim Smith | 6-month |
4 |Mark Johnson|12-month |
I can't use Id to determine which record to use: I have to use ContractType. I want to select all records from a table, but if there are records with the same Name value, I want to pick the 12-month contract record.
The result of the query should be:
ID |Name |ContractType|
---|------------|------------|
1 |Aaron Shatz | 6-month |
2 |Jim Smith |12-month |
4 |Mark Johnson|12-month |
Hard coded version
This solution assumes that there are only two contract types namely 6-month and 12-month. Please scroll to the bottom for dynamic version.
Click here to view the demo in SQL Fiddle.
Script:
CREATE TABLE contracts
(
id INT NOT NULL IDENTITY
, name VARCHAR(30) NOT NULL
, contracttype VARCHAR(30) NOT NULL
);
INSERT INTO contracts (name, contracttype) VALUES
('Aaron Shatz', '6-month'),
('Jim Smith', '12-month'),
('Jim Smith', '12-month'),
('Mark Johnson', '12-month'),
('John Doe', '6-month'),
('Mark Johnson', '6-month'),
('Aaron Shatz', '6-month');
SELECT id
, name
, contracttype
FROM
(
SELECT id
, name
, contracttype
, ROW_NUMBER() OVER(PARTITION BY name ORDER BY contracttype) AS rownum
FROM contracts
) T1
WHERE rownum = 1
ORDER BY id;
Output:
id name contracttype
-- ------------ ------------
1 Aaron Shatz 6-month
2 Jim Smith 12-month
4 Mark Johnson 12-month
5 John Doe 6-month
Dynamic version
This moves the contract type data into a table of its own with a sequence column. Based on how the contract types are ordered, the query will fetch the appropriate records.
Click here to view the demo in SQL Fiddle.
Script:
CREATE TABLE contracts
(
id INT NOT NULL IDENTITY
, name VARCHAR(30) NOT NULL
, contracttypeid INT NOT NULL
);
CREATE TABLE contracttypes
(
id INT NOT NULL IDENTITY
, contracttype VARCHAR(30) NOT NULL
, sequence INT NOT NULL
)
INSERT INTO contracttypes (contracttype, sequence) VALUES
('12-month', 1),
('6-month', 3),
('15-month', 2);
INSERT INTO contracts (name, contracttypeid) VALUES
('Aaron Shatz', 2),
('Jim Smith', 2),
('Jim Smith', 3),
('Mark Johnson', 1),
('John Doe', 2),
('Mark Johnson', 2),
('Aaron Shatz', 2);
SELECT id
, name
, contracttype
FROM
(
SELECT c.id
, c.name
, ct.contracttype
, ROW_NUMBER() OVER(PARTITION BY name ORDER BY ct.sequence) AS rownum
FROM contracts c
LEFT OUTER JOIN contracttypes ct
ON c.contracttypeid = ct.id
) T1
WHERE rownum = 1
ORDER BY id;
Output:
id name contracttype
-- ------------ ------------
1 Aaron Shatz 6-month
3 Jim Smith 15-month
4 Mark Johnson 12-month
5 John Doe 6-month
This works only because the OP has confirmed that only two contract types are possible, and the one he wants (for each contractor) happens to be the one that orders first alphabetically. So a couple of coincidences make this solution straight-forward.
;WITH x AS
(
SELECT ID, Name, ContractType, rn = ROW_NUMBER() OVER
(PARTITION BY Name ORDER BY ContractType)
FROM dbo.some_table
)
SELECT ID, Name, ContractType
FROM x
WHERE rn = 1
ORDER BY ID;
If you need to make this more dynamic, I suppose you could say:
DECLARE #PreferredContractType VARCHAR(32);
SET #PreferredContractType = '12-month';
;WITH x AS
(
SELECT ID, Name, ContractType, rn = ROW_NUMBER() OVER
(PARTITION BY Name ORDER BY CASE ContractType
WHEN #PreferredContractType THEN 1 ELSE 2 END
)
FROM dbo.some_table
)
SELECT ID, Name, ContractType
FROM x
WHERE rn = 1
ORDER BY ID;