How to manage WordPress orphaned taxonomies

Use MySQL queries to debug orphaned taxonomies warnings


            

While developing my website I frequently got warnings. The one below

Warning: Attempt to read property “slug” on null in taxonomy.php on line

means WordPress tried to access the property slug of an object that doesn’t exist (is null) and, as mentioned elsewhere, this usually happens when a menu, a snippet or other piece of code references a category that no longer exist.

Here are a few safe SQL queries you can use to detect and optionally clean orphaned taxonomy entries that can cause the warning. Finally, I will give an example on how to create a stored procedure in mysql that will be much more confortable to use in code.

Detect orphaned taxonomy entries

Find taxonomy records without matching terms

SELECT tt.term_taxonomy_id, tt.term_id, tt.taxonomy
FROM wp_term_taxonomy AS tt
LEFT JOIN wp_terms AS t ON tt.term_id = t.term_id
WHERE t.term_id IS NULL;

This query lists all rows from wp_ala_term_taxonomy that point to a term_id missing from wp_ala_terms. These are broken taxonomy links and often cause slug on null warnings when WordPress tries to load them. This is the result:

Find relationships pointing to missing taxonomy IDs

SELECT tr.object_id, tr.term_taxonomy_id
FROM wp_term_relationships AS tr
LEFT JOIN wp_term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
WHERE tt.term_taxonomy_id IS NULL;

This query finds post–term relationships that reference missing taxonomy entries — another possible cause of the warning. In my case, I have none:

Create and use a stored procedure

Here’s how you can wrap that query into a MySQL stored procedure named SP_show_orphaned_categories. This will return all categories (by ID, name, slug) from your WordPress database that currently have no posts assigned:

DELIMITER $$
CREATE DEFINER=`root`@`localhost` PROCEDURE `SP_show_orphaned_categories`()
BEGIN
    SELECT 
        t.term_id AS category_id,
        t.name AS category_name,
        t.slug AS category_slug,
        COUNT(tr.object_id) AS post_count
    FROM wp_ala_terms AS t
    INNER JOIN wp_term_taxonomy AS tt ON t.term_id = tt.term_id
    LEFT JOIN wp_term_relationships AS tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
    WHERE tt.taxonomy = 'category'
    GROUP BY t.term_id, t.name, t.slug
    HAVING COUNT(tr.object_id) = 0;
END$$
DELIMTER ;

After creating it, you can call the procedure with:

CALL SP_show_orphaned_categories();

I like using stored procedures especially with PHP applications because the PHP code is cleaner and simpler:

$result = mysqli_query($connection, "CALL SP_show_orphaned_categories");

This Works both in MySQL and MariaDB (used by WordPress) and no input parameters needed since it simply lists all orphaned categories. If you ever need to update it, drop it first:

DROP PROCEDURE IF EXISTS SP_show_orphaned_categories;

DELIMITER

You noticed I used the DELIMITER command in the above stored procedure. Well, in MySQL, the DELIMITER command is not part of SQL itself — it’s an instruction to the MySQL client (like the command-line tool, phpMyAdmin, or MySQL Workbench). It tells the client what sequence of characters marks the end of a command you’re sending to the server. By default, MySQL uses a semicolon (;) as the command delimiter. But when you create a stored procedure, you usually need semicolons inside the procedure (to end each SQL statement):

CREATE PROCEDURE myproc()
BEGIN
    SELECT * FROM wp_posts;
    SELECT * FROM wp_users;
END;

If the MySQL client still thinks ; means “end of command,” it will stop after the first ; and try to execute an incomplete command — causing a syntax error. The solution is to temporarily change the delimiter to something else (commonly $$ or //):

DELIMITER $$

CREATE PROCEDURE myproc()
BEGIN
    SELECT * FROM wp_posts;
    SELECT * FROM wp_users;
END$$

DELIMITER ;

Here’s what happens:

  1. DELIMITER $$ tells the MySQL client: “From now on, a command ends when you see $$, not ;.”
  2. Then, the client sends the entire procedure (including internal ;s) as one block, up to the final $$.
  3. DELIMITER ; resets it back to normal afterward.

Take care, in MySQL Workbench, editor complains about this. Just ignore it. It works:

In a future post I will provide some queries to clean the database.

Have a great day !

73

Fan control with dtoverlay on Raspberry

Another day working a bit for my SA818 radio node project. Today I tried active cooling with a 5V fan driven from RPIs 5V rail and controlled from GPIOs with a MOSFET (2N7002).

Python: not enough values to unpack

While creating some code for the Arduino and C struct post, I ran into this issue “not enough values to unpack” and this has to do with Python’s struct format.

Comments are closed.