* Do not RAISE(IGNORE) anywhere, which prevents other code from
defining AFTER UPDATE triggers.

A possible better way of chaining multiple triggers together on SQLite?

BEGIN
  select RAISE(ABORT, 'FK VIOLATION INSERT AlbumID ON AlbumSection')
  where NEW.AlbumID IS NOT NULL and
  NEW.AlbumID not in (select AlbumID from Album);

  select RAISE(ABORT, 'Another problem')
  where another_condition;
END;


-- Now, I promised that I will tell you why we need record with depth 0 and why we need depth column.
-- Let's assume our $table are categories. And we have some products in these categories. Like this:

CREATE TABLE products (
    id          $pktype PRIMARY KEY,
    category_id $pktype NOT NULL REFERENCES $table (id),
    ...
);

-- It is quite common to ask database for all products in given category and it's subcategories.
-- Now, I can simply:

SELECT
    p.*
FROM
    products p
    join $table_tree c on p.category_id = c.id
WHERE
    c.parent = <SOME_ID>;

-- And why do we need depth column?
-- Let's stay with this categories example. When user is <i>in</i> some category, we would like to show him <i>path</i> to this category. So he could easily move to some parent category.
-- Now, it's pretty simple:

SELECT
    o.*
FROM
    $table o
    join $table_tree t on o.id = t.parent
WHERE
    t.id = 4
ORDER BY
    t.depth DESC;


-- Now. Another task for our tree. Let's say we want to generate urls for categories based on their codenames and position in tree.
-- For this I will need another field in $table table. This field will never be null, but I can't make it 'not null', as this would force application to put some data there.

ALTER TABLE $table add column tree_path TEXT;

CREATE OR REPLACE FUNCTION tree_path_${table}_bi()
RETURNS TRIGGER AS
$BODY$
DECLARE
BEGIN
    IF NEW.$parent IS NULL THEN
        NEW.tree_path := NEW.codename;
    ELSE
        SELECT tree_path || '/' || NEW.codename INTO NEW.tree_path FROM $table WHERE id = NEW.$parent;
    END IF;
    RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql';
CREATE TRIGGER tree_path_${table}_bi
BEFORE INSERT ON $table
FOR EACH ROW EXECUTE PROCEDURE tree_path_${table}_bi();

CREATE OR REPLACE FUNCTION tree_path_${table}_bu()
RETURNS TRIGGER AS
$BODY$
DECLARE
    replace_from TEXT := '^';
    replace_to   TEXT := '';
BEGIN
    IF NOT OLD.$parent IS distinct FROM NEW.$parent THEN
        RETURN NEW;
    END IF;
    IF OLD.$parent IS NOT NULL THEN
        SELECT '^' || tree_path || '/' INTO replace_from FROM $table WHERE id = OLD.$parent;
    END IF;
    IF NEW.$parent IS NOT NULL THEN
        SELECT tree_path || '/' INTO replace_to FROM $table WHERE id = NEW.$parent;
    END IF;
    NEW.tree_path := regexp_replace( NEW.tree_path, replace_from, replace_to );

    UPDATE $table SET tree_path = regexp_replace(tree_path, replace_from, replace_to ) WHERE id in (SELECT id FROM $table_tree WHERE parent = NEW.$pk AND depth > 0);

    RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql';
CREATE TRIGGER tree_path_${table}_bu
BEFORE UPDATE ON $table
FOR EACH ROW EXECUTE PROCEDURE tree_path_${table}_bu();


DELETE FROM $table;
INSERT INTO $table (id, codename, parent) VALUES (1, 'a', NULL), (2, 'b', NULL), (3, 'c', 1), (4, 'd', 2), (5, 'e', 3);

SELECT * FROM $table;
--  id | codename | printable_name | some_property | parent | tree_path
-- ----+----------+----------------+---------------+-----------+-----------
--   1 | a        | [null]         |        [null] |    [null] | a
--   2 | b        | [null]         |        [null] |    [null] | b
--   3 | c        | [null]         |        [null] |         1 | a/c
--   4 | d        | [null]         |        [null] |         2 | b/d
--   5 | e        | [null]         |        [null] |         3 | a/c/e

UPDATE $table SET parent = NULL WHERE id = 3;
UPDATE $table SET parent = 5 WHERE id = 2;
UPDATE $table SET parent = 1 WHERE id = 5;
SELECT * FROM $table;
--  id | codename | printable_name | some_property | parent | tree_path
-- ----+----------+----------------+---------------+-----------+-----------
--   1 | a        | [null]         |        [null] |    [null] | a
--   3 | c        | [null]         |        [null] |    [null] | c
--   2 | b        | [null]         |        [null] |         5 | a/e/b
--   4 | d        | [null]         |        [null] |         2 | a/e/b/d
--   5 | e        | [null]         |        [null] |         1 | a/e

SELECT id, tree_path, codename, parent FROM $table ORDER BY tree_path;
--  id | tree_path | codename | parent
-- ----+-----------+----------+-----------
--   1 | a         | a        |    [null]
--   5 | a/e       | e        |         1
--   2 | a/e/b     | b        |         5
--   4 | a/e/b/d   | d        |         2
--   3 | c         | c        |    [null]

