| |

Slug Uniqueness in WordPress: Problem Solving

The Slug Uniqueness

  • In WordPressโ€™s data structure, a single slug must correspond to only one term_id.

Grouped-Slug Count in โ€œwp_termsโ€ Table

SELECT slug, COUNT(*)
FROM wp_terms
GROUP BY slug
HAVING COUNT(*) > 1;

If you group by slug and the count is 2 or more, it means different term_id values exist that share the same slug.


Slug โ€œterm_idโ€œ in โ€œwp_termsโ€œ

SELECT term_id, name, slug
FROM wp_terms
WHERE name IN ('interview', 'liberal-arts', 'wordpress', 'yield');

The โ€˜interviewโ€™ slug is duplicated under term_id 257 and 262, โ€˜wordpressโ€™ is duplicated under 59 and 267, and โ€˜yieldโ€™ is duplicated under 258 and 259.
The reason the โ€˜liberal-artsโ€™ slug does not appear is that the search was performed only on wp_terms, so the search needs to be expanded to additional tables.


Duplicated Slug Locations

-- 1. ๋ถ„๋ฅ˜ ํ•ญ๋ชฉ(Terms) ์กฐํšŒ
SELECT
  t.slug,
  t.term_id AS id_or_postid,
  tt.taxonomy AS type,
  tt.count AS count_or_status
FROM wp_terms t
INNER JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
WHERE t.slug IN ('interview', 'liberal-arts', 'wordpress', 'yield')

UNION ALL

-- 2. ํŽ˜์ด์ง€ ๋ฐ ํฌ์ŠคํŠธ(Posts) ์กฐํšŒ
SELECT
  post_name AS slug,
  ID AS id_or_postid,
  post_type AS type,
  post_status AS count_or_status
FROM wp_posts
WHERE post_name IN ('interview', 'liberal-arts', 'wordpress', 'yield')
AND post_status NOT IN ('inherit', 'trash') -- ์ž๋™ ์ €์žฅ ๋ฐ ํœด์ง€ํ†ต ์ œ์™ธ

ORDER BY slug ASC;

+ Page slug collisions do not occur because the permalink structure (/%postname%-%post_id%/) includes the page ID, preventing duplicates.


Page and Post Titles

SELECT
  t.term_id,
  t.slug,
  tt.term_taxonomy_id,
  tt.taxonomy,
  p.ID AS post_id,
  p.post_title,
  p.post_type,
  p.post_status
FROM wp_terms t
INNER JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
INNER JOIN wp_term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
INNER JOIN wp_posts p ON tr.object_id = p.ID
WHERE t.term_id IN (262, 170, 8, 267, 59, 259, 257)
ORDER BY t.term_id, p.post_date DESC;
  • WordPress (ID 59): This term is correctly linked as a post_tag to 12 posts (IDs 1342, 1334, etc.). It is the most actively used data.
  • Liberal Arts (ID 170): This term is linked as a topic-tag to one post (ID 2199).
  • Error Data (ID 259, 262, 267): These three IDs have invalid taxonomy information (their term_taxonomy_id is 0). If you only modify the terms or taxonomy without deleting the relationships linked to ID 0, you’ll encounter a ‘zombie’ issue. If a new category is accidentally assigned the ID 0, it will automatically reconnect to your old posts (like Post 229), causing ghost relationships to reappear

Problem Solving

You must first break the abnormal connections in wp_term_relationships, and then delete the ghost entries.

Step 1: Delete Abnormal Post Connections
  • Remove the incorrect category relationships for yield, interview, and WordPress that are erroneously linked to Post ID 229.
  • Specifically, target the relationships associated with IDs 259, 262, and 267.
DELETE FROM wp_term_relationships
WHERE object_id = 229 AND term_taxonomy_id IN (
   SELECT term_taxonomy_id FROM wp_term_taxonomy WHERE term_id IN (259, 262, 267)
);
Step 2: Delete Ghost Categories and Tags
  • Delete the IDs where the data is corrupted or where the post_count appears to be 0 in the database.
  • Target IDs include 8, 262, and 267.
  • Note: ID 259 (yield) is to be used as a category.
-- 1. Taxonomy ๊ด€๊ณ„ ์‚ญ์ œ
DELETE FROM wp_term_taxonomy WHERE term_id IN (8, 262, 267);

-- 2. Term ์ •๋ณด ์‚ญ์ œ
DELETE FROM wp_terms WHERE term_id IN (8, 262, 267);
Step 3: Delete Isolated Ghost Entries with Missing Taxonomy
  • Clean up “residue” data like ID 257, which exists in the wp_terms table but has no corresponding entry in wp_term_taxonomy.
  • This step ensures the removal of orphaned terms that no longer have a defined purpose (category, tag, etc.) in the system
3.1 NOT IN Method

This query identifies all “ghost” categories that exist in the wp_terms table but are not appearing on the dashboard due to broken links with wp_term_taxonomy.

DELETE FROM wp_terms
WHERE term_id NOT IN (SELECT term_id FROM wp_term_taxonomy);

When processing a NOT IN clause, the database engine may be inefficient because it must internally scan every value in the subquery first. Furthermore, if the term_id column in the wp_term_taxonomy table contains even a single NULL value, the NOT IN condition may return an empty result, failing to delete or identify anything at all due to SQL’s three-valued logic.

