web-dev-qa-db-fra.com

La méta comparaison avec la date (stockée sous forme de chaîne) ne fonctionne

J'ai un type de message personnalisé qui contient un certain nombre de boîtes de méta. L’un d’eux est un champ expiration_date qui stocke ses informations dans la base de données sous forme de chaîne au format

Feb 1, 2017

J'essaie d'écrire une requête qui filtrera les éléments qui ont une date dans le passé, en ne montrant que ceux à venir. J'ai essayé plusieurs versions de la requête suivante et aucune ne fonctionne:

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

La requête renvoie tous les articles, quelle que soit leur date, ou zéro si j'inverse le carot (<)

Je sais que pour utiliser les champs de date, WP s'attend à ce qu'il soit dans un format différent (AAAA-MM-JJ), mais ce n'est pas ainsi que cette application a été construite (par quelqu'un d'autre). ci-dessus est ce que je travaille avec.

La question est donc la suivante: existe-t-il un moyen de convertir la date et l'heure AVANT la comparaison? Si je retire le champ par lui-même dans n'importe quel enregistrement, je peux le convertir (en utilisant strtotime), mais bien sûr, j'essaie de filtrer les valeurs en fonction de la date avant affichage afin d'avoir le bon numéro dans l'ensemble pour pouvoir travailler avec début.

4
Stephen

Présentation de STR_TO_DATE

MySQL a une fonction STR_TO_DATE que vous pouvez utiliser pour la portée.

Pour l'utiliser, vous devez filtrer la clause WHERE d'un WP_Query, probablement à l'aide de posts_where filter.

Cette fonction permet de formater une valeur de colonne sous forme de date en utilisant un format spécifique.

Par défaut, WordPress utilise la fonction CAST MySQL qui s'attend à ce que la valeur soit au format Y-m-d H:i:s (selon les arguments PHP date).

STR_TO_DATE, au contraire, permet de passer un format spécifique. Le format "espaces réservés" n'est pas le même que PHP utilise, vous pouvez voir les espaces réservés supportés au format MySQL ici: https://dev.mysql.com/doc/refman/5.5/fr/date-and -time-functions.html # format_date_fonction .

Flux de travail

Ce que je peux suggérer comme flux de travail est:

  • créer un objet WP_Query
  • utilisez posts_where pour ajouter une clause WHERE qui utilise STR_TO_DATE
  • supprime le filtre et exécute la requête afin d'éviter toute autre requête

Notez que STR_TO_DATE peut également être utilisé pour ordonner des valeurs. Si vous souhaitez le faire, vous devrez utiliser posts_orderby filter.

La classe de filtrage

Voici une classe (PHP 7+) qui enveloppe le flux de travail décrit ci-dessus pour faciliter sa réutilisation:

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

Désolé si cela n'inclut pas beaucoup de commentaires ou d'explications, il est difficile d'écrire du code ici.

Où la "magie" se produit

Le "noyau" de la classe est le filtre qui est ajouté à posts_where avant l'exécution de la requête, puis supprimé. Il est:

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

Vous pouvez voir ici comment nous demandons à WordPress d’interroger les publications. Notez que STR_TO_DATE est un calcul qui ne vient pas gratuitement, et sera assez lourd (spécialement pour le processeur) dans le cas de plusieurs milliers (ou millions) de lignes.

Comment s'en servir

À propos, la classe peut ensuite être utilisée comme ceci:

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

Si vous souhaitez commander par un champ WordPress standard, par exemple post date, post title ..., vous pouvez simplement transmettre les arguments de requête order et orderby liés à la méthode DateFieldQueryFilter::createWpQuery().

Si vous souhaitez commander avec la même méta-valeur, la classe fournit DateFieldQueryFilter::orderByMeta() pour l'étendue.

Par exemple. dans le dernier extrait, vous créeriez la requête comme ceci:

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

Conclusion

Même si cela fonctionne, lorsque cela est possible, les dates doivent être enregistrées dans une base de données au format MySQL ou à des horodatages, car cela simplifie beaucoup toute opération de filtrage ou de commande.

En fait, il est beaucoup plus facile de convertir la date au format souhaité pour que la sortie après soit extraite de la base de données.

Même si le calcul a lieu dans un sens ou dans l’autre, faire après ne sera effectué que sur les enregistrements retournés, mais laisser MySQL effectuer le calcul dont il a besoin pour agir sur ** tous * les enregistrements, nécessitant beaucoup plus de travail.

Si vous stockez les dates au format MySQL, WordPress fournit une fonction, mysql2date() , qui peut être utilisée pour les convertir au format souhaité, ainsi que pour les paramètres régionaux.

6
gmazzap

Avez-vous essayé d'utiliser meta_query dans WP_Query?

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

assurez-vous également que la date dans la table postmeta est du même format que celui que vous transmettez en tant que valeur dans meta_query.

1
Aamer Shahzad

J'ai fini par contourner le problème du format de date initial en modifiant le format du champ expiration_date en AAAA-MM-JJ (puis en modifiant l'affichage de ce champ ailleurs afin qu'il reste dans le format d'origine lorsque les utilisateurs le consultent). Alors maintenant, une meta_query normale utilisant DATETIME fonctionnera:

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

J'aimerais quand même une réponse à ce qui précède, s'il en existe une, où la valeur peut être convertie à la chaîne avant la comparaison. Je suppose donc que je vais accepter d'accepter ma propre réponse un peu dans l'espoir.

1
Stephen

Peut-être que vous pourriez essayer quelque chose comme ça?

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

Cela me permet d’obtenir uniquement des événements qui sont encore à venir.

EDIT: Je dois m'excuser, cela ne fonctionne pas. Je conduisais à un rendez-vous, quand j'ai réalisé que le tri avait été fait. Au bas de mon dossier, il y avait une déclaration if laissée de mon essai précédent, qui faisait le tri et la commande. Suppression de cette ligne et elle ne fonctionne plus ...

0
Hamsterdrache

La solution intelligente consisterait à utiliser le format de champ expiration_date pour YYYY-MM-DD.

De cette façon, vous pourrez créer de futures méta-requêtes WordPress sans le problème.

La conversion devrait être un jeu d'enfant.

0
prosti

Vous pouvez faire date_parse_from_format pour convertir le format de date:

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

Ensuite, vous pouvez reformer le tableau en date ("Y-m-d").

La structure complète du tableau est la suivante:

[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