INSERT INTO $table (id, codename, parent) VALUES (6, 'f', 3), (7, 'g', 3), (8, 'h', 6), (9, 'i', 7), (10, 'j', 8);

SELECT id, tree_path, codename, parent FROM $table ORDER BY tree_path;
--  id | tree_path | codename | parent
-- ----+-----------+----------+-----------
--   1 | a         | a        |    [null]
--   5 | a/e       | e        |         1
--   2 | a/e/b     | b        |         5
--   4 | a/e/b/d   | d        |         2
--   3 | c         | c        |    [null]
--   6 | c/f       | f        |         3
--   8 | c/f/h     | h        |         6
--  10 | c/f/h/j   | j        |         8
--   7 | c/g       | g        |         3
--   9 | c/g/i     | I        |         7

(10 rows)
-- Nice. But what if I'd like to have c/g <b>before</b> c/f ?
-- This will require input from user/application &#8211; saying what order should we have.
-- Now. We'd like to keep it as simple as possible, both in selects and in writes. To make it as simple we will make this field unique. We could make it unique in pair (parent, ordering), but then moving $table in tree would become difficult.
-- So, next modification of our table:

ALTER TABLE $table add ordering $pktype UNIQUE;

-- Now. This field really shouldn't be null &#8211; otherwise we could get 2 elements with null ordering, and we wouldn't be able to unambiguously order them.
-- So, let's:

UPDATE $table SET ordering = id;
ALTER TABLE $table ALTER column ordering SET NOT NULL;

Now. It's great that we have this field, but sorting with it will be at the very least tedious.
What I mean. While getting all children of given object, ordered, is simple:
select * from $table where parent = 3 order by ordering;
There is next-to-no way to get it properly sorted for whole tree at once.
To allow sorting of whole tree (or just some branch) we need to add new column, also trigger-filled, which will be used for sorting. This field will be globally unique (just like tree_path):

ALTER TABLE $table add column ordering_path TEXT UNIQUE;
-- This field will be filled by these triggers:
CREATE OR REPLACE FUNCTION tree_ordering_path_${table}_bi()
RETURNS TRIGGER AS
$BODY$
DECLARE
BEGIN
    IF NEW.$parent IS NULL THEN
        NEW.ordering_path := to_char(NEW.ordering, '000000000000');

    ELSE
        SELECT ordering_path || '/' || to_char(NEW.ordering, '000000000000') INTO NEW.ordering_path FROM $table WHERE id = NEW.$parent;
    END IF;
    RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql';
CREATE TRIGGER tree_ordering_path_${table}_bi
BEFORE INSERT ON $table
FOR EACH ROW EXECUTE PROCEDURE tree_ordering_path_${table}_bi();

CREATE OR REPLACE FUNCTION tree_ordering_path_${table}_bu()
RETURNS TRIGGER AS
$BODY$
DECLARE
BEGIN
    IF OLD.ordering = NEW.ordering THEN
        RETURN NEW;
    END IF;
    IF NEW.$parent IS NULL THEN
        NEW.ordering_path := to_char(NEW.ordering, '000000000000');

    ELSE
        SELECT ordering_path || '/' || to_char(NEW.ordering, '000000000000') INTO NEW.ordering_path FROM $table WHERE id = NEW.$parent;
    END IF;
    UPDATE $table SET ordering_path = regexp_replace(ordering_path, '^' || OLD.ordering_path, NEW.ordering_path )
        WHERE id in (SELECT id FROM $table_tree WHERE parent = NEW.$pk AND depth > 0);

    RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql';
CREATE TRIGGER tree_ordering_path_${table}_bu
BEFORE UPDATE ON $table
FOR EACH ROW EXECUTE PROCEDURE tree_ordering_path_${table}_bu();

DELETE FROM $table;
INSERT INTO $table (id, codename, parent, ordering) VALUES
    (1, 'a', NULL, 100),
    (2, 'b', NULL, 200),
    (3, 'c', 1, 300),
    (4, 'd', 2, 400),
    (5, 'e', 3, 500),
    (6, 'f', 3, 600),
    (7, 'g', 3, 700),
    (8, 'h', 6, 800),
    (9, 'i', 7, 900),
    (10, 'j', 8, 1000);

SELECT id, tree_path, ordering_path, ordering FROM $table ORDER BY ordering_path;
--  id | tree_path |                             ordering_path                             | ordering
-- ----+-----------+-----------------------------------------------------------------------+----------
--   1 | a         |  000000000100                                                         |      100
--   3 | a/c       |  000000000100/ 000000000300                                           |      300
--   5 | a/c/e     |  000000000100/ 000000000300/ 000000000500                             |      500
--   6 | a/c/f     |  000000000100/ 000000000300/ 000000000600                             |      600
--   8 | a/c/f/h   |  000000000100/ 000000000300/ 000000000600/ 000000000800               |      800
--  10 | a/c/f/h/j |  000000000100/ 000000000300/ 000000000600/ 000000000800/ 000000001000 |     1000
--   7 | a/c/g     |  000000000100/ 000000000300/ 000000000700                             |      700
--   9 | a/c/g/i   |  000000000100/ 000000000300/ 000000000700/ 000000000900               |      900
--   2 | b         |  000000000200                                                         |      200
--   4 | b/d       |  000000000200/ 000000000400                                           |      400