3.2 LEFT JOIN โ€ฆ IS NULL Method

To avoid the issues mentioned above, using a LEFT JOIN is the industry standard for performance and reliability.

DELETE FROM wp_terms
WHERE term_id IN (
    SELECT t.term_id FROM wp_terms t LEFT JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id 
    WHERE tt.term_id IS NULL
);

You can check the ghost category IDs in advance using the query below.

SELECT t.term_id, t.name, t.slug
FROM wp_terms t
LEFT JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
WHERE tt.term_id IS NULL;
Step 4: Permalink Update

Go to WordPress Admin > Settings > Permalinks and click [Save Changes] at the bottom (click the button without making any modifications). This will rebuild the slug rules.


Check

SELECT 'wp_terms' AS table_name, term_id, COUNT(*) AS duplicate_count FROM wp_terms 
    GROUP BY term_id HAVING COUNT(*) > 1

UNION ALL

SELECT 'wp_term_taxonomy' AS table_name, term_id, COUNT(*) AS duplicate_count FROM wp_term_taxonomy 
    GROUP BY term_id HAVING COUNT(*) > 1;

Hedge

๐Ÿ“Integrated Integrity Check
  • Database integrity should be checked periodically.
/* 1. wp_terms ํ…Œ์ด๋ธ” ๋‚ด ์Šฌ๋Ÿฌ๊ทธ ์ค‘๋ณต ํ™•์ธ */ 
SELECT 'Duplicate Slug' AS Issue, slug, COUNT(*) AS Count
FROM wp_terms
GROUP BY slug
HAVING COUNT(*) > 1

UNION ALL

/* 2. wp_terms ํ…Œ์ด๋ธ” ๋‚ด term_id ์ค‘๋ณต ํ™•์ธ */
SELECT 'Duplicate Term ID (Terms)' AS Issue, term_id, COUNT(*) AS Count
FROM wp_terms
GROUP BY term_id
HAVING COUNT(*) > 1

UNION ALL

/* 3. wp_term_taxonomy ํ…Œ์ด๋ธ” ๋‚ด term_id ์ค‘๋ณต ํ™•์ธ */
SELECT 'Duplicate Term ID (Taxonomy)' AS Issue, term_id, COUNT(*) AS Count
FROM wp_term_taxonomy
GROUP BY term_id
HAVING COUNT(*) > 1

UNION ALL

/* 4. ๊ด€๊ณ„๊ฐ€ ๋Š๊ธด ์œ ๋ น ๋ฐ์ดํ„ฐ ํ™•์ธ (wp_term_taxonomy์— ์—†๋Š” terms) */
SELECT 'Orphaned Term (No Taxonomy)' AS Issue, term_id, slug AS Count
FROM wp_terms
WHERE term_id NOT IN (SELECT term_id FROM wp_term_taxonomy);
๐Ÿš€Unique Index
  • If the integrity check returns zero results (Empty set), you can safely enforce permanent protection against slug duplication.
  • Adding a unique index to wp_terms prevents slug duplication at the database level and improves searching performance.
  • A successful execution of this command indicates that the current table contains no duplicate slugs whatsoever.
/* ์Šฌ๋Ÿฌ๊ทธ ์ค‘๋ณต ๋ฐฉ์ง€ ์ธ๋ฑ์Šค ์ถ”๊ฐ€ */
ALTER TABLE wp_terms ADD UNIQUE INDEX idx_unique_slug (slug);

/* term_id ์ค‘๋ณต ๋ฐฉ์ง€ ์ธ๋ฑ์Šค ์ถ”๊ฐ€ (์ด๋ฏธ ๊ธฐ๋ณธํ‚ค๊ฐ€ ์žˆ๋‹ค๋ฉด ์ƒ๋žต ๊ฐ€๋Šฅ) */
/* ๋ณดํ†ต term_id๋Š” PRIMARY KEY๋กœ ์„ค์ •๋˜์–ด ์žˆ์œผ๋‚˜, ๋งŒ์•ฝ ์—†๋‹ค๋ฉด ์ถ”๊ฐ€ ๊ถŒ์žฅ */

Precheck SQL

This is an integrated pre-check code to simultaneously verify if the slug ‘yield’ is already in use within both the Categories (Terms) table and the Posts table before adding it as a new category.

/* 1. ๋ณ€์ˆ˜ ์„ ์–ธ */
SET @target_slug = 'yield';

/* 2. ํ†ตํ•ฉ ์กฐํšŒ ์‹คํ–‰ (COLLATE ์ถ”๊ฐ€) */
SELECT 'Term (Category/Tag)' AS Source, term_id AS ID, name AS Title, slug AS Slug_Name
FROM wp_terms
WHERE slug COLLATE utf8mb4_unicode_ci = @target_slug

UNION ALL 

SELECT 'Post (Page/Post/Menu)' AS Source, ID, post_title AS Title, post_name AS Slug_Name
FROM wp_posts
WHERE post_name COLLATE utf8mb4_unicode_ci = @target_slug;

Appendix

DESC wp_terms;

PRI: Primary Key, UNI: Unique Key, MUL: Multiple Key

DESC wp_term_taxonomy;

Our Score
Click to rate this post!
[Total: 0 Average: 0]
Visited 29 times, 1 visit(s) today

Leave a Comment

Your email address will not be published. Required fields are marked *