meta_query con meta valori come array serializzati


37

Sto lavorando a un progetto in cui sto creando un tipo di post personalizzato e dati personalizzati immessi tramite meta box associate al mio tipo di post personalizzato. Per qualsiasi motivo, ho deciso di codificare le meta box in modo tale che gli input in ciascun metabox facciano parte di un array. Ad esempio, sto memorizzando longitudine e latitudine:

<p> 
    <label for="latitude">Latitude:</label><br /> 
    <input type="text" id="latitude" name="coordinates[latitude]" class="full-width" value="" /> 
</p> 
<p>     
    <label for="longitude">Longitude:</label><br /> 
    <input type="text" id="longitude" name="coordinates[longitude]" class="full-width" value="" /> 
</p>

Per qualsiasi motivo, mi è piaciuta l'idea di avere una voce postmeta singolare per ogni metabox. Al save_postgancio, salvo i dati in questo modo:

update_post_meta($post_id, '_coordinates', $_POST['coordinates']);

L'ho fatto perché ho tre metabox e mi piace avere solo 3 valori postmeta per ogni post; tuttavia, ora ho realizzato un potenziale problema con questo. Potrei voler usare WP_Query per estrarre solo determinati post basati su questi meta valori. Ad esempio, potrei voler ottenere tutti i post con valori di latitudine superiori a 50. Se avessi questi dati nel database individualmente, magari usando la chiave latitude, farei qualcosa del tipo:

$args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => '50',
            'compare' => '>'
        )
    )
 );
$query = new WP_Query( $args );

Dal momento che ho la latitudine come parte della _coordinatespostmeta, questo non avrebbe funzionato.

Quindi, la mia domanda è: c'è un modo per utilizzare meta_queryper interrogare un array serializzato come ho in questo scenario?

Risposte:


37

No, non è possibile e potrebbe persino essere pericoloso.

Consiglio vivamente di annullare la serializzazione dei dati e di modificare la routine di salvataggio. Qualcosa di simile a questo dovrebbe convertire i tuoi dati nel nuovo formato:

$args = array(
    'post_type' => 'my-post-type',
    'meta_key' => '_coordinates',
    'posts_per_page' => -1
 );
$query = new WP_Query( $args );
if($query->have_posts()){
    while($query->have_posts()){
        $query->the_post();
        $c = get_post_meta($post->id,'_coordinates',true);
        add_post_meta($post->ID,'_longitude',$c['longitude']);
        add_post_meta($post->ID,'_latitude',$c['latitude']);
        delete_post_meta($post->ID,'_coordinates',$c);
    }
}

Quindi sarai in grado di eseguire una query come desideri con le singole chiavi

Se è necessario memorizzare più longitudini e più latitudini, è possibile memorizzare più meta post con lo stesso nome. Usa semplicemente il terzo parametro di get_post_metae li restituirà tutti come un array

Perché non è possibile eseguire query all'interno dei dati serializzati?

MySQL lo vede solo come una stringa e non può dividerlo in dati strutturati. La suddivisione in dati strutturati è esattamente ciò che fa il codice sopra

Potresti essere in grado di eseguire una query per blocchi parziali della data, ma questo sarà super inaffidabile, costoso, lento e molto fragile, con molti casi limite. I dati serializzati non sono destinati alle query SQL e non sono formattati in modo regolare e costante.

A parte i costi delle ricerche di stringhe parziali, le post-query query sono lente e i dati serializzati possono cambiare a seconda di cose come la lunghezza dei contenuti, rendendo la ricerca incredibilmente costosa, se non impossibile a seconda del valore che stai cercando

Una nota sulla memorizzazione di record / entità / oggetti come oggetti serializzati in Meta

Potresti voler archiviare un record di transazione in post meta, o qualche altro tipo di struttura di dati in meta utente, quindi imbatterti nel problema sopra.

La soluzione qui non è quella di suddividerla in meta post individuale, ma di rendersi conto che non avrebbe mai dovuto essere una meta all'inizio, ma un tipo di post personalizzato. Ad esempio, un registro o un record può essere un tipo di post personalizzato, con il post originale come genitore o unito tramite un termine di tassonomia

