Ordinare per valore meta includendo i post che non ne hanno uno
Ho modificato la ricerca predefinita di WP utilizzando il filtro pre_get_posts
, permettendo all'utente di ordinare i post (inclusi diversi tipi di post personalizzati) per campi differenti.
Il problema che sto riscontrando è che quando chiedo a WP di ordinare per un valore meta, questo esclude tutti i post che non hanno quel valore meta impostato. Ciò fa sì che il numero dei risultati cambi se si passa dall'ordinamento per "Prezzo" a "Data" poiché i "Post" non hanno "Prezzo" impostato mentre gli "Articoli" sì.
Non è questo che voglio, quindi vorrei sapere se c'è un modo per includere TUTTI i post - anche quelli che non hanno il valore meta su cui sto ordinando - e mettere quelli senza valore per ultimi.
So come ordinare per più di un campo ma questo non aiuta.
Grazie
Sembra che non sia l'unico con questa domanda: Come includere post sia con che senza una certa meta_key negli argomenti per wp_query? ma lì non c'è una soluzione.
Aggiornamento
Ho provato la risposta ma non sono sicuro di aver capito correttamente, ecco cosa ho al momento:
<?php
function my_stuff ($qry) {
$qry->set('meta_query', array(array(
'key' => 'item_price',
'value' => '',
'compare' => 'NOT EXISTS'
)));
$qry->set('orderby', 'meta_value date'); # L'ordinamento funziona sia con meta_value che con meta_value_num - ho provato entrambi
$qry->set('order', 'ASC DESC');
$qry->set('meta_key', 'item_price');
}
Il valore meta è un numero (viene utilizzato per memorizzare un prezzo come suggerisce il nome)
Aggiornamento 2
Ho commentato la parte dell'ordinamento e ora ho solo questo:
<?php
$qry->set('meta_query', array(array(
'key' => 'item_price',
'value' => '',
'compare' => 'NOT EXISTS'
)));
Con questo codice la query sembra restituire tutti i post che non hanno la chiave item_price
e nessuno dei post che ce l'hanno. Cioè il problema ora è invertito.
Se aggiungo anche il codice dell'ordinamento ottengo 0 risultati.
Modifica: ...tre anni dopo... :P Ho avuto di nuovo questo problema. Ho provato tutte le risposte date e nessuna funziona. Non so perché alcune persone sembrano pensare che funzionino ma per me non funzionano.
La soluzione che ho trovato alla fine è utilizzare il filtro save_post
- assicurandomi che tutti i post abbiano il campo personalizzato su cui voglio ordinare. È un po' fastidioso dover farlo, ma finché lo fai all'inizio probabilmente non avrai problemi.
In questo caso stavo costruendo un "contatore di visualizzazioni" sui post e volevo che gli utenti potessero ordinare per i post più letti. Di nuovo, i post che non sono mai stati visualizzati (immagino sia abbastanza improbabile - ma comunque) scomparivano quando si ordinava per numero di visualizzazioni. Ho aggiunto questo pezzo di codice per assicurarmi che tutti i post abbiano un conteggio delle visualizzazioni:
add_action('save_post', function ($postId) {
add_post_meta($postId, '_sleek_view_count', 0, true);
});

