{"id":10392,"date":"2026-09-12T07:20:00","date_gmt":"2026-09-12T05:20:00","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/?p=10392"},"modified":"2026-09-09T17:07:11","modified_gmt":"2026-09-09T15:07:11","slug":"meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs","status":"publish","type":"post","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/","title":{"rendered":"meta_query in WP_Query: How Each Clause Becomes a Join and What It Costs"},"content":{"rendered":"<p>A page that filters posts by three custom fields is one array in PHP and four joins in SQL. The array reads like a filter; the SQL reads like a table being visited once for every condition, because that is exactly what it is.<\/p>\n<p>The interesting part is not that it is slow &#8211; most sites never grow large enough to care. It is that the cost is invisible in the code that causes it, and that it grows with two things at once: the number of clauses and the size of a table nobody looks at.<\/p>\n<figure class=\"lw-diagram\">\n<img decoding=\"async\" src=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/diagrams\/verbindungstreppe-en.png\" width=\"1120\" height=\"580\" loading=\"lazy\" alt=\"A staircase of query stages: the base query reads 4800 rows, each of three meta_query joins adds 192000 rows, and the same filter as a taxonomy reads 5120\"><figcaption>The same result set, four ways of arriving at it. The bar for the base query is not missing; it is three pixels wide.<\/figcaption><\/figure>\n<h2>What WP_Query Builds Out of the Array<\/h2>\n<p>Each clause in <code>meta_query<\/code> gets its own alias on <code>wp_postmeta<\/code> and its own join condition. Two clauses, two aliases; five clauses, five. The table is joined to itself as many times as there are conditions, and every one of those joins carries the full weight of the table behind it.<\/p>\n<pre class=\"wp-block-kevinbatdorf-code-block-pro\"><code>SELECT wp_posts.ID FROM wp_posts\n  INNER JOIN wp_postmeta pm1 ON wp_posts.ID = pm1.post_id\n  INNER JOIN wp_postmeta pm2 ON wp_posts.ID = pm2.post_id\n  INNER JOIN wp_postmeta pm3 ON wp_posts.ID = pm3.post_id\nWHERE post_type = 'post' AND post_status = 'publish'\n  AND ( pm1.meta_key = '_region'  AND pm1.meta_value = 'north' )\n  AND ( pm2.meta_key = '_level'   AND pm2.meta_value = 'b' )\n  AND ( pm3.meta_key = '_active'  AND pm3.meta_value = '1' )\nORDER BY wp_posts.post_date DESC<\/code><\/pre>\n<p>Nothing here is wrong. It is the literal translation of what was asked for, and the database will answer it correctly &#8211; after reading, on a modest site, something in the order of half a million rows to return forty.<\/p>\n<h2>The Index That Does Not Exist<\/h2>\n<p><code>wp_postmeta<\/code> carries three indexes: the primary key on <code>meta_id<\/code>, one on <code>post_id<\/code> and one on the first 191 characters of <code>meta_key<\/code>. There is no index on <code>meta_value<\/code>, and there cannot usefully be one: the column is <code>LONGTEXT<\/code>, and an index on a text column of unbounded length is either a prefix or nothing.<\/p>\n<p>So every clause is answered in two movements. The join finds the rows for a post by <code>post_id<\/code> &#8211; fast, indexed, exactly what an index is for. Then the value is compared inside each of those rows, one at a time, with no help from anything. The work is proportional to how many meta rows a post has, which on a site with a page builder and a few plugins is rarely under thirty.<\/p>\n<h2>The Clauses That Cost More Than the Others<\/h2>\n<table style=\"width:100%;border-collapse:collapse\">\n<thead>\n<tr>\n<th style=\"vertical-align:top\">Clause<\/th>\n<th>What the database has to do<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"white-space:nowrap;vertical-align:top\"><code>'compare' =&gt; '='<\/code><\/td>\n<td>the baseline: one join, one string comparison per row<\/td>\n<\/tr>\n<tr>\n<td style=\"white-space:nowrap;vertical-align:top\"><code>'compare' =&gt; 'LIKE'<\/code><\/td>\n<td>the same, with a pattern match; a leading wildcard rules out every shortcut<\/td>\n<\/tr>\n<tr>\n<td style=\"white-space:nowrap;vertical-align:top\"><code>'type' =&gt; 'NUMERIC'<\/code><\/td>\n<td>wraps the column in <code>CAST()<\/code>, which removes any remaining chance of index use<\/td>\n<\/tr>\n<tr>\n<td style=\"white-space:nowrap;vertical-align:top\"><code>'compare' =&gt; 'NOT EXISTS'<\/code><\/td>\n<td>a LEFT JOIN plus an IS NULL test &#8211; every non-matching row has to be produced first<\/td>\n<\/tr>\n<tr>\n<td style=\"white-space:nowrap;vertical-align:top\"><code>'relation' =&gt; 'OR'<\/code><\/td>\n<td>the clauses can no longer narrow each other; the intermediate set is the union<\/td>\n<\/tr>\n<tr>\n<td style=\"white-space:nowrap;vertical-align:top\"><code>'orderby' =&gt; 'meta_value'<\/code><\/td>\n<td>a temporary table and a filesort over a text column<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The last row is the one that turns a slow page into a timeout. Sorting by a meta value cannot be done from an index, so the database materialises the whole intermediate result and sorts it &#8211; and because <code>meta_value<\/code> is <code>LONGTEXT<\/code>, that sort happens on disk rather than in memory as soon as the set is large.<\/p>\n<p><code>NOT EXISTS<\/code> deserves its own warning. It reads as the cheap opposite of <code>EXISTS<\/code> and is the opposite of cheap: proving that something is absent means visiting every candidate, which is the one thing an index cannot shorten.<\/p>\n<h2>Where the Filter Belongs Instead<\/h2>\n<p>A value that is filtered on is not a property of a post in the way a subtitle is. It is a category, and WordPress already has a structure for categories with the right indexes: <code>wp_term_relationships<\/code> is indexed on both of its columns, so a term filter is one join answered entirely from indexes, with no text comparison anywhere.<\/p>\n<p>The rule that follows is short. Anything used for filtering or sorting belongs in a taxonomy &#8211; or, when the values are truly numeric and continuous, in a column of its own table. Anything only ever read after the post has been found can stay in post meta, where it costs a single lookup by <code>post_id<\/code> and nothing else.<\/p>\n<h2>Measuring Instead of Guessing<\/h2>\n<p>The query built by WP_Query is available before it runs: <code>$query-&gt;request<\/code> holds the finished SQL, and running it through <code>EXPLAIN<\/code> answers the only two questions that matter. The <code>rows<\/code> column is the estimate of how many rows each step will read; the <code>Extra<\/code> column names the expensive shapes outright, and <code>Using temporary; Using filesort<\/code> together are the signature of a sort that will not scale.<\/p>\n<pre class=\"wp-block-kevinbatdorf-code-block-pro\"><code>wp db query \"EXPLAIN SELECT wp_posts.ID FROM wp_posts INNER JOIN ...\"\n\nid  select_type  table     type    key       rows    Extra\n1   SIMPLE       wp_posts  ref     type_st.  4800    Using temporary; Using filesort\n1   SIMPLE       pm1       ref     post_id     40    Using where\n1   SIMPLE       pm2       ref     post_id     40    Using where\n1   SIMPLE       pm3       ref     post_id     40    Using where<\/code><\/pre>\n<p>Three rows saying <code>Using where<\/code> after a <code>post_id<\/code> lookup is the picture described above: the index found the rows, and the condition was applied afterwards. The number in <code>rows<\/code> multiplied across the joins is the figure that grows with the site, and it is worth knowing on the day the query is written rather than on the day the page stops loading.<\/p>\n<div class=\"lw-quellen\">\n<h2>Sources<\/h2>\n<ul>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/classes\/wp_query\/\" target=\"_blank\" rel=\"noopener noreferrer\">WP_Query reference<\/a><\/li>\n<\/ul>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Every meta_query clause becomes another INNER JOIN on wp_postmeta, and wp_postmeta has no index on the column being compared. Three clauses and a sort turn a trivial query into half a million rows.<\/p>\n","protected":false},"author":1,"featured_media":15167,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9],"tags":[91104,91315],"class_list":["post-10392","post","type-post","status-publish","format-standard","hentry","category-wordpress-plugins-tricks","tag-mysql","tag-web-performance"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>meta_query in WP_Query: How Each Clause Becomes a Join and What It Costs - Lukas Wojcik - Blog<\/title>\n<meta name=\"description\" content=\"How WP_Query turns meta_query into joins, why meta_value cannot be indexed usefully, and which filters belong in a taxonomy instead.\" \/>\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\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"meta_query in WP_Query: How Each Clause Becomes a Join and What It Costs - Lukas Wojcik - Blog\" \/>\n<meta property=\"og:description\" content=\"How WP_Query turns meta_query into joins, why meta_value cannot be indexed usefully, and which filters belong in a taxonomy instead.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-09-12T05:20:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-10392-one-clause-one-join-what-meta-query--n.png\" \/>\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\/png\" \/>\n<meta name=\"author\" content=\"luky\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"luky\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\\\/\"},\"author\":{\"name\":\"luky\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"headline\":\"meta_query in WP_Query: How Each Clause Becomes a Join and What It Costs\",\"datePublished\":\"2026-09-12T05:20:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\\\/\"},\"wordCount\":783,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/hero-10392-one-clause-one-join-what-meta-query--n.png\",\"keywords\":[\"MySQL\",\"Web Performance\"],\"articleSection\":[\"WordPress Plugins &amp; Tricks\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\\\/\",\"name\":\"meta_query in WP_Query: How Each Clause Becomes a Join and What It Costs - Lukas Wojcik - Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/hero-10392-one-clause-one-join-what-meta-query--n.png\",\"datePublished\":\"2026-09-12T05:20:00+00:00\",\"description\":\"How WP_Query turns meta_query into joins, why meta_value cannot be indexed usefully, and which filters belong in a taxonomy instead.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/hero-10392-one-clause-one-join-what-meta-query--n.png\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/hero-10392-one-clause-one-join-what-meta-query--n.png\",\"width\":1200,\"height\":630,\"caption\":\"meta_query in WP_Query: How Each Clause Becomes a Join and What It Costs\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/wordpress-plugins-tricks\\\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"meta_query in WP_Query: How Each Clause Becomes a Join and What It Costs\"}]},{\"@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\"],\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/author\\\/luky\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"meta_query in WP_Query: How Each Clause Becomes a Join and What It Costs - Lukas Wojcik - Blog","description":"How WP_Query turns meta_query into joins, why meta_value cannot be indexed usefully, and which filters belong in a taxonomy instead.","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\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/","og_locale":"en_US","og_type":"article","og_title":"meta_query in WP_Query: How Each Clause Becomes a Join and What It Costs - Lukas Wojcik - Blog","og_description":"How WP_Query turns meta_query into joins, why meta_value cannot be indexed usefully, and which filters belong in a taxonomy instead.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/","og_site_name":"Lukas Wojcik - Blog","article_published_time":"2026-09-12T05:20:00+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-10392-one-clause-one-join-what-meta-query--n.png","type":"image\/png"}],"author":"luky","twitter_card":"summary_large_image","twitter_misc":{"Written by":"luky","Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/#article","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/"},"author":{"name":"luky","@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"headline":"meta_query in WP_Query: How Each Clause Becomes a Join and What It Costs","datePublished":"2026-09-12T05:20:00+00:00","mainEntityOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/"},"wordCount":783,"commentCount":0,"publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-10392-one-clause-one-join-what-meta-query--n.png","keywords":["MySQL","Web Performance"],"articleSection":["WordPress Plugins &amp; Tricks"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/","name":"meta_query in WP_Query: How Each Clause Becomes a Join and What It Costs - Lukas Wojcik - Blog","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/#primaryimage"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-10392-one-clause-one-join-what-meta-query--n.png","datePublished":"2026-09-12T05:20:00+00:00","description":"How WP_Query turns meta_query into joins, why meta_value cannot be indexed usefully, and which filters belong in a taxonomy instead.","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/#primaryimage","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-10392-one-clause-one-join-what-meta-query--n.png","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-10392-one-clause-one-join-what-meta-query--n.png","width":1200,"height":630,"caption":"meta_query in WP_Query: How Each Clause Becomes a Join and What It Costs"},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/wordpress-plugins-tricks\/meta_query-in-wp_query-how-each-clause-becomes-a-join-and-what-it-costs\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"meta_query in WP_Query: How Each Clause Becomes a Join and What It Costs"}]},{"@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"],"url":"https:\/\/www.lukaswojcik.com\/blog\/author\/luky\/"}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/10392","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"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=10392"}],"version-history":[{"count":3,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/10392\/revisions"}],"predecessor-version":[{"id":17431,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/10392\/revisions\/17431"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media\/15167"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=10392"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/categories?post=10392"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=10392"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}