Sicurezza e oggetti serializzati

La memorizzazione di oggetti PHP serializzati tramite la serializefunzione può essere pericolosa , il che è sfortunato poiché passare un oggetto a WordPress significa che viene serializzato. Questo perché quando l'oggetto viene serializzato, viene creato un oggetto e tutti i suoi metodi di sveglia e costruttori vengono eseguiti. Questo potrebbe non sembrare un grosso problema fino a quando un utente non riesce a intrufolare un input accuratamente predisposto, portando all'esecuzione di codice in modalità remota quando i dati vengono letti dal database e deserializzati da WordPress.

Ciò può essere evitato utilizzando JSON, che semplifica anche le query, ma è molto più facile / veloce archiviare correttamente i dati ed evitare i dati strutturati serializzati.


5
Per le persone che passano, non smettere di leggere: le risposte più utili (e recenti) si trovano di seguito
Erenor Paz

E se avessi una serie di ID da salvare - e ognuno di essi non rappresenta una chiave diversa in cui potrei salvarli come "latitudine" ecc., È solo una chiave per tutti (come quando si salvano le relazioni ecc.). Cosa fare allora? @ la soluzione di rabni?
trainoasi,

1
È possibile memorizzare una chiave più di una volta, le coppie valore-chiave non sono univoche. Per quanto riguarda le relazioni, ecco a cosa servono le tassonomie, se stai usando la meta per mappare più cose su qualcosa, mettile invece in un termine di tassonomia
Tom J Nowell

24

Mi imbatto anche in questa situazione. Ecco cosa ho fatto:

$args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => sprintf(':"%s";', $value),
            'compare' => 'LIKE'
        )
    )
);

Spero che questo aiuto


1
Mi è davvero piaciuta questa soluzione. Sfortunatamente, questo non è applicabile quando $valueè anche un ID. In tal caso, suggerisco di creare funzioni per aggiungere un carattere a ciascun elemento dell'array prima di salvare i dati e un'altra funzione per rimuovere il carattere prima di utilizzare i dati. Questo wat, l' i:2indice serializzato non verrà confuso con i i:D2dati "reali". Il parametro meta query dovrebbe quindi diventare 'value' => sprintf(':"D%s";', $value),e manterrai la corretta funzionalità di questa meravigliosa risposta!
Erenor Paz,

Questa soluzione funziona per me
Vishal,

Anche questo ha funzionato perfettamente per me. Ho avuto un piccolo panico quando ho visto la soluzione accettata però
Shane Jones,

@Erenor Paz, ho appena pubblicato una soluzione che funziona bene sia con ID che con stringhe: wordpress.stackexchange.com/a/299325/25264
Pablo SG Pacheco

l'utilizzo LIKEè un modo eccellente e veloce per arrestare il server (per non parlare dei falsi positivi) è meglio avere una cache molto buona.
Mark Kaplun,

10

Perderai davvero la capacità di interrogare i tuoi dati in modo efficiente durante la serializzazione delle voci nel database WP.

Il risparmio complessivo in termini di prestazioni e guadagni che ritieni di ottenere tramite la serializzazione non sarà evidente in misura rilevante. È possibile ottenere dimensioni del database leggermente inferiori, ma il costo delle transazioni SQL sarà pesante se si eseguono query su tali campi e si tenta di confrontarli in modo utile e significativo.

Invece, salva la serializzazione per i dati che non intendi interrogare in quella natura, ma che invece accederesti in modo passivo solo tramite la chiamata API WP diretta get_post_meta()- da quella funzione puoi decomprimere una voce serializzata per accedere anche alle sue proprietà dell'array.

In effetti assegnato il valore di true come in;

$meta = get_post_meta( $post->ID, 'key', true );

Restituirà i dati come un array, accessibile all'utente per scorrere come al solito.

È possibile concentrarsi su altre ottimizzazioni di database / siti come memorizzazione nella cache, minimizzazione CSS e JS e, se necessario, utilizzare tali servizi come CDN. Per citarne solo alcuni .... WordPress Codex è un buon punto di partenza per scoprire di più su questo argomento: QUI


