In dataview how do you return tasks whose tags are associated with a dv field of some page?

First, search the help docs and this forum. Maybe your question has been answered! The debugging steps can help, too. Still stuck? Delete this line and proceed.

What I’m trying to do

Imagine there is a vault V which contains a bunch of pages P each with a unique set of tags u for a dateview field, attribute A.

Also, imagine that spread throughout vault V on random pages R are a collection of tasks T each of which have a bunch of tags t associated with them.

Next comes the hard part…

I want to find the subset of tasks of T (let’s call it T') for which there is a page in P whose every tag in u of attribute_A has the following property: it belongs to a common bunch of tags associated with some task of T'.

Things I have tried

let pages = dv.pages().where(p => attribute_A);

for (let page in pages){
let tag_ = page.attribute_A;
taskList(page.file.tasks.where(t => t.tags & tag_.every(x => t.tags.includes(x))))
}

There’s a lot of loose ends in that description, so in order for us to help you you need to present some of those pages and tasks markup, so we now what we’re dealing with.

That would also help us to build a test case, if we find it worthwhile and see a way to do it. Without more information, almost any advice could very easily miss the target by a few miles as it doesn’t meet up with your actual markup.

The original post is a dense presentation of the problem. I will try to unpack it:

Part 1

“Imagine there is a vault V which contains a bunch of pages P each with a unique set of tags u for a dateview field, attribute A.”

This means… I want you to imagine there is a stack of markdown files which we can agree comprises an Obsidian vault. Let’s refer to that vault here by the following symbol: V

Let’s agree that each file in V is called a ‘page’.

Please imagine that there is subset of pages (let’s call this sub-collection P) in V that has the following property:

Each page in P has a dataview field called attribute_A, and the value of this field is a unique bunch of tags. (Let’s refer to any one of these unique bunches as u. So, if we want to talk about one of these bunch but it doesn’t matter which one, we call it u.)

To partly concretise what’s been discussed so far we can model five random pages from P as follows:

Random Page 1 in P

attribute_A:: #apple, #cat, #leaf

Blah, blah, blah content; this can be anything.

Random Page 2 in P

attribute_A:: #road, #water, #sky

Blah, blah, blah content; this can be anything.

Random Page 3 in P

attribute_A:: #person, #food, #dog

Blah, blah, blah content; this can be anything.

Random Page 4 in P

attribute_A:: #wheel, #bird, #pencil

Blah, blah, blah content; this can be anything.

Random Page 5 in P

attribute_A:: #shop, #gate, #surfboard

Blah, blah, blah content; this can be anything.

Since we agreed to refer to any of the individual collections of tags with u it follows that #person, #food, #dog can be referred to with u, and so can #road, #water, #sky. The symbol u just refers to any one of tag collections associated with an attribute_A on some page in P, (it doesn’t matter which tag collection).

Recap: I have just shown a random pick of five pages in P, which is the subset of pages in V which have the dataview field attribute_A.

Part 2

“Also, imagine that spread throughout vault V on random pages R are a collection of tasks T each of which have a bunch of tags t associated with them.”

Pages

This means… Some of the pages in V have tasks on them, and we can label this subset of V as R.

Let’s update three of five modeled pages featured above and unveil a couple of pages not in P (but which are in V) to reflect this new information:

Random Page 1 in P

attribute_A:: #apple, #cat, #leaf

Blah, blah, blah content; this can be anything.


- [ ] task r  #frog, #water, #egg
- [ ] task h #road, #water, #sky
- [ ] task a #shop, #gate, #surfboard
- [ ] task q #road, #cat, #sky

Random Page 2 in P

attribute_A:: #road, #water, #sky

Blah, blah, blah content; this can be anything.

- [ ] task w #shop, #gate, #surfboard
- [ ] task b #road, #water, #sky
- [ ] task c #apple, #cat, #leaf
- [ ] task y #shop, #gate, #surfboard

Random Page 5 in P

attribute_A:: #shop, #gate, #surfboard

Blah, blah, blah content; this can be anything.

- [ ] task o #shop, #gate, #surfboard
- [ ] task m #apple, #cat, #leaf

Random Page 6 NOT in P but still a member of V


Blah, blah, blah content; this can be anything.

- [ ] task t #wheel, #bird, #pencil
- [ ] task s #road, #water, #sky
- [ ] task l #apple, #cat, #leaf
- [ ] task z #person, #food, #dog

Random Page 7 NOT in P but still a member of V


Blah, blah, blah content; this can be anything.

- [ ] task x #number, #tree, #circle
- [ ] task f #frog, #water, #egg
- [ ] task k #apple, #cat, #leaf, #egg
- [ ] task n #road, #water, #sky
- [ ] task blah #shop, #gate, #surfboard, #sky

N.B. the last two page models do not have the dataview field attribute_A. So they don’t belong to P, but they do still belong to V. However, remember: we defined pages that have tasks on them as R. Whether a given page belongs to P or not is irrelevant for membership in R.

We have just modeled the pages in V that have tagged tasks, and we called this collection of pages R. Hopefully, these things are clear.

Tags

Please now swing your attention around to focus on tags.

So that we can deal with them easily, let’s treat the tags associated with a given task on a page in R in a similar way to how we treated the tags associated with attribute_A on pages in P: let’s give them a label too.

When it doesn’t matter which collection of tags associated with a task we find on a page in R, but we just want to talk about one of collections, let’s use the label t.

So far now we have two types of tag bunches: t and u.

With these labels noted now you should see that sometimes t exactly matches u of attribute_A, and sometimes it doesn’t.

For example, the tags of - [ ] task l #apple, #cat, #leaf found on Random Page 6 exactly match those of attribute_A on Random Page 1 . However the tags of - [ ] task f #frog, #water, #egg do not exactly match any of the attribute_A fields in P. It’s true that there is tag that is found in attribute_A: The #water is found in Random Page 2. However not every tag in - [ ] task f #frog, #water, #egg is found together in an attribute_A of some page in P… sometimes t matches u… and sometimes it doesn’t.

…and sometimes u is a subset of t. For example, we can see that the tags associated with attribute_A of Random Page 1 are a subset of those in - [ ] task k #apple, #cat, #leaf, #egg of Random Page 7: #apple, #cat, #leaf is clearly a subset of #apple, #cat, #leaf, #egg.

(Note: being A being subset of B means that B is the superset of A… this will worth recalling shortly)

And so…

Part 3

"Next comes the hard part…

I want to find the subset of tasks of T (let’s call it T') for which there is a page in P whose every tag in u of attribute_A has the following property: it belongs to a common bunch of tags associated with some task of T'."

This means… all the tagged tasks can be bundled together in a set we can simply call T. Let’s lift the tags featured in our modeled pages and list them together in a big stack to make this concrete:

- [ ] task r #frog, #water, #egg
- [ ] task h #road, #water, #sky
- [ ] task a #shop, #gate, #surfboard
- [ ] task q #road, #cat, #sky
- [ ] task w #shop, #gate, #surfboard
- [ ] task b #road, #water, #sky
- [ ] task c #apple, #cat, #leaf
- [ ] task y #shop, #gate, #surfboard
- [ ] task o #shop, #gate, #surfboard
- [ ] task m #apple, #cat, #leaf
- [ ] task t #wheel, #bird, #pencil
- [ ] task s #road, #water, #sky
- [ ] task l #apple, #cat, #leaf
- [ ] task z #person, #food, #dog
- [ ] task x #number, #tree, #circle
- [ ] task f #frog, #water, #egg
- [ ] task k #apple, #cat, #leaf, #egg
- [ ] task n #road, #water, #sky
- [ ] task blah #shop, #gate, #surfboard, #sky

I’m looking for a subset of this set above (which we call T). Let’s agree that the subset I’m after is called T'.

T' has the following properties:

For every task in T' there is a page in P whose tags found in attribute_A are such that all of them belong to the bunch of tags associated with the task.

Stated as above, I think the logic of my goal is closer to how it would be stated in a computer program. However, put another way, we can say that I want to find the following tasks T':

  • the tasks whose tags are either exactly the same as the bunch of tags of attribute_A or their superset.

(Note: Set A being the superset of set B is equivalent to set B being a subset of A. )

I hope I have sufficiently unpacked the original post.

Thanks for your time and patience.

Additional Info

I didn’t specify this in the original post, but I’d like to group the tasks of T' under the heading of the pages in P whose set of attribute_A tags their tags are a superset of. Hopefully, I don’t have to unpack this additional info too… LOL

This should say:

Let’s lift the tasks featured in our modeled pages and list them together in a big stack to make this concrete:

Sorry.

So basically for a page with attribute_A you want to list all tasks where the all tags of attribute_A is are present in the tags associated with that task, and that task could have additional tags.

Detour to list all information of tasks

Doing this using pure TASKS query is a pain, as it’s so hard to see extra information, so lets use a TABLE query for starters. To get to the tasks here we need to do FLATTEN file.tasks as task, and then we can start listing out the various information.

You said you wanted this to match against the attribute_A of a given page, this reads as this.attribute_A in a DQL query. So the base query then becomes:

```dataview
TABLE WITHOUT ID task.text, task.tags, this.attribute_A
FLATTEN file.tasks as task
WHERE file.folder = this.file.folder
```

NB! I’m using WHERE file.folder = test.file.folder to limit the test run. Either just remove this, or replace with something more suitable for your case. If that includes a FROM clause, it needs to go in front of the FLATTEN line. (This’ll apply to all the queries shown below here)

The top lines based on the example you gave is (when viewed from Random Page 5 in P :slight_smile: ):

Out of these tasks, you’re wanting to get task a since they match the criteria, as I’ve understood them. Textually you’ll want that all the tags of attribute_A needs to be contained in the list of tags for the task. This can be translated into the following query expression:

all( map( this.attribute_A, (a) => contains(task.tags, a) ) )

Split out into its separate parts:

  • all( ... ) - Require that all elements of the list within are true, for the entire statement to return true
  • map( this.attribute_A, (a) => ... ) – This effectively loops over all element of this.attribute_A, and within the loop each element is known as a
  • contains(task.tags, a) – Test whether the a tag is a part of the tag list in task.tags, and keep this mapped result
  • This means we end up with a list mapping whether each tag is part of the task tags, and the final check is whether all those values are true (as in found)

Our query now looks like:

TABLE WITHOUT ID task.text, task.tags, this.attribute_A, isSubset
FLATTEN file.tasks as task
FLATTEN all( map( this.attribute_A, (a) => contains(task.tags, a) ) ) as isSubset
WHERE file.folder = this.file.folder

And since I’ve used the FLATTEN ... as isSubset this allows us to use isSubset within the WHERE clause, and we can get this query:

```dataview
TABLE WITHOUT ID task.text, task.tags, this.attribute_A, isSubset
FLATTEN file.tasks as task
FLATTEN all( map( this.attribute_A, (a) => contains(task.tags, a) ) ) as isSubset
WHERE file.folder = this.file.folder
WHERE isSubset
```

Which result in this output:

Note especially that the final tasks shows the subset working as expected.

Final steps and query

Now we need to go back to a TASK query, and when doing so, all task.tags & co, becomes just tags & co. And if opening up from my test run (where you might to add something of your own), the query then becomes:

```dataview
TASK
FLATTEN
  all( map( this.attribute_A, 
            (a) => contains(tags, a) )
     ) as isSubset
WHERE isSubset
```

With the output of:
image


Hopefully this is the result you wanted, and a clear explanation on how I arrived at this answer. Do you need for the answer to be in dataviewjs, or is it OK to use this DQL query?

HeHe… Here is an alternate output variant:

attribute_A:: #shop, #gate, #surfboard

```dataview
TASK
FLATTEN all( map( this.attribute_A, (a) => contains(tags, a) ) ) as isSubset
FLATTEN regexreplace(text, "#[^ ]+", "") + 
  join(filter(tags, (t) => !contains(this.attribute_A, t)), ", ") as visual
WHERE isSubset
```

With the output (from Random Page 1 in P) of:
image

This variant removes all “known” tags, and just displays any potential extra tags. Not sure if it’s useful or not, but I wrote it anyways! :smiley:

Hi @holroy,

Thank you for the time and effort you put into your response.

Unfortunately I am looking for a query that will return the tasks I have described that can be displayed on any page. The solution you generously shared seems to be anchored to the current page.

I have come up with a solution as follows, but it’s not quite there. I managed to get the tasks I want, but I want to group tasks by the name (or preferably the link) of the page whose attribute_A tags their tags are the superset of.

Here is what I have so far. It’s okay but it groups the tasks by the page they were found on:

```dataviewjs

// Step 1: Filter pages 


const pagesWithAttributeA = dv.pages()
    .where(page => page.attribute_A);




// Step 2: Get tasks from all pages

const allTasks = dv.pages().flatMap(page => page.file.tasks);



// Step 3: Filter the tasks based on whether their tags are a superset of tags found in the attribute_A field of selected pages


const filteredTasks = [];

for (const page of pagesWithAttributeA) {

  
  for (const task of allTasks) {
    
    if (page.attribute_A.every(tag => task.tags.includes(tag))) {
      filteredTasks.push(task);
    }
  }
}




// Step 4: Display filtered tasks 


dv.taskList(filteredTasks)

Did you try the queries I presented? With the exception of the first few which were limited to the current folder, they’re global. And even the first few queries can be made global by removing the WHERE file.folder... like.

Hi @holroy,

Yes I tried the queries you presented. Thank you for all your time and effort. Unfortunately on my system they return no results. I guessed it was because they rely on this.atttribute_A which seemed to refer to the local page on which the script is located, and the values associated with its attribute_A. Hence my response.

The following output displayed in your post was great, but alas I just couldn’t replicate it. Thank you for you patience:

To be clear: I want a script that has the following properties:

  • it works when placed on any page, regardless of whether the page has attribute_A. (E.g. The script is placed on a page that doesn’t have attribute_A, but it still works.)
  • it returns the tasks whose tags are the superset of tags in the attribute_A field of pages that have the attribute_A field.

I tried modifying yours scripts in a number of ways to get that outcome, but I was not able to make any of them work. I should say, I did try removing WHERE file.folder..., as you suggested, but that didn’t do it either.

Perhaps there is something different in my system… what do you think?

Anyway the dataviewjs script I came up with gets the tasks I’m looking for, but the final step still remains:

  • How do we group the tasks by the pages whose attribute_A tags the task’s tags are the superset of?

Thank you for your time.

I figured out how to group the tasks under a link to the page whose attribute_A tags the task tags are the superset of.

(Note how the dv.taskList() function suppresses the default setting to group by page on which the task occurs)



// Step 1: Filter pages

const pagesWithAttribute_A = dv.pages().where(page => page.attribute_A);

 

// Step 2: Get tasks from all pages

const allTasks = dv.pages().flatMap(page => page.file.tasks);



// Step 3: Filter the tasks based on whether their tags are a superset of tags found in the attribute_A field of selected pages and list them under a link to those selected pages.
 

for (const page of pagesWithAttribute_A) {

	const pageTasks = [];
	
	dv.header(3,page.file.link);
	
	  
	
	for (const task of allTasks) {
	
		if (page.attribute_A.every(tag => task.tags.includes(tag))) {
		
			pageTasks.push(task);
			
			}
		
	}
	
	dv.taskList(pageTasks, false);

}

Is this the final output you wanted? Have you solved your own request? If not, how would you like the output to change?

I see now that I misread part of your request and thought you wanted the tasks with “attribute_A” tags on the page it was defined. So my queries was indeed anchored locally to the page which defined this.attribute_A. Sorry for my misunderstanding.

When doing such a double request, you’re also correct that you need to do at least a double loop. One for the pages having the attribute, and one for the tasks. That, to my knowledge, can’t be done in an ordinary DQL query (as long as there isn’t any strong links between the tasks and the pages (in P)).

Depending on whether any of attribute sets (within P) is a subset of other attribute sets (still within P) causing tasks to potentially belong to two different pages in P, it can be debated whether you need two or three loops. If the attribute sets in P are truly disjoint sets, one should be able to do this in a double loop like indicated here:

  • Loop all pages, and extract list of all pages with attribute_A
  • Loop the list of tasks, and filter out the tasks with tags being a subset of attribute_A for any of the given pages
    • Mutate the task list with the matching page name
  • Display the list grouped by the page name

(With page name, I refer to the page where the attribute is defined)

This variant would skip one loop, and could potentially be used to display all the information in one call to dv.taskList(). Whether that is any point, though, is another matter. Just thought I should mention it.

Here is a script, which I believe do the same as yours, but in a slightly different matter (using fewer loops):

```dataviewjs
const pagesWithAttribute_A = dv.pages()
  .where(page => page.attribute_A)
  .map(p => [p.file.link, p.attribute_A]);

const allTasks = dv.pages()
  .flatMap(page => page.file.tasks)
  .mutate(p => {
    for (const page of pagesWithAttribute_A) {
      if (page[1].every( tag => p.tags.includes(tag) )) {
        // Only sets p.superset if the tags matches, and
        // if _one_ match is found, it breaks out
        p.superset = page[0] 
        break
      }
    }
  })
  // Loose any task, not having the link
  .filter(p => p.superset) 
  // Group by the attribute page link
  .groupBy(p => p.superset)
  
dv.taskList(allTasks)
```

I’m in a strange mood, so here is yet another version of the same script but even a little more condensed:

```dataviewjs
const pagesWithAttribute_A = dv.pages()
  .where(page => page.attribute_A)
  .map(p => [p.file.link, p.attribute_A]);

const allTasks = dv.pages()
  .flatMap(page => page.file.tasks)
  .mutate(p =>  p.superset = pagesWithAttribute_A
      .filter( page => page[1].every( tag => p.tags.includes(tag) ) )
      .map( page => page[0] ))
  // Loose any task, not having the link
  .filter(p => p.superset) 
  // Group by the attribute page link
  .groupBy(p => p.superset[0]) // Only the first match is used
  
dv.taskList(allTasks)
```

Thanks, yes this works too! Thanks!

This one seems to list all the other tasks in my vault too before the ones I’m interested in… The other script you wrote just before this one get’s the right result.

Thank you for your help.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.