Recursive CTE not getting desired result. How to assign anchor only? - sql

The application is for an employee id get all managers
declare #emp table (id int primary key, mgr int);
insert into #emp values
(1, null)
, (2, 1)
, (3, 2)
, (4, null)
, (5, 4);
select * from #emp;
; with cte as
( select e.id, e.mgr, cnt = 1
from #emp e
union all
select e.id, e.mgr, cnt + 1
from #emp e
join cte
on cte.mgr = e.id
)
select id, mgr, cnt
from cte
where id = 3;
The above only returns the single row for id = 3. I get that is expected but not what I desire. I want to start (anchor) at 3 and get the manager chain.
If I hard code the anchor I get the desired result.
See below:
; with cte as
( select e.id, e.mgr, cnt = 1
from #emp e
where e.id = 3
union all
select e.id, e.mgr, cnt + 1
from #emp e
join cte
on cte.mgr = e.id
)
select id, mgr, cnt
from cte;
My question is how to assign the anchor (top part) only in a where on the cte?
If not in the where is there another way to assign the anchor only (not hard coding in the cte)?

You need to keep the starting position in your cte:
declare #emp table (id int primary key, mgr int);
insert into #emp values
(1, null)
, (2, 1)
, (3, 2)
, (4, null)
, (5, 4);
; with cte as
( select e.id ori, e.id, e.mgr, cnt = 1
from #emp e
union all
select cte.ori, e.id, e.mgr, cnt + 1
from #emp e
join cte
on cte.mgr = e.id
)
select ori, id, mgr, cnt
from cte
where cte.ori = 3;
Result:
+-----+----+------+-----+
| ori | id | mgr | cnt |
+-----+----+------+-----+
| 3 | 3 | 2 | 1 |
| 3 | 2 | 1 | 2 |
| 3 | 1 | NULL | 3 |
+-----+----+------+-----+

I made just one change and removed (seemingly) unnecessary column:
declare #emp table (id int primary key, mgr int);
insert into #emp values
(1, null)
, (2, 1)
, (3, 2)
, (4, null)
, (5, 4);
select * from #emp;
; with cte as
( select e.id, e.mgr
from #emp e
union all
select cte.id, e.mgr
from #emp e
join cte
on cte.mgr = e.id
)
select id, mgr
from cte
where id = 3;
Result is:
id | mgr
3 | 2
3 | 1
3 | NULL