3

Ho appena affrontato i campi serializzati e potrei interrogarli. Non usando la meta_query ma usando una query SQL.

global $wpdb; 

$search = serialize('latitude').serialize(50);

$query = $wpdb->prepare("SELECT `post_id`
FROM `wp_postmeta`
WHERE `post_id` IN (SELECT `ID` FROM `wp_posts` WHERE `post_type` = 'my-post-type')
AND `meta_key` = '_coordinates'
AND `meta_value` LIKE '%s'",'%'.$search.'%');

$ids = $wpdb->get_col($query);

$args = array(
    'post__in' => $ids
    'post_type' => 'team' //add the type because the default will be 'post'
);

$posts = get_posts($args);

La query cerca innanzitutto i post con il post_type corrispondente, quindi la quantità di record wp_postmeta sarà inferiore da filtrare. Quindi ho aggiunto un'istruzione where per ridurre ulteriormente le righe filtrandometa_key

Gli ID finiscono bene in un array secondo necessità per get_posts.

PS. MySQL v5.6 o versioni successive è necessario per buone prestazioni di subquery


1

Questo esempio mi ha davvero aiutato. È specifico per il plugin S2Members (che serializza i metadati dell'utente). Ma ti permette di interrogare una porzione di un array serializzato all'interno della meta_key.

Funziona usando la funzione MySQL REGEXP.

Ecco la fonte

Ecco il codice che interroga tutti gli utenti che vivono negli Stati Uniti. L'ho modificato facilmente per eseguire una query su uno dei miei campi di registrazione personalizzati e farlo funzionare in pochissimo tempo.

  <?php
global $wpdb;
$users = $wpdb->get_results ("SELECT `user_id` as `ID` FROM `" . $wpdb->usermeta . 
          "` WHERE `meta_key` = '" . $wpdb->prefix . "s2member_custom_fields' AND 
           `meta_value` REGEXP '.*\"country_code\";s:[0-9]+:\"US\".*'");
if (is_array ($users) && count ($users) > 0)
    {
        foreach ($users as $user)
            {
                $user = /* Get full User object now. */ new WP_User ($user->ID);
                print_r($user); /* Get a full list of properties when/if debugging. */
            }
    }
?>

1

Penso che ci siano 2 soluzioni che possono tentare di risolvere il problema dei risultati archiviati sia come String sia come interi. Tuttavia, è importante dire, come altri hanno sottolineato, che non è possibile garantire l'integrità dei risultati archiviati come Numero intero, poiché poiché questi valori vengono archiviati come array serializzati, l'indice e i valori vengono archiviati esattamente con lo stesso modello. Esempio:

array(37,87);

viene archiviato come un array serializzato, in questo modo

a:2:{i:0;i:37;i:1;i:87;}

Nota i:0come prima posizione dell'array e i:37come primo valore. Lo schema è lo stesso. Ma andiamo alle soluzioni


1) Soluzione REGEXP

Questa soluzione funziona per me indipendentemente dal meta valore salvato come stringa o numero / ID. Tuttavia utilizza REGEXP, che non è così veloce come l'utilizzoLIKE

$args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => '\;i\:' . $value . '\;|\"' . $value . '\";',
            'compare' => 'REGEXP'
        )
    )
);

2) COME soluzione

Non sono sicuro della differenza di prestazioni, ma questa è una soluzione che utilizza LIKEe funziona anche per numero e stringhe

 $args = array(
        'post_type' => 'my-post-type',
        'meta_query' => array(
            'relation' => 'OR',
            array(
                'key' => 'latitude',
                'value' => sprintf(':"%s";', $value),
                'compare' => 'LIKE'
            ),
            array(
                'key' => 'latitude',
                'value' => sprintf(';i:%d;', $value),
                'compare' => 'LIKE'
            )
        )
    );

REGEXPè bello in certe situazioni, ma se puoi usarlo LIKE, penso che sia il metodo preferibile. Un vecchio link, ma ancora abbastanza utile, secondo me: thingsilearn.wordpress.com/2008/02/28/… :-)
Erenor Paz