Facile facile, testato nel 2018, attualmente utilizzato in produzione.
Aggiornamento 2022: Modificato in modo che la query NOT_EXISTS
venga prima della query EXISTS
, e chiarisce l'effetto che chiavi multiple hanno sulla clausola orderby
.
$query->set( 'meta_query', array(
'relation' => 'OR',
array(
'key' => 'custom_meta_key',
'compare' => 'NOT EXISTS'
),
array(
'key' => 'custom_meta_key',
'compare' => 'EXISTS'
),
) );
$query->set( 'orderby', 'meta_value title' );
Questo controlla tutti gli elementi con e senza la meta chiave, senza specificare un valore. La meta query fornisce la chiave per l'ordinamento in modo affidabile. È stato testato. La clausola orderby
con meta_value
/meta_value_num
utilizzerà l'ultima chiave nella catena.
Esempio pratico
/**
* Modifica la query prima di recuperare i post. Imposta i
* parametri `meta_query` e `orderby` quando non è impostato
* alcun parametro `orderby` (ordinamento predefinito).
*
* @param WP_Query $query L'oggetto completo `WP_Query`.
* @return void
*/
function example_post_ordering( $query ) {
// Se non siamo in wp-admin,
// e la query è la query principale,
// e la query non è una query singola,
// e la query non ha un parametro orderby impostato...
// Nota: controlla i tipi di post, ecc. qui se necessario.
if ( ! is_admin()
&& $query->is_main_query()
&& ! $query->is_singular()
&& empty( $query->get( 'orderby' ) ) {
// Impostare solo `meta_key` non è sufficiente, poiché questo
// ignorerà i post che non hanno ancora, o non avranno mai,
// un valore per la chiave specificata. Questa meta query
// registra la `meta_key` per l'ordinamento, ma non
// ignora quei post senza un valore per questa chiave.
$query->set( 'meta_query', array(
'relation' => 'OR',
array(
'key' => 'custom_meta_key',
'compare' => 'NOT EXISTS'
),
array(
'key' => 'custom_meta_key',
'compare' => 'EXISTS'
),
) );
// Ordina per il meta valore, poi per il titolo se più
// post condividono lo stesso valore per la meta chiave fornita.
// Usa `meta_value_num` se i meta valori sono numerici.
$query->set( 'orderby', 'meta_value title' );
}
}
add_action( 'pre_get_posts', 'example_post_ordering', 10 );
Questo ordinerà i post per custom_meta_key
di default, e non ignorerà i post senza un valore per quella chiave.

Solo leggendo il codice, sembra che faccia semplicemente ottenere i post che hanno custom_meta_key
e i post che non hanno custom_meta_key
. Sentiti libero di includere un esempio pratico funzionante con l'ordinamento.

Hai ragione, è tutto ciò che sta facendo, ma la riga sottostante è responsabile dell'ordinamento per meta_value (della meta key interrogata). $query->set( 'orderby', 'meta_value title' );
(Ordina per meta value, poi per titolo quando più post hanno lo stesso valore per la meta key). Questo dovrebbe essere fatto nell'hook pre_get_posts
, utilizzando la variabile $query
passata. Tieni presente che la domanda era come ordinare per meta value, senza ignorare i post che non hanno un valore per quella meta key.

Va bene, proverò la prossima volta che mi troverò di fronte a questo problema.

Ha funzionato per me in una chiamata personalizzata get_posts()
per spingere i post con meta _featured
in cima, ordinando poi per data. Grazie!

Questo ha funzionato solo quando ho invertito l'ordine di NOT EXISTS ed EXISTS. Funzionava, ma l'ordinamento veniva ignorato.

@mikemike grazie per l'informazione, facendo questa modifica ha funzionato anche per me

Questo metodo restituirà tutti i post, inclusi quelli con e senza il meta_key
richiesto, ma produrrà risultati strani nell'ordinamento.
add_action('pre_get_posts', 'my_stuff');
function my_stuff ($qry) {
$qry->set(
'meta_query',
array(
'relation' => 'OR', # Le corrispondenze a questa meta_query verranno aggiunte a quelle che corrispondono alla query 'meta_key'
array(
'key' => 'item_price',
'value' => 'bug #23268',
'compare' => 'NOT EXISTS'
)
)
);
$qry->set('orderby', 'meta_value date'); # L'ordinamento funziona con meta_value così come con meta_value_num - ho provato entrambi
$qry->set('order', 'ASC DESC');
$qry->set('meta_key', 'item_price');
}
Ho scoperto questo sperimentando con tutte le diverse risposte a questa domanda e analizzando l'SQL generato attraverso tentativi ed errori. Sembra che impostare array('meta_query' => array('relation' => 'OR'))
produca un appropriato LEFT JOIN
invece di un INNER JOIN
che è necessario per includere i post mancanti dei metadati. Specificare NOT EXISTS
impedisce alla clausola WHERE
di filtrare i post privi del campo meta. Per questa particolare WP_Query
, l'SQL generato è (sono stati aggiunti rientri/a capo):
SELECT SQL_CALC_FOUND_ROWS
wp_posts.ID
FROM wp_posts
INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)
INNER JOIN wp_postmeta ON wp_posts.ID = wp_postmeta.post_id
LEFT JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id AND mt1.meta_key = 'item_price')
WHERE 1=1
AND ( wp_term_relationships.term_taxonomy_id IN (2) )
AND wp_posts.post_type = 'post'
AND (wp_posts.post_status = 'publish'
OR wp_posts.post_status = 'private')
AND (wp_postmeta.meta_key = 'item_price'
-- Oh guarda, qui diamo a SQL il permesso di scegliere una riga
-- casuale da wp_postmeta quando questo particolare post manca
-- di 'item_price':
OR mt1.post_id IS NULL )
GROUP BY wp_posts.ID
ORDER BY wp_postmeta.meta_value,wp_posts.post_date DESC
LIMIT 0, 10
Il risultato è un elenco di tutti i post con meta_value di item_price
e quelli che mancano di item_price
. Tutti i post con item_price
saranno ordinati correttamente tra loro, ma i post mancanti di item_price
utilizzeranno qualche altro valore meta casuale (ad esempio, _edit_last
che spesso sembra essere 1
nel mio database o qualche altro metadato interno di WordPress completamente arbitrario) per il loro wp_postmeta.meta_value
nella clausola ORDER BY
. Quindi, sebbene questo metodo sia vicino e possa sembrare funzionare per alcuni dati, è difettoso. Quindi, tutto ciò che posso dire è che, se i tuoi valori di item_price
non entrano in conflitto con i campi meta casuali che MySQL sceglie per i post mancanti di item_price
, questo potrebbe funzionare bene per te. Se tutto ciò di cui hai bisogno è la garanzia che i tuoi post con item_price
siano ordinati correttamente tra loro senza riguardo per l'ordinamento degli altri post, potrebbe andare bene. Ma penso che questa sia solo una carenza in WordPress. Correggimi, spero di sbagliarmi e che ci sia un modo per risolvere questo problema ;-).
Sembra che per l'INNER JOIN wp_postmeta
, MySQL stia scegliendo una riga casuale tra le multiple righe postmeta
associate al post quando il meta_key
è mancante dal post specificato. Da una prospettiva SQL, dobbiamo capire come dire a WordPress di produrre ORDER BY mt1.meta_value
. Questa colonna è propriamente NULL
quando il nostro meta_key
richiesto è mancante, a differenza di wp_postmeta.meta_value
. Se potessimo fare ciò, SQL ordinerebbe questi NULL
(voci mancanti) prima di qualsiasi altro valore, dandoci un ordine ben definito: prima arrivano tutti i post mancanti del particolare campo postmeta, poi arrivano i post che lo possiedono. Ma questo è l'intero problema: 'orderby' => 'meta_value'
può riferirsi solo a 'meta_key' => 'item_price'
e il wp_postmeta
non aliasato è sempre un INNER JOIN
invece di un LEFT JOIN
, il che significa che wp_postmeta.meta_value
e wp_postmeta.meta_key
non potranno mai essere NULL
.
Quindi suppongo di dover dire che questo non è possibile con il WP_Query
integrato di WordPress così com'è documentato attualmente (in wordpress-3.9.1). Fastidioso. Quindi, se hai effettivamente bisogno che questo funzioni correttamente, probabilmente devi agganciarti a WordPress altrove e modificare direttamente l'SQL generato.

