You are viewing a plain text version of this content. The canonical link for it is here.
Posted to github@datafusion.apache.org by "TheIndifferent (via GitHub)" <gi...@apache.org> on 2025/09/22 10:42:08 UTC

[D] Query data with "WHERE" clause for nested objects [datafusion]

GitHub user TheIndifferent created a discussion: Query data with "WHERE" clause for nested objects

Hey everyone,

It seems like I am able to import the data structure with nested objects into DataFusion, but I cannot figure out how to query it, could someone please point me to the right example or how do I do that?

In the following example code, struct `A` has a field of type `B`, and I am trying to query all `A` where `B.e == {}`. I was trying to use `JOIN UNNEST` but I doubt that is the right approach on how to work with a data structure like that:
```rust
use datafusion::arrow::datatypes::*;
use datafusion::arrow::json::ReaderBuilder;
use datafusion::prelude::*;
use serde::{Deserialize, Serialize};
use std::io::Cursor;
use std::sync::Arc;

#[tokio::test]
async fn test_nested_unnest_query() {
    // Given:
    let ctx = SessionContext::new();

    let test_data = TestData {
        items: vec![
            A {
                c: 1,
                d: "first".to_string(),
                f: vec![
                    B { c: "x".to_string(), d: "y".to_string(), e: "target".to_string() },
                    B { c: "p".to_string(), d: "q".to_string(), e: "other".to_string() },
                ],
            },
            A {
                c: 2,
                d: "second".to_string(),
                f: vec![
                    B { c: "m".to_string(), d: "n".to_string(), e: "other".to_string() },
                ],
            },
        ],
    };

    let schema = create_schema();

    let json_data = serde_json::to_string(&test_data).unwrap();
    let cursor = Cursor::new(json_data.as_bytes());
    let mut reader = ReaderBuilder::new(Arc::new(schema)).build(cursor).unwrap();
    let batch = reader.next().unwrap().unwrap();
    ctx.register_batch("test_data", batch).unwrap();

    // When:
    let query = "
        SELECT a.c, a.d, b.e
        FROM test_data
        CROSS JOIN UNNEST(items) AS a
        CROSS JOIN UNNEST(a.f) AS b
        WHERE b.e = 'target'
    ";

    let result = ctx.sql(query).await.unwrap();

    // Then:
    let batches = result.collect().await.unwrap();
    let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
    assert_eq!(total_rows, 1, "Should find exactly 1 record with b.e = 'target'");
}

fn create_schema() -> Schema {
    Schema::new(vec![Field::new(
        "items",
        DataType::List(Arc::new(Field::new(
            "item",
            DataType::Struct(Fields::from(vec![
                Field::new("c", DataType::UInt32, false),
                Field::new("d", DataType::Utf8, false),
                Field::new(
                    "f",
                    DataType::List(Arc::new(Field::new(
                        "item",
                        DataType::Struct(Fields::from(vec![
                            Field::new("c", DataType::Utf8, false),
                            Field::new("d", DataType::Utf8, false),
                            Field::new("e", DataType::Utf8, false),
                        ])),
                        true,
                    ))),
                    false,
                ),
            ])),
            true,
        ))),
        false,
    )])
}

#[derive(Serialize, Deserialize)]
struct TestData {
    items: Vec<A>,
}

#[derive(Serialize, Deserialize)]
struct A {
    c: u32,
    d: String,
    f: Vec<B>,
}

#[derive(Serialize, Deserialize)]
struct B {
    c: String,
    d: String,
    e: String,
}

```

GitHub link: https://github.com/apache/datafusion/discussions/17714

----
This is an automatically sent email for github@datafusion.apache.org.
To unsubscribe, please send an email to: github-unsubscribe@datafusion.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: github-unsubscribe@datafusion.apache.org
For additional commands, e-mail: github-help@datafusion.apache.org


Re: [D] Query data with "WHERE" clause for nested objects [datafusion]

Posted by "ryux1 (via GitHub)" <gi...@apache.org>.
GitHub user ryux1 added a comment to the discussion: Query data with "WHERE" clause for nested objects

You do not need a join for this shape. In DataFusion, `unnest(...)` is used as a set-returning expression in the `SELECT` list. Unnest the outer list first, access fields on the resulting struct, then unnest its nested `f` list:

```sql
WITH a_rows AS (
  SELECT unnest(items) AS a
  FROM test_data
),
b_rows AS (
  SELECT
    a['c'] AS c,
    a['d'] AS d,
    unnest(a['f']) AS b
  FROM a_rows
)
SELECT c, d, b['e'] AS e
FROM b_rows
WHERE b['e'] = 'target';
```

For the data in the question, that returns:

```text
1  first  target
```

The important distinction is that `a` and `b` above are struct-valued columns, not table aliases. Bracket field access (`a['f']`, `b['e']`) makes that explicit and avoids the ambiguity in `CROSS JOIN UNNEST(items) AS a`.

I verified this query against current DataFusion `main` with the same `List<Struct<..., f: List<Struct<...>>>>` shape; the focused SQLLogicTest passes.


GitHub link: https://github.com/apache/datafusion/discussions/17714#discussioncomment-18322226

----
This is an automatically sent email for github@datafusion.apache.org.
To unsubscribe, please send an email to: github-unsubscribe@datafusion.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: github-unsubscribe@datafusion.apache.org
For additional commands, e-mail: github-help@datafusion.apache.org