Ordering by the order of values in a SQL IN clause
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
SQL does not guarantee the order of rows returned by a query unless you specify an ORDER BY clause. When you use an IN() clause to filter by a specific set of values, the results come back in whatever order the database engine finds most efficient, not in the order you listed the values. This article covers techniques across different database systems to force the result order to match the order of values in your IN() list.
Using FIELD() in MySQL
MySQL provides the FIELD() function, which returns the position of a value within a list of arguments. It returns 0 if the value is not found, and 1-based positions otherwise. This makes it straightforward to order results by the order of your IN() values.
The FIELD() function maps each id to its position in the provided list, so id = 5 gets position 1, id = 3 gets position 2, and so on. The ORDER BY then sorts by these positions. Note that you must repeat the value list in both the IN() and FIELD() clauses.
Using array_position() in PostgreSQL
PostgreSQL does not have a FIELD() function, but it provides array_position(), which returns the index of an element within an array.
The ARRAY[5, 3, 8, 1, 9] literal creates a PostgreSQL array, and array_position() returns the 1-based index of each id within that array. This approach is clean and idiomatic for PostgreSQL.
Using CASE WHEN for Cross-Database Compatibility
The CASE WHEN expression works on all major SQL databases. You manually assign a sort position to each value.
This approach is verbose but universally supported. Each WHEN clause maps a value to its desired position. For long lists, the query becomes unwieldy, so consider generating it dynamically in your application code.
Using a VALUES List Joined as a Derived Table
You can create a derived table that pairs each value with its ordinal position, then join it to your main query. This keeps the ordering logic in a clean, separate structure.
This syntax works in PostgreSQL and SQL Server. In MySQL 8.0 and later, you can use a similar approach with a CTE.
This CTE approach works across MySQL, PostgreSQL, SQL Server, and SQLite, making it one of the most portable solutions.
Using FIND_IN_SET() in MySQL
MySQL also offers FIND_IN_SET(), which searches for a string within a comma-separated list and returns its position.
Note that FIND_IN_SET() operates on strings, so numeric values are implicitly cast. The comma-separated list must not contain spaces after the commas. This function is MySQL-specific and works similarly to FIELD(), but accepts the list as a single string argument.
Generating the ORDER BY Dynamically
In application code, you often build the IN() list from a programmatic array. You can generate the corresponding ORDER BY clause at the same time. Here is an example in Python.
Generating the query dynamically avoids the tedium of writing long CASE expressions by hand and keeps your code maintainable when the value list changes.
Common Pitfalls
- Assuming IN() preserves order: The SQL standard does not guarantee any ordering unless
ORDER BYis specified. Never rely on theIN()clause to implicitly order results. - Forgetting to duplicate the value list: With
FIELD()andarray_position(), you must pass the values both in theIN()filter and in the ordering function. Mismatched lists produce incorrect sorting. - Using FIND_IN_SET with spaces:
FIND_IN_SET(id, '5, 3, 8')fails because the spaces become part of the search strings. Always use'5,3,8'with no spaces. - Performance on large value lists: Functions like
FIELD()andCASE WHENwith hundreds of values add overhead to the sort. For very large ordered lists, insert the values into a temporary table with a sort column and join against it. - Mixing data types:
FIND_IN_SET()andarray_position()expect consistent types. Passing integers where strings are expected, or vice versa, can cause silent type coercion bugs or errors depending on the database.
Summary
- Use MySQL
FIELD()for a concise, MySQL-native solution to order byIN()value position. - Use PostgreSQL
array_position()for the equivalent behavior in PostgreSQL. - Use
CASE WHENfor a portable solution that works on all SQL databases. - Use a CTE or derived table with explicit sort positions for clean, readable queries that are easy to generate dynamically.
- Always include an explicit
ORDER BYwhen result order matters, because SQL never guarantees row order without one.

