{
  "id": "sql_to_redis_queries",
  "title": "Write SQL Queries for Redis",
  "url": "https://redis.io/docs/latest/develop/ai/redisvl/0.17.0/user_guide/sql_to_redis_queries/",
  "summary": "",
  "content": "\n\nWhile Redis does not natively support SQL, RedisVL provides a `SQLQuery` class that translates SQL-like queries into Redis queries.\n\nThe `SQLQuery` class wraps the [`sql-redis`](https://pypi.org/project/sql-redis/) package. This package is not installed by default, so install it with:\n\n```bash\npip install redisvl[sql-redis]\n```\n\n## Prerequisites\n\nBefore you begin, ensure you have:\n- Installed RedisVL with SQL support: `pip install redisvl[sql-redis]`\n- A running Redis instance ([Redis 8+](https://redis.io/downloads/) or [Redis Cloud](https://redis.io/cloud))\n\n## What You'll Learn\n\nBy the end of this guide, you will be able to:\n- Write SQL-like queries for Redis using `SQLQuery`\n- Translate SELECT, WHERE, and ORDER BY clauses to Redis queries\n- Combine SQL queries with vector search\n- Use aggregate functions and grouping\n- Query geographic data with `geo_distance()`\n- Filter and extract date/time data with `YEAR()`, `MONTH()`, and `DATE_FORMAT()`\n\n## Table of Contents\n\n1. [Define the schema](#define-the-schema)\n2. [Create sample dataset](#create-sample-dataset)\n3. [Create a SearchIndex](#create-a-searchindex)\n4. [Load data](#load-data)\n5. [Write SQL queries](#write-sql-queries)\n6. [Query types](#query-types)\n   - [Text searches](#text-searches)\n   - [Aggregations](#aggregations)\n   - [Vector search](#vector-search)\n   - [Geographic queries](#geographic-queries)\n   - [Date and datetime queries](#date-and-datetime-queries)\n7. [Async support](#async-support)\n8. [Additional query examples](#additional-query-examples)\n9. [Cleanup](#cleanup)\n\n## Define the schema\n\n\n```python\nfrom redisvl.utils.vectorize import HFTextVectorizer\n\nhf = HFTextVectorizer()\n\nschema = {\n    \"index\": {\n        \"name\": \"user_simple\",\n        \"prefix\": \"user_simple_docs\",\n        \"storage_type\": \"json\",\n    },\n    \"fields\": [\n        {\"name\": \"user\", \"type\": \"tag\"},\n        {\"name\": \"region\", \"type\": \"tag\"},\n        {\"name\": \"job\", \"type\": \"tag\"},\n        {\"name\": \"job_description\", \"type\": \"text\"},\n        {\"name\": \"age\", \"type\": \"numeric\"},\n        {\"name\": \"office_location\", \"type\": \"geo\"},\n        {\n            \"name\": \"job_embedding\",\n            \"type\": \"vector\",\n            \"attrs\": {\n                \"dims\": len(hf.embed(\"get embed length\")),\n                \"distance_metric\": \"cosine\",\n                \"algorithm\": \"flat\",\n                \"datatype\": \"float32\"\n            }\n        }\n    ]\n}\n```\n\n    /Users/robert.shelton/Documents/redis-vl-python/.venv/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n      from .autonotebook import tqdm as notebook_tqdm\n\n\n## Create sample dataset\n\n\n```python\n# Office locations use \"longitude,latitude\" format (lon,lat - Redis convention)\n# San Francisco: -122.4194, 37.7749\n# Chicago: -87.6298, 41.8781\n# New York: -73.9857, 40.7580\ndata = [\n    {\n        'user': 'john',\n        'age': 34,\n        'job': 'software engineer',\n        'region': 'us-west',\n        'job_description': 'Designs, develops, and maintains software applications and systems.',\n        'office_location': '-122.4194,37.7749'  # San Francisco\n    },\n    {\n        'user': 'bill',\n        'age': 54,\n        'job': 'engineer',\n        'region': 'us-central',\n        'job_description': 'Applies scientific and mathematical principles to solve technical problems.',\n        'office_location': '-87.6298,41.8781'  # Chicago\n    },\n    {\n        'user': 'mary',\n        'age': 24,\n        'job': 'doctor',\n        'region': 'us-central',\n        'job_description': 'Diagnoses and treats illnesses, injuries, and other medical conditions in the healthcare field.',\n        'office_location': '-87.6298,41.8781'  # Chicago\n    },\n    {\n        'user': 'joe',\n        'age': 27,\n        'job': 'dentist',\n        'region': 'us-east',\n        'job_description': 'Provides oral healthcare including diagnosing and treating teeth and gum issues.',\n        'office_location': '-73.9857,40.7580'  # New York\n    },\n    {\n        'user': 'stacy',\n        'age': 61,\n        'job': 'project manager',\n        'region': 'us-west',\n        'job_description': 'Plans, organizes, and oversees projects from inception to completion.',\n        'office_location': '-122.4194,37.7749'  # San Francisco\n    }\n]\n\ndata = [\n    {  \n        **d,\n        \"job_embedding\": hf.embed(f\"{d['job_description']=} {d['job']=}\"),\n    } \n    for d in data\n]\n```\n\n## Create a SearchIndex\n\nWith the schema and sample dataset ready, create a `SearchIndex`.\n\n### Bring your own Redis connection instance\n\nThis is ideal in scenarios where you have custom settings on the connection instance or if your application will share a connection pool:\n\n\n```python\nfrom redisvl.index import SearchIndex\nfrom redis import Redis\n\nclient = Redis.from_url(\"redis://localhost:6379\")\nindex = SearchIndex.from_dict(schema, redis_client=client)\n```\n\n### Let the index manage the connection instance\n\nThis is ideal for simple cases:\n\n\n```python\nindex = SearchIndex.from_dict(schema, redis_url=\"redis://localhost:6379\")\n```\n\n### Create the index\n\nNow that we are connected to Redis, we need to run the create command.\n\n\n```python\nindex.create(overwrite=True, drop=True)\n```\n\n## Load data\n\nLoad the sample dataset to Redis.\n\n### Validate data entries on load\nRedisVL uses pydantic validation under the hood to ensure loaded data is valid and conforms to your schema. This setting is optional and can be configured via `validate_on_load=True` in the `SearchIndex` class.\n\n**Note**: This guide omits `validate_on_load` because GEO fields use `longitude,latitude` format (Redis convention), which differs from the validation expectation. A future RedisVL release will align GEO validation with Redis conventions.\n\n\n```python\nkeys = index.load(data)\n\nprint(keys)\n```\n\n    ['user_simple_docs:01KN7Y4J630537VY4Y5D9EZMYX', 'user_simple_docs:01KN7Y4J630537VY4Y5D9EZMYY', 'user_simple_docs:01KN7Y4J630537VY4Y5D9EZMYZ', 'user_simple_docs:01KN7Y4J630537VY4Y5D9EZMZ0', 'user_simple_docs:01KN7Y4J630537VY4Y5D9EZMZ1']\n\n\n## Write SQL queries\n\nFirst, let's test a simple select statement such as the one below.\n\n\n```python\nfrom redisvl.query import SQLQuery\n\nsql_str = \"\"\"\n    SELECT user, region, job, age\n    FROM user_simple\n    WHERE age \u003e 17\n    \"\"\"\n\n# Optional sql_redis_options are passed through to sql-redis.\n# schema_cache_strategy balances startup cost vs repeated-query speed:\n# use \"lazy\" (default) to load schemas on demand, or \"load_all\"\n# to preload schemas up front for broader repeated-query workloads.\nsql_query = SQLQuery(\n    sql_str, sql_redis_options={\"schema_cache_strategy\": \"lazy\"}\n)\n\n```\n\n## Check the created query string\n\n\n```python\nsql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\n```\n\n\n\n\n    'FT.SEARCH user_simple \"@age:[(17 +inf]\" RETURN 4 user region job age DIALECT 2'\n\n\n\n### Executing the query\n\n\n```python\nresults = index.query(sql_query)\nresults\n```\n\n\n\n\n    [{'user': 'john',\n      'region': 'us-west',\n      'job': 'software engineer',\n      'age': '34'},\n     {'user': 'bill', 'region': 'us-central', 'job': 'engineer', 'age': '54'},\n     {'user': 'mary', 'region': 'us-central', 'job': 'doctor', 'age': '24'},\n     {'user': 'joe', 'region': 'us-east', 'job': 'dentist', 'age': '27'},\n     {'user': 'stacy', 'region': 'us-west', 'job': 'project manager', 'age': '61'}]\n\n\n\n## Query types\n\n### Conditional operators\n\n\n```python\nsql_str = \"\"\"\n    SELECT user, region, job, age\n    FROM user_simple\n    WHERE age \u003e 17 and region = 'us-west'\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\nresults\n```\n\n    Resulting redis query:  FT.SEARCH user_simple \"@age:[(17 +inf] @region:{us\\-west}\" RETURN 4 user region job age DIALECT 2\n\n\n\n\n\n    [{'user': 'john',\n      'region': 'us-west',\n      'job': 'software engineer',\n      'age': '34'},\n     {'user': 'stacy', 'region': 'us-west', 'job': 'project manager', 'age': '61'}]\n\n\n\n\n```python\nsql_str = \"\"\"\n    SELECT user, region, job, age\n    FROM user_simple\n    WHERE region = 'us-west' or region = 'us-central'\n    \"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\nresults\n```\n\n    Resulting redis query:  FT.SEARCH user_simple \"((@region:{us\\-west})|(@region:{us\\-central}))\" RETURN 4 user region job age\n\n\n\n\n\n    [{'user': 'mary', 'region': 'us-central', 'job': 'doctor', 'age': '24'},\n     {'user': 'bill', 'region': 'us-central', 'job': 'engineer', 'age': '54'},\n     {'user': 'stacy', 'region': 'us-west', 'job': 'project manager', 'age': '61'},\n     {'user': 'john',\n      'region': 'us-west',\n      'job': 'software engineer',\n      'age': '34'}]\n\n\n\n\n```python\n# job is a tag field therefore this syntax works\nsql_str = \"\"\"\n    SELECT user, region, job, age\n    FROM user_simple\n    WHERE job IN ('software engineer', 'engineer', 'pancake tester')\n    \"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\nresults\n```\n\n    Resulting redis query:  FT.SEARCH user_simple \"@job:{software engineer|engineer|pancake tester}\" RETURN 4 user region job age\n\n\n\n\n\n    [{'user': 'bill', 'region': 'us-central', 'job': 'engineer', 'age': '54'},\n     {'user': 'john',\n      'region': 'us-west',\n      'job': 'software engineer',\n      'age': '34'}]\n\n\n\n### Text searches\n\nSee [the docs](https://redis.io/docs/latest/develop/ai/search-and-query/query/full-text/) for available text queries in Redis.\n\nFor more on exact matching see [here](https://redis.io/docs/latest/develop/ai/search-and-query/query/exact-match/).\n\nWith `sql-redis \u003e= 0.4.0`, TEXT search operators are explicit:\n\n- `WHERE job_description = 'healthcare including'` for exact phrase matching\n- `WHERE job_description LIKE 'sci%'`, `LIKE '%care'`, or `LIKE '%diagnose%'` for wildcard matching\n- `WHERE fuzzy(job_description, 'diagnose')` for typo-tolerant matching\n- `WHERE fulltext(job_description, 'healthcare OR diagnosing')` for tokenized search\n\n\n\n```python\n# Prefix (LIKE)\nsql_str = \"\"\"\n    SELECT user, region, job, job_description, age\n    FROM user_simple\n    WHERE job_description LIKE 'sci%'\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\nresults\n```\n\n    Resulting redis query:  FT.SEARCH user_simple \"@job_description:sci*\" RETURN 5 user region job job_description age\n\n\n\n\n\n    [{'user': 'bill',\n      'region': 'us-central',\n      'job': 'engineer',\n      'job_description': 'Applies scientific and mathematical principles to solve technical problems.',\n      'age': '54'}]\n\n\n\n\n```python\n# Suffix (LIKE)\nsql_str = \"\"\"\n    SELECT user, region, job, job_description, age\n    FROM user_simple\n    WHERE job_description LIKE '%care'\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\nresults\n```\n\n    Resulting redis query:  FT.SEARCH user_simple \"@job_description:*care\" RETURN 5 user region job job_description age\n\n\n\n\n\n    [{'user': 'mary',\n      'region': 'us-central',\n      'job': 'doctor',\n      'job_description': 'Diagnoses and treats illnesses, injuries, and other medical conditions in the healthcare field.',\n      'age': '24'},\n     {'user': 'joe',\n      'region': 'us-east',\n      'job': 'dentist',\n      'job_description': 'Provides oral healthcare including diagnosing and treating teeth and gum issues.',\n      'age': '27'}]\n\n\n\n\n```python\n# Contains (LIKE)\nsql_str = \"\"\"\n    SELECT user, region, job, job_description, age\n    FROM user_simple\n    WHERE job_description LIKE '%diagnose%'\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\nresults\n```\n\n    Resulting redis query:  FT.SEARCH user_simple \"@job_description:*diagnose*\" RETURN 5 user region job job_description age\n\n\n\n\n\n    [{'user': 'mary',\n      'region': 'us-central',\n      'job': 'doctor',\n      'job_description': 'Diagnoses and treats illnesses, injuries, and other medical conditions in the healthcare field.',\n      'age': '24'}]\n\n\n\n\n```python\n# Phrase no stop words\nsql_str = \"\"\"\n    SELECT user, region, job, job_description, age\n    FROM user_simple\n    WHERE job_description = 'healthcare including'\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\nresults\n```\n\n    Resulting redis query:  FT.SEARCH user_simple \"@job_description:\"healthcare including\"\" RETURN 5 user region job job_description age\n\n\n\n\n\n    [{'user': 'joe',\n      'region': 'us-east',\n      'job': 'dentist',\n      'job_description': 'Provides oral healthcare including diagnosing and treating teeth and gum issues.',\n      'age': '27'}]\n\n\n\n\n```python\n# Phrase with stop words (sql-redis strips default stopwords and warns)\nsql_str = \"\"\"\n    SELECT user, region, job, job_description, age\n    FROM user_simple\n    WHERE job_description = 'diagnosing and treating'\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\nresults\n```\n\n    Resulting redis query:  FT.SEARCH user_simple \"@job_description:\"diagnosing treating\"\" RETURN 5 user region job job_description age\n\n\n\n\n\n    [{'user': 'joe',\n      'region': 'us-east',\n      'job': 'dentist',\n      'job_description': 'Provides oral healthcare including diagnosing and treating teeth and gum issues.',\n      'age': '27'}]\n\n\n\n\n```python\nsql_str = \"\"\"\n    SELECT user, region, job, age\n    FROM user_simple\n    WHERE age BETWEEN 40 and 60\n    \"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\nresults\n```\n\n    Resulting redis query:  FT.SEARCH user_simple \"@age:[40 60]\" RETURN 4 user region job age\n\n\n\n\n\n    [{'user': 'bill', 'region': 'us-central', 'job': 'engineer', 'age': '54'}]\n\n\n\n### Aggregations\n\nSee docs for redis supported reducer functions: [docs](https://redis.io/docs/latest/develop/ai/search-and-query/advanced-concepts/aggregations/#supported-groupby-reducers).\n\n\n```python\nsql_str = \"\"\"\n    SELECT\n        user,\n        COUNT(age) as count_age,\n        COUNT_DISTINCT(age) as count_distinct_age,\n        MIN(age) as min_age,\n        MAX(age) as max_age,\n        AVG(age) as avg_age,\n        STDEV(age) as std_age,\n        FIRST_VALUE(age) as fist_value_age,\n        ARRAY_AGG(age) as to_list_age,\n        QUANTILE(age, 0.99) as quantile_age\n    FROM user_simple\n    GROUP BY region\n    \"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\nresults\n```\n\n    Resulting redis query:  FT.AGGREGATE user_simple \"*\" LOAD 3 @age @region @user GROUPBY 1 @region REDUCE COUNT 0 AS count_age REDUCE COUNT_DISTINCT 1 @age AS count_distinct_age REDUCE MIN 1 @age AS min_age REDUCE MAX 1 @age AS max_age REDUCE AVG 1 @age AS avg_age REDUCE STDDEV 1 @age AS std_age REDUCE FIRST_VALUE 1 @age AS fist_value_age REDUCE TOLIST 1 @age AS to_list_age REDUCE QUANTILE 2 @age 0.99 AS quantile_age\n\n\n\n\n\n    [{'region': 'us-west',\n      'count_age': '2',\n      'count_distinct_age': '2',\n      'min_age': '34',\n      'max_age': '61',\n      'avg_age': '47.5',\n      'std_age': '19.091883092',\n      'fist_value_age': '61',\n      'to_list_age': ['34', '61'],\n      'quantile_age': '61'},\n     {'region': 'us-central',\n      'count_age': '2',\n      'count_distinct_age': '2',\n      'min_age': '24',\n      'max_age': '54',\n      'avg_age': '39',\n      'std_age': '21.2132034356',\n      'fist_value_age': '24',\n      'to_list_age': ['24', '54'],\n      'quantile_age': '54'},\n     {'region': 'us-east',\n      'count_age': '1',\n      'count_distinct_age': '1',\n      'min_age': '27',\n      'max_age': '27',\n      'avg_age': '27',\n      'std_age': '0',\n      'fist_value_age': '27',\n      'to_list_age': ['27'],\n      'quantile_age': '27'}]\n\n\n\n### Vector search\n\n\n```python\nsql_str = \"\"\"\n    SELECT user, job, job_description, cosine_distance(job_embedding, :vec) AS vector_distance\n    FROM user_simple\n    ORDER BY vector_distance ASC\n    \"\"\"\n\nvec = hf.embed(\"looking for someone to use base principles to solve problems\", as_buffer=True)\nsql_query = SQLQuery(sql_str, params={\"vec\": vec})\n\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\n\nresults\n```\n\n    Resulting redis query:  FT.SEARCH user_simple \"*=\u003e[KNN 10 @job_embedding $vector AS vector_distance]\" PARAMS 2 vector $vector DIALECT 2 RETURN 4 user job job_description vector_distance SORTBY vector_distance ASC\n\n\n\n\n\n    [{'vector_distance': '0.823510587215',\n      'user': 'bill',\n      'job': 'engineer',\n      'job_description': 'Applies scientific and mathematical principles to solve technical problems.'},\n     {'vector_distance': '0.965160369873',\n      'user': 'john',\n      'job': 'software engineer',\n      'job_description': 'Designs, develops, and maintains software applications and systems.'},\n     {'vector_distance': '1.00401353836',\n      'user': 'mary',\n      'job': 'doctor',\n      'job_description': 'Diagnoses and treats illnesses, injuries, and other medical conditions in the healthcare field.'},\n     {'vector_distance': '1.0062687397',\n      'user': 'stacy',\n      'job': 'project manager',\n      'job_description': 'Plans, organizes, and oversees projects from inception to completion.'},\n     {'vector_distance': '1.01110625267',\n      'user': 'joe',\n      'job': 'dentist',\n      'job_description': 'Provides oral healthcare including diagnosing and treating teeth and gum issues.'}]\n\n\n\n\n```python\nsql_str = \"\"\"\n    SELECT user, region, cosine_distance(job_embedding, :vec) AS vector_distance\n    FROM user_simple\n    WHERE region = 'us-central'\n    ORDER BY vector_distance ASC\n    \"\"\"\n\nvec = hf.embed(\"looking for someone to use base principles to solve problems\", as_buffer=True)\nsql_query = SQLQuery(sql_str, params={\"vec\": vec})\n\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\n\nresults\n```\n\n    Resulting redis query:  FT.SEARCH user_simple \"(@region:{us\\-central})=\u003e[KNN 10 @job_embedding $vector AS vector_distance]\" PARAMS 2 vector $vector DIALECT 2 RETURN 3 user region vector_distance SORTBY vector_distance ASC\n\n\n\n\n\n    [{'vector_distance': '0.823510587215', 'user': 'bill', 'region': 'us-central'},\n     {'vector_distance': '1.00401353836', 'user': 'mary', 'region': 'us-central'}]\n\n\n\n### Geographic queries\n\nUse `geo_distance()` to filter by location or calculate distances between points.\n\n**Syntax:**\n- Filter: `WHERE geo_distance(field, POINT(lon, lat), 'unit') \u003c radius`\n- Distance: `SELECT geo_distance(field, POINT(lon, lat)) AS distance`\n\n**Units:** `'km'` (kilometers), `'mi'` (miles), `'m'` (meters), `'ft'` (feet)\n\n**Note:** `POINT()` uses longitude first, then latitude - matching Redis conventions.\n\n\n```python\n# Find users within 500km of San Francisco\nsql_str = \"\"\"\n    SELECT user, job, region, office_location\n    FROM user_simple\n    WHERE geo_distance(office_location, POINT(-122.4194, 37.7749), 'km') \u003c 500\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\nresults\n```\n\n    Resulting redis query:  FT.SEARCH user_simple \"*\" GEOFILTER office_location -122.4194 37.7749 500.0 km RETURN 4 user job region office_location\n\n\n\n\n\n    [{'user': 'stacy',\n      'job': 'project manager',\n      'region': 'us-west',\n      'office_location': '-122.4194,37.7749'},\n     {'user': 'john',\n      'job': 'software engineer',\n      'region': 'us-west',\n      'office_location': '-122.4194,37.7749'}]\n\n\n\n\n```python\n# Find users within 50 miles of Chicago (using miles)\nsql_str = \"\"\"\n    SELECT user, job, region\n    FROM user_simple\n    WHERE geo_distance(office_location, POINT(-87.6298, 41.8781), 'mi') \u003c 50\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\nresults\n```\n\n    Resulting redis query:  FT.SEARCH user_simple \"*\" GEOFILTER office_location -87.6298 41.8781 50.0 mi RETURN 3 user job region\n\n\n\n\n\n    [{'user': 'mary', 'job': 'doctor', 'region': 'us-central'},\n     {'user': 'bill', 'job': 'engineer', 'region': 'us-central'}]\n\n\n\n\n```python\n# Combine GEO filter with TAG filter - find engineers near Chicago\nsql_str = \"\"\"\n    SELECT user, job, region\n    FROM user_simple\n    WHERE job = 'engineer' AND geo_distance(office_location, POINT(-87.6298, 41.8781), 'mi') \u003c 50\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\nresults\n```\n\n    Resulting redis query:  FT.SEARCH user_simple \"@job:{engineer}\" GEOFILTER office_location -87.6298 41.8781 50.0 mi RETURN 3 user job region\n\n\n\n\n\n    [{'user': 'bill', 'job': 'engineer', 'region': 'us-central'}]\n\n\n\n\n```python\n# Combine GEO with NUMERIC filter - find users over 30 near San Francisco\nsql_str = \"\"\"\n    SELECT user, job, age\n    FROM user_simple\n    WHERE age \u003e 30 AND geo_distance(office_location, POINT(-122.4194, 37.7749), 'km') \u003c 100\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\nresults\n```\n\n    Resulting redis query:  FT.SEARCH user_simple \"@age:[(30 +inf]\" GEOFILTER office_location -122.4194 37.7749 100.0 km RETURN 3 user job age\n\n\n\n\n\n    [{'user': 'stacy', 'job': 'project manager', 'age': '61'},\n     {'user': 'john', 'job': 'software engineer', 'age': '34'}]\n\n\n\n\n```python\n# Combine GEO with TEXT search - find users with \"technical\" in job description near Chicago\nsql_str = \"\"\"\n    SELECT user, job, job_description\n    FROM user_simple\n    WHERE job_description LIKE 'technical%' AND geo_distance(office_location, POINT(-87.6298, 41.8781), 'km') \u003c 100\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\nresults\n```\n\n    Resulting redis query:  FT.SEARCH user_simple \"@job_description:technical*\" GEOFILTER office_location -87.6298 41.8781 100.0 km RETURN 3 user job job_description\n\n\n\n\n\n    [{'user': 'bill',\n      'job': 'engineer',\n      'job_description': 'Applies scientific and mathematical principles to solve technical problems.'}]\n\n\n\n\n```python\n# Calculate distances from New York to all users\n# Note: geo_distance() in SELECT uses FT.AGGREGATE and returns distance in meters\nsql_str = \"\"\"\n    SELECT user, region, geo_distance(office_location, POINT(-73.9857, 40.7580)) AS distance_meters\n    FROM user_simple\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = index.query(sql_query)\n\n# Convert meters to km for readability and sort by distance\nprint(\"\\nDistances from NYC:\")\nfor r in sorted(results, key=lambda x: float(x.get('distance_meters', 0))):\n    dist_km = float(r.get('distance_meters', 0)) / 1000\n    print(f\"  {r['user']:10} | {r['region']:12} | {dist_km:,.0f} km\")\n```\n\n    Resulting redis query:  FT.AGGREGATE user_simple \"*\" LOAD 3 @office_location @region @user APPLY geodistance(@office_location, -73.9857, 40.758) AS distance_meters\n    \n    Distances from NYC:\n      joe        | us-east      | 0 km\n      mary       | us-central   | 1,145 km\n      bill       | us-central   | 1,145 km\n      stacy      | us-west      | 4,131 km\n      john       | us-west      | 4,131 km\n\n\n### GEO Query Summary\n\n| Method | Pattern | Example |\n|--------|---------|---------|\n| **SQL - Basic radius** | `WHERE geo_distance(field, POINT(lon, lat), 'unit') \u003c radius` | `WHERE geo_distance(location, POINT(-122.4, 37.8), 'km') \u003c 50` |\n| **SQL - With miles** | Same with `'mi'` unit | `WHERE geo_distance(location, POINT(-73.9, 40.7), 'mi') \u003c 10` |\n| **SQL - With TAG** | Combined with `AND` | `WHERE category = 'retail' AND geo_distance(...) \u003c 100` |\n| **SQL - With NUMERIC** | Combined with `AND` | `WHERE age \u003e 30 AND geo_distance(...) \u003c 100` |\n| **SQL - Distance calc** | `SELECT geo_distance(...)` | `SELECT geo_distance(location, POINT(lon, lat)) AS dist` |\n| **Native - Within** | `Geo(field) == GeoRadius(...)` | `Geo(\"location\") == GeoRadius(-122.4, 37.8, 100, \"km\")` |\n| **Native - Outside** | `Geo(field) != GeoRadius(...)` | `Geo(\"location\") != GeoRadius(-87.6, 41.9, 1000, \"km\")` |\n| **Native - Combined** | Use `\u0026` and `\\|` operators | `geo_filter \u0026 tag_filter \u0026 num_filter` |\n\n**Key Points:**\n1. **Coordinate Format**: `\"longitude,latitude\"` - longitude first!\n2. **POINT() Syntax**: `POINT(lon, lat)` - longitude first (matches Redis)\n3. **Units**: `'km'`, `'mi'`, `'m'`, `'ft'`\n4. **geo_distance()**: Returns meters, divide by 1000 for km\n\n### Date and datetime queries\n\nUse date literals and functions to query timestamp data. Redis stores dates as Unix timestamps in NUMERIC fields.\n\n**Key Concepts:**\n- Date literals like `'2024-01-01'` are auto-converted to Unix timestamps\n- Date functions (`YEAR()`, `MONTH()`, `DAY()`) extract date parts\n- `DATE_FORMAT()` formats timestamps as readable strings\n\n\n```python\n# Create a separate index for date examples\nfrom datetime import datetime, timezone\n\ndef to_timestamp(date_str):\n    \"\"\"Convert ISO date string to Unix timestamp (UTC).\"\"\"\n    dt = datetime.strptime(date_str, \"%Y-%m-%d\")\n    dt = dt.replace(tzinfo=timezone.utc)\n    return int(dt.timestamp())\n\n# Define schema with NUMERIC fields for timestamps\nevents_schema = {\n    \"index\": {\n        \"name\": \"events\",\n        \"prefix\": \"event:\",\n        \"storage_type\": \"hash\",\n    },\n    \"fields\": [\n        {\"name\": \"name\", \"type\": \"text\", \"attrs\": {\"sortable\": True}},\n        {\"name\": \"category\", \"type\": \"tag\", \"attrs\": {\"sortable\": True}},\n        {\"name\": \"created_at\", \"type\": \"numeric\", \"attrs\": {\"sortable\": True}},\n    ],\n}\n\nevents_index = SearchIndex.from_dict(events_schema, redis_url=\"redis://localhost:6379\")\nevents_index.create(overwrite=True)\n\n# Sample events spanning 2023-2024\nevents = [\n    {\"name\": \"New Year Kickoff\", \"category\": \"meeting\", \"created_at\": to_timestamp(\"2024-01-01\")},\n    {\"name\": \"Q1 Planning\", \"category\": \"meeting\", \"created_at\": to_timestamp(\"2024-01-15\")},\n    {\"name\": \"Product Launch\", \"category\": \"release\", \"created_at\": to_timestamp(\"2024-02-20\")},\n    {\"name\": \"Team Offsite\", \"category\": \"meeting\", \"created_at\": to_timestamp(\"2024-03-10\")},\n    {\"name\": \"Summer Summit\", \"category\": \"conference\", \"created_at\": to_timestamp(\"2024-07-15\")},\n    {\"name\": \"Holiday Party 2023\", \"category\": \"conference\", \"created_at\": to_timestamp(\"2023-12-15\")},\n    {\"name\": \"Year End Review 2023\", \"category\": \"meeting\", \"created_at\": to_timestamp(\"2023-12-20\")},\n]\n\nevents_index.load(events)\n\nprint(f\"Loaded {len(events)} events:\")\nfor e in events:\n    date = datetime.fromtimestamp(e[\"created_at\"], tz=timezone.utc).strftime(\"%Y-%m-%d\")\n    print(f\"  - {e['name']:25} | {date} | {e['category']}\")\n```\n\n    Loaded 7 events:\n      - New Year Kickoff          | 2024-01-01 | meeting\n      - Q1 Planning               | 2024-01-15 | meeting\n      - Product Launch            | 2024-02-20 | release\n      - Team Offsite              | 2024-03-10 | meeting\n      - Summer Summit             | 2024-07-15 | conference\n      - Holiday Party 2023        | 2023-12-15 | conference\n      - Year End Review 2023      | 2023-12-20 | meeting\n\n\n\n```python\n# Find events after January 1st, 2024 using date literal\nsql_str = \"\"\"\n    SELECT name, category\n    FROM events\n    WHERE created_at \u003e '2024-01-01'\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = events_index.query(sql_query)\n\nprint(f\"\\nEvents after 2024-01-01 ({len(results)} found):\")\nfor r in results:\n    print(f\"  - {r['name']}\")\n```\n\n    Resulting redis query:  FT.SEARCH events \"@created_at:[(1704067200 +inf]\" RETURN 2 name category\n    \n    Events after 2024-01-01 (4 found):\n      - Summer Summit\n      - Q1 Planning\n      - Team Offsite\n      - Product Launch\n\n\n\n```python\n# Find events in Q1 2024 using BETWEEN\nsql_str = \"\"\"\n    SELECT name, category\n    FROM events\n    WHERE created_at BETWEEN '2024-01-01' AND '2024-03-31'\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = events_index.query(sql_query)\n\nprint(f\"\\nEvents in Q1 2024 ({len(results)} found):\")\nfor r in results:\n    print(f\"  - {r['name']} ({r['category']})\")\n```\n\n    Resulting redis query:  FT.SEARCH events \"@created_at:[1704067200 1711843200]\" RETURN 2 name category\n    \n    Events in Q1 2024 (4 found):\n      - Q1 Planning (meeting)\n      - New Year Kickoff (meeting)\n      - Team Offsite (meeting)\n      - Product Launch (release)\n\n\n\n```python\n# Combine date filter with TAG filter - find meetings in H1 2024\nsql_str = \"\"\"\n    SELECT name\n    FROM events\n    WHERE category = 'meeting' AND created_at BETWEEN '2024-01-01' AND '2024-06-30'\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nresults = events_index.query(sql_query)\n\nprint(f\"Meetings in H1 2024 ({len(results)} found):\")\nfor r in results:\n    print(f\"  - {r['name']}\")\n```\n\n    Meetings in H1 2024 (3 found):\n      - Q1 Planning\n      - New Year Kickoff\n      - Team Offsite\n\n\n### Date Query Summary\n\n| Pattern | Example |\n|---------|---------|\n| **After date** | `WHERE created_at \u003e '2024-01-01'` |\n| **Before date** | `WHERE created_at \u003c '2024-12-31'` |\n| **Date range** | `WHERE created_at BETWEEN '2024-01-01' AND '2024-03-31'` |\n| **Extract year** | `SELECT YEAR(created_at) AS year` |\n| **Extract month** | `SELECT MONTH(created_at) AS month` (returns 0-11) |\n| **Filter by year** | `WHERE YEAR(created_at) = 2024` |\n| **Group by date** | `GROUP BY YEAR(created_at)` |\n| **Format date** | `DATE_FORMAT(created_at, '%Y-%m-%d')` |\n\n**Key Points:**\n1. **Storage**: Dates stored as Unix timestamps in NUMERIC fields\n2. **Date Literals**: ISO 8601 strings auto-converted to timestamps\n3. **Timezone**: Dates without timezone are treated as UTC\n4. **Month Index**: Redis `MONTH()` returns 0-11, not 1-12\n\n## Async support\n\nSQL queries also work with `AsyncSearchIndex` for async applications:\n\n\n```python\nfrom redisvl.index import AsyncSearchIndex\nfrom redisvl.query import SQLQuery\n\n# Create async index\nasync_index = AsyncSearchIndex.from_dict(schema, redis_url=\"redis://localhost:6379\")\n\n# Execute SQL query asynchronously\nsql_query = SQLQuery(f\"SELECT user, age FROM {async_index.name} WHERE age \u003e 30\")\nresults = await async_index.query(sql_query)\n\n# Cleanup\nawait async_index.disconnect()\n```\n\n## Additional Query Examples\n\nThe following sections provide more detailed examples for geographic and date queries.\n\n### Native GEO filters\n\nAs an alternative to SQL syntax, RedisVL provides native `Geo` and `GeoRadius` filter classes.\nThese can be combined with other filters using `\u0026` (AND) and `|` (OR) operators.\n\n\n```python\nfrom redisvl.query import FilterQuery\nfrom redisvl.query.filter import Geo, GeoRadius, Tag, Num\n\n# Find users within 100km of Chicago using native filters\ngeo_filter = Geo(\"office_location\") == GeoRadius(-87.6298, 41.8781, 100, \"km\")\n\nprint(f\"Filter expression: {geo_filter}\\n\")\n\nquery = FilterQuery(\n    filter_expression=geo_filter,\n    return_fields=[\"user\", \"job\", \"region\"]\n)\n\nresults = index.query(query)\nprint(f\"Users within 100km of Chicago ({len(results)} found):\")\nfor r in results:\n    print(f\"  - {r['user']} ({r['job']}) - {r['region']}\")\n```\n\n    Filter expression: @office_location:[-87.6298 41.8781 100 km]\n    \n    Users within 100km of Chicago (2 found):\n      - mary (doctor) - us-central\n      - bill (engineer) - us-central\n\n\n\n```python\n# Find users OUTSIDE 1000km of Chicago (using !=)\ngeo_filter_outside = Geo(\"office_location\") != GeoRadius(-87.6298, 41.8781, 1000, \"km\")\n\nprint(f\"Filter expression: {geo_filter_outside}\\n\")\n\nquery = FilterQuery(\n    filter_expression=geo_filter_outside,\n    return_fields=[\"user\", \"region\"]\n)\n\nresults = index.query(query)\nprint(f\"Users OUTSIDE 1000km of Chicago ({len(results)} found):\")\nfor r in results:\n    print(f\"  - {r['user']} ({r['region']})\")\n```\n\n    Filter expression: (-@office_location:[-87.6298 41.8781 1000 km])\n    \n    Users OUTSIDE 1000km of Chicago (3 found):\n      - joe (us-east)\n      - stacy (us-west)\n      - john (us-west)\n\n\n\n```python\n# Combine GEO + TAG + NUMERIC filters\n# Find engineers over 40 within 500km of Chicago\ngeo_filter = Geo(\"office_location\") == GeoRadius(-87.6298, 41.8781, 500, \"km\")\njob_filter = Tag(\"job\") == \"engineer\"\nage_filter = Num(\"age\") \u003e 40\n\ncombined_filter = geo_filter \u0026 job_filter \u0026 age_filter\n\nprint(f\"Combined filter: {combined_filter}\\n\")\n\nquery = FilterQuery(\n    filter_expression=combined_filter,\n    return_fields=[\"user\", \"job\", \"age\", \"region\"]\n)\n\nresults = index.query(query)\nprint(f\"Engineers over 40 within 500km of Chicago ({len(results)} found):\")\nfor r in results:\n    print(f\"  - {r['user']} (age: {r['age']}) - {r['region']}\")\n```\n\n    Combined filter: ((@office_location:[-87.6298 41.8781 500 km] @job:{engineer}) @age:[(40 +inf])\n    \n    Engineers over 40 within 500km of Chicago (1 found):\n      - bill (age: 54) - us-central\n\n\n### Additional Date Examples\n\nMore advanced date query patterns including date function extraction and formatting.\n\n\n```python\n# Extract YEAR and MONTH using date functions in SELECT\nsql_str = \"\"\"\n    SELECT name, YEAR(created_at) AS year, MONTH(created_at) AS month\n    FROM events\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = events_index.query(sql_query)\n\nprint(f\"\\nEvents with year/month:\")\nfor r in results:\n    # Note: MONTH returns 0-11 in Redis (0=January)\n    month_num = int(r.get('month', 0)) + 1\n    print(f\"  - {r['name']:25} | {r.get('year')}-{month_num:02d}\")\n```\n\n    Resulting redis query:  FT.AGGREGATE events \"*\" LOAD 2 @created_at @name APPLY year(@created_at) AS year APPLY monthofyear(@created_at) AS month\n    \n    Events with year/month:\n      - Summer Summit             | 2024-07\n      - Q1 Planning               | 2024-01\n      - Year End Review 2023      | 2023-12\n      - New Year Kickoff          | 2024-01\n      - Holiday Party 2023        | 2023-12\n      - Team Offsite              | 2024-03\n      - Product Launch            | 2024-02\n\n\n\n```python\n# Filter by YEAR using date function in WHERE\nsql_str = \"\"\"\n    SELECT name\n    FROM events\n    WHERE YEAR(created_at) = 2024\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = events_index.query(sql_query)\n\nprint(f\"\\nEvents in 2024 ({len(results)} found):\")\nfor r in results:\n    print(f\"  - {r['name']}\")\n```\n\n    Resulting redis query:  FT.AGGREGATE events \"*\" LOAD 2 @created_at @name APPLY year(@created_at) AS year_created_at FILTER @year_created_at == 2024\n    \n    Events in 2024 (5 found):\n      - Summer Summit\n      - Q1 Planning\n      - New Year Kickoff\n      - Team Offsite\n      - Product Launch\n\n\n\n```python\n# Count events per year using GROUP BY\nsql_str = \"\"\"\n    SELECT YEAR(created_at) AS year, COUNT(*) AS event_count\n    FROM events\n    GROUP BY year\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = events_index.query(sql_query)\n\nprint(\"\\nEvents per year:\")\nfor r in sorted(results, key=lambda x: x.get('year', 0)):\n    print(f\"  {r['year']}: {r['event_count']} events\")\n```\n\n    Resulting redis query:  FT.AGGREGATE events \"*\" LOAD 2 @created_at @year APPLY year(@created_at) AS year GROUPBY 1 @year REDUCE COUNT 0 AS event_count\n    \n    Events per year:\n      2023: 2 events\n      2024: 5 events\n\n\n\n```python\n# Format dates using DATE_FORMAT\nsql_str = \"\"\"\n    SELECT name, DATE_FORMAT(created_at, '%Y-%m-%d') AS event_date\n    FROM events\n\"\"\"\n\nsql_query = SQLQuery(sql_str)\nredis_query = sql_query.redis_query_string(redis_url=\"redis://localhost:6379\")\nprint(\"Resulting redis query: \", redis_query)\nresults = events_index.query(sql_query)\n\nprint(\"\\nEvents with formatted dates:\")\nfor r in results:\n    print(f\"  - {r['name']:25} | {r.get('event_date', 'N/A')}\")\n```\n\n    Resulting redis query:  FT.AGGREGATE events \"*\" LOAD 2 @created_at @name APPLY timefmt(@created_at, \"%Y-%m-%d\") AS event_date\n    \n    Events with formatted dates:\n      - Summer Summit             | 2024-07-15\n      - Q1 Planning               | 2024-01-15\n      - Year End Review 2023      | 2023-12-20\n      - New Year Kickoff          | 2024-01-01\n      - Holiday Party 2023        | 2023-12-15\n      - Team Offsite              | 2024-03-10\n      - Product Launch            | 2024-02-20\n\n\n## Next Steps\n\nNow that you understand SQL queries for Redis, explore these related guides:\n\n- [Use Advanced Query Types](11_advanced_queries.ipynb) - Learn about TextQuery, HybridQuery, and MultiVectorQuery\n- [Query and Filter Data](02_complex_filtering.ipynb) - Apply filters using native RedisVL query syntax\n- [Getting Started](01_getting_started.ipynb) - Review the basics of RedisVL indexes\n\n## Cleanup\n\nTo remove all data from Redis associated with the index, use the `.clear()` method. This leaves the index in place for future insertions or updates.\n\nTo remove everything including the index, use `.delete()` which removes both the index and the underlying data.\n\n\n```python\n# Delete both indexes and all associated data\nevents_index.delete(drop=True)\nindex.delete(drop=True)\n```\n",
  "tags": [],
  "last_updated": "2026-04-21T14:39:33+02:00"
}