Sembra molto promettente! Proverò questa soluzione la prossima volta che avrò questo problema. Vorrei darti la risposta adesso, ma preferirei prima confermare che funzioni nel mio caso.

Questo ha impedito che qualsiasi cosa venisse visualizzata per me. Non mostrava nulla dopo aver implementato questa soluzione.

@Jake Sì, stesso problema qui. Oggi ho avuto di nuovo questo problema e ho provato questa soluzione. Restituisce 0 risultati.

Il problema che tutti stanno riscontrando qui riguarda l'ordine delle meta query. Per ordinare correttamente, dovrai inserire la query "NOT EXISTS" prima della query "EXISTS".
Il motivo è che WordPress utilizza il meta_value dell'ultima istruzione "LEFT JOIN" nella clausola "ORDER BY".
Ad esempio:
$pageQuery = new WP_Query([
'meta_query' => [
'relation' => 'OR',
['key' => 'item_price', 'compare' => 'NOT EXISTS'], // questo viene per primo!
['key' => 'item_price', 'compare' => 'EXISTS'],
],
'order' => 'DESC',
'orderby' => 'meta_value_num',
'post_status' => 'publish',
'post_type' => 'page',
'posts_per_page' => 10,
]);

Ci sono due possibili soluzioni a questo problema:
1. Tutti i post hanno il meta
La soluzione migliore che ho trovato qui è assegnare un prezzo di 0 agli altri post/prodotti. Puoi farlo manualmente, o scorrere tutti i post e, se il prezzo è vuoto, aggiornarlo.
Per renderlo gestibile in futuro, puoi agganciarti a save_post
e assegnare un valore quando vengono aggiunti per la prima volta (solo se è vuoto).
2. Query multipli
Potresti eseguire la prima query come stai facendo e memorizzare gli ID dei post restituiti. Potresti poi eseguire un'altra query per tutti i post e ordinarli per data, escludendo gli ID restituiti dalla prima query.
Puoi quindi stampare i due risultati separatamente in ordine e otterrai i risultati desiderati.

