1

I want to order result of SELECT statement by values given within IN().

I have 200+ values within the IN operator.

Pseudo code:

select * 
from EmployeeId 
where EmployeeId in (2, 198, 5,...till 200)

Result set I am trying to achieve:

EmployeeId
----------    
     2
   198
     5
     .
     .
     .
   200

My research so far: when I use this, I cannot add square brackets ([]) within the IN operator.

Update: I am pasting values in in() operator from excel.

Thanks in advance.

shaadi
  • 161
  • 2
  • 4
  • 21

3 Answers3

3

2 things... 1) The IN operator doesn't provide any sorting capabilities. 2) Due to performance considerations, it's not a good idea to use more than a few values with the IN operator. The IN operator is evaluated as if were a series if OR conditions. So basically, it's the equivalent of writing out 200 Column = 2 OR column = 198... and so on.

Given your goal, I'd suggest using a #temp table. This will improve performance, allow you to sort and, if you assign the sorting column as the PK, you'll be able to sort the final result w/o having a sort operation in the execution plan of the final query (nice little bonus)...

IF OBJECT_ID('tempdb..#SearchValues', 'U') IS NOT NULL 
DROP TABLE #SearchValues;

CREATE TABLE #SearchValues (
    Sort INT NOT NULL IDENTITY(1,1) PRIMARY KEY CLUSTERED,
    Search INT NOT NULL 
    );
INSERT #SearchValues (Search) VALUES 
    (2),(198),(5),(75),(22),(300),(4),
    (122),(201),(40),(200);

SELECT 
    e.*
FROM
    dbo.Employee e
    JOIN #SearchValues sv
        ON e.EmployeeId = sv.Search
ORDER BY
    sv.Sort;
Jason A. Long
  • 4,382
  • 1
  • 12
  • 17
  • 1
    By the way, can you point to some trustworthy source where it would be shown that the order in which `IDENTITY` values are generated is **guaranteed** to match the order of values as they are listed in the `VALUES` clause? I'm afraid you need to insert values one-by-one to guarantee the order. I'd love to be proven wrong. – Vladimir Baranov Aug 30 '17 at 04:41
  • 1
    @VladimirBaranov, good point/question... No I don't have anything other than anecdotal evidence and I'm sure we both know that MS doesn't guarantee order w/o and ORDER BY clause. That said, I can't recall an instance where the order of the values in table constructor didn't match the insert order. If it were life and death, you're right... I'd dump the IDENTITY property and manually code the sort values... – Jason A. Long Aug 30 '17 at 05:02
  • 1
    `IDENTITY` itself is perfectly fine, but `INSERT` statement that inserts several rows at once without `ORDER BY` is most likely not. If you have an `ORDER BY` in the `INSERT` statement, then `IDENTITY` values **would be** generated in that order, guaranteed. In this specific case, though, I don't see how an `ORDER BY` can be specified. So, the only other option I see is to expand that single `INSERT` into multiple statements that insert one value at a time. – Vladimir Baranov Aug 30 '17 at 05:13
  • 1
    @VladimirBaranov, I'm not bagging the use of IDENTITY. Nor am I disputing the fact that individual inserts would in fact guarantee the proper IDENTITY order. It just wouldn't be my "go to" approach. Manually coding the sort values in a table constructor is easier (for me at least) to code, makes for cleaner syntax (IMO) and inserts faster than individual inserts. – Jason A. Long Aug 30 '17 at 05:26
  • Thanks but I forgot to tell you that I am pasting values from excel I can't use select statement within in clause.I updated my question. – shaadi Aug 31 '17 at 01:01
  • Actually, that makes it easier... Simply use a formula like this... ="(" & ROW() & ", " & A1 & "),"... Now you have the guaranteed sort order and you don't have to waste time with additional coding and you don't have to worry about the IDENTITY/INSERT order thing Vladimir and I were discussing above. – Jason A. Long Aug 31 '17 at 01:22
  • 1
    This is a good idea @JasonA.Long and +1 from me. However, I do think you could make it easier with a split function and skip the temp table all together which would require more typing (see my answer below). Good idea though for sure.. – S3S Aug 31 '17 at 13:21
1

This is how conditional order by is done.

Order by 
case 
when EmployeeId = 2 then 1
when EmployeeId = 198 then 2
...
End

EDIT

Assuming you are taking in this comma separated string as a variable, or even if you are not you could store it as a variable, here is a smooth way by using a string splitter which would prevent you from having to manually create a temp table.

declare @table table(col1 int)

insert into @table
values
(1),
(2),
(3),
(4),
(5),
(6),
(7),
(8),
(9),
(10),
(11),
(12)

declare @searchVariable varchar (256) = '10,12,3,5,7,1'

select
    t.* 
from 
    @table t
cross apply
    dbo.DelimitedSplit8K (@searchVariable,',') s
where
    s.Item = t.col1
order by 
    s.ItemNumber    

The split function I used is from Jeff Moden and is added below. It's a good function to have as it is very performant compared to many other split functions.

CREATE FUNCTION [dbo].[DelimitedSplit8K] (@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
;
GO
S3S
  • 24,809
  • 5
  • 26
  • 45
  • I have 200+ values and values are continuously changing it is not viable to use this method. – shaadi Aug 31 '17 at 00:25
  • Well you didn't state that in the original question. Also if that's the case use a table variable or a split string function – S3S Aug 31 '17 at 00:29
  • Thanks for your recommendation.Sorry I forgot to mention that I updates my question If you can please share your knowledge with an example it would be really helpful. – shaadi Aug 31 '17 at 00:35
  • @shaadi i have added an edit. This will eliminate the temp table in the other answer. – S3S Aug 31 '17 at 13:20
0

I know it's a bit late but the best way would be

SELECT * 
FROM EmployeeId 
WHERE EmployeeId in (2, 198, 5,...till 200)
ORDER  BY CHARINDEX(CAST(EmployeeId AS VARCHAR), (2, 198, 5,...till 200))