SQL duplicate rows with reference to other table - sql

I have two tables which are designed like this.
CREATE TABLE employee
(
id INT PRIMARY KEY,
name VARCHAR(200)
);
CREATE TABLE job
(
id INT PRIMARY KEY,
name VARCHAR(200),
emp_id INT,
FOREIGN KEY (emp_id) REFERENCES employee(id)
);
insert into employee (id, name) values (1, 'user1');
insert into employee (id, name) values (2, 'user2');
insert into employee (id, name) values (3, 'user3');
insert into job (id, name, emp_id) values (1, 'job1', 1);
insert into job (id, name, emp_id) values (2, 'job2', 2);
insert into job (id, name, emp_id) values (3, 'job3', 3);
insert into job (id, name, emp_id) values (4, 'job3', 3);
And what I need is
a query
to duplicate user1,2,3 into 4,5,6, but meanwhile, a old set of job 1,2,3,4 that are referenced with user 1,2,3 will be copied as job 5,6,7,8 and the new user 4,5,6 will have reference to the new 5,6,7,8.
So the result before the query.
Job Table:
id name emp_id
1 t_job1 1
2 t_job2 2
3 t_job3 3
4 t_job3 3
Employee table:
id name
1 user1
2 user2
3 user3
After running the query for duplicating user1,2,3:
Job Table:
id name emp_id
1 t_job1 1
2 t_job2 2
3 t_job3 3
4 t_job3 3
5 t_job1 4
6 t_job2 5
7 t_job3 6
8 t_job3 6
Employee table:
id name
1 user1
2 user2
3 user3
4 user1
5 user2
6 user3
This is an ideal case, but in the code we really work on, there is indefinite amount of job and employee relationship.
So how could this be achieved with query?
For one thing, I cannot change the table structure, if I could, I would.
Attach the link:
http://sqlfiddle.com/#!9/de15f/1
We are using oracle, but I put the example under MySQL.

