web-dev-qa-db-ja.com

直列化配列としてのメタ値を持つmeta_query

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

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

どういうわけか、私は各メタボックスに対して単一のpostmetaエントリを持つという考えが好きでした。 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を利用する方法がありますか?

36
tollmanz

いいえ、それは不可能であり、そして危険でさえあるかもしれません。

データのシリアル化を解除し、保存ルーチンを変更することを強くお勧めします。これに似たものがあなたのデータを新しいフォーマットに変換するはずです。

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

そうすれば、個々のキーを使って必要に応じてクエリを実行できます。

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

なぜ直列化されたデータの内側をクエリできないのですか?

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

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

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

レコード/エンティティ/オブジェクトを直列化オブジェクトとしてメタに格納する際の注意

トランザクションレコードをpost metaに、あるいは他の種類のデータ構造をuser metaに格納してから、上記の問題に遭遇することがあります。

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

セキュリティと直列化オブジェクト

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

代わりにJSONを使用することでこれを回避できます。これによりクエリも容易になりますが、データを正しく格納し、構造化された直列化データを最初から回避する方がはるかに簡単です。

36
Tom J Nowell

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

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

この助けを願っています

23
rabni

WPデータベースへのエントリをシリアル化すると、実際には効率的な方法でデータをクエリすることができなくなります。

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

代わりに、そのような性質で問い合わせるつもりはないデータのシリアライゼーションを保存してください。代わりに直接WPAPI呼び出しget_post_meta()によるパッシブな方法でのみアクセスしてくださいその配列プロパティにもアクセスします。

実際、 true の値を次のように割り当てました。

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

データを配列として返します。通常どおりに繰り返してアクセスできます。

必要に応じて、キャッシング、CSSおよびJSの縮小、CDNなどのサービスの使用など、他のデータベース/サイトの最適化に集中できます。ほんの数例を挙げれば.... WordPress Codexはその話題についてもっと知るための良い出発点です: _ here _

10
userabuser

直列化されたフィールドを扱ったばかりで、それらを照会することができました。 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でpostを検索するため、wp_postmetaレコードの量が少なくなります。それから私はmeta_keyでフィルタリングすることによってさらに行を減らすためにwhereステートメントを追加しました

Get_postsの必要に応じて、IDは配列に収まります。

PS。副照会のパフォーマンスを向上させるには、MySQL v5.6以降が必要です。

3
Tomas

私は、結果が文字列と整数の両方として格納されるという問題を解決することを試みることができる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 Solution

パフォーマンスの違いについてはよくわかりませんが、これは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'
            )
        )
    );
1

この例は私を本当に助けました。これは特にS2Membersプラグイン(ユーザーのメタデータをシリアル化する)のためのものです。しかし、それによってmeta_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
BC Smith

シリアル化された配列でWP_Queryフィルタリングを実行するためのたくさんのヒントを読んだ後、最後にやり方を説明します:$wpdbを使用してFIND_IN_SETカスタムSQLクエリと組み合わせてカンマ区切り値の配列を作成する方法要求された値.

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

1. functions.php:

Functions.phpファイル(またはメタボックスを設定している場所)のyourname_save_post()関数の中で

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

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

また、yourname_post_meta() adminメタボックス構築関数の出力変数を次のように変更します。

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

2.テンプレートPHP file内:

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

これで、$wpdbを使用してカスタムSQLクエリを作成しました。

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);をしてください。あなたには "the loop"を設定してはいけませんが(私はこうするのが好きです)、望むならループを設定するために簡単に変更することができます(codexのsetup_postdata($post);を見てください、おそらくSELECT *を変更する必要があります)投稿IDと$wpdb->get_resultsのみを正しい$wpdb型に選択するには - that subjectに関する情報についても$wpdbのコーデックスを参照してください)。

Whelp、少し手間がかかりましたが、wp_query'compare' => 'IN'のシリアル化値またはカンマ区切り値の実行をサポートしていないため、このシムは最善の選択肢です。

これが誰かに役立つことを願っています。

0
Gifford N.

meta_query_coordinatesではなくキーlatitudeをターゲットにしている上記の答えに興味がありました。シリアル化された配列内の特定のキーを対象とすることがメタクエリで本当に可能かどうかを調べてテストする必要がありました。 :)

それは明らかにそうではありませんでした。

そのため、正しいターゲットキーはlatitudeではなく_coordinatesです。

$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),を使用できます。 (テストしていません)

0
jgangso

あなたのメタクエリで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
benklocek

私のメタデータが配列型の場合、私はメタによる問い合わせにこのメソッドを使います。

$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);
0
Den Media