diff --git a/docs/querying/filtering-query-results/_category_.json b/docs/querying/filtering-query-results/_category_.json new file mode 100644 index 0000000000..e789d38f90 --- /dev/null +++ b/docs/querying/filtering-query-results/_category_.json @@ -0,0 +1,4 @@ +{ + "position": 5, + "label": "Filtering Query Results" +} diff --git a/docs/querying/filtering-query-results/content/_filter-by-date-and-time-csharp.mdx b/docs/querying/filtering-query-results/content/_filter-by-date-and-time-csharp.mdx new file mode 100644 index 0000000000..9c706bdb39 --- /dev/null +++ b/docs/querying/filtering-query-results/content/_filter-by-date-and-time-csharp.mdx @@ -0,0 +1,982 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import ContentFrame from '@site/src/components/ContentFrame'; +import Panel from '@site/src/components/Panel'; + + + +* You can filter query results by date and time in one of the following ways: + * An explicit date + * The current UTC date-time, using [now()](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#filter-using-now), with or without an offset + * The start of the current UTC day, using [today()](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#filter-using-today) + +* The `now()` and `today()` functions are evaluated **server-side** when the query is executed. + +* In this article: + * [Filter by an explicit date](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#filter-by-an-explicit-date) + * [Filter by single date](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#filter-by-single-date) + * [Filter by date range](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#filter-by-date-range) + * [Filter by date range using `between`](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#filter-by-date-range-using-between) + * [Filter using `now()`](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#filter-using-now) + * [Filter using `now()` with offset](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#filter-using-now-with-offset) + * [Negative offset (past)](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#negative-offset-past) + * [Positive offset (future)](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#positive-offset-future) + * [Offset format](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#offset-format) + * [Filter using `today()`](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#filter-using-today) + * [Restrictions](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#restrictions) + * [Result caching with time functions](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#result-caching-with-time-functions) + * [Syntax](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#syntax) + + + + + +* To filter by an explicit date-time value, compare the date field directly with a `DateTime` value. + +* Use **UTC** values to avoid time-zone-related issues. RavenDB stores and compares date-time values in UTC. + In the C# examples below, [DateTimeKind](https://learn.microsoft.com/dotnet/api/system.datetimekind) is passed to the `DateTime` constructor to mark the value as UTC. + +* Unlike the [Restrictions](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#restrictions) on `now()` and `today()`, + explicit date values are deterministic and **can be used** in [Exploration queries](../../../indexes/querying/exploration-queries.mdx) and [Subscription queries](../../../client-api/data-subscriptions/creation/examples.mdx). + +--- + +### Filter by single date + +The following query returns all employees hired on or after January 1st, 1995 (UTC): + + + +```csharp +var cutoff = new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc); + +List employees = session + .Query() + .Where(e => e.HiredAt >= cutoff) + .ToList(); +``` + + +```csharp +var cutoff = new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc); + +List employees = await asyncSession + .Query() + .Where(e => e.HiredAt >= cutoff) + .ToListAsync(); +``` + + +```csharp +var cutoff = new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc); + +List employees = session.Advanced + .DocumentQuery() + .WhereGreaterThanOrEqual("HiredAt", cutoff) + .ToList(); +``` + + +```csharp +var cutoff = new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc); + +List employees = await asyncSession.Advanced + .AsyncDocumentQuery() + .WhereGreaterThanOrEqual("HiredAt", cutoff) + .ToListAsync(); +``` + + +```csharp +var cutoff = new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc); + +List employees = session.Advanced + .RawQuery("from Employees where HiredAt >= $cutoff") + .AddParameter("cutoff", new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc)) + .ToList(); +``` + + +```csharp +var cutoff = new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc); + +List employees = await asyncSession.Advanced + .AsyncRawQuery("from Employees where HiredAt >= $cutoff") + .AddParameter("cutoff", new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc)) + .ToListAsync(); +``` + + +```sql +from Employees +where HiredAt >= "1995-01-01T00:00:00.0000000Z" +``` + + + +--- + +### Filter by date range + +You can filter by a date range by combining two predicates. +The following query returns all employees hired between 1995-01-01 (**inclusive**) and 2010-01-01 (**exclusive**): + + + +```csharp +var from = new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc); +var to = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc); + +List employees = session + .Query() + .Where(e => e.HiredAt >= from && e.HiredAt < to) + .ToList(); +``` + + +```csharp +var from = new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc); +var to = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc); + +List employees = await asyncSession + .Query() + .Where(e => e.HiredAt >= from && e.HiredAt < to) + .ToListAsync(); +``` + + +```csharp +var from = new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc); +var to = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc); + +List employees = session.Advanced + .DocumentQuery() + .WhereGreaterThanOrEqual("HiredAt", from) + .AndAlso() + .WhereLessThan("HiredAt", to) + .ToList(); +``` + + +```csharp +var from = new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc); +var to = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc); + +List employees = await asyncSession.Advanced + .AsyncDocumentQuery() + .WhereGreaterThanOrEqual("HiredAt", from) + .AndAlso() + .WhereLessThan("HiredAt", to) + .ToListAsync(); +``` + + +```csharp +List employees = session.Advanced + .RawQuery("from Employees where HiredAt >= $from and HiredAt < $to") + .AddParameter("from", new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc)) + .AddParameter("to", new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc)) + .ToList(); +``` + + +```csharp +List employees = await asyncSession.Advanced + .AsyncRawQuery("from Employees where HiredAt >= $from and HiredAt < $to") + .AddParameter("from", new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc)) + .AddParameter("to", new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc)) + .ToListAsync(); +``` + + +```sql +from Employees +where HiredAt >= "1995-01-01T00:00:00.0000000Z" + and HiredAt < "2010-01-01T00:00:00.0000000Z" +``` + + + +--- + +### Filter by date range using between + +When **both bounds are inclusive**, RQL offers a dedicated `between` operator, +equivalent to `field >= from AND field <= to`. + +The following query returns all employees hired between January 1st, 1995 and January 1st, 2010, **inclusive**: + + + +```csharp +// LINQ has no dedicated Between method - +// the query translator emits the 'between' operator in the generated RQL +// when both bounds are inclusive. +var from = new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc); +var to = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc); + +List employees = session + .Query() + .Where(e => e.HiredAt >= from && e.HiredAt <= to) + .ToList(); +``` + + +```csharp +// LINQ has no dedicated Between method — the query translator emits +// RQL 'between' when both bounds are inclusive. +var from = new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc); +var to = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc); + +List employees = await asyncSession + .Query() + .Where(e => e.HiredAt >= from && e.HiredAt <= to) + .ToListAsync(); +``` + + +```csharp +var from = new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc); +var to = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc); + +List employees = session.Advanced + .DocumentQuery() + .WhereBetween("HiredAt", from, to) + .ToList(); +``` + + +```csharp +var from = new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc); +var to = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc); + +List employees = await asyncSession.Advanced + .AsyncDocumentQuery() + .WhereBetween("HiredAt", from, to) + .ToListAsync(); +``` + + +```csharp +List employees = session.Advanced + .RawQuery("from Employees where HiredAt between $from and $to") + .AddParameter("from", new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc)) + .AddParameter("to", new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc)) + .ToList(); +``` + + +```csharp +List employees = await asyncSession.Advanced + .AsyncRawQuery("from Employees where HiredAt between $from and $to") + .AddParameter("from", new DateTime(1995, 1, 1, 0, 0, 0, DateTimeKind.Utc)) + .AddParameter("to", new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc)) + .ToListAsync(); +``` + + +```sql +from Employees +where HiredAt between "1995-01-01T00:00:00.0000000Z" + and "2010-01-01T00:00:00.0000000Z" +``` + + + + + + + +* RQL provides the `now()` function for comparing date fields with the current UTC date-time. + In RQL, the function name is case-insensitive. For example, `now()` and `NOW()` are equivalent. + +* `now()` is evaluated **server-side** when the query is executed, so the value reflects the server clock, not the client clock. + Use `now()` when you need to compare a date field with the current UTC date-time when the query runs. + +* For example, the following query returns employees whose _HiredAt_ date is **before** the current server time: + + + + ```csharp + + List employees = session + .Query() + .Where(e => e.HiredAt < RavenQuery.Now()) + .ToList(); + ``` + + + ```csharp + List employees = await asyncSession + .Query() + .Where(e => e.HiredAt < RavenQuery.Now()) + .ToListAsync(); + ``` + + + ```csharp + List employees = session.Advanced + .DocumentQuery() + .WhereLessThan("HiredAt", RavenDocumentQuery.Now()) + .ToList(); + ``` + + + ```csharp + List employees = await asyncSession.Advanced + .AsyncDocumentQuery() + .WhereLessThan("HiredAt", RavenDocumentQuery.Now()) + .ToListAsync(); + ``` + + + ```csharp + List employees = session.Advanced + .RawQuery("from Employees where HiredAt <= now()") + .ToList(); + ``` + + + ```csharp + List employees = await asyncSession.Advanced + .AsyncRawQuery("from Employees where HiredAt < now()") + .ToListAsync(); + ``` + + + ```sql + from Employees + where HiredAt < now() + ``` + + + +* All comparison operators are supported (`=`, `!=`, `<`, `<=`, `>`, `>=`). + For example, the following query returns orders whose _RequireAt_ date is **after** the current server time: + + + + ```csharp + List orders = session + .Query() + .Where(o => o.RequireAt > RavenQuery.Now()) + .ToList(); + ``` + + + ```csharp + List orders = await asyncSession + .Query() + .Where(o => o.RequireAt > RavenQuery.Now()) + .ToListAsync(); + ``` + + + ```csharp + List orders = session.Advanced + .DocumentQuery() + .WhereGreaterThan("RequireAt", RavenDocumentQuery.Now()) + .ToList(); + ``` + + + ```csharp + List orders = await asyncSession.Advanced + .AsyncDocumentQuery() + .WhereGreaterThan("RequireAt", RavenDocumentQuery.Now()) + .ToListAsync(); + ``` + + + ```csharp + List orders = session.Advanced + .RawQuery("from Orders where RequireAt > now()") + .ToList(); + ``` + + + ```csharp + List orders = await asyncSession.Advanced + .AsyncRawQuery("from Orders where RequireAt > now()") + .ToListAsync(); + ``` + + + ```sql + from Orders + where RequireAt > now() + ``` + + + +* You can also combine `now()` with other predicates. + For example, query for employees named _Alice_ who were hired _on or before_ the current server time: + + + + ```csharp + List aliceHired = session + .Query() + .Where(e => e.HiredAt <= RavenQuery.Now() && e.FirstName == "Alice") + .ToList(); + ``` + + + ```csharp + List aliceHired = await asyncSession + .Query() + .Where(e => e.HiredAt <= RavenQuery.Now() && e.FirstName == "Alice") + .ToListAsync(); + ``` + + + ```csharp + List aliceHired = session.Advanced + .DocumentQuery() + .WhereLessThanOrEqual("HiredAt", RavenDocumentQuery.Now()) + .AndAlso() + .WhereEquals("FirstName", "Alice") + .ToList(); + ``` + + + ```csharp + List aliceHired = await asyncSession.Advanced + .AsyncDocumentQuery() + .WhereLessThanOrEqual("HiredAt", RavenDocumentQuery.Now()) + .AndAlso() + .WhereEquals("FirstName", "Alice") + .ToListAsync(); + ``` + + + ```csharp + List aliceHired = session.Advanced + .RawQuery("from Employees where HiredAt <= now() and FirstName = 'Alice'") + .ToList(); + ``` + + + ```csharp + List aliceHired = await asyncSession.Advanced + .AsyncRawQuery("from Employees where HiredAt <= now() and FirstName = 'Alice'") + .ToListAsync(); + ``` + + + ```sql + from Employees + where HiredAt <= now() and FirstName = 'Alice' + ``` + + + + + + + + + + * `now()` can accept an optional positive or negative offset string, such as `+7d`, `-3d`, or `+1d5h`. + + * When an offset is provided, RavenDB first **SHIFTS the current time** by the specified amount + and then **ROUNDS the generated time value down** to the start of the smallest unit in the offset. + + * This keeps the generated time value stable for the duration of the smallest unit in the offset, + allowing identical queries to benefit from the server query cache and return `304 Not Modified` when applicable. + Learn more about caching in: [Result caching with time functions](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#result-caching-with-time-functions). + + + +--- + +### Negative offset (past) + +* Use a negative offset to compare a date field with a time value in the past. + +* **For example**: + `now('-7d')` subtracts 7 days from the current server time and then rounds the generated time value down to the start of that UTC day. + The generated timestamp is truncated to `00:00:00.000` UTC, discarding the hour, minute, second, and millisecond components. + + If the server’s current UTC time is `2026-05-10T14:37:22Z`, then `now('-7d')` is evaluated as follows: + + **Shift**: `2026-05-10T14:37:22Z - 7 days` => `2026-05-03T14:37:22Z` + **Round down to the day**: `2026-05-03T14:37:22Z` => `2026-05-03T00:00:00Z` + **So this predicate**: `HiredAt <= now('-7d')` + **effectively becomes**: `HiredAt <= 2026-05-03T00:00:00Z` + +* The following query returns employees whose _HiredAt_ value is earlier than or equal to 7 days ago, + rounded down to the day: + + + + ```csharp + List employees = session.Query() + .Where(e => e.HiredAt <= RavenQuery.Now("-7d")) + .ToList(); + ``` + + + ```csharp + List employees = await asyncSession.Query() + .Where(e => e.HiredAt <= RavenQuery.Now("-7d")) + .ToListAsync(); + ``` + + + ```csharp + List employees = session.Advanced + .DocumentQuery() + .WhereLessThanOrEqual("HiredAt", RavenDocumentQuery.Now("-7d")) + .ToList(); + ``` + + + ```csharp + List employees = await asyncSession.Advanced + .AsyncDocumentQuery() + .WhereLessThanOrEqual("HiredAt", RavenDocumentQuery.Now("-7d")) + .ToListAsync(); + ``` + + + ```csharp + // Using raw RQL - the offset string is passed directly to now() + List employees = session.Advanced + .RawQuery("from Employees where HiredAt <= now('-7d')") + .ToList(); + + // Using parameter - the offset string is supplied via AddParameter(). + // Note: with a parameter, repeated calls won't return 304 Not Modified. + List employeesParam = session.Advanced + .RawQuery("from Employees where HiredAt <= now($offset)") + .AddParameter("offset", "-7d") + .ToList(); + ``` + + + ```csharp + // Using raw RQL - the offset string is passed directly to now() + List employees = await asyncSession.Advanced + .AsyncRawQuery("from Employees where HiredAt <= now('-7d')") + .ToListAsync(); + + // Using parameter - the offset string is supplied via AddParameter(). + // Note: with a parameter, repeated calls won't return 304 Not Modified. + List employeesParam = await asyncSession.Advanced + .AsyncRawQuery("from Employees where HiredAt <= now($offset)") + .AddParameter("offset", "-7d") + .ToListAsync(); + ``` + + + ```sql + // Offset string is passed as a literal string: + from Employees + where HiredAt <= now('-7d') + + // Offset string is passed as a parameter: + // Note: with a parameter, repeated calls won't return 304 Not Modified. + from Employees + where HiredAt <= now($offset) + { "offset": "-7d" } + ``` + + + +--- + +### Positive offset (future) + +* Use a positive offset to compare a date field with a time value in the future. + +* **For example**: + `now('+7d')` adds 7 days to the current server time and then rounds the generated time value down to the start of that UTC day. + The generated timestamp is truncated to `00:00:00.000` UTC, discarding the hour, minute, second, and millisecond components. + + If the server's current UTC time is `2026-05-10T14:37:22Z`, then `now('+7d')` is evaluated as follows: + + **Shift**: `2026-05-10T14:37:22Z + 7 days` => `2026-05-17T14:37:22Z` + **Round down to the day**: `2026-05-17T14:37:22Z` => `2026-05-17T00:00:00Z` + **So this predicate**: `RequireAt <= now('+7d')` + **effectively becomes**: `RequireAt <= 2026-05-17T00:00:00Z` + +* The following query returns orders whose _RequireAt_ value is earlier than or equal to 7 days from now, + rounded down to the day: + + + +```csharp +List orders = session.Query() + .Where(o => o.RequireAt <= RavenQuery.Now("+7d")) + .ToList(); +``` + + +```csharp +List orders = await asyncSession.Query() + .Where(o => o.RequireAt <= RavenQuery.Now("+7d")) + .ToListAsync(); +``` + + +```csharp +List orders = session.Advanced + .DocumentQuery() + .WhereLessThanOrEqual("RequireAt", RavenDocumentQuery.Now("+7d")) + .ToList(); +``` + + +```csharp +List orders = await asyncSession.Advanced + .AsyncDocumentQuery() + .WhereLessThanOrEqual("RequireAt", RavenDocumentQuery.Now("+7d")) + .ToListAsync(); +``` + + +```csharp +// Using raw RQL - the offset string is passed directly to now() +List orders = session.Advanced + .RawQuery("from Orders where RequireAt <= now('+7d')") + .ToList(); + +// Using parameter - the offset string is supplied via AddParameter(). +// Note: with a parameter, repeated calls won't return 304 Not Modified. +List ordersParam = session.Advanced + .RawQuery("from Orders where RequireAt <= now($offset)") + .AddParameter("offset", "+7d") + .ToList(); +``` + + +```csharp +// Using raw RQL - the offset string is passed directly to now() +List orders = await asyncSession.Advanced + .AsyncRawQuery("from Orders where RequireAt <= now('+7d')") + .ToListAsync(); + +// Using parameter - the offset string is supplied via AddParameter(). +// Note: with a parameter, repeated calls won't return 304 Not Modified. +List ordersParam = await asyncSession.Advanced + .AsyncRawQuery("from Orders where RequireAt <= now($offset)") + .AddParameter("offset", "+7d") + .ToListAsync(); +``` + + +```sql +// Offset string is passed as a literal string: +from Orders +where RequireAt <= now('+7d') + +// Offset string is passed as a parameter: +// Note: with a parameter, repeated calls won't return 304 Not Modified. +from Orders +where RequireAt <= now($offset) +{ "offset": "+7d" } +``` + + + +--- + +### Offset format + +* The offset is a signed sequence of `` tokens. + Whitespace is allowed after the sign and between tokens. + Unit names are case-insensitive. + + | Unit | Short form | Aliases | + |---------|------------|----------------------------| + | Years | `y` | `year`, `years` | + | Months | `mo` | `month`, `months` | + | Days | `d` | `day`, `days` | + | Hours | `h` | `hour`, `hours` | + | Minutes | `m` | `min`, `minute`, `minutes` | + | Seconds | `s` | `sec`, `second`, `seconds` | + +* **Rules**: + + * The sign (`+` or `-`) is optional. + When no sign is specified, the offset is treated as positive. + * Units must appear in strictly descending order: + years → months → days → hours → minutes → seconds. For example, `'+5h1d'` is rejected. + * A unit may not appear more than once. + * The smallest unit present determines the rounding precision. + * An empty offset string, such as `now('')`, is rejected. + +* **Examples**: + + | Offset | Effect | + |--------------------|-------------------------------------------------------------------------------------------| + | `+7d` | Add 7 days, then round down to the start of the day. | + | `-3d` | Subtract 3 days, then round down to the start of the day. | + | `+1d5h` | Add 1 day and 5 hours, then round down to the start of the hour. | + | `7y0s` | Add 7 years, then round down to the start of the second. | + | `0h` | Do not shift the time, but round down to the start of the hour. | + | `+1 year 6 months` | Add 1 year and 6 months, then round down to the start of the month. (whitespace allowed). | + | `1Y2MO3D4H5M6S` | Add all specified units; unit names are case-insensitive. | + + + + + +* RQL provides the `today()` function for comparing date fields with the start of the current UTC day, `(00:00:00.000)`. + In RQL, the function name is case-insensitive. For example, `today()` and `TODAY()` are equivalent. + +* `today()` is evaluated **server-side** when the query is executed, so the value reflects the server clock, not the client clock. + Use `today()` when you need to compare a date field with the start of the current UTC day, rather than with the current date and time. + +* `today()` does **not** accept any arguments. + To filter relative to another day, use [now() with offset](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#filter-using-now-with-offset) instead. + For example, use `now('+1d')` to compare with the start of tomorrow’s UTC day. + +* The following query returns employees whose _HiredAt_ value is earlier than the start of the current UTC day: + + + + ```csharp + List employees = session + .Query() + .Where(e => e.HiredAt < RavenQuery.Today()) + .ToList(); + ``` + + + ```csharp + List employees = await asyncSession + .Query() + .Where(e => e.HiredAt < RavenQuery.Today()) + .ToListAsync(); + ``` + + + ```csharp + List employees = session.Advanced + .DocumentQuery() + .WhereLessThan("HiredAt", RavenDocumentQuery.Today()) + .ToList(); + ``` + + + ```csharp + List employees = await asyncSession.Advanced + .AsyncDocumentQuery() + .WhereLessThan("HiredAt", RavenDocumentQuery.Today()) + .ToListAsync(); + ``` + + + ```csharp + List employees = session.Advanced + .RawQuery("from Employees where HiredAt < today()") + .ToList(); + ``` + + + ```csharp + List employees = await asyncSession.Advanced + .AsyncRawQuery("from Employees where HiredAt < today()") + .ToListAsync(); + ``` + + + ```sql + from Employees + where HiredAt < today() + ``` + + + +* `today()` and `now()` can be combined in the same query. + For example, to return all employees hired so far today: + + + ```sql + from Employees + where HiredAt >= today() and HiredAt <= now() + ``` + + + + + + +* **Exploration queries** + Both `now()` and `today()` are not supported in [exploration queryies](../../../indexes/querying/exploration-queries.mdx). + For example, the following query is not allowed: + + + ```sql + // Not allowed: now() / today() are not supported in exploration query filter clauses + from Employees + filter HiredAt <= now() + ``` + + + In exploration queries, these functions are blocked in `filter` clauses because `filter` expressions are evaluated as JavaScript expressions. + Since `now()` and `today()` depend on the query execution time, using them in this context produces time-dependent results that conflict with the query result caching mechanism. + + [Filtering by an explicit date](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#filter-by-an-explicit-date) is fully supported in exploration queries. + +--- + +* **Subscription queries** + Both `now()` and `today()` are not supported in [subscription queries](../../../client-api/data-subscriptions/creation/examples.mdx). + For example, the following query is not allowed: + + + ```sql + // Not allowed: now() / today() are not supported in subscription queries + from Employees + where HiredAt <= now() + ``` + + + In subscription queries, these functions are blocked because a subscription is an ongoing task. + Using `now()` or `today()` would make the matching results depend on when the subscription task evaluates each document, + yielding nondeterministic results: the set of matching documents could change dynamically even when the documents themselves have not changed. + + [Filtering by an explicit date](../../../querying/filtering-query-results/filter-by-date-and-time.mdx#filter-by-an-explicit-date) is fully supported in subscription queries. + +--- + +* **`today()` does not accept arguments** + To filter relative to another day, use `now()` with an offset instead, for example `now('+1d')`. + +--- + +* **Invalid `now()` offset values** + When using `now()` with an offset, the offset must be a non-empty string in a valid offset format. + + + + + +When query caching is enabled, the server can return `304 Not Modified` for time-based queries only while the resolved time value remains unchanged +and no documents in the queried collection have been added, modified, or deleted in the meantime. + +* **today()** + `today()` resolves to the start of the current UTC day (_00:00:00_). + Because this value remains the same throughout the UTC day, repeated queries can receive `304 Not Modified` from the server + (provided no documents in the queried collection have changed in the meantime). + When the UTC day changes, `today()` resolves to a new value and the query is re-evaluated. + +* **now() without an offset** + `now()` without an offset resolves to the current UTC date and time each time the query is executed. + Because the value changes on every call, the server re-evaluates the query and does not return `304 Not Modified`. + +* **now() with a literal offset string** + `now()` with a literal offset string is rounded down to the smallest time unit specified in the offset + (e.g. `'+7d'` rounds to the day, `'+1h30m'` rounds to the minute). + Repeated queries can receive `304 Not Modified` from the server until the rounded value changes + (and as long as the queried collection has not changed). + + **Note**: + When the offset is supplied as a **query parameter** (e.g. `now($offset)`) instead of a literal string (`now('-7d')`), + the server can't tell from the query text alone what value `now()` will resolve to - it can't evaluate the parameter at query-planning time, + and the value could be different on the next call. + It therefore behaves like `now()` without an offset: the query is re-run every time, and `304 Not Modified` is never returned. + + Parameterized offsets are only reachable through raw RQL, + e.g. via `session.Advanced.RawQuery` / `AsyncRawQuery` combined with `AddParameter`. + + + + +### Client-side caching must be enabled + +* The behavior described above relies on the client's HTTP response cache to recognize the server's + `304 Not Modified` reply and serve the previous result from memory. + +* If client-side caching is **disabled**, the client does not store responses and does not send the cache-validation header (`If-None-Match`). + The server therefore cannot reply with `304 Not Modified`, and every call returns a full `200 OK` response, even when `today()` or `now('')` resolved to the same value. + +* Client-side caching can be disabled at different scopes: + * **per-session**, by setting `session.Advanced.NoCaching` or by using the [NoCaching session option](../../../client-api/session/configuration/how-to-disable-caching.mdx), + * **per-query**, by using `IDocumentQuery.NoCaching()` / `DocumentQueryCustomization.NoCaching()`, or + * **store-level**, by setting [`DocumentConventions.MaxHttpCacheSize = 0`](../../../client-api/configuration/conventions.mdx#maxhttpcachesize). + + + + + +### Aggressive caching bypasses server re-evaluation + +* **When the request runs inside an [Aggressive caching](../../../client-api/how-to/setup-aggressive-caching.mdx) scope**, + the client can return the previously cached response without contacting the server at all, as long as: + * the cached entry is younger than the duration passed to `AggressivelyCacheFor(...)`, + * the query does not use `WaitForNonStaleResults` and was not issued with `NoCaching()`, and + * in `AggressiveCacheMode.TrackChanges` (the default), the client's background database-changes notification has not flagged the cached entry as possibly modified. + +* As a consequence, `now()` and `today()` queries can return stale results inside an aggressive caching block. + The server-side non-determinism for `now()` and the daily ETag rollover for `today()` that normally trigger a fresh evaluation are irrelevant once the client decides to skip the round-trip. + In particular, a `today()` query cached just before midnight UTC can keep returning the **previous** day's result even after the UTC day has changed. + The cached response is refreshed only when: + * the cache entry's age exceeds the duration passed to `AggressivelyCacheFor(...)` + or the store-level `AggressiveCache.Duration` default, + * a database change is observed via the Changes API + (only when running in `AggressiveCacheMode.TrackChanges`, which is the default), or + * the aggressive-caching scope is exited. + +* If you need `now()` or `today()` to be re-evaluated on every call, + run the query outside the aggressive caching scope, or wrap the call in `store.DisableAggressiveCaching()`. + + + +--- + +Assuming the request **reaches** the server and client-side HTTP caching is **enabled**, +the following table summarizes regular query caching behavior: + +| Expression | Resolves to | Can the server return `304`? | Valid until | +| ------------------------------------------------- | ----------- | ---------------------------- | -------------- | +| `today()` | Start of the current UTC day (`00:00:00`) | ✅ yes | Until UTC day rolls over or the queried collection changes. | +| `now()`
_(no offset)_ | Current UTC date and time | ❌ no | n/a
Re-evaluated on every call. | +| `now('')`
_(literal string offset)_ | `now + offset`
Floored to the smallest unit in the offset.
(`'+7d'` → day, `'+1h30m'` → minute, `'+15s'` → second) | ✅ yes | Floored value changes or the queried collection changes. | +| `now($param)`
_(parameter offset)_ | `now + offset`
Treated as non-deterministic. | ❌ no | n/a
Re-evaluated on every call. | + +
+ + + + + +```csharp +// Inside a System.Linq.Expressions.Expression>: +RavenQuery.Now() // current server UTC time +RavenQuery.Now(string offset) // current server UTC time + offset, rounded +RavenQuery.Today() // start of current UTC day +``` + + + + + +```csharp +RavenDocumentQuery.Now() +RavenDocumentQuery.Now(string offset) +RavenDocumentQuery.Today() +``` + + + + + +```sql +now() +now('') +today() +``` + + + +| Parameter | Type | Description | +|------------|----------|-------------| +| **offset** | `string` | _(optional, `now()` only)_
Signed time-shift expression (e.g. `'+7d'`, `'-1d5h'`).
Sets the rounding unit. | + +
\ No newline at end of file diff --git a/docs/querying/filtering-query-results/filter-by-date-and-time.mdx b/docs/querying/filtering-query-results/filter-by-date-and-time.mdx new file mode 100644 index 0000000000..555ab30e21 --- /dev/null +++ b/docs/querying/filtering-query-results/filter-by-date-and-time.mdx @@ -0,0 +1,28 @@ +--- +title: "Filter by Date and Time" +sidebar_label: "Filter by Date and Time" +description: "Learn how to filter RavenDB query results by date and time using explicit UTC date-time values, now(), today(), and time offsets, including supported syntax, restrictions, and query caching behavior." +sidebar_position: 0 +supported_languages: ["csharp"] +see_also: + - title: "Query Overview" + link: "querying/overview" + source: "docs" + path: "Querying" + - title: "What is a Document Query" + link: "querying/document-query/what-is-document-query" + source: "docs" + path: "Querying > DocumentQuery" +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import FilterByDateAndTimeCsharp from './content/_filter-by-date-and-time-csharp.mdx'; + + + + + + + diff --git a/docs/querying/rql/what-is-rql.mdx b/docs/querying/rql/what-is-rql.mdx index 432256a37d..fd145d424a 100644 --- a/docs/querying/rql/what-is-rql.mdx +++ b/docs/querying/rql/what-is-rql.mdx @@ -210,7 +210,7 @@ The following options are available: #### Query a specific collection:    `from ` -```csharp +```sql // Full collection query // Data source: The raw collection documents (Auto-index is Not created) from "Employees" @@ -218,7 +218,7 @@ from "Employees" -```csharp +```sql // Collection query - by ID // Data source: The raw collection documents (Auto-index is Not created) from "Employees" where id() = "employees/1-A" @@ -226,7 +226,7 @@ from "Employees" where id() = "employees/1-A" -```csharp +```sql // Dynamic query - with filtering // Data source: Auto-index (server uses an existing auto-index or creates a new one) from "Employees" where FirstName = "Laura" @@ -240,7 +240,7 @@ from "Employees" where FirstName = "Laura" #### Query all documents:    `from @all_docs` -```csharp +```sql // All collections query // Data source: All raw collections (Auto-index is Not created) from @all_docs @@ -248,7 +248,7 @@ from @all_docs -```csharp +```sql // Dynamic query - with filtering // Data source: Auto-index (server uses an existing auto-index or creates a new one) from @all_docs where FirstName = "Laura" @@ -262,7 +262,7 @@ from @all_docs where FirstName = "Laura" #### Query an index:    `from index ` -```csharp +```sql // Index query // Data source: The specified index from index "Employees/ByFirstName" @@ -270,7 +270,7 @@ from index "Employees/ByFirstName" -```csharp +```sql // Index query - with filtering // Data source: The specified index from index "Employees/ByFirstName" where FirstName = "Laura" @@ -294,7 +294,7 @@ For example, you can return every document from the [Companies collection](../.. whose _field value_ **=** _a given input_. -```csharp +```sql from "Companies" where Name == "The Big Cheese" // Can use either '=' or'==' ``` @@ -304,7 +304,7 @@ Filtering on **nested properties** is also supported. So in order to return all companies from 'Albuquerque' we need to execute following query: -```csharp +```sql from "Companies" where Address.City = "Albuquerque" ``` @@ -320,14 +320,14 @@ The operator `between` returns results inclusively, and the type of border value It works on both 'numbers' and 'strings' and can be substituted with the `>=` and `<=` operators. -```csharp +```sql from "Products" where PricePerUnit between 10.5 and 13.0 // Using between ``` -```csharp +```sql from "Products" where PricePerUnit >= 10.5 and PricePerUnit <= 13.0 // Using >= and <= ``` @@ -343,14 +343,14 @@ The `in` operator evaluates a field against a list of values. It will return results if the field matches **any** of the items in that list. -```csharp +```sql from "Companies" where Name in ("The Big Cheese", "Unknown company name") ``` -```csharp +```sql from "Orders" where Lines[].ProductName in ("Chang", "Spegesild", "Unknown product name") ``` @@ -365,7 +365,7 @@ To ensure accuracy, especially when fields might be missing from some documents, use `exists(fieldName)` as the anchor: -```csharp +```sql from "Companies" where exists(Name) and NOT Name in ("The Big Cheese", "The Cracker Box") ``` @@ -387,7 +387,7 @@ Due to its mechanics, it is only useful when used on array fields. The following query will yield no results in contrast to the `in` operator. -```csharp +```sql from "Orders" where Lines[].ProductName all in ("Chang", "Spegesild", "Unknown product name") ``` @@ -397,7 +397,7 @@ Removing 'Unknown product name' will return only orders that contain products wi 'Chang' and 'Spegesild' names. -```csharp +```sql from "Orders" where Lines[].ProductName all in ("Chang", "Spegesild") ``` @@ -407,27 +407,27 @@ where Lines[].ProductName all in ("Chang", "Spegesild") -#### Binary Operators:    `AND`, `OR`, `NOT` +#### Binary operators:    `AND`, `OR`, `NOT` Binary operators can be used to build more complex statements. The `NOT` operator can only be used with one of the other binary operators creating `OR NOT` or `AND NOT` ones. -```csharp +```sql from "Companies" where Name = "The Big Cheese" OR Name = "Richter Supermarkt" ``` -```csharp +```sql from "Orders" where Freight > 500 AND ShippedAt > '1998-01-01' ``` -```csharp +```sql from "Orders" where Freight > 500 AND ShippedAt > '1998-01-01' AND NOT Freight = 830.75 ``` @@ -435,6 +435,31 @@ where Freight > 500 AND ShippedAt > '1998-01-01' AND NOT Freight = 830.75 + + +#### Methods:    `today()`, `now()` + +Use `today()` and `now()` to compare date/time fields with the current server date or current server time. +Learn more in [Filter by date and time](../../querying/filtering-query-results/filter-by-date-and-time.mdx). + + +```sql +// Find orders that were shipped before the current date +from "Orders" +where ShippedAt < today() +``` + + + +```sql +// Find orders that are required at or after the current date and time +from "Orders" +where RequiredAt >= now() +``` + + + + #### Subclauses:    `(`, `)` @@ -495,7 +520,7 @@ Specify the number of items to **skip** from the beginning of the result set and This is useful when [paging](../../indexes/querying/paging.mdx) results. -```csharp +```sql // Available syntax options: // ========================= @@ -525,7 +550,7 @@ For more information, please refer to this [patching](../../client-api/operation Single-line comments start with `//` and end at the end of that line. -```csharp +```sql // This is a single-line comment. from "Companies" where Name = "The Big Cheese" OR Name = "Richter Supermarkt" @@ -533,7 +558,7 @@ where Name = "The Big Cheese" OR Name = "Richter Supermarkt" -```csharp +```sql from "Companies" where Name = "The Big Cheese" // OR Name = "Richter Supermarkt" ``` @@ -548,7 +573,7 @@ where Name = "The Big Cheese" // OR Name = "Richter Supermarkt" Multiline comments start with `/*` and end with `*/`. -```csharp +```sql /* This is a multiline comment. Any text here will be ignored. @@ -559,7 +584,7 @@ where Name = "The Big Cheese" OR Name = "Richter Supermarkt" -```csharp +```sql from "Companies" where Name = "The Big Cheese" /* this part is a comment */ OR Name = "Richter Supermarkt" ``` diff --git a/docs/querying/sorting-query-results/_category_.json b/docs/querying/sorting-query-results/_category_.json index 0dd610f2ef..3b2e671aa1 100644 --- a/docs/querying/sorting-query-results/_category_.json +++ b/docs/querying/sorting-query-results/_category_.json @@ -1,4 +1,4 @@ { - "position": 5, + "position": 6, "label": "Sorting Query Results" }