As mentioned above, the answer may depend on the database system that's being used. Below mentioned is a working code built for SQL Server. Hope this helps. :-)
DECLARE #NewEmployee AS TABLE (id INT, [name] VARCHAR(200));
DECLARE #NewJob AS TABLE (id INT, [name] VARCHAR(200), emp_id INT);
DECLARE #Max INT, #i INT = 0;
INSERT INTO #NewEmployee (id, [name])
SELECT e.id + m.MaxId, e.[name]
FROM employee e
CROSS APPLY(SELECT max(id) MaxId FROM employee) m;
INSERT #NewJob (id, [name])
SELECT J.id + m.MaxId id, J.[name]
FROM job j
CROSS APPLY(SELECT max(id) MaxId FROM job) m;
SELECT #Max = MAX(id), #i = MAX(id) - COUNT(1) + 1 FROM #NewJob;
WHILE #i <= #Max
BEGIN
UPDATE nj
SET nj.emp_id = ISNULL(ne.id, me.id)
FROM #NewJob nj
CROSS APPLY (SELECT MIN(id) id FROM
( SELECT e.id id FROM #NewEmployee e
EXCEPT
SELECT emp_id id FROM #NewJob) T) ne
CROSS APPLY (SELECT MAX(e.id) id FROM #NewEmployee e) me
WHERE nj.id = #i
SET #i = #i + 1;
END;
INSERT INTO employee
SELECT * FROM #NewEmployee;
INSERT INTO job
SELECT * FROM #NewJob;
SELECT * FROM employee;
SELECT * FROM job;

Related

Find data by multiple Lookup table clauses

declare #Character table (id int, [name] varchar(12));
insert into #Character (id, [name])
values
(1, 'tom'),
(2, 'jerry'),
(3, 'dog');
declare #NameToCharacter table (id int, nameId int, characterId int);
insert into #NameToCharacter (id, nameId, characterId)
values
(1, 1, 1),
(2, 1, 3),
(3, 1, 2),
(4, 2, 1);
The Name Table has more than just 1,2,3 and the list to parse on is dynamic
NameTable
id | name
----------
1 foo
2 bar
3 steak
CharacterTable
id | name
---------
1 tom
2 jerry
3 dog
NameToCharacterTable
id | nameId | characterId
1 1 1
2 1 3
3 1 2
4 2 1
I am looking for a query that will return a character that has two names. For example
With the above data only "tom" will be returned.
SELECT *
FROM nameToCharacterTable
WHERE nameId in (1,2)
The in clause will return every row that has a 1 or a 3. I want to only return the rows that have both a 1 and a 3.
I am stumped I have tried everything I know and do not want to resort to dynamic SQL. Any help would be great
The 1,3 in this example will be a dynamic list of integers. for example it could be 1,3,4,5,.....
Filter out a count of how many times the Character appears in the CharacterToName table matching the list you are providing (which I have assumed you can convert into a table variable or temp table) e.g.
declare #Character table (id int, [name] varchar(12));
insert into #Character (id, [name])
values
(1, 'tom'),
(2, 'jerry'),
(3, 'dog');
declare #NameToCharacter table (id int, nameId int, characterId int);
insert into #NameToCharacter (id, nameId, characterId)
values
(1, 1, 1),
(2, 1, 3),
(3, 1, 2),
(4, 2, 1);
declare #RequiredNames table (nameId int);
insert into #RequiredNames (nameId)
values
(1),
(2);
select *
from #Character C
where (
select count(*)
from #NameToCharacter NC
where NC.characterId = c.id
and NC.nameId in (select nameId from #RequiredNames)
) = 2;
Returns:
id
name
1
tom
Note: Providing DDL+DML as shown here makes it much easier for people to assist you.
This is classic Relational Division With Remainder.
There are a number of different solutions. #DaleK has given you an excellent one: inner-join everything, then check that each set has the right amount. This is normally the fastest solution.
If you want to ensure it works with a dynamic amount of rows, just change the last line to
) = (SELECT COUNT(*) FROM #RequiredNames);
Two other common solutions exist.
Left-join and check that all rows were joined
SELECT *
FROM #Character c
WHERE EXISTS (SELECT 1
FROM #RequiredNames rn
LEFT JOIN #NameToCharacter nc ON nc.nameId = rn.nameId AND nc.characterId = c.id
HAVING COUNT(*) = COUNT(nc.nameId) -- all rows are joined
);
Double anti-join, in other words: there are no "required" that are "not in the set"
SELECT *
FROM #Character c
WHERE NOT EXISTS (SELECT 1
FROM #RequiredNames rn
WHERE NOT EXISTS (SELECT 1
FROM #NameToCharacter nc
WHERE nc.nameId = rn.nameId AND nc.characterId = c.id
)
);
A variation on the one from the other answer uses a windowed aggregate instead of a subquery. I don't think this is performant, but it may have uses in certain cases.
SELECT *
FROM #Character c
WHERE EXISTS (SELECT 1
FROM (
SELECT *, COUNT(*) OVER () AS cnt
FROM #RequiredNames
) rn
JOIN #NameToCharacter nc ON nc.nameId = rn.nameId AND nc.characterId = c.id
HAVING COUNT(*) = MIN(rn.cnt)
);
db<>fiddle

CTE Recursive Queries

I have a table with records of employees that shows a relationship of employees and who they report to:
From_ID position TO_ID position
----------------------------------------
1 Lowest_employee 3 employee
3 employee 4 employee
4 employee 5 BOSS
2 Lowest_employee 6 employee
6 employee 3 employee
10 Lowest_employee 50 BOSS2
I would like to show results that look like this, with the employee / boss IDs:
EmployeeID BossID
--------------------
1 5
2 5
10 50
This means employees 1 and 2 report to ID 5 and employee 10 reports to another boss with ID 50.
I know I need to use CTE and Recursive Queries, but cannot understand how it can be done, I'm newer to CTE Recursive Queries.
I read this article but it doesn't make any sense to me MS link
Any help with the query required to achieve this would be useful.
This includes setting up test data, however I think this is what you want:
Test Data:
DECLARE #Table TABLE
(
From_ID int,
TO_ID int
)
INSERT INTO #Table VALUES(1,3)
INSERT INTO #Table VALUES(3,4)
INSERT INTO #Table VALUES(4,5)
INSERT INTO #Table VALUES(2,6)
INSERT INTO #Table VALUES(6,3)
INSERT INTO #Table VALUES(10,50)
Query to get answer:
;WITH Hierarchy (Employee, Superior, QueryLevel)
AS
(
--root is all employees that have no subordinates
SELECT E.From_ID, E.TO_ID, 1
FROM #Table E
LEFT
JOIN #Table S
ON S.TO_ID = E.From_ID
WHERE S.TO_ID IS NULL
--recurse up tree to final superior
UNION ALL
SELECT H.Employee, S.TO_ID, H.QueryLevel + 1
FROM Hierarchy H
JOIN #Table S
ON S.From_ID = H.Superior
)
SELECT Employee, Superior
FROM
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY Employee ORDER BY QueryLevel DESC) AS RowNumber
FROM Hierarchy
) H
WHERE RowNumber = 1
Essentially, this works by :
1) get all employees with no reportees (the root)
2) recurses up through the bosses, recording the 'level'
3) use over/partition to select only the 'final' boss
WITH q (employee, boss) AS
(
SELECT fromId, toId
FROM mytable
WHERE fromId NOT IN
(
SELECT toId
FROM mytable
)
UNION ALL
SELECT employee, toId
FROM q
JOIN mytable t
ON t.fromId = boss
)
SELECT *
FROM q
WHERE boss NOT IN
(
SELECT fromId
FROM mytable
)
You could try something like this?
DECLARE #Employees TABLE (
EmployeeId INT,
PositionName VARCHAR(50),
ReportsToId INT);
INSERT INTO #Employees VALUES (1, 'Driver', 3);
INSERT INTO #Employees VALUES (3, 'Head of Driving Pool', 4);
INSERT INTO #Employees VALUES (4, 'Corporate Flunky', 5);
INSERT INTO #Employees VALUES (2, 'Window Cleaner', 6);
INSERT INTO #Employees VALUES (6, 'Head of Office Services', 3);
INSERT INTO #Employees VALUES (10, 'Minion', 50);
INSERT INTO #Employees VALUES (5, 'BOSS', NULL);
INSERT INTO #Employees VALUES (50, 'BOSS2', NULL);
WITH Employees AS (
SELECT
EmployeeId,
1 AS [Level],
EmployeeID AS [Path],
ISNULL(ReportsToId, EmployeeId) AS ReportsToId
FROM
#Employees
WHERE
ReportsToId IS NULL
UNION ALL
SELECT
e.EmployeeID,
x.[Level] + 1 AS [Level],
x.[Path] + e.EmployeeID AS [Path],
x.ReportsToId
FROM
#Employees e
INNER JOIN Employees x ON x.EmployeeID = e.ReportsToId)
SELECT
ec.EmployeeId,
e.PositionName,
ec.[Level],
CASE WHEN ec.ReportsToId = ec.EmployeeId THEN NULL ELSE ec.ReportsToId END AS ReportsToId --Can't really report to yourself
FROM
Employees ec
INNER JOIN #Employees e ON e.EmployeeId = ec.EmployeeId
ORDER BY
ec.[Path];

