Najbardziej wydajny sposób na otrzymywanie postów za pomocą postmeta

36

Muszę zdobyć kilka postów z ich metadanymi. Oczywiście nie można uzyskać metadanych za pomocą standardowego zapytania do postów, więc na ogół musisz zrobić get_post_custom()dla każdego postu.

Próbuję z jednym niestandardowym zapytaniem, takim jak to:

$results = $wpdb->get_results("
    SELECT  p.ID,
        p.post_title,
        pm1.meta_value AS first_field,
        pm2.meta_value AS second_field,
        pm3.meta_value AS third_field
    FROM    $wpdb->posts p LEFT JOIN $wpdb->postmeta pm1 ON (
            pm1.post_id = p.ID  AND
            pm1.meta_key    = 'first_field_key'
        ) LEFT JOIN $wpdb->postmeta pm2 ON (
            pm2.post_id = p.ID  AND
            pm2.meta_key    = 'second_field_key'
        ) LEFT JOIN $wpdb->postmeta pm3 ON (
            pm3.post_id = p.ID  AND
            pm3.meta_key    = 'third_field_key'
        )
    WHERE   post_status = 'publish'
");

Wydaje się działać. Występuje, jeśli użyjesz któregokolwiek z tych pól meta w sposób, który pozwala na wiele meta wartości dla tego samego postu. Nie mogę wymyślić takiego połączenia.

Pytanie 1: Czy istnieje sprzężenie, zapytanie cząstkowe lub cokolwiek innego, aby wprowadzić pola meta o wielu wartościach?

Ale pytanie 2: czy warto? Ile postmetasprzężeń tabel dodam, zanim podejście z dwoma zapytaniami stanie się preferowane? Mogłem pobrać wszystkie dane posta w jednym zapytaniu, a następnie pobrać odpowiednią postmetę w innym i połączyć meta z danymi postu w jednym zestawie wyników w PHP. Czy skończyłoby się to szybciej niż pojedyncze, coraz bardziej złożone zapytanie SQL, jeśli to w ogóle możliwe?

Zawsze myślę: „Daj bazę danych jak najwięcej pracy”. Nie jestem pewien w tej sprawie!

Steve Taylor
źródło
Nie jestem pewien, czy w ogóle chcesz wykonać połączenia. kombinacja get_posts () i get_post_meta () daje te same dane z powrotem. W rzeczywistości jest mniej wydajne przy użyciu sprzężeń, ponieważ możesz pobierać dane, których później nie będziesz używać.
rexposadas
2
Czy i tak meta dane nie są buforowane automatycznie?
Manny Fleurmond 11.01.12
@rxn, jeśli mam kilkaset postów wracających (są to niestandardowe typy postów), to z pewnością jest to dość duże obciążenie bazy danych get_posts(), to get_post_meta()dla każdego z nich? @MannyFleurmond, trudno jest znaleźć twarde informacje na temat wbudowanego buforowania WP, ale AFAIK będzie buforować rzeczy na żądanie. Wezwanie do serwera, aby pobrać te dane, jest wywołaniem AJAX i nie sądzę, aby cokolwiek innego mogło przechwycić coś przed tym.
Steve Taylor
Właściwie idę na wiele zapytań i buforuję wyniki. Okazuje się, że potrzebujemy nie tylko post meta, w tym pól, które mają wiele wartości, potrzebujemy również danych o użytkownikach połączonych z postami za pomocą meta pól (dwóch ich zestawów), a także meta użytkownika na nich. Czysty SQL jest zdecydowanie za oknem!
Steve Taylor

Odpowiedzi:

58

Informacje meta po opublikowaniu są automatycznie buforowane w pamięci dla standardowego WP_Query(i głównego zapytania), chyba że wyraźnie powiesz mu, aby tego nie robił za pomocą update_post_meta_cacheparametru.

Dlatego nie powinieneś pisać w tym celu własnych zapytań.

Jak działa meta-buforowanie w przypadku normalnych zapytań:

Jeśli update_post_meta_cacheparametr parametru WP_Querynie jest ustawiony na false, to po pobraniu postów z bazy danych update_post_caches()zostanie wywołana funkcja, która z kolei wywoła update_postmeta_cache().

Ta update_postmeta_cache()funkcja jest opakowaniem update_meta_cache()i w zasadzie wywołuje prosty SELECTz wszystkimi pobranymi identyfikatorami postów. Spowoduje to, że pobierze całą postmetę dla wszystkich postów w zapytaniu i zapisze te dane w pamięci podręcznej obiektu (używając wp_cache_add()).

Kiedy robisz coś takiego get_post_custom(), najpierw sprawdza pamięć podręczną tego obiektu. Więc nie robi dodatkowych zapytań, aby uzyskać meta post w tym momencie. Jeśli dostałeś post w WP_Querymeta, to meta jest już w pamięci i od razu go pobiera.

Korzyści tutaj są wielokrotnie większe niż tworzenie złożonych zapytań, ale największą zaletą jest użycie pamięci podręcznej obiektów. Jeśli korzystasz z trwałego rozwiązania do buforowania pamięci, takiego jak XCache lub memcached lub APC lub coś takiego, i masz wtyczkę, która może powiązać z nim pamięć podręczną obiektu (na przykład W3 Total Cache), wtedy pamięć podręczna całego obiektu jest przechowywana w szybkiej pamięci już. W takim przypadku, nie ma zerowe zapytania niezbędne do pobierania danych; jest już w pamięci. Trwałe buforowanie obiektów jest niesamowite pod wieloma względami.

Innymi słowy, twoje zapytanie prawdopodobnie ładuje się i ładuje wolniej niż przy użyciu właściwego zapytania i prostego trwałego rozwiązania pamięci. Użyj normalnego WP_Query. Zaoszczędź sobie wysiłku.

Dodatkowo: update_meta_cache() jest inteligentny, BTW. Nie będzie pobierać meta informacji dla postów, które już mają swoje buforowane metadane. Zasadniczo nie otrzymuje tej samej meta dwa razy. Super wydajny.

Dodatkowe dodatkowe: „Daj bazę danych jak najwięcej pracy.” ... Nie, to jest sieć. Obowiązują różne zasady. Ogólnie rzecz biorąc, zawsze chcesz poświęcić jak najmniej pracy bazie danych, jeśli jest to wykonalne. Bazy danych są powolne lub źle skonfigurowane (jeśli nie skonfigurowałeś go specjalnie, możesz postawić dobre pieniądze, że to prawda). Często są one udostępniane wielu witrynom i do pewnego stopnia przeciążone. Zwykle masz więcej serwerów internetowych niż baz danych. Ogólnie rzecz biorąc, chcesz po prostu pobrać potrzebne dane z bazy danych tak szybko i prosto, jak to możliwe, a następnie posortować je za pomocą kodu po stronie serwera WWW. Zasadniczo różne przypadki są różne.

Otto
źródło
30

Poleciłbym zapytanie przestawne. Na twoim przykładzie:

SELECT  p.ID,   
        p.post_title, 
        MAX(CASE WHEN wp_postmeta.meta_key = 'first_field' then wp_postmeta.meta_value ELSE NULL END) as first_field,
        MAX(CASE WHEN wp_postmeta.meta_key = 'second_field' then wp_postmeta.meta_value ELSE NULL END) as second_field,
        MAX(CASE WHEN wp_postmeta.meta_key = 'third_field' then wp_postmeta.meta_value ELSE NULL END) as third_field,

 FROM    wp_posts p LEFT JOIN wp_postmeta pm1 ON ( pm1.post_id = p.ID)                      
GROUP BY
   wp_posts.ID,wp_posts.post_title
Ethan Seifert
źródło
Ta odpowiedź powinna być oznaczona jako poprawna.
Łukasza
Jeśli szukasz zapytania do bazy danych, to jest poprawna odpowiedź
Alex Popov
To zapytanie skróciło mój czas korzystania z WP_Query z ~ 25 sekund do ~ 3 sekund. Moim wymaganiem było wystrzelenie tego tylko raz, więc nie było potrzeby buforowania.
Kush
11

Natknąłem się na przypadek, w którym chcę również szybko pobrać wiele postów z powiązanymi z nimi meta informacjami. Muszę pobrać posty O (2000).

Wypróbowałem to, korzystając z sugestii Otto - uruchomiłem zapytanie WP_Query :: dla wszystkich postów, a następnie zapętliłem i uruchomiłem get_post_custom dla każdego postu. Zajęło to średnio około 3 sekund .

Następnie spróbowałem kwerendy przestawnej Ethana (chociaż nie lubiłem ręcznie pytać o każdy meta_key, który mnie interesował). Nadal musiałem przeglądać wszystkie pobrane posty, aby odserializować wartość meta_value. Zajęło to średnio około 1,3 sekundy .

Następnie spróbowałem użyć funkcji GROUP_CONCAT i znalazłem najlepszy wynik. Oto kod:

global $wpdb;
$wpdb->query('SET SESSION group_concat_max_len = 10000'); // necessary to get more than 1024 characters in the GROUP_CONCAT columns below
$query = "
    SELECT p.*, 
    GROUP_CONCAT(pm.meta_key ORDER BY pm.meta_key DESC SEPARATOR '||') as meta_keys, 
    GROUP_CONCAT(pm.meta_value ORDER BY pm.meta_key DESC SEPARATOR '||') as meta_values 
    FROM $wpdb->posts p 
    LEFT JOIN $wpdb->postmeta pm on pm.post_id = p.ID 
    WHERE p.post_type = 'product' and p.post_status = 'publish' 
    GROUP BY p.ID
";

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

// massages the products to have a member ->meta with the unserialized values as expected
function massage($a){
    $a->meta = array_combine(explode('||',$a->meta_keys),array_map('maybe_unserialize',explode('||',$a->meta_values)));
    unset($a->meta_keys);
    unset($a->meta_values);
    return $a;
}

$products = array_map('massage',$products);

Zajęło to średnio 0,7 sekundy . To mniej więcej jedna czwarta czasu rozwiązania WP get_post_custom () i około połowy rozwiązania zapytań przestawnych.

Może to kogoś zainteresuje.

Trevor Mills
źródło
Byłbym zainteresowany, jakie wyniki uzyskasz dzięki rozwiązaniu pamięci podręcznej obiektów trwałych. Pamięć podręczna obiektów będzie czasami wolniejsza w przypadku podstawowym, w zależności od bazy danych i konfiguracji, ale wyniki rzeczywiste z większością hostów dają bardzo różne wyniki. Buforowanie oparte na pamięci jest absurdalnie szybkie.
Otto
Cześć @Otto. Bez względu na to, której metody używam do uzyskania danych, zdecydowanie chcę buforować wynik. Próbowałem do tego celu użyć przejściowego interfejsu API, ale mam problemy z pamięcią. Serializowany ciąg dla moich 2000 obiektów osiąga zegary o ~ 8M i set_transient () nie działa (pamięć wyczerpana). Ponadto należy zmienić ustawienie max_allowed_packet MySQL. Zajmę się buforowaniem go do pliku, ale nie jestem jeszcze pewien wydajności tam. Czy istnieje sposób buforowania pamięci, który utrzymuje się w żądaniach?
Trevor Mills,
Tak, jeśli masz trwałą pamięć podręczną (XCache, memcached, APC itp.) I używasz wtyczki buforowania obiektów (W3 Total Cache obsługuje wiele rodzajów pamięci podręcznych), to przechowuje całą pamięć podręczną obiektów w pamięci, dając ci wielokrotne przyspieszenie prawie wszystkiego.
Otto,
Zwracam 6000 elementów do użycia w schemacie filtrowania js sieci szkieletowej / podkreślenia. To wymagało niestandardowego zapytania 6s, którego nawet nie mogłem uruchomić jako WP_Query, ponieważ upłynął limit czasu, i uczyniło go zapytaniem 2s. Mimo że array_map spowalnia go nieco z powrotem ...
Jake
Czy jest jakieś wsparcie dla budowania wysokiej wydajności wsparcia dla zwracania wszystkich metadanych w ramach WP_Query?
atwellpub
2

Znalazłem się w sytuacji, w której musiałem wykonać to zadanie, aby ostatecznie utworzyć dokument CSV, skończyłem z bezpośrednią współpracą z mysql, aby to zrobić. Mój kod dołącza do tabel pocztowych i meta, aby pobrać informacje o cenach woocommerce, wcześniej opublikowane rozwiązanie wymagało używania aliasów tabel w sql do poprawnego działania.

SELECT p.ID, p.post_title, 
    MAX(CASE WHEN pm1.meta_key = '_price' then pm1.meta_value ELSE NULL END) as price,
    MAX(CASE WHEN pm1.meta_key = '_regular_price' then pm1.meta_value ELSE NULL END) as regular_price,
    MAX(CASE WHEN pm1.meta_key = '_sale_price' then pm1.meta_value ELSE NULL END) as sale_price,
    MAX(CASE WHEN pm1.meta_key = '_sku' then pm1.meta_value ELSE NULL END) as sku
    FROM wp_posts p LEFT JOIN wp_postmeta pm1 ON ( pm1.post_id = p.ID)                 
    WHERE p.post_type in('product', 'product_variation') AND p.post_status = 'publish'
    GROUP BY p.ID, p.post_title

Ostrzegam jednak, woocommerce utworzyło ponad 300 000 wierszy w mojej tabeli meta, więc było bardzo duże, a zatem bardzo wolne.

Terry Kernan
źródło
1

BRAK WERSJI SQL:

Pobierz wszystkie posty i wszystkie ich meta wartości (meta) bez SQL:

Załóżmy, że masz listę identyfikatorów postów przechowywanych jako tablica identyfikatorów, coś w rodzaju

$post_ids_list = [584, 21, 1, 4, ...];

Teraz uzyskanie wszystkich postów i wszystkich meta w jednym zapytaniu nie jest możliwe bez użycia co najmniej odrobiny SQL, więc musimy wykonać 2 zapytania (nadal tylko 2):

1. Zdobądź wszystkie posty (używając WP_Query )

$request = new WP Query([
  'post__in' => $post_ids_list,
  'ignore_sticky_posts' => true, //if you want to ignore the "stickiness"
]);

(Nie zapomnij zadzwonić, wp_reset_postdata();jeśli później wykonujesz „pętlę” ;))

