Skip to content

Commit 0060cab

Browse files
authored
Feat: add dtntz date macro variant (#4838)
1 parent 84d5341 commit 0060cab

File tree

5 files changed

+74
-0
lines changed

5 files changed

+74
-0
lines changed

docs/concepts/macros/macro_variables.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Prefixes:
6868
Postfixes:
6969

7070
* dt - A python datetime object that converts into a native SQL `TIMESTAMP` (or SQL engine equivalent)
71+
* dtntz - A python datetime object that converts into a native SQL `TIMESTAMP WITHOUT TIME ZONE` (or SQL engine equivalent)
7172
* date - A python date object that converts into a native SQL `DATE`
7273
* ds - A date string with the format: '%Y-%m-%d'
7374
* ts - An ISO 8601 datetime formatted string: '%Y-%m-%d %H:%M:%S'
@@ -83,6 +84,11 @@ All predefined temporal macro variables:
8384
* @end_dt
8485
* @execution_dt
8586

87+
* dtntz
88+
* @start_dtntz
89+
* @end_dtntz
90+
* @execution_dtntz
91+
8692
* date
8793
* @start_date
8894
* @end_date

docs/integrations/engines/redshift.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,37 @@ pip install "sqlmesh[redshift]"
3333
| `serverless_acct_id` | The account ID of the serverless cluster | string | N |
3434
| `serverless_work_group` | The name of work group for serverless end point | string | N |
3535
| `enable_merge` | Whether the incremental_by_unique_key model kind will use the native Redshift MERGE operation or SQLMesh's logical merge. (Default: `False`) | bool | N |
36+
37+
## Performance Considerations
38+
39+
### Timestamp Macro Variables and Sort Keys
40+
41+
When working with Redshift tables that have a `TIMESTAMP` sort key, using the standard `@start_dt` and `@end_dt` macro variables may lead to performance issues. These macros render as `TIMESTAMP WITH TIME ZONE` values in SQL queries, which prevents Redshift from performing efficient pruning when filtering against `TIMESTAMP` (without timezone) sort keys.
42+
43+
This can result in full table scans instead, causing significant performance degradation.
44+
45+
**Solution**: Use the `_dtntz` (datetime no timezone) variants of macro variables:
46+
47+
- `@start_dtntz` instead of `@start_dt`
48+
- `@end_dtntz` instead of `@end_dt`
49+
50+
These variants render as `TIMESTAMP WITHOUT TIME ZONE`, allowing Redshift to properly utilize sort key optimizations.
51+
52+
**Example**:
53+
54+
```sql linenums="1"
55+
-- Inefficient: May cause full table scan
56+
SELECT * FROM my_table
57+
WHERE timestamp_column >= @start_dt
58+
AND timestamp_column < @end_dt
59+
60+
-- Efficient: Uses sort key optimization
61+
SELECT * FROM my_table
62+
WHERE timestamp_column >= @start_dtntz
63+
AND timestamp_column < @end_dtntz
64+
65+
-- Alternative: Cast to timestamp
66+
SELECT * FROM my_table
67+
WHERE timestamp_column >= @start_ts::timestamp
68+
AND timestamp_column < @end_ts::timestamp
69+
```

sqlmesh/utils/date.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,15 +253,20 @@ def date_dict(
253253

254254
for prefix, time_like in prefixes:
255255
dt = to_datetime(time_like)
256+
dtntz = dt.replace(tzinfo=None)
257+
256258
millis = to_timestamp(time_like)
259+
257260
kwargs[f"{prefix}_dt"] = dt
261+
kwargs[f"{prefix}_dtntz"] = dtntz
258262
kwargs[f"{prefix}_date"] = to_date(dt)
259263
kwargs[f"{prefix}_ds"] = to_ds(time_like)
260264
kwargs[f"{prefix}_ts"] = to_ts(dt)
261265
kwargs[f"{prefix}_tstz"] = to_tstz(dt)
262266
kwargs[f"{prefix}_epoch"] = millis / 1000
263267
kwargs[f"{prefix}_millis"] = millis
264268
kwargs[f"{prefix}_hour"] = dt.hour
269+
265270
return kwargs
266271

267272

tests/core/test_model.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10518,3 +10518,28 @@ def test_boolean_property_validation() -> None:
1051810518
)
1051910519
model = load_sql_based_model(expressions, dialect="tsql")
1052010520
assert model.enabled
10521+
10522+
10523+
def test_datetime_without_timezone_variable_redshift() -> None:
10524+
expressions = d.parse(
10525+
"""
10526+
MODEL (
10527+
name test,
10528+
kind INCREMENTAL_BY_TIME_RANGE (
10529+
time_column test_time_col,
10530+
batch_size 1,
10531+
batch_concurrency 1
10532+
),
10533+
start '2025-06-01',
10534+
dialect redshift
10535+
);
10536+
10537+
SELECT @start_dtntz AS test_time_col
10538+
"""
10539+
)
10540+
model = load_sql_based_model(expressions, dialect="redshift")
10541+
10542+
assert (
10543+
model.render_query_or_raise().sql("redshift")
10544+
== '''SELECT CAST('1970-01-01 00:00:00' AS TIMESTAMP) AS "test_time_col"'''
10545+
)

tests/utils/test_date.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,10 @@ def test_date_dict():
258258
"execution_dt": datetime(2020, 1, 2, 1, 0, 0, tzinfo=UTC),
259259
"start_dt": datetime(2020, 1, 1, 0, 0, 0, tzinfo=UTC),
260260
"end_dt": datetime(2020, 1, 2, 0, 0, 0, tzinfo=UTC),
261+
"latest_dtntz": datetime(2020, 1, 2, 1, 0, 0, tzinfo=None),
262+
"execution_dtntz": datetime(2020, 1, 2, 1, 0, 0, tzinfo=None),
263+
"start_dtntz": datetime(2020, 1, 1, 0, 0, 0, tzinfo=None),
264+
"end_dtntz": datetime(2020, 1, 2, 0, 0, 0, tzinfo=None),
261265
"latest_date": date(2020, 1, 2),
262266
"execution_date": date(2020, 1, 2),
263267
"start_date": date(2020, 1, 1),

0 commit comments

Comments
 (0)