How to update only one record of duplicates

I want to update status to inactive ( Status=ā€™Iā€™) for all duplicate record except one in sql, default status is active (Status=ā€™Aā€™ ) for all records in table. It should be done without using any inbuilt sql function ex: row_num(), rank(), set rowcount etc.
CREATE TABLE dup_test
(
Emp_ID INT,
Mgr_ID INT,
Status Varchar(5)
)
INSERT INTO dup_test VALUES (1,1,'A');
INSERT INTO dup_test VALUES (1,1,'A');
INSERT INTO dup_test VALUES (1,1,'A');
INSERT INTO dup_test VALUES (2,2,'A');
INSERT INTO dup_test VALUES (2,2,'A');
INSERT INTO dup_test VALUES (3,3,'A');
Expected Result:
Emp_ID, Mgr_ID, Status
1 1 A
1 1 I
1 1 I
2 2 A
2 2 I
3 3 A
Thanks in advance.
Alter the table and add an identity column (ID):
ALTER TABLE dup_test
ADD id INT NOT NULL IDENTITY (1, 1)
Then something like the following will work:
UPDATE dup_test SET
Status='I'
FROM dup_test dt LEFT OUTER JOIN
(SELECT Emp_ID, MAX(ID) AS maxid
FROM dup_test
GROUP BY Emp_ID) AS dt2 ON dt.Emp_ID=dt2.Emp_ID AND dt.ID=dt2.maxid
WHERE dt2.maxID IS NULL

comparing two colums in sqlserver and returing the remaining data

