Display order of a SQL Query without order by clause - sql

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

Related

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

Rows Columns Traverse

I have data in the below format
id idnew
1 2
3 4
2
4 7
6 8
7
Result Should be something like this
ID should be followed by idnew
1
2
3
4
2
4
7
6
8
7
Thanks in advance
This should maintain the order:
SELECT id
FROM (
SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS RowNumber
FROM myTable
UNION ALL
SELECT idnew, ROW_NUMBER() OVER (ORDER BY idnew) +
(SELECT COUNT(*) FROM dbo.myTable) AS RowNumber
FROM myTable
WHERE idnew IS NOT NULL
) a
ORDER BY RowNumber
I am assuming the id column is NOT NULL-able.
NOTE: If you want to keep the NULL values from the idnew column AND maintain the order, then remove the WHERE clause and ORDER BY id in the second select:
SELECT id
FROM (
SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS RowNumber
FROM myTable
UNION ALL
SELECT idnew, ROW_NUMBER() OVER (ORDER BY id) +
(SELECT COUNT(*) FROM dbo.myTable) AS RowNumber
FROM myTable
) a
ORDER BY RowNumber
This is fully tested, try it here: https://rextester.com/DVZXO21058
Setting up the table as you described:
CREATE TABLE myTable (id INT, idnew INT);
INSERT INTO myTable (id, idnew)
VALUES (1, 2),
(3, 4),
(2, NULL),
(4, 7),
(6, 8),
(7, NULL);
SELECT * FROM myTable;
Here is the query to do the trick:
SELECT mixed_id FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS row_num,
id,
idnew
FROM myTable
) AS x
UNPIVOT
(
mixed_id for item in (id, idnew)
) AS y
WHERE mixed_id IS NOT NULL
ORDER BY row_num, mixed_id;
In order not to further complicate the query, this is taking advantage of 'id' would rank ahead of 'idnew' as a string. I believe string ranking is not the key issue here.
Using Cross Apply
;WITH CTE (id,idnew)
AS
(
SELECT 1,2 UNION ALL
SELECT 3,4 UNION ALL
SELECT 2,NULL UNION ALL
SELECT 4,7 UNION ALL
SELECT 6,8 UNION ALL
SELECT 7,NULL
)
SELECT New
FROM CTE
CROSS APPLY ( VALUES (id),(idnew))AS Dt (New)
WHERE dt.New IS NOT NULL
Result
New
---
1
2
3
4
2
4
7
6
8
7

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

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?

SQL Query Peers and above