@ErenorPaz Hai ragione. LIKEè più veloce. Ma questa è una soluzione che funziona sia per stringhe che per numeri
Pablo SG Pacheco,

Sì..quindi, la risposta è (come sempre): a seconda della situazione, se puoi usare "MI PIACE"; è preferibile, altrimenti lo farà anche
REGEXP

@ErenorPaz, ho modificato la mia risposta aggiungendo una nuova soluzione che utilizza LIKEma funziona sia per i numeri che per le stringhe. Non sono sicuro delle prestazioni perché deve confrontare i risultati usandoOR
Pablo SG Pacheco

Esattamente !!! di cui ho bisogno per ottenere il risultato in questo modo .... Grazie amico !!!
Kuldip Makadiya,

0

Dopo aver letto una serie di suggerimenti per l'esecuzione di un WP_Queryfiltro per array serializzati, ecco come l'ho finalmente fatto: creando un array di valori separati da virgola utilizzando implode insieme a una $wpdbquery SQL personalizzata che utilizza FIND_IN_SETper cercare l'elenco separato da virgola per il valore richiesto.

(è simile alla risposta di Tomas, ma è un po 'meno impegnativo per le prestazioni della query SQL)

1. In Functions.php:

Nel tuo file Functions.php (o ovunque tu stia configurando la meta-box) nella yourname_save_post()funzione usa

update_post_meta($post->ID, 'checkboxArray', implode(",", $checkboxArray)); //adding the implode

per creare l'array contenente valori separati da virgola.

Ti consigliamo anche di modificare la variabile di output nella yourname_post_meta()funzione di costruzione della meta box admin in

$checkboxArray = explode(",", get_post_custom($post->ID)["checkboxArray"][0]); //adding the explode

2. Nel file PHP modello:

Test: se esegui un get_post_meta( $id );, dovresti vedere checkboxArrayun array contenente i tuoi valori separati da virgola invece di un array serializzato.

Ora costruiamo la nostra query SQL personalizzata utilizzando $wpdb.

global $wpdb;

$search = $post->ID;

$query = "SELECT * FROM wp_posts
          WHERE FIND_IN_SET( $search, (
              SELECT wp_postmeta.meta_value FROM wp_postmeta
              WHERE wp_postmeta.meta_key = 'blogLocations'
              AND wp_postmeta.post_id = wp_posts.ID )
          )
          AND ( wp_posts.post_type = 'post' )
          AND ( wp_posts.post_status = 'publish' );";

$posts = $wpdb->get_results($query);

foreach ($posts as $post) {
    //your post content here
}

Notate il FIND_IN_SET, ecco dove accade la magia.

Ora ... dal momento che sto usando SELECT *questo restituisce tutti i dati dei post e all'interno foreachpuoi riecheggiare quello che vuoi da quello (fai un print_r($posts);se non sai cosa è incluso. Non imposta "il ciclo" per tu (lo preferisco in questo modo), ma può essere facilmente modificato per impostare il ciclo se preferisci (dai un'occhiata al setup_postdata($post);codice, probabilmente dovrai cambiare SELECT *per selezionare solo gli ID dei post e $wpdb->get_resultsil $wpdbtipo corretto - - vedi il codice per $wpdbanche per informazioni su quel soggetto).

Whelp, ci è voluto un po 'di sforzo, ma poiché wp_querynon supporta il fare 'compare' => 'IN'valori serializzati o separati da virgola, questo shim è la tua migliore opzione!

Spero che questo aiuti qualcuno.


0

Se usi l' likeoperatore di confronto nella tua meta query, dovrebbe funzionare bene per guardare all'interno di un array serializzato.

$wp_user_search = new WP_User_Query(array(
    'meta_query' => array(
        array(
            'key'     => 'wp_capabilities',
            'value'   => 'subscriber',
            'compare' => 'not like'
            )
        )
    )
);

risulta in:

[query_where] => WHERE 1=1 AND (
  ( wp_usermeta.meta_key = 'wp_capabilities' 
  AND CAST(wp_usermeta.meta_value AS CHAR) NOT LIKE '%subscriber%' )

0

Se i miei metadati sono di tipo array, sto usando questo metodo per interrogare per meta:

$args = array(
    'post_type' => 'fotobank',
    'posts_per_page' => -1,
    'meta_query' => array(
            array(
                   'key' => 'collections',
                   'value' => ':"'.$post->ID.'";',
                   'compare' => 'LIKE'
            )
     )
);
$fotos = new WP_Query($args);

Ciò potrebbe portare a risultati indesiderati quando un ID post ha lo stesso valore dell'ID della stringa serializzata
Erenor Paz

0

Mi sono incuriosito dalle risposte di cui sopra, in cui meta_queryla chiave era la chiave latitudeanziché _coordinates. Ho dovuto andare a testare se era davvero possibile in meta query indirizzare una chiave specifica all'interno di un array serializzato. :)

Ovviamente non era così.

Quindi, si noti che la chiave corretta per target è _coordinatesinvece di latitude.

$args = array(
     'post_type' => 'my-post-type',
     'meta_query' => array(
         array(
             'key' => '_coordinates',
             'value' => sprintf(':"%s";', $value),
             'compare' => 'LIKE'
         )
     )
 );

GLI APPUNTI:

  1. Questo approccio consente solo di individuare corrispondenze esatte. Quindi cose come tutte le latitudini maggiori di 50 non sono possibili.

  2. Per includere le corrispondenze di sottostringa, si potrebbe usare 'value' => sprintf(':"%%%s%%";', $value),. (non testato)


-1

Ho la stessa domanda Forse hai bisogno del parametro 'type'? Dai un'occhiata a questa domanda correlata: Query di campo personalizzata - Meta Value è array

Forse prova:

    $ args = array (
    'post_type' => 'my-post-type',
    'meta_query' => array (
        array (
            'key' => 'latitude',
            'value' => '50',
            'compare' => '>',
            'type' => 'numeric'
        )
    )
    );

Grazie per il suggerimento, ma non è proprio quello che sto cercando. Il problema è che il valore che sto cercando di abbinare fa parte di un array serializzato all'interno del database.
tollmanz

Sì, hai ragione. L'ho provato stamattina e non ha funzionato neanche per me. Ho lo stesso problema. Memorizzare un valore di una meta chiave come un array. Sto iniziando a pensare che questo non possa essere fatto e potrei invece doverli archiviare come metadati separati con lo stesso nome ... e gestire semplicemente la loro cancellazione / aggiornamento in modo corretto.
user4356,

@ user4356 ... è esattamente quello che farò. Speravo di ridurre il numero di righe che avrei inserito per ogni post, ma immagino che non sia possibile.
tollmanz

-1

Mi sono imbattuto in qualcosa di simile durante l'utilizzo del plug-in Magic Fields. Questo potrebbe fare il trucco

$values_serialized = serialize(array('50'));
$args = array(
    'post_type' => 'my-post-type',
    'meta_query' => array(
        array(
            'key' => 'latitude',
            'value' => $values_serialized,
            'compare' => '>'
        )
    )
);

1
Grazie per il suggerimento! Penso che questo sia il più vicino possibile, ma in realtà non funzionerà perché il confronto di un array serializzato con un altro array serializzato non ha senso a meno che non stia cercando una corrispondenza esatta.
tollmanz

5
Quindi questa non dovrebbe essere contrassegnata come la risposta corretta ed è irresponsabile che tu lo faccia. La risposta corretta sarebbe quindi "No, non è possibile"
Tom J Nowell

1
D'accordo, anche WP gestisce la serializzazione per te, serialize()in questo caso non è richiesto ...
Adam,

2
In realtà la risposta di @ seth-stevenson è ottima quando si fa esattamente quello che ha detto, usando il plugin "Magic Fields". Poiché tale plug-in serializza determinati tipi di dati per impostazione predefinita, questo è il modo migliore per eseguire una corrispondenza EXACT.
Zmonteca,

@TomJNowell Done! Mi ci sono voluti solo 5 mesi;)
tollmanz
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.