I have two tables. First one is student table where he can select two optional courses and other table is current semester's optional courses list.
When ever the student selects a course, row is inserted with basic details such as roll number, inserted time, selected course and status as "1". When ever a selected course is de-selected the status is set as "0" for that row.
Suppose the student has select course id 1 and 2.
Now using this query
select SselectedCourse AS [text()] FROM Sample.dbo.Tbl_student_details where var_rollnumber = '020803009' and status = 1 order by var_courseselectedtime desc FOR XML PATH('')
This will give me the result as "12" where 1 is physics and 2 is social.
the second table holds the value from 1-9
For e.g course id
1 = physics
2 = social
3 = chemistry
4 = geography
5 = computer
6 = Spoken Hindi
7 = Spoken English
8 = B.EEE
9 = B.ECE
now the current student has selected 1 and 2. So on first column, i get "12" and second column i need to get "3456789"(remaining courses).
How to write a query for this?
This is not in single query but is simple.
DECLARE #STUDENT AS TABLE(ID INT, COURSEID INT)
DECLARE #SEM AS TABLE (COURSEID INT, COURSE VARCHAR(100))
INSERT INTO #STUDENT VALUES(1, 1)
INSERT INTO #STUDENT VALUES(1, 2)
INSERT INTO #SEM VALUES(1, 'physics')
INSERT INTO #SEM VALUES(2, 'social')
INSERT INTO #SEM VALUES(3, 'chemistry')
INSERT INTO #SEM VALUES(4, 'geography')
INSERT INTO #SEM VALUES(5, 'computer')
INSERT INTO #SEM VALUES(6, 'Spoken Hindi')
INSERT INTO #SEM VALUES(7, 'Spoken English')
INSERT INTO #SEM VALUES(8, 'B.EEE')
INSERT INTO #SEM VALUES(9, 'B.ECE')
DECLARE #COURSEIDS_STUDENT VARCHAR(100), #COURSEIDS_SEM VARCHAR(100)
SELECT #COURSEIDS_STUDENT = COALESCE(#COURSEIDS_STUDENT, '') + CONVERT(VARCHAR(10), COURSEID) + ' ' FROM #STUDENT
SELECT #COURSEIDS_SEM = COALESCE(#COURSEIDS_SEM , '') + CONVERT(VARCHAR(10), COURSEID) + ' ' FROM #SEM WHERE COURSEID NOT IN (SELECT COURSEID FROM #STUDENT)
SELECT #COURSEIDS_STUDENT COURSEIDS_STUDENT, #COURSEIDS_SEM COURSEIDS_SEM
try this:
;WITH CTE as (select ROW_NUMBER() over (order by (select 0)) as rn,* from Sample.dbo.Tbl_student_details)
,CTE1 As(
select rn,SselectedCourse ,replace(stuff((select ''+courseid from course_details for xml path('')),1,1,''),SselectedCourse,'') as rem from CTE a
where rn = 1
union all
select c2.rn,c2.SselectedCourse,replace(rem,c2.SselectedCourse,'') as rem
from CTE1 c1 inner join CTE c2
on c2.rn=c1.rn+1
)
select STUFF((select ''+SselectedCourse from CTE1 for xml path('')),1,0,''),(select top 1 rem from CTE1 order by rn desc)

Struggling to count and order by a column by reference in T-SQL database

I'm not sure if I'm writing the following SQL statement correctly? (Using T-SQL)
I have two tables:
Table 1: [dbo].[t_Orgnzs]
[id] = INT
[nm] = NVARCHAR(256)
Table 2: [dbo].[t_Usrs]
[id] = INT
[ds] = NVARCHAR(256)
[oid] = INT (referenced [dbo].[t_Orgnzs].[id])
I need to select elements from Table 2, ordered by the [oid] column ascending from 1 to 16, but the catch is that the [oid] references a string in the Table 1, that I actually need to return as a result.
So for say, if tables were laid out like so:
Table 1:
id nm
1 Name 1
2 Name 2
3 Name 3
4 Name 4
And Table 2:
id ds oid
1 A 2
2 B 4
3 C 1
The resulting query must return:
3 C Name 1
1 A Name 2
2 B Name 4
So here's the SQL I'm using:
WITH ctx AS (
SELECT [id],
[ds],
(SELECT [nm] FROM [dbo].[t_Orgnzs] WHERE [id]=[dbo].[t_Usrs].[oid]) AS organizName,
ROW_NUMBER() OVER (ORDER BY organizName ASC) AS rowNum
FROM [dbo].[t_Usrs]
)
SELECT [id], [ds], organizName
FROM ctx
WHERE rowNum>=1 AND rowNum<=16;
And I'm getting an error: "Invalid column name 'organizName'."
I do not understand the meaning of use ROW_NUMBER() in your case. Why?
CREATE TABLE [t_Orgnzs] ([id] int PRIMARY KEY, [nm] NVARCHAR(256))
GO
CREATE TABLE [t_Usrs] ([id] int, [ds] NVARCHAR(256), [oid] int FOREIGN KEY REFERENCES [t_Orgnzs]([id]))
GO
INSERT [t_Orgnzs] VALUES (1,'Name_1')
INSERT [t_Orgnzs] VALUES (2,'Name_2')
INSERT [t_Orgnzs] VALUES (3,'Name_3')
INSERT [t_Orgnzs] VALUES (4,'Name_4')
GO
INSERT [t_Usrs] VALUES (1,'A',2)
INSERT [t_Usrs] VALUES (2,'B',4)
INSERT [t_Usrs] VALUES (3,'C',1)
GO
SELECT *
FROM [t_Orgnzs]
INNER JOIN [t_Usrs] ON [t_Orgnzs].[id]=[t_Usrs].[oid]
ORDER BY [oid]
How about this one
select id, ds, nm
from
(
select ROW_NUMBER() OVER (ORDER BY o.nm ASC) AS rowNum, u.id, u.ds, o.nm
from t_Usrs u inner join t_Orgnzs o on (u.oid = o.id)
) t
WHERE rowNum>=1 AND rowNum<=16;
SELECT TOP 16 * FROM [t_Orgnzs]
INNER JOIN [t_Usrs]
ON [t_Orgnzs].[id] = [t_Usrs].[oid]
ORDER BY [oid]