How to handle book re-reads

What I’m trying to do

I have a digital library where I keep track of my books. Up until recently I never had the need to track re-reads books. As I am planning to do some re-reads I was trying to figure out a way to represent that into my obsidian vault - meaning that I want them to appear correctly on my “shelfs” and the data (number of books read per year and etc.)

Things I have tried

The only reference that I could find for this is here but I cant make it work because I can’t figure out how the author was able to make that readdates property work.

I also tries to add a boolean ReRead property on the book note which kinda works but my struggle is to tell dataview when it was re-read and make it appear on the read books shelf - I have a note where I have the read books divided by year.

If I try to make a list for the re-read years it seems that dataview handles that as a single “number”.

Example metadata of a book

---
tags:
  - book
  - fiction
  - fantasy
  - legendarium
Cover: https://www.dymocks.com.au/Pages/ImageHandler.ashx?q=9780007597338&w=&h=570
Type:
  - Fiction
  - Fantasy
Series: Legendarium
Order: 1.3
Author: J. R. R. Tolkien
Status: Read
Year: 2024
Month: April
Pages: 259
OrderInYear: 21
StartedDate: 2024-01-29
FinishedDate: 2024-04-10
---

I realize that it would be pretty much impossible to have some properties work properly like OrderInYear but I am ok with that

Example of a query to show all books read after 2023:

let groups = dv.pages("#book and -#manga").where(p => p.Year != null && p.Year > "2023")
.groupBy(p => p.Year)

for (let group of groups.sort(g => g.key, 'desc')) { 
	dv.header(2, group.key); 
	dv.table( 
		["Cover", "Title", "Pages", "FinishedDate"], 
		group.rows 
			.sort(k => k.OrderInYear, 'asc')  
			.map(k => ["![coverimg|120](" + k.Cover + ")", k.file.link, k["Pages"], k["FinishedDate"]])) 
} 

Example of a query to show the number of books read per year:

TABLE length(rows)
FROM #book AND -#manga
GROUP BY Year
WHERE Year != null

I know that this might not be possible and appreciate the help in advance!

one thing you could do is have a list property with dates that you finished reading the book on. each date in the list would represent a completed read and count towards your books read for the year without counting towards unique books read for the year.

you would need to adjust the supporting code to utilise the new schema, but once it was working you’d be in good shape.

1 Like

Yeah, I tried the list approach for the year, but I guess that I couldn’t make the code work. Will need to try that again. Thanks

what part did you get stuck on? I might be able to help

Let’s say that I create a FinishedDate property that is a list of all the dates that I finish a book.

By creating a Dataview query like this:

TABLE
FROM #book
WHERE FinishedDate >= date("2024-01-01")
	AND FinishedDate <= date("2024-12-31")
SORT FinishedDate ASC

It works fine if the list only contains 1 value. With 2 or more the book stops showing because now the date being considered is 2024-04-25,2024-01-02 and I was not able to find a way to separate the values (but that is probably due to my noobness in code)

try this!

TABLE F
FROM #book
FLATTEN FinishedDate as F
WHERE F >= date("2024-01-01")
	AND F <= date("2024-12-31")
SORT FinishedDate ASC

The only potential issue is that if you read the book twice in the period it will be listed twice. That may be what you want, but if you want to filter for only the oldest or most recent in that case it’ll take more work.

Thanks for the availability to help!

At first glance it seemed to have done the trick but although it appears as it was reread the dates are not being handled individually

This is only an issue because I have the finished date appearing on the table (might reconsider though depending of future developments of this query ahah)

And also I have other queries that to do other things for example:

  • number of books read per year:
TABLE length(rows)
FROM #book AND -#manga
GROUP BY Year
WHERE Year != null
  • number of pages read per year:
TABLE sum(rows.Pages) as NumberOfPages
FROM #book AND -#manga
GROUP BY Year
  • books read in the Year uses dataviewjs:
let groups = dv.pages("#book and -#manga").where(p => p.Year != null && p.Year > "2023")
.groupBy(p => p.Year)

for (let group of groups.sort(g => g.key, 'desc')) { 
	dv.header(2, group.key); 
	dv.table( 
		["Cover", "Title", "Pages", "FinishedDate"], 
		group.rows 
			.sort(k => k.OrderInYear, 'asc')  
			.map(k => ["![coverimg|120](" + k.Cover + ")", k.file.link, k["Pages"], k["FinishedDate"]])) 
} 