UPDATE $table SET ordering = 550 WHERE id = 7;
SELECT id, tree_path, ordering_path, ordering FROM $table ORDER BY ordering_path;
--  id | tree_path |                             ordering_path                             | ordering
-- ----+-----------+-----------------------------------------------------------------------+----------
--   1 | a         |  000000000100                                                         |      100
--   3 | a/c       |  000000000100/ 000000000300                                           |      300
--   5 | a/c/e     |  000000000100/ 000000000300/ 000000000500                             |      500
--   7 | a/c/g     |  000000000100/ 000000000300/ 000000000550                             |      550
--   9 | a/c/g/i   |  000000000100/ 000000000300/ 000000000550/ 000000000900               |      900
--   6 | a/c/f     |  000000000100/ 000000000300/ 000000000600                             |      600
--   8 | a/c/f/h   |  000000000100/ 000000000300/ 000000000600/ 000000000800               |      800
--  10 | a/c/f/h/j |  000000000100/ 000000000300/ 000000000600/ 000000000800/ 000000001000 |     1000
--   2 | b         |  000000000200                                                         |      200
--   4 | b/d       |  000000000200/ 000000000400                                           |      400

UPDATE $table SET ordering = 50 WHERE id = 2;
SELECT id, tree_path, ordering_path, ordering FROM $table ORDER BY ordering_path;
--  id | tree_path |                             ordering_path                             | ordering
-- ----+-----------+-----------------------------------------------------------------------+----------
--   2 | b         |  000000000050                                                         |       50
--   4 | b/d       |  000000000050/ 000000000400                                           |      400
--   1 | a         |  000000000100                                                         |      100
--   3 | a/c       |  000000000100/ 000000000300                                           |      300
--   5 | a/c/e     |  000000000100/ 000000000300/ 000000000500                             |      500
--   7 | a/c/g     |  000000000100/ 000000000300/ 000000000550                             |      550
--   9 | a/c/g/i   |  000000000100/ 000000000300/ 000000000550/ 000000000900               |      900
--   6 | a/c/f     |  000000000100/ 000000000300/ 000000000600                             |      600
--   8 | a/c/f/h   |  000000000100/ 000000000300/ 000000000600/ 000000000800               |      800
--  10 | a/c/f/h/j |  000000000100/ 000000000300/ 000000000600/ 000000000800/ 000000001000 |     1000

UPDATE $table SET ordering = 5 WHERE id = 1;
SELECT id, tree_path, ordering_path, ordering FROM $table ORDER BY ordering_path;
-- id | tree_path |                             ordering_path                             | ordering
------+-----------+-----------------------------------------------------------------------+----------
--  1 | a         |  000000000005                                                         |        5
--  3 | a/c       |  000000000005/ 000000000300                                           |      300
--  5 | a/c/e     |  000000000005/ 000000000300/ 000000000500                             |      500
--  7 | a/c/g     |  000000000005/ 000000000300/ 000000000550                             |      550
--  9 | a/c/g/i   |  000000000005/ 000000000300/ 000000000550/ 000000000900               |      900
--  6 | a/c/f     |  000000000005/ 000000000300/ 000000000600                             |      600
--  8 | a/c/f/h   |  000000000005/ 000000000300/ 000000000600/ 000000000800               |      800
-- 10 | a/c/f/h/j |  000000000005/ 000000000300/ 000000000600/ 000000000800/ 000000001000 |     1000
--  2 | b         |  000000000050                                                         |       50
--  4 | b/d       |  000000000050/ 000000000400                                           |      400

-- That basically concludes this post, as a last thing.
-- At one of companies I worked for we had this problem of getting 2nd level element in tree, that would be parent of given object.
-- For example, let's assume you have geographical tree. Top-level elements are countries, 2nd level are states, 3rd level are cities, 4th level are districts, and 5th level are streets.
-- Now, somebody tells you &#8211; I have this street with id 123, and I want to know in which state (or city or whatever) it is.
-- With standard adjacency-list way &#8211; oops, welcome loops. With nested sets &#8211; it's simpler, but you will be looking at some subselects.
-- And here? It's pretty simple:
-- select o.*
-- from $table o join $table_tree t on o.id = t.parent
-- where t.id = 123
-- order by t.depth desc
-- limit 1 offset 1;
-- If I would add 'tree_level' (also calculated on triggers of course) to $table_tree table, I could even do it without order/limit.
-- Now. At the end &#8211; you might be scared by amount of triggers in this code. Yes. There are some. You could easily get rid of some of them by simply bundling functionalities in single trigger.