Tre anni dopo ho avuto lo stesso problema di nuovo :P Ho dovuto usare il metodo save_post
(ho aggiornato la mia domanda con il codice che ho usato).

Questo è possibile senza dover usare hook o eseguire query multiple. Vedi la mia risposta.

Anch'io ho riscontrato un problema simile e la seguente soluzione mi ha aiutato:
$args = array(
'post_type' => 'kosh_products',
'posts_per_page' => -1,
'meta_query' => array(
'relation' => 'OR',
'category_sort_order' => array(
'key' => '_sort_order',
'compare' => 'EXISTS'
),
'category_sort_order_not_exists' => array(
'key' => '_sort_order',
'compare' => 'NOT EXISTS'
),
),
'orderby' => array(
'category_sort_order' => 'ASC',
'date' => 'ASC'
));
$query = new WP_Query( $args );
Ho trovato una descrizione su WordPress Codex con il titolo "'orderby' con multiple 'meta_key'": https://codex.wordpress.org/Class_Reference/WP_Query#Order_.26_Orderby_Parameters

Penso di avere una soluzione.
Puoi utilizzare due meta_key
, uno che tutti i post hanno (come "_thumbnail_id")
, e il meta_key
che desideri usare come filtro.
Quindi i tuoi argomenti:
$qry->set(
'meta_query',
array(
'relation' => 'OR',
array(
'key' => 'item_price',
'value' => '',
'compare' => 'EXISTS'
),
array(
'key' => 'item_price',
'value' => '',
'compare' => 'EXISTS'
)
)
);
$qry->set('orderby', 'meta_value date'); # L'ordinamento funziona sia con meta_value che con meta_value_num - ho provato entrambi
$qry->set('order', 'ASC DESC');
$qry->set('meta_key', 'item_price');

Ho avuto lo stesso problema con i valori meta numerici e ho notato che l'ordine della query è importante. Nel mio caso, la query NOT EXISTS
deve essere la prima.
Esempio:
$query->set( 'orderby', 'meta_value_num' );
$query->set( 'meta_query', [
'relation' => 'OR',
[ 'key' => 'your_meta_name', 'compare' => 'NOT EXISTS' ],
[
'key' => 'your_meta_name',
'compare' => 'EXISTS',
],
] );
È anche importante impostare il parametro generale ’orderby’
su ’meta_value_num’
per ottenere il giusto ordinamento dei valori numerici. Altrimenti si ottengono risultati strani per i valori numerici, ad esempio:
1, 2, 20, 21, 3, 4, 5 …
Invece di:
1, 2, 3, 4, 5 … 20, 21

La query meta OR che combina NOT EXISTS
e EXISTS
funziona ma causava una query lenta.
Questa soluzione funziona molto più velocemente.
La chiave principale era nella clausola SQL order by:
order by YOUR_FIELD is NULL, YOUR_FIELD
add_filter('posts_orderby', function (string $orderby, WP_Query $query) {
if ('MY_CUSTOM_SORT' === $query->get('orderby')) {
$orderby = "mta.meta_value is NULL, mta.meta_value ='', mta.meta_value ASC";
}
return $orderby;
}, 10, 2);
add_filter('posts_join', function (string $join, WP_Query $query) {
if ('MY_CUSTOM_SORT' === $query->get('orderby')) {
$join .= " LEFT JOIN wp_postmeta AS mta ON ( wp_posts.ID = mta.post_id AND mta.meta_key = 'SOME_META_KEY')";
}
return $join;
}, 10, 2);
E nei tuoi argomenti WP_Query:
$args['orderby'] = 'MY_CUSTOM_SORT';

