{"id":10277,"date":"2026-08-15T01:10:50","date_gmt":"2026-08-14T23:10:50","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/toolbox\/wp-meta-query-join-analyzer\/"},"modified":"2026-08-15T01:10:50","modified_gmt":"2026-08-14T23:10:50","slug":"wp-meta-query-join-analyzer","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wp-meta-query-join-analyzer\/","title":{"rendered":"WordPress meta_query JOIN Analyzer"},"content":{"rendered":"<div class=\"gtm-analyser-container\" style=\"background: var(--bg-panel, #1e1e24); padding: 25px; border-radius: 8px; border: 1px solid var(--border, #2a2a35);\">\n<p style=\"color: var(--text-secondary, #a0a0b0); margin-bottom: 20px;\">Every clause of a meta_query adds another JOIN on wp_postmeta, and postmeta has no index on the value. Three conditions are enough to turn a product listing into a query that reads hundreds of thousands of rows. This analyzer shows that and generates the table that replaces it.<\/p>\n<p>    <label for=\"mq2-abfrage\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">meta_query as JSON<\/label><br \/>\n    <textarea id=\"mq2-abfrage\" class=\"form-control\" style=\"width: 100%; height: 160px; padding: 12px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; font-family: monospace; font-size: 0.82rem; box-sizing: border-box;\">{<br \/>\n  &#8220;relation&#8221;: &#8220;AND&#8221;,<br \/>\n  &#8220;0&#8221;: { &#8220;key&#8221;: &#8220;_preis&#8221;, &#8220;value&#8221;: 100, &#8220;compare&#8221;: &#8220;>=&#8221;, &#8220;type&#8221;: &#8220;NUMERIC&#8221; },<br \/>\n  &#8220;1&#8221;: { &#8220;key&#8221;: &#8220;_lager&#8221;, &#8220;value&#8221;: &#8220;0&#8221;, &#8220;compare&#8221;: &#8220;!=&#8221; },<br \/>\n  &#8220;2&#8221;: { &#8220;key&#8221;: &#8220;_marke&#8221;, &#8220;value&#8221;: &#8220;Acme&#8221;, &#8220;compare&#8221;: &#8220;LIKE&#8221; }<br \/>\n}<\/textarea><\/p>\n<div style=\"display: flex; gap: 12px; flex-wrap: wrap; margin-top: 12px;\">\n<div style=\"flex: 1; min-width: 170px;\">\n            <label for=\"mq2-posts\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Posts in the table<\/label><br \/>\n            <input type=\"text\" id=\"mq2-posts\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; font-family: monospace; box-sizing: border-box;\" value=\"20000\">\n        <\/div>\n<div style=\"flex: 1; min-width: 170px;\">\n            <label for=\"mq2-meta\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Rows in postmeta<\/label><br \/>\n            <input type=\"text\" id=\"mq2-meta\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; font-family: monospace; box-sizing: border-box;\" value=\"600000\">\n        <\/div>\n<div style=\"flex: 1; min-width: 170px;\">\n            <label for=\"mq2-tabelle\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Name of the custom table<\/label><br \/>\n            <input type=\"text\" id=\"mq2-tabelle\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; font-family: monospace; box-sizing: border-box;\" value=\"wp_produkt_daten\">\n        <\/div>\n<\/p><\/div>\n<div style=\"display: flex; gap: 10px; flex-wrap: wrap; margin: 18px 0 0;\">\n        <button id=\"mq2-btn\" class=\"button\" style=\"background: var(--accent, #7ee787); color: #0b1114; border: none; padding: 12px 24px; border-radius: 6px; font-weight: 700; cursor: pointer;\">Analyse<\/button>\n    <\/div>\n<div id=\"mq2-ergebnis\" style=\"margin-top: 22px;\"><\/div>\n<\/div>\n<p><script>\n(function () {\n    'use strict';<\/p>\n<p>    const T = {\"einleitung\":\"Every clause of a meta_query adds another JOIN on wp_postmeta, and postmeta has no index on the value. Three conditions are enough to turn a product listing into a query that reads hundreds of thousands of rows. This analyzer shows that and generates the table that replaces it.\",\"label_abfrage\":\"meta_query as JSON\",\"label_posts\":\"Posts in the table\",\"label_meta\":\"Rows in postmeta\",\"label_tabelle\":\"Name of the custom table\",\"knopf\":\"Analyse\",\"fehler_json\":\"The query is not valid JSON: %s\",\"fehler_keine_klausel\":\"No clause with a key was found.\",\"fehler_posts\":\"The number of posts has to be a whole number above zero.\",\"fehler_meta\":\"The number of postmeta rows has to be a whole number above zero.\",\"fehler_tabelle\":\"The table name may only hold letters, digits and underscores.\",\"h_klauseln\":\"Clauses and their JOINs\",\"h_befunde\":\"Findings\",\"h_ddl\":\"Custom table instead\",\"h_abfrage\":\"The query on that table\",\"index_ja\":\"index\",\"index_nein\":\"full scan\",\"zusammenfassung\":\"%j JOINs on wp_postmeta, joined with %r. On average %m meta rows per post.\",\"ddl_erklaerung\":\"One row per post, one column per criterion, one index per column. What was a JOIN becomes a comparison.\",\"b_viele_joins\":\"%s clauses mean %s JOINs on the same table. MySQL has to build an intermediate result for each of them before the first row is returned.\",\"b_oder\":\"With OR the query planner cannot narrow the set down step by step; it collects every clause separately and merges afterwards.\",\"b_like\":\"%c on %k cannot use an index. Every row carrying that key is read and compared.\",\"b_not_exists\":\"NOT EXISTS on %k turns into a LEFT JOIN with a null check, which reads the whole key.\",\"b_numerisch\":\"%k is compared as a number, so MySQL casts meta_value for every row. A cast makes any index on the value useless.\",\"b_viele_meta\":\"At %s meta rows per post the table is the largest in the installation, and every additional JOIN is felt.\",\"b_index\":\"postmeta has an index on meta_key. About %s rows carry each key here, and that is what a single clause reads through.\",\"b_autoload\":\"Nothing of this touches the options table, but the same rule applies there: what is queried gets an index, or it gets its own table.\"};<\/p>\n<p>    \/\/ --- reine Rechenlogik ---<\/p>\n<p>    \/\/ Vergleiche, die den Index auf meta_key noch nutzen koennen, und solche,\n    \/\/ bei denen MySQL jede Zeile des Schluessels anfassen muss.\n    const OHNE_INDEX = ['LIKE', 'NOT LIKE', 'REGEXP', 'NOT REGEXP', 'RLIKE'];\n    const OHNE_WERT = ['EXISTS', 'NOT EXISTS'];<\/p>\n<p>    function zahl(wert) {\n        const roh = String(wert === undefined || wert === null ? '' : wert).trim().replace(\/[\\s.]\/g, '');\n        if (roh === '') { return NaN; }\n        if (!\/^[0-9]+$\/.test(roh)) { return NaN; }\n        return parseInt(roh, 10);\n    }<\/p>\n<p>    function klauselnSammeln(objekt, tiefe, raus) {\n        \/\/ Eine meta_query darf sich schachteln; jede Ebene zaehlt.\n        if (!objekt || typeof objekt !== 'object') { return; }\n        Object.keys(objekt).forEach(function (name) {\n            if (name === 'relation') { return; }\n            const eintrag = objekt[name];\n            if (!eintrag || typeof eintrag !== 'object') { return; }\n            if (eintrag.key !== undefined || eintrag.compare !== undefined) {\n                raus.push({\n                    key: String(eintrag.key === undefined ? '' : eintrag.key),\n                    compare: String(eintrag.compare === undefined ? '=' : eintrag.compare).toUpperCase(),\n                    type: String(eintrag.type === undefined ? 'CHAR' : eintrag.type).toUpperCase(),\n                    value: eintrag.value,\n                    tiefe: tiefe\n                });\n            } else {\n                klauselnSammeln(eintrag, tiefe + 1, raus);\n            }\n        });\n    }<\/p>\n<p>    function spaltenName(schluessel) {\n        return String(schluessel).replace(\/^_+\/, '').toLowerCase().replace(\/[^a-z0-9]+\/g, '_').replace(\/^_+|_+$\/g, '') || 'wert';\n    }<\/p>\n<p>    function spaltenTyp(klausel) {\n        if (klausel.type === 'NUMERIC') { return 'BIGINT'; }\n        if (klausel.type === 'DECIMAL') { return 'DECIMAL(12,2)'; }\n        if (klausel.type === 'DATE') { return 'DATE'; }\n        if (klausel.type === 'DATETIME') { return 'DATETIME'; }\n        if (typeof klausel.value === 'number') { return 'BIGINT'; }\n        return 'VARCHAR(191)';\n    }<\/p>\n<p>    function untersuchen(e) {\n        const ergebnis = { ok: false, fehler: '', klauseln: [], befunde: [], ddl: '', abfrage: '' };\n        const eingabe = e || {};<\/p>\n<p>        let objekt;\n        try {\n            objekt = JSON.parse(String(eingabe.abfrage === undefined ? '' : eingabe.abfrage));\n        } catch (fehler) {\n            ergebnis.fehler = T.fehler_json.replace('%s', fehler && fehler.message ? fehler.message : '');\n            return ergebnis;\n        }<\/p>\n<p>        const klauseln = [];\n        klauselnSammeln(objekt, 0, klauseln);\n        if (!klauseln.length) { ergebnis.fehler = T.fehler_keine_klausel; return ergebnis; }<\/p>\n<p>        const posts = zahl(eingabe.posts);\n        const meta = zahl(eingabe.meta);\n        if (!isFinite(posts) || posts <= 0) { ergebnis.fehler = T.fehler_posts; return ergebnis; }\n        if (!isFinite(meta) || meta <= 0) { ergebnis.fehler = T.fehler_meta; return ergebnis; }\n\n        const tabelle = String(eingabe.tabelle === undefined ? '' : eingabe.tabelle).trim() || 'wp_eigene_daten';\n        if (!\/^[A-Za-z0-9_]+$\/.test(tabelle)) { ergebnis.fehler = T.fehler_tabelle; return ergebnis; }\n\n        const relation = String((objekt &#038;&#038; objekt.relation) || 'AND').toUpperCase();\n        const schluessel = {};\n        klauseln.forEach(function (k) { if (k.key) { schluessel[k.key] = true; } });\n        const verschiedene = Object.keys(schluessel).length;\n        const zeilenJeSchluessel = Math.round(meta \/ Math.max(1, verschiedene));\n\n        ergebnis.klauseln = klauseln.map(function (k) {\n            return {\n                key: k.key,\n                compare: k.compare,\n                type: k.type,\n                indexfreundlich: OHNE_INDEX.indexOf(k.compare) === -1,\n                braucht_wert: OHNE_WERT.indexOf(k.compare) === -1,\n                tiefe: k.tiefe\n            };\n        });\n        ergebnis.verbindungen = klauseln.length;\n        ergebnis.relation = relation;\n        ergebnis.verschiedene = verschiedene;\n        ergebnis.zeilenJeSchluessel = zeilenJeSchluessel;\n        ergebnis.posts = posts;\n        ergebnis.meta = meta;\n        ergebnis.metaJePost = Math.round(meta \/ posts * 10) \/ 10;\n\n        \/\/ --- Befunde ---\n        if (klauseln.length >= 3) {\n            ergebnis.befunde.push({ schwer: true, text: T.b_viele_joins.replace('%s', String(klauseln.length)) });\n        }\n        if (relation === 'OR' && klauseln.length > 1) {\n            ergebnis.befunde.push({ schwer: true, text: T.b_oder });\n        }\n        klauseln.forEach(function (k) {\n            if (OHNE_INDEX.indexOf(k.compare) !== -1) {\n                ergebnis.befunde.push({ schwer: true, text: T.b_like.replace('%k', k.key).replace('%c', k.compare) });\n            }\n            if (k.compare === 'NOT EXISTS') {\n                ergebnis.befunde.push({ schwer: true, text: T.b_not_exists.replace('%k', k.key) });\n            }\n            if (k.type === 'NUMERIC' || k.type === 'DECIMAL') {\n                ergebnis.befunde.push({ schwer: true, text: T.b_numerisch.replace('%k', k.key) });\n            }\n            if (!k.key) {\n                ergebnis.befunde.push({ schwer: true, text: T.b_ohne_key });\n            }\n        });\n        if (ergebnis.metaJePost > 20) {\n            ergebnis.befunde.push({ schwer: true, text: T.b_viele_meta.replace('%s', String(ergebnis.metaJePost)) });\n        }\n        ergebnis.befunde.push({ schwer: false, text: T.b_index.replace('%s', String(zeilenJeSchluessel)) });\n        ergebnis.befunde.push({ schwer: false, text: T.b_autoload });<\/p>\n<p>        \/\/ --- Eigene Tabelle ---\n        const spalten = [];\n        const gesehen = {};\n        klauseln.forEach(function (k) {\n            if (!k.key) { return; }\n            const name = spaltenName(k.key);\n            if (gesehen[name]) { return; }\n            gesehen[name] = true;\n            spalten.push({ name: name, typ: spaltenTyp(k), key: k.key });\n        });<\/p>\n<p>        const zeilen = [];\n        zeilen.push('CREATE TABLE ' + tabelle + ' (');\n        zeilen.push('  post_id BIGINT UNSIGNED NOT NULL,');\n        spalten.forEach(function (s) {\n            zeilen.push('  ' + s.name + ' ' + s.typ + ' DEFAULT NULL,   -- ' + s.key);\n        });\n        zeilen.push('  PRIMARY KEY (post_id)' + (spalten.length ? ',' : ''));\n        spalten.forEach(function (s, i) {\n            zeilen.push('  KEY idx_' + s.name + ' (' + s.name + ')' + (i < spalten.length - 1 ? ',' : ''));\n        });\n        zeilen.push(') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;');\n        ergebnis.ddl = zeilen.join('\\n');\n\n        const bedingungen = ergebnis.klauseln.filter(function (k) { return k.key; }).map(function (k) {\n            const name = spaltenName(k.key);\n            if (k.compare === 'EXISTS') { return 'd.' + name + ' IS NOT NULL'; }\n            if (k.compare === 'NOT EXISTS') { return 'd.' + name + ' IS NULL'; }\n            if (k.compare === 'LIKE') { return 'd.' + name + \" LIKE '%wert%'\"; }\n            return 'd.' + name + ' ' + k.compare + ' ?';\n        });\n        ergebnis.abfrage = 'SELECT p.ID, p.post_title\\n'\n            + 'FROM wp_posts p\\n'\n            + 'INNER JOIN ' + tabelle + ' d ON d.post_id = p.ID\\n'\n            + \"WHERE p.post_type = 'product' AND p.post_status = 'publish'\\n\"\n            + (bedingungen.length ? '  AND ' + bedingungen.join('\\n  ' + (relation === 'OR' ? 'OR' : 'AND') + ' ') + '\\n' : '')\n            + 'ORDER BY p.post_date DESC\\n'\n            + 'LIMIT 20;';\n\n        ergebnis.ok = true;\n        return ergebnis;\n    }\n\n    if (typeof window !== 'undefined') {\n        window.LW_TEST = window.LW_TEST || {};\n        window.LW_TEST.MQ2 = { untersuchen: untersuchen, spaltenName: spaltenName, zahl: zahl };\n    }\n\n    \/\/ --- Anbindung an das Dokument ---\n\n    const btn = document.getElementById('mq2-btn');\n    const out = document.getElementById('mq2-ergebnis');\n    if (!btn || !out) { return; }\n\n    const KASTEN = 'background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-radius: 6px; padding: 14px; margin-bottom: 18px;';\n    const UEBERSCHRIFT = 'color: var(--text-primary, #e8e8ee); font-weight: 700; margin-bottom: 8px;';\n\n    function el(tag, css, inhalt) {\n        const knoten = document.createElement(tag);\n        if (css) { knoten.setAttribute('style', css); }\n        if (inhalt !== undefined &#038;&#038; inhalt !== null) { knoten.textContent = inhalt; }\n        return knoten;\n    }\n\n    function wert(id) {\n        const knoten = document.getElementById(id);\n        return knoten ? knoten.value : '';\n    }\n\n    btn.addEventListener('click', function () {\n        const erg = untersuchen({\n            abfrage: wert('mq2-abfrage'),\n            posts: wert('mq2-posts'),\n            meta: wert('mq2-meta'),\n            tabelle: wert('mq2-tabelle')\n        });\n\n        out.textContent = '';\n        if (!erg.ok) {\n            out.appendChild(el('div', 'background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-left: 3px solid #ffb86c; color: var(--text-secondary, #a0a0b0); padding: 14px; border-radius: 6px;', erg.fehler));\n            return;\n        }\n\n        out.appendChild(el('div', UEBERSCHRIFT, T.h_klauseln));\n        const kasten = el('div', KASTEN);\n        erg.klauseln.forEach(function (k, i) {\n            const z = el('div', 'display: flex; gap: 12px; align-items: center; padding: 6px 0; border-bottom: 1px solid var(--border, #2a2a35);');\n            z.appendChild(el('span', 'width: 60px; color: var(--text-secondary, #a0a0b0); font-family: monospace; font-size: 0.8rem;', 'mt' + (i + 1)));\n            z.appendChild(el('span', 'flex: 1; color: var(--text-primary, #e8e8ee); font-family: monospace; font-size: 0.85rem;', k.key || '(ohne)'));\n            z.appendChild(el('span', 'width: 90px; color: var(--text-secondary, #a0a0b0); font-family: monospace; font-size: 0.8rem;', k.compare));\n            z.appendChild(el('span', 'width: 80px; color: ' + (k.indexfreundlich ? 'var(--accent, #7ee787)' : '#ffb86c') + '; font-family: monospace; font-size: 0.78rem;', k.indexfreundlich ? T.index_ja : T.index_nein));\n            kasten.appendChild(z);\n        });\n        kasten.appendChild(el('div', 'color: var(--text-secondary, #a0a0b0); font-size: 0.85rem; padding-top: 10px;',\n            T.zusammenfassung.replace('%j', String(erg.verbindungen)).replace('%r', erg.relation).replace('%m', String(erg.metaJePost))));\n        out.appendChild(kasten);\n\n        out.appendChild(el('div', UEBERSCHRIFT, T.h_befunde));\n        const befunde = el('div', KASTEN);\n        erg.befunde.forEach(function (b) {\n            const z = el('div', 'display: flex; gap: 10px; padding: 5px 0;');\n            z.appendChild(el('span', 'color: ' + (b.schwer ? '#ffb86c' : 'var(--text-secondary, #a0a0b0)') + '; font-family: monospace;', b.schwer ? '!' : '-'));\n            z.appendChild(el('span', 'color: var(--text-secondary, #a0a0b0); font-size: 0.9rem; line-height: 1.5;', b.text));\n            befunde.appendChild(z);\n        });\n        out.appendChild(befunde);\n\n        out.appendChild(el('div', UEBERSCHRIFT, T.h_ddl));\n        out.appendChild(el('div', 'color: var(--text-secondary, #a0a0b0); font-size: 0.85rem; margin-bottom: 8px;', T.ddl_erklaerung));\n        out.appendChild(el('pre', 'background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); padding: 16px; border-radius: 6px; overflow-x: auto; font-family: monospace; font-size: 0.8rem; line-height: 1.5; white-space: pre; margin-bottom: 18px;', erg.ddl));\n\n        out.appendChild(el('div', UEBERSCHRIFT, T.h_abfrage));\n        out.appendChild(el('pre', 'background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); padding: 16px; border-radius: 6px; overflow-x: auto; font-family: monospace; font-size: 0.8rem; line-height: 1.5; white-space: pre; margin-bottom: 18px;', erg.abfrage));\n    });\n})();\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Counts the JOINs a meta_query produces, marks the clauses that cannot use an index, and generates a custom table with the matching query as the way out.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":38,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-tool-base.php","meta":{"footnotes":""},"tags":[91104,91315,91096],"class_list":["post-10277","page","type-page","status-publish","hentry","tag-mysql","tag-web-performance","tag-wordpress"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>WordPress meta_query JOIN Analyzer - Lukas Wojcik - Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wp-meta-query-join-analyzer\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"WordPress meta_query JOIN Analyzer - Lukas Wojcik - Blog\" \/>\n<meta property=\"og:description\" content=\"Counts the JOINs a meta_query produces, marks the clauses that cannot use an index, and generates a custom table with the matching query as the way out.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wp-meta-query-join-analyzer\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/wp-meta-query-join-analyzer\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/wp-meta-query-join-analyzer\\\/\",\"name\":\"WordPress meta_query JOIN Analyzer - Lukas Wojcik - Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-08-14T23:10:50+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/wp-meta-query-join-analyzer\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/wp-meta-query-join-analyzer\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/wp-meta-query-join-analyzer\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Toolbox\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/toolbox\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"WordPress meta_query JOIN Analyzer\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"luky\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"luky\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"WordPress meta_query JOIN Analyzer - Lukas Wojcik - Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wp-meta-query-join-analyzer\/","og_locale":"en_US","og_type":"article","og_title":"WordPress meta_query JOIN Analyzer - Lukas Wojcik - Blog","og_description":"Counts the JOINs a meta_query produces, marks the clauses that cannot use an index, and generates a custom table with the matching query as the way out.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wp-meta-query-join-analyzer\/","og_site_name":"Lukas Wojcik - Blog","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wp-meta-query-join-analyzer\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wp-meta-query-join-analyzer\/","name":"WordPress meta_query JOIN Analyzer - Lukas Wojcik - Blog","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-08-14T23:10:50+00:00","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wp-meta-query-join-analyzer\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wp-meta-query-join-analyzer\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/wp-meta-query-join-analyzer\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Toolbox","item":"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/"},{"@type":"ListItem","position":3,"name":"WordPress meta_query JOIN Analyzer"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"luky","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"luky"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/10277","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=10277"}],"version-history":[{"count":0,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/10277\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/38"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=10277"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=10277"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}