You must decide whether to
start with a root node (add WHERE mgr IS NULL or an explicit id to your anchor) and move down the chain, or to
start with any child node (add where not exists(SELECT 1 FROM #emp AS x WHERE e.id=x.mgr) to the anchor) and move up or to
start with an explicit child (add where e.id=3 to the anchor) and move up.
The general advise is: Start with the narrowest anchor-set possible!
The first query you state in your question (as well as other answers here) will do a huge overdose creating each an any chain starting from everywhere with overlapping results.
As the recursive CTE is a hidden RBAR the engine has no chance to predict its result and will create the full load - just to throw most of this away.
If this is a 1:n relation (always 1 mgr on-top) moving upwards will be much faster. Starting from a given child node You have exactly one step per level - that's it.
Applying the filter in a WHERE after the recursice CTE was worked out, means to create any possible chain just to throw most of them away...
Your second approach is the best I can think of actually.
So the question is: Why do you want to apply this filter at the end?

Related

oracle lookup translation for a column value

I have a table with data below
create table A
(id number,
code varchar2(100));
insert into A values
(1, '20,21,22');
insert into A values
(2, '22,23,24');
commit;
create table code_descriptions
(
code_id number,
code_desc varchar2(100));
insert into code_descriptions values
(20, 'ABC');
insert into code_descriptions values
(21, 'BBC');
insert into code_descriptions values
(22, 'CAP');
insert into code_descriptions values
(23, 'INC');
insert into code_descriptions values
(24, 'ABC');
commit;
I would like to come up with sql to update table A code values with descriptions
Could you please suggest any functions or ideas, to achieve this?
You can tokenize your comma-separated lists:
select id, level as rn, regexp_substr(code, '(.*?)(,|$)', 1, level, null, 1) as code_id
from a
connect by level <= regexp_count(code, ',') + 1
and id = prior id
and prior dbms_random.value is not null;
ID RN CODE_ID
---------- ---------- -------
1 1 20
1 2 21
1 3 22
2 1 22
2 2 23
2 3 24
Then use that as a CTE (subquery factoring) and join to the descriptions (I've made it a left-join so you choose how to handle any missing codes):
with cte (id, rn, code_id) as (
select id, level, regexp_substr(code, '(.*?)(,|$)', 1, level, null, 1)
from a
connect by level <= regexp_count(code, ',') + 1
and id = prior id
and prior dbms_random.value is not null
)
select cte.id, cte.rn, cte.code_id, coalesce(cd.code_desc, '??') as code_desc
from cte
left join code_descriptions cd on cd.code_id = cte.code_id;
ID RN CODE_ID CODE_DESC
---------- ---------- ------- ---------
1 1 20 ABC
1 2 21 BBC
2 1 22 CAP
1 3 22 CAP
2 2 23 INC
2 3 24 ABC
and then aggregate those back into single comma-separated values:
with cte (id, rn, code_id) as (
select id, level, regexp_substr(code, '(.*?)(,|$)', 1, level, null, 1)
from a
connect by level <= regexp_count(code, ',') + 1
and id = prior id
and prior dbms_random.value is not null
)
select cte.id,
listagg(coalesce(cd.code_desc, '??'), ',') within group (order by rn) as code
from cte
left join code_descriptions cd on cd.code_id = cte.code_id
group by cte.id;
ID CODE
---------- ----------------
1 ABC,BBC,CAP
2 CAP,INC,ABC
and then merge or us a correlated update:
update a
set code = (
with cte (id, rn, code_id) as (
select id, level, regexp_substr(code, '(.*?)(,|$)', 1, level, null, 1)
from a
connect by level <= regexp_count(code, ',') + 1
and id = prior id
and prior dbms_random.value is not null
)
select listagg(coalesce(cd.code_desc, '??'), ',') within group (order by rn) as code
from cte
left join code_descriptions cd on cd.code_id = cte.code_id
where cte.id = a.id
group by cte.id
);
2 rows updated.
select * from a;
ID CODE
---------- ----------------
1 ABC,BBC,CAP
2 CAP,INC,ABC
or
rollback; -- just to undo previous update
merge into a
using (
with cte (id, rn, code_id) as (
select id, level, regexp_substr(code, '(.*?)(,|$)', 1, level, null, 1)
from a
connect by level <= regexp_count(code, ',') + 1
and id = prior id
and prior dbms_random.value is not null
)
select cte.id, listagg(coalesce(cd.code_desc, '??'), ',') within group (order by rn) as code
from cte
left join code_descriptions cd on cd.code_id = cte.code_id
group by cte.id
) tmp
on (tmp.id = a.id)
when matched then update set a.code = tmp.code;
2 rows merged.
select * from a;
ID CODE
---------- ----------------
1 ABC,BBC,CAP
2 CAP,INC,ABC
db<>fiddle
The simplest solution is if the number of elements in the "code" column of table A is fixed - by directly gluing the subqueries to the code_descriptions table by codes (we cut each code from the enumeration line).

Is it possible to update rows randomly with a group of set values?

I have a work assignment table that I would like help with. What I would like to do is randomly assign peoples names to the rows in the table. For example, the table currently looks like:
TASK |NAME
1 Get Chicken |
2 Clean Chicken|
3 Cook Chicken |
4 Eat Chicken |
5 Eat Corn |
6 Takeout Trash|
I have 4 employees that I want to assign these tasks to, but do not want to show any favoritism. Here is what that table looks like:
NAME
John
Lucy
Fred
Jasmine
How can I randomly update the NAME field based on the above names?
edit based on comments. I changed the number of tasks to something not divisible by 4. In this case the number of tasks is now 6. I want to make it to where no one can get 2 or more tasks more then the rest of their colleagues. But in this case, it's ok for someone to have 1 more task then their colleagues. he result should be something like (but random):
TASK |NAME
1 Get Chicken |John
2 Clean Chicken|Jasmine
3 Cook Chicken |Lucy
4 Eat Chicken |Fred
5 Eat Corn |Fred
6 Takeout Trash|Jasmine
Here is a pure SQL way to do it.
MERGE INTO so_tasks t USING (
WITH numbered_tasks AS ( SELECT t.*,
row_number() OVER (ORDER BY dbms_random.value) task_number,
count(*) OVER () total_tasks FROM so_tasks t ),
numbered_employees AS ( SELECT e.*,
row_number() OVER (ORDER BY dbms_random.value) employee_number,
count(*) OVER () total_employees FROM so_employees e)
SELECT nt.task,
ne.name
FROM numbered_tasks nt
INNER JOIN numbered_employees ne
ON ne.employee_number-1 = mod(nt.task_number-1, ne.total_employees) ) u
ON ( t.task = u.task )
WHEN MATCHED THEN UPDATE SET t.name = u.name;
It sorts each list randomly and assigned a number to each row in each list. It then gets the row from the employee list whose number matched the task number MOD the total number of employees.
Here is a fully example:
Create tables
CREATE TABLE so_tasks
( task VARCHAR2(30) NOT NULL PRIMARY KEY,
name VARCHAR2(30) );
INSERT INTO so_tasks ( task ) VALUES ('Get Chicken');
INSERT INTO so_tasks ( task ) VALUES ('Clean Chicken');
INSERT INTO so_tasks ( task ) VALUES ('Cook Chicken');
INSERT INTO so_tasks ( task ) VALUES ('Eat Chicken');
INSERT INTO so_tasks ( task ) VALUES ('Eat Corn');
INSERT INTO so_tasks ( task ) VALUES ('Takeout Trash');
CREATE TABLE so_employees
( name VARCHAR2(30) NOT NULL PRIMARY KEY );
INSERT INTO so_employees ( name ) VALUES ('John');
INSERT INTO so_employees ( name ) VALUES ('Lucy');
INSERT INTO so_employees ( name ) VALUES ('Fred');
INSERT INTO so_employees ( name ) VALUES ('Jasmine');
COMMIT;
Merge
MERGE INTO so_tasks t USING (
WITH numbered_tasks AS ( SELECT t.*,
row_number() OVER (ORDER BY dbms_random.value) task_number,
count(*) OVER () total_tasks FROM so_tasks t ),
numbered_employees AS ( SELECT e.*,
row_number() OVER (ORDER BY dbms_random.value) employee_number,
count(*) OVER () total_employees FROM so_employees e)
SELECT nt.task,
ne.name
FROM numbered_tasks nt
INNER JOIN numbered_employees ne
ON ne.employee_number-1 = mod(nt.task_number-1, ne.total_employees) ) u
ON ( t.task = u.task )
WHEN MATCHED THEN UPDATE SET t.name = u.name;
Results
SELECT * FROM so_tasks;
+---------------+---------+
| TASK | NAME |
+---------------+---------+
| Get Chicken | John |
| Clean Chicken | Jasmine |
| Cook Chicken | Lucy |
| Eat Chicken | Fred |
| Eat Corn | Jasmine |
| Takeout Trash | Fred |
+---------------+---------+
Your exact assignments for each task will be different, but there will never be more than a one task difference between any two employees.
You can give the tasks random sequential numbers and the employees another random sequential number and then join the two tables using those numbers and then use a MERGE statement to update the table correlating on the ROWID pseudo-column to uniquely identify each task.
Oracle Setup:
CREATE TABLE table_name ( task VARCHAR2(20), name VARCHAR2(20) );
INSERT INTO table_name ( TASK )
SELECT 'Get Chicken' FROM DUAL UNION ALL
SELECT 'Clean Chicken' FROM DUAL UNION ALL
SELECT 'Cook Chicken' FROM DUAL UNION ALL
SELECT 'Eat Chicken' FROM DUAL UNION ALL
SELECT 'Eat Corn' FROM DUAL UNION ALL
SELECT 'Takeout Trash' FROM DUAL;
CREATE TABLE employees ( NAME ) AS
SELECT 'John' FROM DUAL UNION ALL
SELECT 'Lucy' FROM DUAL UNION ALL
SELECT 'Fred' FROM DUAL UNION ALL
SELECT 'Jasmine' FROM DUAL;
Merge:
MERGE INTO table_name dst
USING (
WITH random_tasks ( rid, rn ) AS (
SELECT ROWID,
ROW_NUMBER() OVER ( ORDER BY DBMS_RANDOM.VALUE )
FROM table_name
),
random_names ( name, rn, num_employees ) AS (
SELECT name,
ROW_NUMBER() OVER ( ORDER BY DBMS_RANDOM.VALUE ),
COUNT(*) OVER ()
FROM employees
)
SELECT rid,
name
FROM random_tasks t
INNER JOIN
random_names n
ON ( MOD( t.rn, n.num_employees ) + 1 = n.rn )
) src
ON ( src.rid = dst.ROWID )
WHEN MATCHED THEN
UPDATE SET name = src.name;
Result:
SELECT * FROM table_name;
TASK | NAME
:------------ | :------
Get Chicken | John
Clean Chicken | Jasmine
Cook Chicken | Fred
Eat Chicken | Lucy
Eat Corn | Fred
Takeout Trash | Lucy
db<>fiddle here
Assuming you're fine with a PL/SQL solution (you could do it in a single update statement but unless it's performance critical, I'd find the loop easier to follow)
begin
for src in (select t.task_id, e.name
from (select t.*,
row_number() over (order by dbms_random.value) rnk
from task t) t
join
(select e.*,
row_number() over (order by dbms_random.value) rnk,
count(*) over () num_emps
from employee e) e
on( mod( t.rnk, e.num_emps ) = e.rnk - 1 ) )
loop
update task
set name = src.name
where task_id = src.task_id;
end loop;
end;
/
Basically, this is randomly sorting both lists and then going down the list of tasks and assigning the next employee to that task. If the number of tasks isn't a multiple of the number of employees, some employees will get an extra task but no employee will have more than 1 more task than another.

Get all subordinates based on line manager marker

I have an employees table that holds an employeeID, Name...etc.... and intLineManager. intLine manager is the employeeID of your line manager. Something like this, with Dave being the top boss with no manager.
intEmpID |Name|intLineManager
1 |Dave| NULL
2 |Sue |1
3 |Mike|1
4 |Matt|2
5 |Sara|3
6 |Paul|4
I'm looking for an SQL script that will return all employees that are underneath a person.
So a search on Mike will return Sara, a search on Sue will return Matt + Paul and finally a search on Dave will return everyone.
You can use a recursive CTE like so:
CREATE TABLE #Employees(EmpID int, Name varchar(10), LineManager int NULL)
INSERT INTO #Employees VALUES
(1, 'Dave', NULL),
(2, 'Sue', 1),
(3, 'Mike', 1),
(4, 'Matt', 2),
(5, 'Sara', 3),
(6, 'Paul', 4)
;WITH CTE as
(
SELECT EmpID, Name, LineManager
FROM #Employees
WHERE EmpID = 2 --Sue
UNION ALL
SELECT e.EmpID, e.Name, e.LineManager
FROM #Employees AS E
INNER JOIN CTE AS C ON c.EmpID = e.LineManager
)
SELECT *
FROM CTE
where EmpID > (SELECT TOP 1 EmpID FROM CTE)
ORDER BY EmpID
Result for sue(EmpID 2)
EmpID Name LineManager
----------- ---------- -----------
4 Matt 2
6 Paul 4

SQL: How can I use CTE instead of self join? How it affects performance? [duplicate]

I have a table which has employee details
EmpId ManagerId Level Value
1 0 5 CEO
2 1 4 EMP
3 1 4 ORG
4 2 3 NULL
5 2 3 NULL
6 2 2 NULL
7 1 1 NULL
8 5 0 NULL
Now, I have to start wil Employee Id 2 and found all it's low level hirerachy (i.e. 2, 4, 5, 6, 8) and assign them value same as "2" (i.e. EMP).
Expected output :
EmpId ManagerId Level Value
1 0 5 CEO
2 1 4 EMP
3 1 4 ORG
4 2 3 EMP
5 2 3 EMP
6 2 2 EMP
7 1 1 NULL
8 5 0 EMP
What I am trying:
; WITH LevelHire AS
(
SELECT EmpId, ManagerId,Level
FROM EmployeeTable
WHERE EmpId =2
UNION ALL
SELECT Lh.EmpId, RC.ManagerId, Lh.Level
FROM LevelHire LH
INNER JOIN [EmployeeTable] RC
ON LH.EmpId= RC.EmpId
)
SELECT * FROM LevelHire
option (maxrecursion 0)
How can I achieve the same?
you can try something like this
;WITH EmployeeTable AS
(
SELECT 1 EmpId,0 ManagerId , 5 Level ,'CEO' Value
UNION ALL SELECT 2,1, 4,'EMP'
UNION ALL SELECT 3,1, 4,'ORG'
UNION ALL SELECT 4,2, 3,NULL
UNION ALL SELECT 5,2, 3,NULL
UNION ALL SELECT 6,2, 2,NULL
UNION ALL SELECT 7,1, 1,NULL
UNION ALL SELECT 8,5, 0,NULL
),LevelHire AS
(
SELECT EmpId, ManagerId,Level,Value
FROM EmployeeTable
WHERE EmpId = 2
UNION ALL
SELECT RC.EmpId, RC.ManagerId, Lh.Level,LH.Value
FROM LevelHire LH
INNER JOIN [EmployeeTable] RC
ON LH.EmpId= RC.ManagerId
)
SELECT E.EmpId, E.ManagerId,E.Level,ISNULL(E.Value ,LH.Value) Value
FROM EmployeeTable E
LEFT JOIN LevelHire LH
ON E.EmpId = LH.EmpId
Time to learn about hierarchyid. First, some code:
IF object_id('tempdb.dbo.#employees') IS NOT NULL
DROP TABLE #employees;
go
WITH Employees AS (
SELECT *
FROM ( VALUES
( 1, NULL, 5, 'CEO'),
( 2, 1, 4, 'EMP'),
( 3, 1, 4, 'ORG'),
( 4, 2, 3, NULL ),
( 5, 2, 3, NULL ),
( 6, 2, 2, NULL ),
( 7, 1, 1, NULL ),
( 8, 5, 0, NULL )
) AS x ( EmpId, ManagerId, Level, Value )
), rcte AS (
SELECT e.EmpId ,
e.ManagerId ,
e.Level ,
e.Value,
CAST('/' + CAST(e.EmpId AS VARCHAR) + '/' AS VARCHAR(MAX)) AS h
FROM Employees AS e
WHERE e.ManagerId IS NULL
UNION ALL
SELECT e.EmpId ,
e.ManagerId ,
e.Level ,
e.Value ,
m.h + CAST(e.EmpId AS VARCHAR) + '/' AS h
FROM Employees AS e
JOIN rcte AS m
ON e.ManagerId = m.EmpId
)
SELECT rcte.EmpId ,
rcte.ManagerId ,
rcte.Level ,
rcte.Value ,
CAST(rcte.h AS HIERARCHYID) AS h
INTO #employees
FROM rcte;
GO
SELECT e.EmpId ,
e.ManagerId ,
e.Level ,
e.Value ,
e.h.ToString() AS h
FROM #employees AS e
JOIN #employees AS m
ON e.h.IsDescendantOf(m.h) = 1
WHERE m.EmpId = 1
SELECT m.EmpId ,
m.ManagerId ,
m.Level ,
m.Value ,
m.h.ToString() AS h
FROM #employees AS e
JOIN #employees AS m
ON e.h.IsDescendantOf(m.h) = 1
WHERE e.EmpId = 8
While I needed a recursive CTE to actually establish the hierarchy, any of the actual queries of the form "who does this person report to?" and "who reports to this person?" are ultimately satisfied from the persisted hierarchy in the #employees table. The two queries at the end show how to traverse the hierarchy in either direction. This sort of thing is important if your hierarchy is large (wide, deep, or both). You do need to maintain it when the org chart changes, but that's a one-time operation. The querying of the data should be fast because the lineage is persisted with the employee record.
Incidentally, your Level column is a bit odd to me. Specifically, it seems backwards (i.e. CEO has the highest level). I say this because if/when you add another level to the org chart, you'll need to re-level everyone from the CEO down. If you have the CEO have the lowest level, you just tack that level onto the bottom and don't have to re-level anyone.

Display order of a SQL Query without order by clause

I am writing a simple query in SQL Server:
Select
EmpId, EmpName, Sal
from
Emp
where
EmpId in (10,9,5,7,3,8);
I want to get the output in a same order which is given i.e; 10,9,5,7,3,8
Actually whatever I'll give the result will display with given order without
order by ascending or descending.
How can I do that? Please help.
No way to do this natively.
Try:
SELECT EmpId,EmpName,Sal
FROM Emp
WHERE EmpId IN (10,9,5,7,3,8)
ORDER BY CASE EmpId
WHEN 10 THEN 1
WHEN 9 THEN 2
WHEN 5 THEN 3
WHEN 7 THEN 4
WHEN 3 THEN 5
WHEN 8 THEN 6
ELSE 7
END;
You can use a table variable to pass the inputs. You must insert the records into this table variable in the desired order.
Declare #empids table(id int identity(1,1),empid int)
insert into #empids values(10),(9),(5),(7),(3),(8)
Select e.EmpId,e.empname,e.sal from Emp e
join #empids t on e.EmpId = t.empid
order by t.id
Try this.
You can use CHARINDEX function in an odd way: search for the id in the comma separated list and order the result by the position.
Consider this list for example 10,9,5,7,3,8... the substring 10 appears at 1st position while 9 appears at 4th. Just order by the substring position.
CREATE TABLE Emp
(EmpId int, EmpName varchar(100), Sal int)
;
INSERT INTO Emp
(EmpId, EmpName, Sal)
VALUES
(1, 'John', NULL),
(2, 'Jane', NULL),
(3, 'Smith', NULL),
(4, 'Doe', NULL),
(5, 'Ben', NULL),
(6, 'Steve', NULL),
(7, 'Andrew', NULL),
(8, 'Simon', NULL),
(9, 'Jack', NULL),
(10, 'Allen', NULL)
;
SELECT
EmpId, EmpName, Sal
FROM
Emp
WHERE
EmpId in (10,9,5,7,3,8)
ORDER BY
CHARINDEX(CONCAT(',', EmpId, ','), CONCAT(',', '10,9,5,7,3,8', ','))
;
Result:
EmpId | EmpName | Sal
------+---------+-----
10 | Allen | NULL
9 | Jack | NULL
5 | Ben | NULL
7 | Andrew | NULL
3 | Smith | NULL
8 | Simon | NULL
You can do this dynamically if your list is a comma-delimited-string. First, you must have a splitter function. Here is the DelimitedSplit8k written by Jeff Moden:
CREATE FUNCTION [dbo].[DelimitedSplit8K](
#pString VARCHAR(8000), #pDelimiter CHAR(1)
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)
,E2(N) AS (SELECT 1 FROM E1 a, E1 b)
,E4(N) AS (SELECT 1 FROM E2 a, E2 b)
,cteTally(N) AS(
SELECT TOP (ISNULL(DATALENGTH(#pString), 0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
,cteStart(N1) AS(
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString, t.N, 1) = #pDelimiter
),
cteLen(N1, L1) AS(
SELECT
s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter, #pString, s.N1),0) - s.N1, 8000)
FROM cteStart s
)
SELECT
ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
Then, you declare the list of empIds as csv string and use the splitter:
DECLARE #empIds VARCHAR(MAX) = '10,9,5,7,3,8';
SELECT e.EmpId, e.EmpName, e.Sal
FROM Emp e
INNER JOIN dbo.DelimitedSplit8K(#empIds, ',') s
ON s.Item = l.EmpId
ORDER BY s.ItemNumber
Following query will give you the exact result (without order by):
SELECT EmpId,EmpName,Sal
FROM Emp
WHERE EmpId IN (10)
union all
SELECT EmpId,EmpName,Sal
FROM Emp
WHERE EmpId IN (9)
union all
SELECT EmpId,EmpName,Sal
FROM Emp
WHERE EmpId IN (5)
union all
SELECT EmpId,EmpName,Sal
FROM Emp
WHERE EmpId IN (7)
union all
SELECT EmpId,EmpName,Sal
FROM Emp
WHERE EmpId IN (3)
union all
SELECT EmpId,EmpName,Sal
FROM Emp
WHERE EmpId IN (8)
If Ids is a variable passed as input parameter to a stored procedure, you can split it using CTE.
--#inputIDs VARCHAR(300) => '10,9,5,7,3,8'
;WITH MyIds AS
(
SELECT 1 AS Position, CONVERT(INT, LEFT(#inputIds, CHARINDEX(',', #inputIDs)-1)) AS MyId,
RIGHT(#inputIds, LEN(#inputIds) - CHARINDEX(',', #inputIDs)) AS Remainder
WHERE CHARINDEX(',', #inputIDs)>0
UNION ALL
SELECT Position +1 AS Position, CONVERT(INT, LEFT(Remainder, CHARINDEX(',', Remainder)-1)) AS MyId,
RIGHT(Remainder, LEN(Remainder) - CHARINDEX(',', Remainder)) AS Remainder
FROM MyIds
WHERE CHARINDEX(',', Remainder)>0
UNION ALL
SELECT Position +1 AS Position, CONVERT(INT, Remainder) AS MyId,
NULL AS Remainder
FROM MyIds
WHERE CHARINDEX(',', Remainder)=0
)
SELECT e.EmpId, e.EmpName, e.Sal
FROM Emp AS e INNER JOIN MyIds AS a ON e.EmpId = a.MyId
ORDER BY a.Position
The simplest way to achieve this is probably to use a table-valued constructor as a table expression containing the ids with a sort value, then order on this value:
Select
Emp.EmpId, Emp.EmpName, Emp.Sal
from
Emp
Inner Join
(
Values
(10, 1),
(9, 2),
(5, 3),
(7, 4),
(3, 5),
(8, 6)
)
EmpIds (EmpId, Sort)
On
Emp.EmpId = EmpIds.EmpId
Order By
EmpIds.Sort Asc