Questa è una domanda molto vecchia ma nessuna delle risposte era abbastanza robusta per essere utilizzata e soddisfatta con i risultati. La soluzione qui sotto utilizza ancora WP_Query
, ma manipola le istruzioni di join e order by.
Sfrutta una funzione di MySQL chiamata ORDER BY IF
, che permette di eseguire efficacemente della logica durante l'istruzione ORDER BY. Questo è importante perché le altre soluzioni qui presenti raggruppano semplicemente i risultati senza valore all'inizio o alla fine della lista, il che è inutile quando si cerca di ordinare per 2 chiavi.
Nell'esempio qui sotto ho un CPT di 'businesses'. Voglio che siano tutti ordinati per titolo, ma quelli con is_premium
dovrebbero venire prima. Ho commentato il codice in modo da poter vedere cosa succede in ogni punto.
<?php
$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
$args = [
'post_type' => 'business',
'posts_per_page' => 12,
'meta_query' => [
'relation' => 'OR',
[
'key' => 'is_premium',
'compare' => 'NOT EXISTS' // NOT EXISTS prima è importante
],
[
'key' => 'is_premium',
'compare' => 'EXISTS'
],
]
,
'orderby' => [
'meta_value' => 'DESC', // Questo è il campo is_premium
'title' => 'ASC',
],
'paged' => $paged
];
// Aggiungiamo il nostro join personalizzato alla query
function custom_join($join) {
global $wpdb;
if( ! is_admin() ) {
$join .= $wpdb->prepare(
' LEFT JOIN ' . $wpdb->postmeta . ' cpm ON cpm.post_id = ' . $wpdb->posts . '.ID AND cpm.meta_key = %s'
, 'is_premium' );
}
return $join;
}
add_filter('posts_join','custom_join');
// Aggiungiamo il nostro order by personalizzato alla query
function custom_orderby($orderby_statement){
global $wpdb;
if ( ! is_admin() ) {
// Qui ordiniamo per meta_value SOLO se è 1, altrimenti lo ignoriamo per quella istruzione di ordinamento, il che
// significa che verrà ordinato dalla prossima istruzione (titolo), come gli altri risultati
$orderby_statement = "IF(cpm.meta_value = 1, 1, 0) DESC, wp_posts.post_title ASC ";
}
return $orderby_statement;
}
add_filter('posts_orderby','custom_orderby', 10, 2 );
// Eseguiamo la query
$business = new WP_Query( $args );
// Rimuoviamo i filtri
remove_filter('posts_orderby','custom_orderby');
remove_filter('posts_join','custom_join');

Se appropriato, puoi aggiungere un valore meta predefinito ogni volta che un articolo viene salvato o aggiornato, se il valore meta non esiste.
function addDefaultMetaValue($post_id) {
add_post_meta($post_id, 'item_price', 0, true);
}
add_action('save_post', 'addDefaultMetaValue');
Se stai usando un custom post type, sostituisci add_action('save_post', 'addDefaultMetaValue');
con add_action('save_post_{post_type}', 'addDefaultMetaValue');
ad esempio add_action('save_post_product', 'addDefaultMetaValue');

Esiste un possibile valore orderby
di meta_value
per questo.
$query = new WP_Query( array (
'meta_key' => 'nome_della_tua_chiave',
'orderby' => 'meta_value',
'order' => 'DESC',
'meta_query' => array( array(
'key' => 'tua_meta_chiave',
'value' => '',
'compare' => 'NOT EXISTS',
// 'type' => 'CHAR',
) )
) );
Se hai valori numerici, puoi usare meta_value_num
invece.
Nota: Questo non è stato testato, ma dovrebbe funzionare. Il punto è che devi specificare i tuoi valori meta_key
e key
. Altrimenti non puoi confrontare valori non esistenti, il che dovrebbe permettere di interrogare entrambi i tipi di post. È un po' un hack, ma finché funziona...

Grazie per la tua risposta, per favore controlla la mia domanda aggiornata, non sono sicuro di averti capito correttamente.