I just want to say that I appreciate the help but this might be something to cumbersome to do without completely changing the properties or so. Please do not waste much time on this. Thanks!

how are you formatting your multiple dates?
they should look like this:

FinishedDate: 
  - 2024-04-10
  - 2024-05-01

That makes an array of dates.
It may be that dataview js makes life easier if you want to accommodate multiple reads. If you want I can have a go at making dataviewjs versions of the queries that work on this approach, I think it just involves a bit of map and dv.table

It is formatted as you are describing. Yeah I will most likely need dataviewjs

this bit shows how you could make a book record for each time the book is read, with all the properties intact. You can also derive the year property from FinishedDate.year if you wanted to.

The only disadvantage so far is I’m not sure how to use a groupBy() outside of a pages() call, so you might need to write something to get back the automatic tabulation by year.
In any case, hopefully this is helpful reference if you decide to take it further.

let books = dv.pages("#books AND -#manga")

var records = []

books.map(x => x.FinishedDate?.map(y => {
	x["date"] = y
	records.push(x)
	})
)

dv.paragraph(records.map(x => [x.file.name, x.date, x.date.year]))

What kind of output do you get from a query like the following:

```dataview 
TABLE length(rows) as "Book count", sum(rows.pages) as "Page count"
FROM #book AND -#manga
WHERE FinishedDate 
FLATTEN FinishedDate as F
GROUP BY F.year as Year
SORT key ASC
```

And what does your Year property denote? The year the book was written or first read? Why did you use this property to group on?

@holroy sorry. For context: Year is the year when the book was read. I only started adding the FinishedDate somewhere last year and it does not exist on older notes. Your query works very well, I was able to do one similar, but it only brings books with the FinishedDate property as one might expect. So this is something that I should probably figure out on my notes.

To answer the second question, because I only used Year to register when they where read I group all the queries based on that property.
Examples:


Thanks. I’ll need to try it out, doesn’t seem to work out of the box

1 Like

yep, this doesn’t generate tables yet, but gives you the data structures to allow you to get at the date more easily and treat each book read as a separate event

Thanks for all the help and to have taken the time to look into this.

With your inputs and the help of perplexity.ai I was able to make a dataviewjs script to present all the books in the year, including re-reads as a separate row and with the FinishedDate not presenting the whole list in the card

let reReads = dv.pages("#book and -#manga").where(p => p.Status === "Read");

let combinedRows = [];

for (let reReadBook of reReads) {
    let finishedDates = Array.isArray(reReadBook["FinishedDate"]) ? reReadBook["FinishedDate"] : [reReadBook["FinishedDate"]];
    finishedDates.forEach(date => {
        let finishedYear = new Date(date).getFullYear();
        if (finishedYear > 2023) {
            combinedRows.push([
                "![coverimg|150](" + reReadBook.Cover + ")",
                reReadBook.file.link,
                reReadBook["Pages"],
                date
            ]);
        }
    });
}

// Sort the combined rows by FinishedDate
combinedRows.sort((a, b) => {
	let yearDiff = new Date(b[3]).getFullYear() - new Date (a[3]).getFullYear();
	if (yearDiff !== 0) {
		return yearDiff;
	} else {
		return new Date(a[3]) - new Date(b[3]);
	}
});

let currentYear = null;
let yearRows = [];

for (let row of combinedRows) {
    let year = new Date(row[3]).getFullYear();
    if (year !== currentYear) {
	    if (yearRows.length > 0) {
		    dv.header(2, currentYear);
		    dv.table(["Cover", "Title", "Pages", "FinishedDate"], yearRows);
	    }
        currentYear = year;
        yearRows = [];
    }
    yearRows.push(row);
}

if (yearRows.length > 0) { 
	dv.header(2, currentYear); 
	dv.table(["Cover", "Title", "Pages", "FinishedDate"], yearRows); }

This is basically picking all books read in the year based on te FinishedDate and grouping them in the year that was read. The year is being presented as a header and in descending order.
The books are then presented in ascending order in the year based on the FinishedDate

Example:

1 Like