There are recursive queries available to find all children or parents in a hierarchy (for example in an employee-manager hierarchy) but I couldn't find any example of peers and above/parents
EmpID FName LName MgrID
11 Sally Smith NULL
1 Alex Adams 11
2 Barry Brown 11
3 Lee Osaka 11
4 David Kennson 11
5 Eric Bender 11
7 David Lonning 11
6 Lisa Kendall 4
8 John Marshbank 4
12 Barbara ONeill 4
13 Phil Wilconkinski 4
9 James Newton 3
10 Terry OHaire 3
So if I want peer-above of EmpID=6 then it should return
EmpID FName LName MgrID
11 Sally Smith NULL grandparent/peer
1 Alex Adams 11 parent-peer
2 Barry Brown 11 parent-peer
3 Lee Osaka 11 parent-peer
4 David Kennson 11 (parent of id=6)
5 Eric Bender 11 parent-peer
7 David Lonning 11 parent-peer
6 Lisa Kendall 4 (id=6)
8 John Marshbank 4 peer
12 Barbara ONeill 4 peer
13 Phil Wilconkinski 4 peer
Any help will be very appreciated
With the way your table is currently structured, one way of achieving this is to include a path in your recursive CTE, then parse the path to figure out whether someone belongs to that path in the hierarchy.
For example,
DECLARE #Table TABLE
(
EmpID INT NOT NULL,
FName VARCHAR(20) NOT NULL,
LName VARCHAR(20) NOT NULL,
MgrID INT
);
INSERT #Table (EmpID, FName, LName, MgrID)
VALUES
(11, 'Sally', 'Smith', NULL),
(1, 'Alex', 'Adams', 11),
(2, 'Barry', 'Brown', 11),
(3, 'Lee', 'Osaka', 11),
(4, 'David', 'Kennson', 11),
(5, 'Eric', 'Bender', 11),
(7, 'David', 'Lonning', 11),
(6, 'Lisa', 'Kendall', 4),
(8, 'John', 'Marshbank', 4),
(12, 'Barbara', 'ONeill', 4),
(13, 'Phil', 'Wilconkinski', 4),
(9, 'James', 'Newton', 3),
(10, 'Terry', 'OHaire', 3);
WITH CTE AS
(
SELECT EmpID,
FName,
LName,
MgrID,
Tree = CAST(EmpID AS VARCHAR(255)),
Lvl = 1
FROM #Table
WHERE MgrID IS NULL
UNION ALL
SELECT T.EmpID,
T.FName,
T.LName,
T.MgrID,
CAST(CTE.Tree + '-' + CAST(T.EmpID AS VARCHAR(255)) AS VARCHAR(255)), -- Your paths will look like '11-4-6'.
CTE.Lvl + 1
FROM CTE
JOIN #Table T ON T.MgrID = CTE.EmpID
)
SELECT EmpID,
FName,
LName,
MgrID,
Lvl,
Direct = CASE MIN(CASE WHEN EmpID = Item THEN 1 ELSE 2 END) WHEN 1 THEN 'Direct' ELSE 'Peer' END
FROM CTE
JOIN
(
SELECT Item
FROM CTE
CROSS APPLY dbo.[DelimitedSplit8K](Tree, '-') D
WHERE CTE.EmpID = 6
) D ON D.Item = CTE.EmpID
OR D.Item = CTE.MgrID
GROUP BY EmpID, FName, LName, MgrID, Lvl;
This uses a split function to break up the path and only display people in that path. The split function can be found here and looks like this:
CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
(#pString VARCHAR(8000), #pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
-- enough to cover VARCHAR(8000)
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
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;

Need a query to insert 'level' into an adjacent list

I have a table like so
ID Node ParentID
1 A 0
2 B 1
3 C 1
4 D 2
5 E 2
6 F 3
7 G 3
8 H 3
9 I 4
10 J 4
11 K 10
12 L 11
I need a query to generate a 'level' field that shows how many levels deep a particular node is. Example below
ID Node ParentID Level
1 A 0 1
2 B 1 2
3 C 1 2
4 D 2 3
5 E 2 3
6 F 3 4
7 G 3 4
8 H 3 4
9 I 4 5
10 J 4 5
11 K 10 6
12 L 11 7
Select Id,
Node,
ParentID,
Dense_Rank() Over(Order by ParentID) as Level
from Table_Name
SQL Fiddle Demo
You can use DENSE_RANK function
SELECT i.ID, p.Node, i.ParentID
,Dense_Rank() Over(Order by ParentID) as Level
FROM TableName AS i;
for more detail visit: http://blog.sqlauthority.com/2007/10/09/sql-server-2005-sample-example-of-ranking-functions-row_number-rank-dense_rank-ntile/
I think the correct way to do it will be to get the parent level and increment it by 1 when inserting the data since all other ways are expensive performance wise.
Something like:
;with tree (ID, ParentID, Level)
as (
select ID, ParentID, 1 from TableName where ParentID = 0
union all
select t.ID, t.ParentID, 1 + tree.Level
from Tree join TableName t on t.ParentID = Tree.ID
)
select ID, Level from Tree
Try this
CREATE TABLE #Table1
([ID] int, [Node] varchar(1), [ParentID] int)
;
INSERT INTO #Table1
([ID], [Node], [ParentID])
VALUES
(1, 'A', 0),
(2, 'B', 1),
(3, 'C', 1),
(4, 'D', 2),
(5, 'E', 2),
(6, 'F', 3),
(7, 'G', 3),
(8, 'H', 3),
(9, 'I', 4),
(10, 'J', 4),
(11, 'K', 10),
(12, 'L', 11)
;
;WITH CTE ([ID], [ParentID], [Node], [Level])
as (
SELECT [ID], [ParentID], [Node], 1 FROM #Table1 WHERE ParentID = 0
UNION all
select t.[ID], t.[ParentID], t.[Node], 1 + c.[Level]
from CTE c inner join #Table1 t ON t.[ParentID] = c.[ID]
)
select ID, [Node], [ParentID], [Level] from CTE
ORDER BY [Node]
DROP TABLE #Table1
Here, you need to set level by grouping ParentID then join both tables by ParentID.
WITH CTE (ParentID, Level)
AS (
SELECT ParentID
, Row_Number() OVER (ORDER BY ParentID) AS Level
FROM Table1
GROUP BY ParentID
)
SELECT t1.ID, t1.Node, t1.ParentID, CTE.Level
FROM Table1 t1
JOIN CTE ON t1.ParentID = CTE.ParentID;
See this SQLFiddle
Update: (for MySQL - just to help others)
To do the same in MySQL try to get row number like this:
SELECT t1.ID, t1.Node, t1.ParentID, Tbl.Level
FROM Table1 t1
JOIN
(
SELECT #Level:=#Level+1 AS Level , ParentID
FROM (SELECT DISTINCT ParentID FROM Table1) t
, (SELECT #Level:=0) r
ORDER BY ParentID
) Tbl
ON t1.ParentID = Tbl.ParentID;
See this SQLFiddle