How to fix ER_WINDOW_RANGE_FRAME_ORDER_TYPE (3587): RANGE requires ORDER BY with numeric/temporal type in MySQL

MySQLINTERMEDIATEMEDIUMUpdated Dec 22, 2025· 138 views

This MySQL error occurs when using the RANGE frame specification in a window function without a proper ORDER BY clause that uses numeric or temporal data types. The error indicates that MySQL cannot determine the range boundaries because the ordering column is not compatible with range-based window frames.

Quick fix

Fixes ER_WINDOW_RANGE_FRAME_ORDER_TYPE (3587): RANGE requires ORDER BY with numeric/temporal type

-- Problematic query with RANGE
SELECT
  employee_id,
  salary,
  AVG(salary) OVER (
    PARTITION BY department_id
    ORDER BY employee_name  -- String column!
    RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) as avg_salary
FROM employees;

-- Fixed query using ROWS instead
SELECT
  employee_id,
  salary,
  AVG(salary) OVER (
    PARTITION BY department_id
    ORDER BY employee_name
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW  -- Changed to ROWS
  ) as avg_salary
FROM employees;
  1. Change RANGE to ROWS frame specification
  2. Use numeric/temporal column in ORDER BY with RANGE
  3. Convert string columns to numeric if appropriate
  4. Use RANGE only when you need value-based windows
mysql — error
-- Problematic query with RANGE
SELECT
employee_id,
salary,
AVG(salary) OVER (
PARTITION BY department_id
ORDER BY employee_name -- String column!
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) as avg_salary
FROM employees;
-- Fixed query using ROWS instead
SELECT
employee_id,
salary,
AVG(salary) OVER (
PARTITION BY department_id
ORDER BY employee_name
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW -- Changed to ROWS
) as avg_salary
FROM employees;
ER_WINDOW_RANGE_FRAME_ORDER_TYPE (3587): RANGE requires ORDER BY with numeric/temporal type

What this error means

The ER_WINDOW_RANGE_FRAME_ORDER_TYPE (3587) error is specific to MySQL window functions. When you use the RANGE frame specification (as opposed to ROWS), MySQL needs to understand how to calculate the range boundaries relative to the current row. RANGE frames work by comparing values in the ORDER BY column(s). For example, "RANGE BETWEEN 10 PRECEDING AND 10 FOLLOWING" means include rows where the ORDER BY column value is within ±10 of the current row's value. This only makes sense when the ORDER BY column has numeric or temporal (date/time) data types, as these types support arithmetic operations needed for range calculations. When you use RANGE with ORDER BY columns that are strings, enums, or other non-numeric/temporal types, MySQL cannot perform the necessary value comparisons to determine which rows fall within the specified range, resulting in this error.

Typical symptoms

  • MySQL query fails with "ER_WINDOW_RANGE_FRAME_ORDER_TYPE (3587): RANGE requires ORDER BY with numeric/temporal type"
  • Window function queries using RANGE frames fail when executed
  • Queries work with ROWS frames but fail when changed to RANGE frames
  • Error occurs specifically with ORDER BY on string, enum, or other non-numeric columns
  • Migration from other databases (like PostgreSQL) may fail due to different window function semantics

Common causes

  1. Using RANGE frame specification with ORDER BY on string/varchar columns
  2. ORDER BY clause uses ENUM, SET, or other non-numeric data types with RANGE frames
  3. Attempting to use RANGE with ORDER BY on JSON or spatial data types
  4. Migrating window function queries from databases with different RANGE semantics
  5. Confusion between ROWS and RANGE frame specifications in window functions

How to fix "ER_WINDOW_RANGE_FRAME_ORDER_TYPE (3587): RANGE requires ORDER BY with numeric/temporal type"

1Change RANGE to ROWS frame specification

If you don't specifically need RANGE semantics, switch to ROWS which works with any data type:

sql
-- Problematic query with RANGE
SELECT
  employee_id,
  salary,
  AVG(salary) OVER (
    PARTITION BY department_id
    ORDER BY employee_name  -- String column!
    RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) as avg_salary
FROM employees;

-- Fixed query using ROWS instead
SELECT
  employee_id,
  salary,
  AVG(salary) OVER (
    PARTITION BY department_id
    ORDER BY employee_name
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW  -- Changed to ROWS
  ) as avg_salary
FROM employees;

ROWS frames work by counting rows (physical offset), not by comparing values, so they work with any ORDER BY data type.

2Use numeric/temporal column in ORDER BY with RANGE

If you need RANGE semantics, ensure ORDER BY uses numeric or temporal columns:

sql
-- Problem: ORDER BY on string column
SELECT
  order_id,
  amount,
  SUM(amount) OVER (
    ORDER BY customer_name  -- String column causes error
    RANGE BETWEEN 1000 PRECEDING AND 1000 FOLLOWING
  ) as rolling_sum
FROM orders;

-- Solution: ORDER BY on numeric column
SELECT
  order_id,
  amount,
  SUM(amount) OVER (
    ORDER BY amount  -- Numeric column works with RANGE
    RANGE BETWEEN 1000 PRECEDING AND 1000 FOLLOWING
  ) as rolling_sum
FROM orders;

-- Temporal example with dates
SELECT
  date,
  revenue,
  SUM(revenue) OVER (
    ORDER BY date  -- Date column works with RANGE
    RANGE BETWEEN INTERVAL 7 DAY PRECEDING AND CURRENT ROW
  ) as weekly_revenue
FROM daily_sales;
3Convert string columns to numeric if appropriate

If your string column represents numeric data, convert it explicitly:

sql
-- Problem: String column that looks numeric
SELECT
  product_id,
  price_string,  -- VARCHAR like '99.99'
  AVG(CAST(price_string AS DECIMAL(10,2))) OVER (
    ORDER BY price_string  -- Still a string!
    RANGE BETWEEN 10 PRECEDING AND 10 FOLLOWING
  ) as avg_price
FROM products;

-- Solution: Convert in ORDER BY
SELECT
  product_id,
  price_string,
  AVG(CAST(price_string AS DECIMAL(10,2))) OVER (
    ORDER BY CAST(price_string AS DECIMAL(10,2))  -- Converted to numeric
    RANGE BETWEEN 10 PRECEDING AND 10 FOLLOWING
  ) as avg_price
FROM products;

-- Or better: Store as proper numeric type
ALTER TABLE products MODIFY COLUMN price DECIMAL(10,2);

SELECT
  product_id,
  price,
  AVG(price) OVER (
    ORDER BY price  -- Now a proper numeric column
    RANGE BETWEEN 10 PRECEDING AND 10 FOLLOWING
  ) as avg_price
FROM products;
4Use RANGE only when you need value-based windows

Understand when to use RANGE vs ROWS:

sql
-- ROWS: Count 3 rows before current row
SELECT
  timestamp,
  temperature,
  AVG(temperature) OVER (
    ORDER BY timestamp
    ROWS BETWEEN 3 PRECEDING AND CURRENT ROW
  ) as moving_avg
FROM sensor_readings;
-- Includes exactly 3 previous rows, regardless of time gaps

-- RANGE: Include rows within 5 minutes
SELECT
  timestamp,
  temperature,
  AVG(temperature) OVER (
    ORDER BY timestamp
    RANGE BETWEEN INTERVAL 5 MINUTE PRECEDING AND CURRENT ROW
  ) as moving_avg
FROM sensor_readings;
-- Includes all rows within 5-minute window, even if many rows

-- Common use cases for RANGE:
-- 1. Time-based windows (dates, timestamps)
-- 2. Value-based windows (amounts, scores, measurements)
-- 3. When you want to include "peer" rows with identical values

