DQL & DataviewJS: Query and list all the items and subitems under multiple (nested) headings

The first issue with your query, is that the markdown you’re using are not producing correct headers as seen by Dataview. To see my point try doing this query in the file with the bookmarks:

```dataview

TABLE meta(item.section) 
WHERE file = this.file
FLATTEN file.lists as item
WHERE item.Browser
```

This should display a table starting with something like:

And as you can see, it doesn’t list the “bookmark:: 2” as the header, but the semantically correct header of “Firefox bookmarks”.


To be able to sort your current structure into something resembling your wanted output, you’ll need to focus on the item with the bookmark and then look into its children to pull out the wanted information from those.

Try playing around with the following query:

```dataview

TABLE item.bookmark, item.children.text, browser, url, name, category 
WHERE file = this.file
FLATTEN file.lists as item
FLATTEN default(nonnull(item.children.Browser)[0], "") as browser
FLATTEN default(nonnull(item.children.Category)[0], "") as category
FLATTEN default(nonnull(item.children.URL)[0], "") as url
FLATTEN default(nonnull(item.children.Name)[0], "") as name
WHERE item.bookmark
SORT item.bookmark, browser
```

The item.children.text is just for debug purposes, as well as the limitation of WHERE file = this.file which focuses on the current file only. The monstrosity of this query is the FLATTEN lines, which I can try to decipher for you:

  • FLATTEN ... as browser – This will evaulate the expression, and store it into browser readily available for use later on in the query
  • default( ..., "") – This function will use the value of the expression, and if that doesn’t hold a value, use the empty string as a result, "". This ensures that if you don’t have that particular value in your sublist, the query won’t break
  • nonnull( ... )[0] – After evaluation the expression in a list context, it removes any entries not having any value, and then it picks the first item (and hopefully only) item out of that list with the trailing [0]
  • item.children.Browser – Using some Dataview magic, this will scan the items of the current item (which due to the WHERE clause is the item with the bookmark definition) for any variable called Browser. In short pull out any Browser field from the sub-list

So in a more natural language, it’ll scan the sub-lists for a given variable, forget about any items not having a Browser field, and assign that value, if found, to another variable browser. If not found, let browser be the empty string.

The query above when run on your example provides something like:


Hopefully this’ll get you moving forward. But remember that your syntax of using header notation within a list isn’t treated as a header as such by Obsidian.

3 Likes