Normally, you want all parent related posts to be the children of a single post. This makes it a lot easier to navigate through the children of a post and see all variants. Abuse of the parent system can lead to parent-chains, parent-trees, and worst of all – parent-graphs.

[9:09 PM] idem: are post-parent chains still discouraged of any length greater than 1? e.g. If I have 3 posts they should be in a pool instead of A->B->C
[9:11 PM] idem: or C<-A->B
[9:11 PM] Mairo: I at least personally still highly discourage those and constantly remove them when see any, because it is literally just like pool, but so much harder to navigate
[9:11 PM] Mairo: Either have A as parent to other posts or pool.
[9:12 PM] Poofinator: I usually make pools for sequences of at least 4 or 5 pics
[9:13 PM] Poofinator: sometimes I make them for less if it's comics with multiple panels
[9:15 PM] Mairo: Technically with pools there's no rules, but if it's 3 pages, just make a pool, if it's like 3 panels, it's not that hard to navigate

First some background. This is what parent relations should look like on e621.

many posts pointing to a single parent

Some people end up making parent-chains like the image below, but when you find a long chain of posts it should probably be in a pool. Pools provide a lot of niceties over regular parent-chains: titles, descriptions, easy organization, in built navigation.

a long chain of posts. one linking to the next

Posts with parent relations like this are easy to navigate and can help users find all other posts that are related. Conversely, something like the examples below are difficult, cumbersome, and confusing to navigate.

two posts linking to each other a post with two children, one of which has two more children three posts in a cycle with an offshoot of two posts in a chain

Finding these bad parent graphs can be a bit tricky. First we need to have a definition for what a good parent graph is. I would say it is a tree with all nodes having a depth of 1. To find that, I (with the help of Fifteen) created a recursive SQL query to find all relation trees. From there we can filter away the trees that have a depth of 1 and union it will all posts that have a parent relation but are not part of a tree.

with recursive post_parent_tree(tree_id, post_id, child_id) as (
	-- all root nodes
	select
		parent.post_id as tree_id,
		parent.post_id,
		child.post_id as child_id
	from posts_metadata as parent
	inner join posts_metadata as child on (parent.post_id = child.parent_id)
	where parent.parent_id is null
		and parent.is_deleted = 0
		and child.is_deleted = 0
	--
	union all
	-- the children of the root nodes 
	select 
		tree_id,
		current.child_id,
		deeper.post_id
	from post_parent_tree as current
	inner join posts_metadata as deeper on(current.child_id = deeper.parent_id)
	where deeper.is_deleted = 0
),
posts_with_a_relation as (
	-- all children
	select post_id
	from posts_metadata
	where parent_id is not null
		and is_deleted = 0
	--
	union
	-- all parents
	select parent_id as post_id
	from posts_metadata
	where parent_id is not null
		and is_deleted = 0
),
posts_in_a_tree as (
	select post_id from post_parent_tree
	union
	select child_id as post_id from post_parent_tree
),
-- If its not in a tree, it could be in a cycle or the root could be deleted
posts_not_in_a_tree as (
	select post_id
	from posts_with_a_relation
	where post_id not in (select post_id from posts_in_a_tree)
),
-- Because post_parent_tree is id-child instead of parent-id, posts are
-- good when the post_id matches the tree_id. It keeps track of children
-- instead of keeping track of parents.
trees_with_problems as (
	select distinct tree_id
	from post_parent_tree
	inner join posts_metadata using (post_id)
	where tree_id != post_id
		and is_deleted = 0
)
-- Get and format bad trees
select
	tree_id,
	-- Add one to count for the tree-post
	count(*) + 1 as num_posts
from post_parent_tree
inner join trees_with_problems using(tree_id)
group by tree_id
union all
-- Get and format bad non-trees
select
	post_id as tree_id,
	-1 as num_posts 
from posts_not_in_a_tree
--
order by tree_id;
import fs from 'fs';
import db from './../../_code/database.mjs';
import { file_here } from './../../_code/file.mjs'

const query_path = file_here(import.meta.url, './query.sql');
const query = fs.readFileSync(query_path, 'utf8');

const rows = db.prepare(query)
	.all()
	.map(e => {
		const post_count = e.num_posts === -1 
			? 'malformed tree'
			: `${e.num_posts} posts`;
		const link = `https://e621.net/posts/${e.tree_id}`;
		const a = `<a href="${link}">${e.tree_id}</a>`;
		return `<tr><td>${a}</td><td>${post_count}</td></tr>`;
	})
	.join('\n');

const table = `
<link href="/static/default_style.css" rel="stylesheet" type="text/css">
<base target="_blank" rel="noopener noreferrer">
<table>
	<thead><tr><th>Post id</th><th>Number of Posts</th></tr></thead>
	<tbody>${rows}</tbody>
</table>`;

const file_path = file_here(import.meta.url, 'bad_relations.generated.html');
fs.writeFileSync(file_path, table);

The results of this query can be found here. The reason being, it is a large (1-10 MB) file and can slow down even fast web browsers. It is best to leave that page for people that want to actually see it.

While not all encompassing, a quick, simple, and effective search can be used to find posts that are part of a chain. {{isparent:true ischild:true}}. This returns all posts that are in the middle of a graph.