Ancora non sono riuscito a far funzionare questo, quindi se hai una soluzione mi piacerebbe sapere cosa sto sbagliando. Inoltre, ho messo una ricompensa su SO se vuoi rivendicarla: http://stackoverflow.com/questions/17016770/wordpress-order-by-meta-value-if-it-exists-else-date/17183618

Due cose. 'your_keys_name'
e 'your_meta_key'
dovrebbero essere la stessa stringa invece di essere distinte, altrimenti sembra che tu abbia frainteso la domanda. In secondo luogo, ho testato questo sul mio setup locale ed esclude tutti i post dove la chiave esiste (attraverso il meta_query
) e esclude anche i post dove la chiave manca (attraverso meta_key
), risultando in nessun post visualizzato. Tuttavia, questa risposta è un passo verso qualcosa che funziona almeno ;-).

Oh, interessante, questa risposta funziona se aggiungi semplicemente 'relation' => 'OR'
al meta_query
. Roba strana o_o.

@binki Fai semplicemente una [modifica] alla mia domanda e cambia le parti che pensi dovrebbero essere modificate. Questo è un sito guidato dalla comunità :)

@kaiser, ho fatto ulteriori prove, persino dei test, e ho scritto un piccolo monologo. Presumo che la mia nuova risposta sia il posto appropriato per conservare le mie divagazioni ;-).

Questa soluzione ha funzionato per me:
add_action( 'pre_get_posts', 'orden_portfolio' );
function orden_portfolio( $query ) {
if( ! is_admin() ) {
$query->set( 'orderby', 'meta_value_num' );
$query->set( 'order', 'ASC' );
$query->set( 'meta_query', [
'relation' => 'OR',
[
'key' => 'ce_orden',
'compare' => 'NOT EXISTS' ],
[
'key' => 'ce_orden',
'compare' => 'EXISTS',
],
] );
return $query;
}
}
Tuttavia, questa soluzione mostra prima i record con meta_value nullo. Quest'altra soluzione mostra l'ordine ASC con i valori nulli alla fine:
function custom_join($join) {
global $wpdb;
if( ! is_admin() ) {
$join .= $wpdb->prepare(
' LEFT JOIN ' . $wpdb->postmeta . ' cpm ON cpm.post_id = ' . $wpdb->posts . '.ID AND cpm.meta_key = %s'
, 'ce_orden' );
}
return $join;
}
add_filter('posts_join','custom_join');
function custom_orderby($orderby_statement){
global $wpdb;
if ( ! is_admin() ) {
$orderby_statement = "CAST( COALESCE(cpm.meta_value,99999) as SIGNED INTEGER) ASC";
}
return $orderby_statement;
}
add_filter('posts_orderby','custom_orderby', 10, 2 );

Sono riuscito a risolvere questo problema utilizzando una singola query con gli argomenti appropriati. Per WordPress 4.1+
$args = array(
'posts_per_page' => -1,
'post_status' => 'publish',
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'custom_sort',
'compare' => 'EXISTS'
),
array(
'key' => 'custom_sort',
'compare' => 'NOT EXISTS'
)
),
'orderby' => 'meta_value_num title',
'order' => 'ASC',
));
$query = new WP_Query($args);
Vedi la mia risposta completa qui: https://wordpress.stackexchange.com/a/370841/15209