-- RANGE with peer rows example:
SELECT
  student_id,
  test_score,
  RANK() OVER (
    ORDER BY test_score
    RANGE BETWEEN CURRENT ROW AND CURRENT ROW  -- Includes peers with same score
  ) as rank_with_peers
FROM test_results;
5Check MySQL version and window function support

Ensure your MySQL version properly supports window functions:

sql
-- Check MySQL version
SELECT VERSION();

-- Window functions require MySQL 8.0+ or MariaDB 10.2+
-- MySQL 5.7 and earlier do not support window functions at all

-- Test basic window function support
SELECT
  1 as test,
  ROW_NUMBER() OVER () as row_num;

-- If this fails, you need to upgrade MySQL
-- Upgrade path: MySQL 5.7 → 8.0

-- Check for known bugs or limitations in your version
-- Some early MySQL 8.0 releases had window function bugs

-- For MariaDB users:
SELECT @@version, @@version_comment;
-- MariaDB 10.2+ supports window functions

-- Consider compatibility mode if migrating from other databases
SET SESSION sql_mode = 'ANSI';
-- Or explicitly set window function behavior
SET SESSION windowing_use_high_precision = ON;

Advanced notes

The RANGE frame specification has specific requirements and behaviors in MySQL:

1. Peer Rows: RANGE includes "peer" rows (rows with identical ORDER BY values) in the frame. This is different from ROWS, which treats each row individually even with identical values.

2. Performance Considerations: RANGE frames can be slower than ROWS frames, especially with large datasets, because MySQL needs to evaluate value comparisons rather than simple row offsets.

3. Data Type Limitations: Only numeric (INT, DECIMAL, FLOAT, etc.) and temporal (DATE, DATETIME, TIMESTAMP) types work with RANGE. Even numeric-like strings (VARCHAR containing numbers) won't work without explicit casting.

4. NULL Handling: NULL values in ORDER BY columns with RANGE frames can have unexpected behavior. All NULLs are considered "peers" and included together in RANGE calculations.

5. Multiple ORDER BY Columns: When using multiple columns in ORDER BY with RANGE, only the first column's data type matters for RANGE compatibility. Subsequent columns can be any type.

6. Fractional Ranges: With decimal/numeric types, fractional ranges work correctly (RANGE BETWEEN 0.5 PRECEDING AND 0.5 FOLLOWING).

7. Temporal Intervals: MySQL supports complex temporal intervals with RANGE: INTERVAL 1 DAY, INTERVAL 2 HOUR, INTERVAL 30 MINUTE, etc.

8. Migration from Other Databases: PostgreSQL and SQL Server have different RANGE semantics (especially regarding EXCLUDE clause support). Test thoroughly when migrating window function queries.

Frequently asked questions

What does "ER_WINDOW_RANGE_FRAME_ORDER_TYPE (3587): RANGE requires ORDER BY with" mean?
The ER_WINDOW_RANGE_FRAME_ORDER_TYPE (3587) error is specific to MySQL window functions. When you use the RANGE frame specification (as opposed to ROWS), MySQL needs to understand how to calculate the range boundaries relative to the current row. RANGE frames work by comparing values in the ORDER BY column(s).
How do I fix "ER_WINDOW_RANGE_FRAME_ORDER_TYPE (3587): RANGE requires ORDER BY with"?
This MySQL error occurs when using the RANGE frame specification in a window function without a proper ORDER BY clause that uses numeric or temporal data types. The error indicates that MySQL cannot determine the range boundaries because the ordering column is not compatible with range-based window frames.
What causes "ER_WINDOW_RANGE_FRAME_ORDER_TYPE (3587): RANGE requires ORDER BY with"?
Using RANGE frame specification with ORDER BY on string/varchar columns ORDER BY clause uses ENUM, SET, or other non-numeric data types with RANGE frames Attempting to use RANGE with ORDER BY on JSON or spatial data types
Is the "ER_WINDOW_RANGE_FRAME_ORDER_TYPE (3587): RANGE requires ORDER BY with" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading