web-dev-qa-db-ja.com

日付とメタ比較(文字列として格納)が機能しない

カスタム投稿タイプがあり、その中にいくつかのメタボックスがあります。そのうちの1つはexpiration_dateフィールドで、その情報を次のようにフォーマットされた文字列としてdbに格納します。

Feb 1, 2017

過去の日付を持つアイテムを除外し、将来のアイテムのみを表示するクエリを作成しようとしています。私は次のクエリのいくつかのバージョンを試してみましたが、どれも動作しません。

$myquery = array (
'post_type' => $cpt,
    'numberposts'=>-1,
    'meta_key' => 'expiration_date',
    'meta_value' => date('M j, Y'),
    'meta_compare' => '<'
    ),
 );

クエリは日付に関係なくすべての投稿を返します。またはcarotを反転した場合は投稿を0件(<)にします。

日付フィールドWPを使用するには、別の形式(YYYY-MM-DD)になることを想定していますが、これはこのアプリが他のユーザーによって作成された方法ではないため上記は私が取り組んでいるものです。

だから質問は:比較する前に日時を変換する方法はありますか?任意のレコードでフィールドを自分で取り出す場合は、(strtotimeを使用して)変換できますが、もちろん表示前の日付に基づいて値を除外しようとしているので、セットで正しい数字を使用して作業します。開始。

4
Stephen

STR_TO_DATEの紹介

MySQLには、スコープに活用できる STR_TO_DATE 関数があります。

これを使用するには、おそらく WP_Query フィルターを使用して、posts_whereWHERE句をフィルターする必要があります。

この関数を使用すると、特定の形式を使用して列値を日付としてフォーマットできます。

デフォルトでは、WordPressは、値がY-m-d H:i:s(PHP CAST引数による)形式であることを期待する date MySQL関数を使用します。

反対に、STR_TO_DATEは特定の形式を渡すことができます。 「プレースホルダー」の形式はPHPの使用と同じではありません。サポートされているMySQL形式のプレースホルダーは次のとおりです。 https://dev.mysql.com/doc/refman/5.5/en/date -and-time-functions.html#function_date-format

ワークフロー

ワークフローとして提案できるのは:

  • WP_Queryオブジェクトを作成します
  • posts_whereを使用して、STR_TO_DATEを使用するWHERE句を追加します
  • 他のクエリへの影響を避けるため、クエリを実行するフィルターを削除します

STR_TO_DATEは値の順序付けにも使用できることに注意してください。そうしたい場合は、 posts_orderby filterを使用する必要があります。

フィルタリングクラス

ここでは、簡単に再利用できるように上記のワークフローをラップするクラス(PHP 7以降)があります。

class DateFieldQueryFilter {

    const COMPARE_TYPES = [ '<', '>', '=', '<=', '>=' ];
    private $date_key;
    private $date_value;
    private $format;
    private $compare = '=';
    private $order_by_meta = '';

    public function __construct(
        string $date_key,
        string $date_value,
        string $mysql_format,
        string $compare = '='
    ) {
        $this->date_key   = $date_key;
        $this->date_value = $date_value;
        $this->format     = $mysql_format;
        in_array($compare, self::COMPARE_TYPES, TRUE) and $this->compare = $compare;
    }

    public function orderByMeta(string $direction = 'DESC'): DateFieldQueryFilter {
        if (in_array(strtoupper($direction), ['ASC', 'DESC'], TRUE)) {
            $this->order_by_meta = $direction;
        }
        return $this;
    }

    public function createWpQuery(array $args = []): \WP_Query {
        $args['meta_key'] = $this->date_key;
        $this->whereFilter('add');
        $this->order_by_meta and $this->orderByFilter('add');
        $query = new \WP_Query($args);
        $this->whereFilter('remove');
        $this->order_by_meta and $this->orderByFilter('remove');
        return $query;
    }

    private function whereFilter(string $action) {
        static $filter;
        if (! $filter && $action === 'add') {
            $filter = function ($where) {
                global $wpdb;
                $where and $where .= ' AND ';
                $sql = "STR_TO_DATE({$wpdb->postmeta}.meta_value, %s) ";
                $sql .= "{$this->compare} %s";
                return $where . $wpdb->prepare($sql, $this->format, $this->date_value);
            };
        }
        $action === 'add'
            ? add_filter('posts_where', $filter)
            : remove_filter('posts_where', $filter);
    }

    private function orderByFilter(string $action) {
        static $filter;
        if (! $filter && $action === 'add') {
            $filter = function () {
                global $wpdb;
                $sql = "STR_TO_DATE({$wpdb->postmeta}.meta_value, %s) ";
                $sql .= $this->order_by_meta;
                return $wpdb->prepare($sql, $this->format);
            };
        }
        $action === 'add'
            ? add_filter('posts_orderby', $filter)
            : remove_filter('posts_orderby', $filter);
    }
}

これに多くのコメントや説明が含まれていない場合、申し訳ありませんが、ここにコードを書くことは困難です。

「魔法」が起こる場所

クラスの「コア」は、クエリが実行される前にposts_whereに追加され、その後削除されるフィルターです。それは:

function ($where) {
    global $wpdb;
    $where and $where .= ' AND ';
    // something like: STR_TO_DATE(wp_postmeta.meta_value,'%b %e, %Y') < 'Feb 1, 2017'
    $sql = "STR_TO_DATE({$wpdb->postmeta}.meta_value, %s) {$this->compare} %s";

    return $where . $wpdb->prepare($sql, $this->format, $this->date_value);
};

ここで、WordPressに投稿を照会するよう指示している方法を確認できます。 STR_TO_DATEは無料ではない計算であり、数千(または数百万)の行がある場合は非常に重い(特にCPUの場合)ことに注意してください。

それを利用する方法

ちなみに、クラスは次のように使用できます。

$query_filter = new DateFieldQueryFilter (
    'expiration_date', // meta key
    date('M j, Y'),    // meta value
    '%b %e, %Y',       // date format using MySQL placeholders
    '<'                // comparison to use
);

$query = $query_filter->createWpQuery(['posts_per_page' => - 1]);

while($query->have_posts()) {
     $query->the_post();
     // rest of the loop here
}

標準のWordPressフィールドで注文したい場合、例えば投稿日、投稿タイトル...、関連するorderおよびorderbyクエリ引数をDateFieldQueryFilter::createWpQuery()メソッドに簡単に渡すことができます。

同じメタ値を使用して注文する場合、クラスはスコープにDateFieldQueryFilter::orderByMeta()を提供します。

例えば。最後のスニペットでは、次のようなクエリを作成します。

$query = $query_filter
             ->orderByMeta('DESC')
             ->createWpQuery(['posts_per_page' => - 1]);

結論

これが機能しても、可能であれば、MySQL形式またはタイムスタンプを使用してデータベースに日付を保存する必要があります。これにより、フィルタリング操作や順序付け操作が多数簡素化されるためです。

実際、データベースから取得した後の出力afterに、希望する形式で日付を変換する方がはるかに簡単です。

いずれかの方法で計算が行われたとしても、afterそれは返されたレコードに対してのみ行われますが、MySQLが**すべての*レコードに対して実行する必要がある計算を実行できるようにします。より多くの作業が必要です。

日付をMySQL形式で保存する場合、WordPressは関数 mysql2date() を提供します。この関数は、日付を目的の形式に変換するために使用でき、ロケールにも対応しています。

6
gmazzap

WP_Queryでmeta_queryを使用しましたか?

$args = array(
    'post_type'  => $cpt,
    'meta_query' => array(
        array(
            'key'     => 'expiration_date',
            'value'   => date('M j, Y'),
            'compare' => '<',
        ),
    ),
);
$query = new WP_Query( $args );

また、postmetaテーブルの日付がmeta_queryの値として渡したものと同じフォーマットであることを確認してください。

1
Aamer Shahzad

Expiration_dateフィールドの形式をYYYY-MM-DDに変更して(そしてエンドユーザーが表示したときに元の形式のままになるように他の場所でそのフィールドの表示を変更することによって)初期日付形式の問題を回避しました。だから今DATETIMEを使用して通常のmeta_queryが動作します:

$myquery = array (
'post_type' => $cpt,
    'numberposts'=>-1,
    'meta_key' => 'expiration_date',
    'type'=> 'DATETIME',
    'meta_value' => date("Y-m-d"),
    'meta_compare' => '<'
    ),
 );

値が比較の前にオンザフライで文字列変換されることができるものがあるなら、私はまだ上記の答えを望みます、それで私はその答えを期待して少し私自身の答えを受け入れるのを差し控えると思います。

1
Stephen

たぶん、あなたはこのようなことを試すことができますか?

$args2 = array( 
            'post_type'     => $cpt,
            'posts_per_page'=> -1, 
            'meta-key' => 'expiration_date',
            'meta_query' => Array ( 
                                Array ('key' => 'expiration_date', 
                                        'compare' => '>=', 
                                        'value' => date('M j, Y',strtotime('now')), //strtotime for value for now, date() to format the timestamp
                                            'type' => 'DATETIME'
                                            ) 
                                            ),
            'orderby'       =>   'meta_value', 
            'order'         =>   'ASC' //or DESC
            );

これは、将来にまだあるイベントだけを取得するのに役立ちます。

編集:謝罪しなければならない、これは動作しません。並べ替えが実際に行われている場所に気付いたとき、私は予定を運転していました。私の前の試みから残っていたifステートメントはソートと順序付けをしていました。その行を削除し、それが動作を停止しました...

0
Hamsterdrache

スマートな解決策は、expiration_dateYYYY-MM-DDフィールドフォーマットを使用することです。

こうすればあなたは未来の WordPressメタクエリ を問題なく作ることができるでしょう。

変換は簡単です。

0
prosti

Date_parse_from_formatを使って日付フォーマットを変換することができます。

date_parse_from_format('M j, Y', 'Feb 1, 2017');

それから、配列を日付( "Y-m-d")として書き直すことができます。

完全な配列構造は次のとおりです。

[year] =>  
[month] => 
[day] =>  
[hour] =>  
[minute] => 
[second] =>
[fraction] =>  
[warning_count] =>  
[warnings] => Array() 
[error_count] => 
[errors] => Array() 
[is_localtime] => 
[zone_type] => 
[zone] => 
[is_dst] =>
0
Mudy S