Alla fine ho risolto con un piccolo hack (a mio modesto parere), ma ha funzionato nel mio caso.
Puoi agganciarti ai filtri posts_join_paged e posts_orderby per modificare le stringhe di join e ordinamento. Questo ti permetterà di ordinare come preferisci, purché prima effettui il join piuttosto che far assumere a WP_Query che il campo debba esistere per quel particolare post. Puoi quindi rimuovere meta_key
, orderby
e `order dagli argomenti del tuo WP_Query.
Di seguito un esempio. All'inizio di ogni funzione ho dovuto gestire alcuni casi particolari poiché questo verrà aggiunto a tutto ciò che utilizza WP_Query. Potresti doverlo modificare per adattarlo alle tue esigenze specifiche.
La documentazione su questi due filtri è purtroppo carente... buona fortuna! :)
add_filter('posts_join_paged', 'edit_join', 999, 2);
add_filter('posts_orderby', 'edit_orderby', 999, 2);
/**
* Modifica il join
*
* @param string $join_paged_statement
* @param WP_Query $wp_query
* @return string
*/
function edit_join($join_paged_statement, $wp_query)
{
global $wpdb;
if (
!isset($wp_query->query)
|| $wp_query->is_page
|| $wp_query->is_admin
|| (isset($wp_query->query['post_type']) && $wp_query->query['post_type'] != 'my_custom_post_type')
) {
return $join_paged_statement;
}
$join_to_add = "
LEFT JOIN {$wpdb->prefix}postmeta AS my_custom_meta_key
ON ({$wpdb->prefix}posts.ID = my_custom_meta_key.post_id
AND my_custom_meta_key.meta_key = 'my_custom_meta_key')
";
// Aggiungi solo se non è già presente
if (strpos($join_paged_statement, $join_to_add) === false) {
$join_paged_statement = $join_paged_statement . $join_to_add;
}
return $join_paged_statement;
}
/**
* Modifica l'ordinamento
*
* @param string $orderby_statement
* @param WP_Query $wp_query
* @return string
*/
function edit_orderby($orderby_statement, $wp_query)
{
if (
!isset($wp_query->query)
|| $wp_query->is_page
|| $wp_query->is_admin
|| (isset($wp_query->query['post_type']) && $wp_query->query['post_type'] != 'my_custom_post_type')
) {
return $orderby_statement;
}
$orderby_statement = "my_custom_meta_key.meta_value DESC";
return $orderby_statement;
}

Il codice funziona. Ma meta_value viene gestito come una stringa. Quindi 6 viene valutato più alto di 50. È possibile apportare modifiche per trattarli come numeri?

@Drivingralle cast(my_custom_meta_key.meta_value as unsigned) DESC
dovrebbe risolvere il problema...

Alla fine ho utilizzato il filtro 'get_meta_sql' fornito da WordPress
add_filter( 'get_meta_sql', 'adjust_the_meta_sql' ), 10, 2 );
La funzione si presenta così
function adjust_the_meta_sql( $sql, $queries ) {
if ( 'my_post_type' === filter_input( INPUT_GET, 'post_type' )
&& '_my_key' === $queries[0]['key'] ) {
$sql['join'] = preg_replace( '/INNER JOIN wp_postmeta ON \(([^)]+)\)/', 'LEFT JOIN wp_postmeta ON ($1 ' . $sql['where'] . ')', $sql['join'] );
$sql['where'] = '';
}
return $sql;
}
Risolve due problemi con l'SQL generato da WordPress.
- Trasforma l'INNER JOIN in un LEFT JOIN
- Sposta la clausola WHERE nella clausola ON.
La tua query specifica potrebbe essere diversa, quindi assicurati di testare e modificare se necessario.

Credo che quello che @kaiser stesse cercando di fare fosse dire alla query di restituire tutti i post che hanno quella meta key applicando una sorta di condizione where fittizia per non filtrare nessuno di quei post. Quindi, se sai che tutti i valori che i tuoi campi personalizzati possono assumere sono x,y,z potresti dire "WHERE meta_key IN(x,y,z)" ma l'idea è che puoi evitare del tutto quel problema dicendo != (''):
$query = new WP_Query( array (
'orderby' => 'meta_value_num',
'order' => 'DESC',
'meta_query' => array( array(
'key' => 'item_price',
'value' => '',
'compare' => '!=',
) )
) );
Anche questo non è testato, ma sembra valga la pena provare :-).

Ragazzi, grazie per tutte le risposte valide ma, al momento (2022), l'annidamento è la chiave:
$query->set( 'orderby', array(
'meta_value_num' => 'DESC',
'title' => 'ASC' )
); // priorità nell'ordinamento
$query->set( 'meta_query', array(
'featured' => array(
'relation' => 'OR',
array(
'key' => '_featured',
'value' => '1', // mostra per primi i post con valore = 1
),
array( // queste regole annidate hanno priorità minore nell'ordinamento ma assicurano di mostrare tutti gli altri post
'relation' => 'OR',
array(
'key' => '_featured',
'compare' => 'NOT EXISTS', // si spiega da solo
),
array(
'key' => '_featured',
'value' => '0', // il caso in cui il valore esiste ma deve essere considerato come non esistente
)
)
)
));
Bonus: Non ho testato questo caso ma se il meta non è un valore 0/1, la terza regola dovrebbe essere:
array(
'key' => '_featured',
'value' => '[valore priorità]',
'compare' => '!='
)