2. Zaktualizuj meta cache

//don't be confused here: "post" means content type (post X user X ...), NOT post type ;)
update_meta_cache('post', $post_ids_list);

Aby uzyskać metadane, użyj standardu, get_post_meta()który, jak wskazał @Otto:
najpierw sprawdza pamięć podręczną :)

Uwaga: jeśli tak naprawdę nie potrzebujesz innych danych z postów (takich jak tytuł, treść, ...) , możesz zrobić tylko 2. :-)

jave.web
źródło
0

używając rozwiązania trevor i modyfikując go do pracy z zagnieżdżonym SQL. To nie jest testowane.

global $wpdb;
$query = "
    SELECT p.*, (select pm.* From $wpdb->postmeta AS pm WHERE pm.post_id = p.ID)
    FROM $wpdb->posts p 
    WHERE p.post_type = 'product' and p.post_status = 'publish' 
";
$products = $wpdb->get_results($query);
Jonathan Joosten
źródło
-1

Zetknąłem się również z problemem wielu pól meta wartości. Problem dotyczy samego WordPressa. Zajrzyj do wp-include / meta.php. Poszukaj tej linii:

$where[$k] = ' (' . $where[$k] . $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string})", $meta_value );

Problem dotyczy instrukcji CAST. W zapytaniu o meta wartości zmienna $ meta_type jest ustawiona na CHAR. Nie znam szczegółów dotyczących tego, w jaki sposób CASTing wartości CHAR wpływa na szeregowany ciąg, ale aby to naprawić, możesz usunąć rzutowanie, aby SQL wyglądał następująco:

$where[$k] = ' (' . $where[$k] . $wpdb->prepare( "$alias.meta_value {$meta_compare} {$meta_compare_string})", $meta_value );

Teraz, mimo że to działa, masz do czynienia z wewnętrznymi elementami WordPress, więc inne rzeczy mogą się zepsuć i nie jest to trwała poprawka, zakładając, że musisz zaktualizować WordPress.

Naprawiłem to, kopiując kod SQL wygenerowany przez WordPress dla żądanego meta kwerendy, a następnie napisz trochę PHP, aby dodać dodatkowe instrukcje AND dla meta_wartości, których szukam, i użyj $ wpdb-> get_results ($ sql ) dla końcowego wyniku. Hacky, ale działa.

Harry Love
źródło
Nie próbowałem tego, ale użycie get_meta_sqlfiltra następującego po tej linii byłoby oczywiście lepsze niż zhakowanie rdzenia kodu.
Steve Taylor,