Skip to main content

datafusion_functions_window/
nth_value.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! `nth_value` window function implementation
19
20use crate::utils::{get_scalar_value_from_args, get_signed_integer};
21
22use arrow::buffer::NullBuffer;
23use arrow::datatypes::FieldRef;
24use datafusion_common::arrow::array::ArrayRef;
25use datafusion_common::arrow::datatypes::{DataType, Field};
26use datafusion_common::{Result, ScalarValue, exec_datafusion_err, exec_err};
27use datafusion_doc::window_doc_sections::DOC_SECTION_ANALYTICAL;
28use datafusion_expr::window_state::WindowAggState;
29use datafusion_expr::{
30    Documentation, LimitEffect, Literal, PartitionEvaluator, ReversedUDWF, Signature,
31    TypeSignature, Volatility, WindowUDFImpl,
32};
33use datafusion_functions_window_common::field;
34use datafusion_functions_window_common::partition::PartitionEvaluatorArgs;
35use datafusion_physical_expr_common::physical_expr::PhysicalExpr;
36use field::WindowUDFFieldArgs;
37use std::cmp::Ordering;
38use std::fmt::Debug;
39use std::hash::Hash;
40use std::ops::Range;
41use std::sync::{Arc, LazyLock};
42
43define_udwf_and_expr!(
44    First,
45    first_value,
46    [arg],
47    first_value_udwf,
48    "Returns the first value in the window frame",
49    NthValue::first
50);
51define_udwf_and_expr!(
52    Last,
53    last_value,
54    [arg],
55    last_value_udwf,
56    "Returns the last value in the window frame",
57    NthValue::last
58);
59get_or_init_udwf!(
60    NthValue,
61    nth_value,
62    nth_value_udwf,
63    "Returns the nth value in the window frame",
64    NthValue::nth
65);
66
67/// Create an expression to represent the `nth_value` window function
68pub fn nth_value(arg: datafusion_expr::Expr, n: i64) -> datafusion_expr::Expr {
69    nth_value_udwf().call(vec![arg, n.lit()])
70}
71
72/// Tag to differentiate special use cases of the NTH_VALUE built-in window function.
73#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
74pub enum NthValueKind {
75    First,
76    Last,
77    Nth,
78}
79
80impl NthValueKind {
81    fn name(&self) -> &'static str {
82        match self {
83            NthValueKind::First => "first_value",
84            NthValueKind::Last => "last_value",
85            NthValueKind::Nth => "nth_value",
86        }
87    }
88}
89
90#[derive(Debug, PartialEq, Eq, Hash)]
91pub struct NthValue {
92    signature: Signature,
93    kind: NthValueKind,
94}
95
96impl NthValue {
97    /// Create a new `nth_value` function
98    pub fn new(kind: NthValueKind) -> Self {
99        Self {
100            signature: Signature::one_of(
101                vec![
102                    TypeSignature::Nullary,
103                    TypeSignature::Any(1),
104                    TypeSignature::Any(2),
105                ],
106                Volatility::Immutable,
107            ),
108            kind,
109        }
110    }
111
112    pub fn first() -> Self {
113        Self::new(NthValueKind::First)
114    }
115
116    pub fn last() -> Self {
117        Self::new(NthValueKind::Last)
118    }
119    pub fn nth() -> Self {
120        Self::new(NthValueKind::Nth)
121    }
122
123    pub fn kind(&self) -> &NthValueKind {
124        &self.kind
125    }
126}
127
128fn validate_nth_value_n(n: i64) -> Result<i64> {
129    if n == i64::MIN {
130        return exec_err!("The second argument of nth_value must not be i64::MIN");
131    }
132
133    Ok(n)
134}
135
136static FIRST_VALUE_DOCUMENTATION: LazyLock<Documentation> = LazyLock::new(|| {
137    Documentation::builder(
138        DOC_SECTION_ANALYTICAL,
139        "Returns value evaluated at the row that is the first row of the window \
140            frame.",
141        "first_value(expression)",
142    )
143    .with_argument("expression", "Expression to operate on")
144    .with_sql_example(
145        r#"
146```sql
147-- Example usage of the first_value window function:
148SELECT department,
149  employee_id,
150  salary,
151  first_value(salary) OVER (PARTITION BY department ORDER BY salary DESC) AS top_salary
152FROM employees;
153
154+-------------+-------------+--------+------------+
155| department  | employee_id | salary | top_salary |
156+-------------+-------------+--------+------------+
157| Sales       | 1           | 70000  | 70000      |
158| Sales       | 2           | 50000  | 70000      |
159| Sales       | 3           | 30000  | 70000      |
160| Engineering | 4           | 90000  | 90000      |
161| Engineering | 5           | 80000  | 90000      |
162+-------------+-------------+--------+------------+
163```
164"#,
165    )
166    .build()
167});
168
169fn get_first_value_doc() -> &'static Documentation {
170    &FIRST_VALUE_DOCUMENTATION
171}
172
173static LAST_VALUE_DOCUMENTATION: LazyLock<Documentation> = LazyLock::new(|| {
174    Documentation::builder(
175        DOC_SECTION_ANALYTICAL,
176        "Returns value evaluated at the row that is the last row of the window \
177            frame.",
178        "last_value(expression)",
179    )
180    .with_argument("expression", "Expression to operate on")
181        .with_sql_example(r#"```sql
182-- SQL example of last_value:
183SELECT department,
184       employee_id,
185       salary,
186       last_value(salary) OVER (PARTITION BY department ORDER BY salary) AS running_last_salary
187FROM employees;
188
189+-------------+-------------+--------+---------------------+
190| department  | employee_id | salary | running_last_salary |
191+-------------+-------------+--------+---------------------+
192| Sales       | 1           | 30000  | 30000               |
193| Sales       | 2           | 50000  | 50000               |
194| Sales       | 3           | 70000  | 70000               |
195| Engineering | 4           | 40000  | 40000               |
196| Engineering | 5           | 60000  | 60000               |
197+-------------+-------------+--------+---------------------+
198```
199"#)
200    .build()
201});
202
203fn get_last_value_doc() -> &'static Documentation {
204    &LAST_VALUE_DOCUMENTATION
205}
206
207static NTH_VALUE_DOCUMENTATION: LazyLock<Documentation> = LazyLock::new(|| {
208    Documentation::builder(
209        DOC_SECTION_ANALYTICAL,
210        "Returns the value evaluated at the nth row of the window frame \
211         (counting from 1). Returns NULL if no such row exists.",
212        "nth_value(expression, n)",
213    )
214    .with_argument(
215        "expression",
216        "The column from which to retrieve the nth value.",
217    )
218    .with_argument(
219        "n",
220        "Integer. Specifies the row number (starting from 1) in the window frame.",
221    )
222    .with_sql_example(
223        r#"
224```sql
225-- Sample employees table:
226CREATE TABLE employees (id INT, salary INT);
227INSERT INTO employees (id, salary) VALUES
228(1, 30000),
229(2, 40000),
230(3, 50000),
231(4, 60000),
232(5, 70000);
233
234-- Example usage of nth_value:
235SELECT nth_value(salary, 2) OVER (
236  ORDER BY salary
237  ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
238) AS nth_value
239FROM employees;
240
241+-----------+
242| nth_value |
243+-----------+
244| 40000     |
245| 40000     |
246| 40000     |
247| 40000     |
248| 40000     |
249+-----------+
250```
251"#,
252    )
253    .build()
254});
255
256fn get_nth_value_doc() -> &'static Documentation {
257    &NTH_VALUE_DOCUMENTATION
258}
259
260impl WindowUDFImpl for NthValue {
261    fn name(&self) -> &str {
262        self.kind.name()
263    }
264
265    fn signature(&self) -> &Signature {
266        &self.signature
267    }
268
269    fn partition_evaluator(
270        &self,
271        partition_evaluator_args: PartitionEvaluatorArgs,
272    ) -> Result<Box<dyn PartitionEvaluator>> {
273        let state = NthValueState {
274            finalized_result: None,
275            kind: self.kind,
276        };
277
278        if self.kind != NthValueKind::Nth {
279            return Ok(Box::new(NthValueEvaluator {
280                state,
281                ignore_nulls: partition_evaluator_args.ignore_nulls(),
282                n: 0,
283            }));
284        }
285
286        let n = match get_scalar_value_from_args(
287            partition_evaluator_args.input_exprs(),
288            1,
289        )
290        .map_err(|_e| {
291            exec_datafusion_err!(
292                "Expected a signed integer literal for the second argument of nth_value"
293            )
294        })?
295        .map(|v| get_signed_integer(&v))
296        {
297            Some(Ok(n)) => {
298                let n = validate_nth_value_n(n)?;
299                if partition_evaluator_args.is_reversed() {
300                    -n
301                } else {
302                    n
303                }
304            }
305            _ => {
306                return exec_err!(
307                    "Expected a signed integer literal for the second argument of nth_value"
308                );
309            }
310        };
311
312        Ok(Box::new(NthValueEvaluator {
313            state,
314            ignore_nulls: partition_evaluator_args.ignore_nulls(),
315            n,
316        }))
317    }
318
319    fn field(&self, field_args: WindowUDFFieldArgs) -> Result<FieldRef> {
320        let input_field =
321            field_args
322                .input_fields()
323                .first()
324                .cloned()
325                .unwrap_or_else(|| {
326                    Arc::new(Field::new(field_args.name(), DataType::Null, true))
327                });
328
329        // Clone the input field to preserve metadata, update name and nullability
330        Ok(input_field
331            .as_ref()
332            .clone()
333            .with_name(field_args.name())
334            .with_nullable(true)
335            .into())
336    }
337
338    fn reverse_expr(&self) -> ReversedUDWF {
339        match self.kind {
340            NthValueKind::First => ReversedUDWF::Reversed(last_value_udwf()),
341            NthValueKind::Last => ReversedUDWF::Reversed(first_value_udwf()),
342            NthValueKind::Nth => ReversedUDWF::Reversed(nth_value_udwf()),
343        }
344    }
345
346    fn documentation(&self) -> Option<&Documentation> {
347        match self.kind {
348            NthValueKind::First => Some(get_first_value_doc()),
349            NthValueKind::Last => Some(get_last_value_doc()),
350            NthValueKind::Nth => Some(get_nth_value_doc()),
351        }
352    }
353
354    fn limit_effect(&self, _args: &[Arc<dyn PhysicalExpr>]) -> LimitEffect {
355        LimitEffect::None // NthValue is causal
356    }
357}
358
359#[derive(Debug, Clone)]
360pub struct NthValueState {
361    // In certain cases, we can finalize the result early. Consider this usage:
362    // ```
363    //  FIRST_VALUE(increasing_col) OVER window AS my_first_value
364    //  WINDOW (ORDER BY ts ASC ROWS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING) AS window
365    // ```
366    // The result will always be the first entry in the table. We can store such
367    // early-finalizing results and then just reuse them as necessary. This opens
368    // opportunities to prune our datasets.
369    pub finalized_result: Option<ScalarValue>,
370    pub kind: NthValueKind,
371}
372
373#[derive(Debug)]
374pub(crate) struct NthValueEvaluator {
375    state: NthValueState,
376    ignore_nulls: bool,
377    n: i64,
378}
379
380impl PartitionEvaluator for NthValueEvaluator {
381    /// When the window frame has a fixed beginning (e.g UNBOUNDED PRECEDING),
382    /// for some functions such as FIRST_VALUE, LAST_VALUE and NTH_VALUE, we
383    /// can memoize the result.  Once result is calculated, it will always stay
384    /// same. Hence, we do not need to keep past data as we process the entire
385    /// dataset.
386    fn memoize(&mut self, state: &mut WindowAggState) -> Result<()> {
387        let out = &state.out_col;
388        let size = out.len();
389        if self.ignore_nulls {
390            match self.state.kind {
391                // Prune on first non-null output in case of FIRST_VALUE
392                NthValueKind::First => {
393                    if let Some(nulls) = out.nulls() {
394                        if self.state.finalized_result.is_none() {
395                            if let Some(valid_index) = nulls.valid_indices().next() {
396                                let result =
397                                    ScalarValue::try_from_array(out, valid_index)?;
398                                self.state.finalized_result = Some(result);
399                            } else {
400                                // The output is empty or all nulls, ignore
401                            }
402                        }
403                        if state.window_frame_range.start < state.window_frame_range.end {
404                            state.window_frame_range.start =
405                                state.window_frame_range.end - 1;
406                        }
407                        return Ok(());
408                    } else {
409                        // Fall through to the main case because there are no nulls
410                    }
411                }
412                // Do not memoize for other kinds when nulls are ignored
413                NthValueKind::Last | NthValueKind::Nth => return Ok(()),
414            }
415        }
416        let mut buffer_size = 1;
417        // Decide if we arrived at a final result yet:
418        let (is_prunable, is_reverse_direction) = match self.state.kind {
419            NthValueKind::First => {
420                let n_range =
421                    state.window_frame_range.end - state.window_frame_range.start;
422                (n_range > 0 && size > 0, false)
423            }
424            NthValueKind::Last => (true, true),
425            NthValueKind::Nth => {
426                let n_range =
427                    state.window_frame_range.end - state.window_frame_range.start;
428                match self.n.cmp(&0) {
429                    Ordering::Greater => (
430                        n_range >= (self.n as usize) && size > (self.n as usize),
431                        false,
432                    ),
433                    Ordering::Less => {
434                        let reverse_index = (-self.n) as usize;
435                        buffer_size = reverse_index;
436                        // Negative index represents reverse direction.
437                        (n_range >= reverse_index, true)
438                    }
439                    Ordering::Equal => (false, false),
440                }
441            }
442        };
443        if is_prunable {
444            if self.state.finalized_result.is_none() && !is_reverse_direction {
445                let result = ScalarValue::try_from_array(out, size - 1)?;
446                self.state.finalized_result = Some(result);
447            }
448            state.window_frame_range.start =
449                state.window_frame_range.end.saturating_sub(buffer_size);
450        }
451        Ok(())
452    }
453
454    fn evaluate(
455        &mut self,
456        values: &[ArrayRef],
457        range: &Range<usize>,
458    ) -> Result<ScalarValue> {
459        if let Some(ref result) = self.state.finalized_result {
460            Ok(result.clone())
461        } else {
462            // FIRST_VALUE, LAST_VALUE, NTH_VALUE window functions take a single column, values will have size 1.
463            let arr = &values[0];
464            let n_range = range.end - range.start;
465            if n_range == 0 {
466                // We produce None if the window is empty.
467                return ScalarValue::try_from(arr.data_type());
468            }
469            match self.valid_index(arr, range) {
470                Some(index) => ScalarValue::try_from_array(arr, index),
471                None => ScalarValue::try_from(arr.data_type()),
472            }
473        }
474    }
475
476    fn supports_bounded_execution(&self) -> bool {
477        true
478    }
479
480    fn uses_window_frame(&self) -> bool {
481        true
482    }
483}
484
485impl NthValueEvaluator {
486    fn valid_index(&self, array: &ArrayRef, range: &Range<usize>) -> Option<usize> {
487        let n_range = range.end - range.start;
488        if self.ignore_nulls {
489            // Calculate valid indices, inside the window frame boundaries.
490            let slice = array.slice(range.start, n_range);
491            if let Some(nulls) = slice.nulls()
492                && nulls.null_count() > 0
493            {
494                return self.valid_index_with_nulls(nulls, range.start);
495            }
496        }
497        // Either no nulls, or nulls are regarded as valid rows
498        match self.state.kind {
499            NthValueKind::First => Some(range.start),
500            NthValueKind::Last => Some(range.end - 1),
501            NthValueKind::Nth => match self.n.cmp(&0) {
502                Ordering::Greater => {
503                    // SQL indices are not 0-based.
504                    let index = (self.n as usize) - 1;
505                    if index >= n_range {
506                        // Outside the range, return NULL:
507                        None
508                    } else {
509                        Some(range.start + index)
510                    }
511                }
512                Ordering::Less => {
513                    let reverse_index = (-self.n) as usize;
514                    if n_range < reverse_index {
515                        // Outside the range, return NULL:
516                        None
517                    } else {
518                        Some(range.end - reverse_index)
519                    }
520                }
521                Ordering::Equal => None,
522            },
523        }
524    }
525
526    fn valid_index_with_nulls(&self, nulls: &NullBuffer, offset: usize) -> Option<usize> {
527        match self.state.kind {
528            NthValueKind::First => nulls.valid_indices().next().map(|idx| idx + offset),
529            NthValueKind::Last => nulls.valid_indices().last().map(|idx| idx + offset),
530            NthValueKind::Nth => {
531                match self.n.cmp(&0) {
532                    Ordering::Greater => {
533                        // SQL indices are not 0-based.
534                        let index = (self.n as usize) - 1;
535                        nulls.valid_indices().nth(index).map(|idx| idx + offset)
536                    }
537                    Ordering::Less => {
538                        let reverse_index = (-self.n) as usize;
539                        let valid_indices_len = nulls.len() - nulls.null_count();
540                        if reverse_index > valid_indices_len {
541                            return None;
542                        }
543                        nulls
544                            .valid_indices()
545                            .nth(valid_indices_len - reverse_index)
546                            .map(|idx| idx + offset)
547                    }
548                    Ordering::Equal => None,
549                }
550            }
551        }
552    }
553}
554
555#[cfg(test)]
556mod tests {
557    use super::*;
558    use arrow::array::*;
559    use datafusion_common::cast::as_int32_array;
560    use datafusion_physical_expr::expressions::{Column, Literal};
561
562    fn test_i32_result(
563        expr: NthValue,
564        partition_evaluator_args: PartitionEvaluatorArgs,
565        expected: Int32Array,
566    ) -> Result<()> {
567        let arr: ArrayRef = Arc::new(Int32Array::from(vec![1, -2, 3, -4, 5, -6, 7, 8]));
568        let values = vec![arr];
569        let mut ranges: Vec<Range<usize>> = vec![];
570        for i in 0..8 {
571            ranges.push(Range {
572                start: 0,
573                end: i + 1,
574            })
575        }
576        let mut evaluator = expr.partition_evaluator(partition_evaluator_args)?;
577        let result = ranges
578            .iter()
579            .map(|range| evaluator.evaluate(&values, range))
580            .collect::<Result<Vec<ScalarValue>>>()?;
581        let result = ScalarValue::iter_to_array(result)?;
582        let result = as_int32_array(&result)?;
583        assert_eq!(expected, *result);
584        Ok(())
585    }
586
587    #[test]
588    fn first_value() -> Result<()> {
589        let expr = Arc::new(Column::new("c3", 0)) as Arc<dyn PhysicalExpr>;
590        test_i32_result(
591            NthValue::first(),
592            PartitionEvaluatorArgs::new(
593                &[expr],
594                &[Field::new("f", DataType::Int32, true).into()],
595                false,
596                false,
597            ),
598            Int32Array::from(vec![1; 8]).iter().collect::<Int32Array>(),
599        )
600    }
601
602    #[test]
603    fn last_value() -> Result<()> {
604        let expr = Arc::new(Column::new("c3", 0)) as Arc<dyn PhysicalExpr>;
605        test_i32_result(
606            NthValue::last(),
607            PartitionEvaluatorArgs::new(
608                &[expr],
609                &[Field::new("f", DataType::Int32, true).into()],
610                false,
611                false,
612            ),
613            Int32Array::from(vec![
614                Some(1),
615                Some(-2),
616                Some(3),
617                Some(-4),
618                Some(5),
619                Some(-6),
620                Some(7),
621                Some(8),
622            ]),
623        )
624    }
625
626    #[test]
627    fn nth_value_1() -> Result<()> {
628        let expr = Arc::new(Column::new("c3", 0)) as Arc<dyn PhysicalExpr>;
629        let n_value =
630            Arc::new(Literal::new(ScalarValue::Int32(Some(1)))) as Arc<dyn PhysicalExpr>;
631
632        test_i32_result(
633            NthValue::nth(),
634            PartitionEvaluatorArgs::new(
635                &[expr, n_value],
636                &[Field::new("f", DataType::Int32, true).into()],
637                false,
638                false,
639            ),
640            Int32Array::from(vec![1; 8]),
641        )?;
642        Ok(())
643    }
644
645    #[test]
646    fn nth_value_2() -> Result<()> {
647        let expr = Arc::new(Column::new("c3", 0)) as Arc<dyn PhysicalExpr>;
648        let n_value =
649            Arc::new(Literal::new(ScalarValue::Int32(Some(2)))) as Arc<dyn PhysicalExpr>;
650
651        test_i32_result(
652            NthValue::nth(),
653            PartitionEvaluatorArgs::new(
654                &[expr, n_value],
655                &[Field::new("f", DataType::Int32, true).into()],
656                false,
657                false,
658            ),
659            Int32Array::from(vec![
660                None,
661                Some(-2),
662                Some(-2),
663                Some(-2),
664                Some(-2),
665                Some(-2),
666                Some(-2),
667                Some(-2),
668            ]),
669        )?;
670        Ok(())
671    }
672
673    #[test]
674    fn nth_value_i64_min_returns_error() {
675        let expr = Arc::new(Column::new("c3", 0)) as Arc<dyn PhysicalExpr>;
676        let n_value = Arc::new(Literal::new(ScalarValue::Int64(Some(i64::MIN))))
677            as Arc<dyn PhysicalExpr>;
678
679        let err = NthValue::nth()
680            .partition_evaluator(PartitionEvaluatorArgs::new(
681                &[expr, n_value],
682                &[Field::new("f", DataType::Int32, true).into()],
683                false,
684                false,
685            ))
686            .unwrap_err();
687
688        assert!(err.to_string().starts_with(
689            "Execution error: The second argument of nth_value must not be i64::MIN"
690        ));
691    }
692}