メタ値をシリアル化配列として持つmeta_query


37

私は、カスタム投稿タイプと、カスタム投稿タイプに関連付けられたメタボックスを介して入力されたカスタムデータを作成するプロジェクトに取り組んでいます。何らかの理由で、各メタボックスの入力が配列の一部になるようにメタボックスをコーディングすることにしました。たとえば、経度と緯度を保存しています:

<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>

何らかの理由で、各メタボックスに単一のポストメタエントリを作成するというアイデアが気に入りました。オンsave_postフック、私はそうのようなデータを保存します。

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

私がこれを行ったのは、3つのメタボックスがあり、各投稿に3つのpostmeta値があるだけだからです。しかし、これで潜在的な問題に気づきました。WP_Queryを使用して、これらのメタ値に基づいて特定の投稿のみを引き出したい場合があります。たとえば、緯度の値が50を超えるすべての投稿を取得したい場合があります。データベースにこのデータが個別にあり、おそらくキーを使用してlatitudeいた場合、次のようにします。

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

_coordinatesポストメタの一部として緯度があるので、これは機能しません。

だから、私の質問は、meta_queryこのシナリオで持っているようなシリアル化された配列を照会するために利用する方法はありますか?

回答:


37

いいえ、不可能であり、危険ですらあります。

データのシリアル化を解除し、保存ルーチンを変更することを強くお勧めします。これに似た何かがデータを新しい形式に変換するはずです:

$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);
    }
}

その後、必要に応じて個々のキーでクエリを実行できます

複数の経度と複数の緯度を保存する必要がある場合、同じ名前の複数の投稿メタを保存できます。の3番目のパラメータを使用するだけでget_post_meta、すべてが配列として返されます

シリアル化されたデータ内をクエリできないのはなぜですか?

MySQLはそれを単なる文字列と見なし、構造化されたデータに分解することはできません。構造化されたデータに分割することは、まさに上記のコードが行うことです

日付の部分的なチャンクをクエリできるかもしれませんが、これは非常に信頼性が低く、高価で、遅く、非常に壊れやすく、多くのエッジケースがあります。シリアル化されたデータは、SQLクエリを対象としておらず、定期的かつ一定の方法でフォーマットされていません。

部分的な文字列検索のコストは別として、ポストメタクエリは低速であり、シリアル化されたデータはコンテンツの長さなどによって変化する可能性があり、検索する値によっては不可能ではないにしても、非常に高価になります

レコード/エンティティ/オブジェクトをメタにシリアル化されたオブジェクトとして保存する際の注意

トランザクションレコードをポストメタに保存するか、ユーザーメタに他の種類のデータ構造を保存してから、上記の問題に遭遇する場合があります。

ここでの解決策は、個々の投稿メタに分割することではなく、最初はメタではなく、カスタム投稿タイプであるべきであると認識することです。たとえば、ログまたはレコードは、元の投稿を親とするカスタム投稿タイプにすることも、分類用語を介して結合することもできます

セキュリティとシリアル化されたオブジェクト

serialize関数を介してシリアル化されたPHPオブジェクトを保存することは危険です。これは、オブジェクトをWordPressに渡すとシリアル化されることを意味するため、残念です。これは、オブジェクトが逆シリアル化されると、オブジェクトが作成され、そのすべてのウェイクアップメソッドとコンストラクターが実行されるためです。これは、ユーザーが慎重に細工された入力をこっそりとこなし、データがデータベースから読み取られ、WordPressで逆シリアル化されると、リモートでコードが実行されるまで、大したことではないように思われます。

これは、代わりにJSONを使用することで回避できます。これにより、クエリも簡単になりますが、データを正しく保存し、構造化されたシリアル化されたデータを最初から回避する方がはるかに簡単/高速です。


5
下に発見され、より便利な(そして最近の)答えを:渡し人々のために、読み取りを停止しないでください
Erenorパズ

保存するIDの配列があり、それらがそれぞれ「緯度」などのように保存できる異なるキーを表していない場合、それはすべてに対して1つのキーになります(リレーションシップを保存するときなど)。次に何をしますか?@rabniのソリューション?
trainoasis

1
キーを複数回保存できます。キーと値のペアは一意ではありません。リレーションについては、それがタクソノミーの目的です。メタを使用して複数のものを何かにマッピングする場合は、代わりにタクソノミー用語に入れます
トムJノウェル

24

私もこの状況に遭遇します。ここで私がやったこと:

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

この助けを願っています


1
このソリューションは本当に気に入りました。残念ながら、これ$valueもIDである場合には適用できません。その場合、データを保存する前に各配列要素に文字を追加する関数と、データを使用する前に文字を削除する別の関数を作成することをお勧めします。このワット、シリアル化されたi:2インデックスはi:D2、「実際の」データと混同されません。メタクエリパラメータは次のようになり'value' => sprintf(':"D%s";', $value),、このすばらしい答えの正しい機能を維持します!
エレノールパス

このソリューションは、私のために働いている
ヴィシャル

これも私にとって完璧に機能しました。受け入れられた解決策を見たときに小さなパニックがありました
シェーンジョーンズ

@Erenor Paz、IDと文字
Pablo SG Pacheco

使用LIKEは、サーバーをダウンさせるための優れた高速な方法です(誤検知は言うまでもありません)。非常に優れたキャッシングが必要です。
マーク・Kaplun

10

エントリをWPデータベースにシリアル化するときに、効率的な方法でデータをクエリする機能を本当に失います。

シリアル化によって達成していると思われる全体的なパフォーマンスの節約と向上は、それほど目立ったものではありません。わずかに小さいデータベースサイズを取得することもできますが、これらのフィールドを照会し、有用で意味のある方法で比較しようとすると、SQLトランザクションのコストが高くなります。

代わりに、その性質でクエリするつもりはないが、代わりに直接WP API呼び出しによってパッシブな方法でのみアクセスするデータのシリアル化を保存しますget_post_meta()-その関数から、シリアル化されたエントリをアンパックしてその配列プロパティにもアクセスできます。

実際、次のようにtrueの値が割り当てられています。

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

データを配列として返し、通常どおりに繰り返し処理できるようにします。

キャッシング、CSS、JSミニフィケーションなどの他のデータベース/サイトの最適化に焦点を当て、必要に応じてCDNとしてそのようなサービスを使用できます。ほんの数例を挙げましょう... WordPress Codexは、このトピックに関する詳細を明らかにするための良い出発点です:こちら


3

シリアル化されたフィールドを処理したばかりで、クエリを実行できました。meta_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);

クエリは、最初に一致するpost_typeの投稿を検索するので、wp_postmetaレコードの量はフィルタリングする量が少なくなります。次に、フィルタリングして行をさらに減らすためにwhereステートメントを追加しましたmeta_key

IDは、get_postsに必要な配列にうまく収まります。

PS。サブクエリのパフォーマンスを向上させるには、MySQL v5.6以降が必要です。


1

この例は本当に助けになりました。S2Membersプラグイン専用です(ユーザーメタデータをシリアル化します)。ただし、me​​ta_key内のシリアル化された配列の一部を照会できます。

MySQL REGEXP関数を使用して機能します。

ここにソースがあります

以下は、米国に住んでいるすべてのユーザーを照会するコードです。カスタム登録フィールドの1つを照会するように簡単に変更し、すぐに機能するようにしました。

  <?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

文字列と整数の両方として保存される結果の問題を解決しようとする2つの解決策があると思います。ただし、他の人が指摘したように、整数として保存された結果の整合性を保証することはできないと言うことが重要です。これらの値はシリアル化された配列として保存されるため、インデックスと値はまったく同じパターンで保存されるためです。例:

array(37,87);

このように、シリアル化された配列として保存されます

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

i:0配列の最初の位置とi:37最初の値として注意してください。パターンは同じです。しかし、ソリューションに行きましょう


1)REGEXPソリューション

このソリューションは、文字列または数値/ IDとして保存されているメタ値に関係なく機能します。ただし、を使用するREGEXPので、使用するほど高速ではありませんLIKE

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

2)LIKEソリューション

パフォーマンスの違いについてはわかりませんが、これはLIKE数値と文字列の両方を使用して動作するソリューションです

 $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特定の状況では便利ですが、を使用できる場合LIKEは、望ましい方法だと思います。私の意見では、古いリンクですが、まだ非常に便利です:thingsilearn.wordpress.com/2008/02/28/… :-)
Erenor Paz

@ErenorPazあなたは正しい。LIKEより速いです。しかし、これは文字列と数値の両方で機能するソリューションです
パブロ・SGパチェコ

はい。そのため、答えは(いつものように)です。状況に応じて、「LIKE」を使用できるかどうか。それ以外の場合はREGEXPも同様です:-)
Erenor Paz

@ErenorPaz、答えを編集してLIKE、数字と文字列の両方を使用するが機能する新しいソリューションを追加しました。それは使用して結果を比較することがあるので、私はパフォーマンスについてはよく分からないOR
パブロSGパチェコ

まさに!!! このような結果を得る必要があります。ありがとうございます!!!
クルディップマカディヤ

0

WP_Queryシリアル化された配列でフィルター処理を実行するための一連のヒントを読んだ後、私は最終的にそれをどのように実行しましたか:要求された値のコンマ区切りリストを検索$wpdbするFIND_IN_SETために利用するカスタムSQLクエリと組み合わせてimplodeを使用して、コンマ区切り値の配列を作成することによって。

(これはTomasの答えに似ていますが、SQLクエリのパフォーマンスが少し低下します)

1. functions.phpで:

functions.phpファイル(またはメタボックスを設定する場所)で、yourname_save_post()関数を使用します

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

コンマ区切り値を含む配列を作成します。

また、yourname_post_meta()管理メタボックス構築関数の出力変数を次のように変更することもできます。

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

2.テンプレートPHPファイルで:

テスト:を実行すると、シリアル化された配列ではなく、コンマで区切られた値を含む配列としてget_post_meta( $id );表示さcheckboxArrayれます。

次に、を使用してカスタムSQLクエリを作成します$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
}

に注意してくださいFIND_IN_SET、それは魔法が起こるところです。

今...私はSELECT *これを使用しているので、すべての投稿データを返し、その中からforeachあなたが望むものをエコーアウトすることができます(print_r($posts);何が含まれているかわからない場合は行います。「ループ」を設定しませんあなた(私はこの方法が好きです)が、setup_postdata($post);必要に応じて簡単に変更してループを設定できます(コーデックスを見て、おそらくSELECT *投稿IDのみを選択$wpdb->get_resultsして正しい$wpdbタイプに変更する必要があります- - その主題$wpdbに関する情報についてもコーデックスを参照してください)。

Whelp、それは少し手間がかかりましたが、シリアル化された値またはコンマ区切りの値のwp_query実行をサポートしていないため、'compare' => 'IN'このシムが最良のオプションです!

これが誰かを助けることを願っています。


0

likeメタクエリで比較演算子を使用する場合、シリアル化された配列の内部を見るのに問題なく動作するはずです。

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

結果:

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

0

メタデータが配列型の場合、メタによるクエリにはこのメソッドを使用します。

$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);

ポストIDは、シリアライズされた文字列のidと同じ値があるとき、これは、不要な結果につながる可能性
Erenorパズ

0

の代わりにmeta_queryキーlatitudeをターゲットにした上記の答えに興味がありました_coordinates。メタクエリで、シリアル化された配列内の特定のキーをターゲットにすることが本当に可能かどうかをテストする必要がありました。:)

明らかにそうではなかった。

したがって、ターゲットの正しいキーはの_coordinates代わりになりlatitudeます。

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

ノート:

  1. このアプローチでは、完全一致のみをターゲットにすることができます。したがって、すべての緯度が50超えるようなことは不可能です。

  2. 部分文字列の一致を含めるには、を使用できます'value' => sprintf(':"%%%s%%";', $value),。(テストしていない)


-1

同じ質問があります。たぶん、「タイプ」パラメーターが必要ですか?この関連する質問を確認してください: カスタムフィールドクエリ-メタ値は配列です

おそらく試してみてください:

    $ args = array(
    'post_type' => 'my-post-type'、
    'meta_query' =>配列(
        アレイ(
            「キー」=>「緯度」、
            '値' => '50'、
            '比較' => '>'、
            「タイプ」=>「数値」
        )
    )
    );

提案をありがとう、しかしこれは私が望んでいるものではありません。問題は、照合しようとしている値がデータベース内でシリアル化されている配列の一部であることです。
トールマンズ

そうだね。今朝これを試してみたが、うまくいかなかった。同じ問題があります。メタキーの値を配列として保存します。私はこれができないと考え始めており、代わりに同じ名前の別々のメタフィールドとして保存する必要があるかもしれません...そしてそれらの削除/更新を適切に管理するだけです。
user4356

@ user4356 ...それがまさに私がやろうとしていることです。各投稿に挿入する行数を削減したいと思っていましたが、それは不可能だと思います。
トールマンズ

-1

Magic Fieldsプラグインを使用しているときに、似たようなことが起こりました。これはトリックをするかもしれません

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

1
提案をありがとう!これは可能な限り近いと思いますが、完全に一致するものを探している場合を除き、シリアル化された配列を別のシリアル化された配列と比較しても意味がないため、実際には機能しません。
トールマンズ

5
その場合、これは正しい答えとしてマークされるべきではなく、そうすることはあなたにとって無責任です。したがって、正しい答えは「いいえ、できません」
トムJノウェル

1
あなたのためのWPのハンドルのシリアライズも、同意、serialize()...この例では必要ありません
アダム

2
実際、@ seth-stevensonの答えは、「Magic Fields」プラグインを使用して、彼が言ったことを正確に実行するときに最適です。このプラグインはデフォルトで特定のデータ型をシリアル化するため、これは完全一致を行うための最良の方法です。
zmonteca

@TomJNowell完了!ちょうど5ヶ月かかった;)
トールマンズ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.