I have been days over this issue now. Initially it was, how to store a user’s follower data in database, for which I got couple of nice recommendations here at WordPress Answers. After, following the recommendations I have added a new table like this:
id leader_id follower_id
1 2 4
2 3 10
3 2 10
In the table above, the first row has a user with an ID of 2 who is being followed by a user with an ID of 4. In the second row, a user with an ID of 3 is being followed by an user with an ID of 10. The same logic applies for the third row.
Now, essentially I want to extend WP_Query so that I can limit posts fetched to that of, only by the leader(s) of a user. So, taking the above table into consideration, if I were to pass the user ID 10 to WP_Query the results should only contain posts by user ID 2 and user ID 3.
I have searched a lot trying to find an answer. Nor, I have seen any tutorial to help me understand how to extend the WP_Query class. I have seen Mike Schinkel’s answers (extending WP_Query) to similar questions, but I have really not understood how to apply it to my needs. It would be great if someone could help me out with this.
If you’re doing complex joins, you can’t just use the posts_where filter, because you’ll need to modify the join, the select, and possibly the group by or order by sections of the query as well.
Your best bet is to use the ‘posts_clauses’ filter. This is a highly useful filter (that shouldn’t be abused!) that allows you to append / modify the various portions of the SQL that is generated automatically by the many many lines of code within WordPress core. The filter callback signature is:
function posts_clauses_filter_cb( $clauses, $query_object ){ }
and it expects you to return$clauses
.The Clauses
$clauses
is an array that contains the following keys; each key is an SQL string that will be directly used in the final SQL statement sent to the database:If you’re adding a table to the database (only do this if you absolutely can’t leverage post_meta, user_meta or taxonomies) you’ll probably need to touch more than one of these clauses, for example, the
fields
(the “SELECT” portion of the SQL statement), thejoin
(all your tables, other than the one in your “FROM” clause), and maybe theorderby
.Modifying the Clauses
The best way to do this is to subreference the relevant key from the
$clauses
array you got from the filter:Now, if you modify
$join
, you’ll actually be directly modifying$clauses['join']
so the changes will be in$clauses
when you return it.Preserving the Original Clauses
Chances are (no, seriously, listen up) you will want to preserve the existing SQL that WordPress generated for you. If not, you should probably look at the
posts_request
filter instead – that is the complete mySQL query just before it’s sent off to the database, so you can totally clobber it with your own. Why would you want to do this? You probably don’t.So, in order to preserve the existing SQL in the clauses, remember to append to the clauses, not assign to them (ie: use
$join .= ' {NEW SQL STUFF}';
not$join = '{CLOBBER SQL STUFF}';
. Note that because each element of the$clauses
array is a string, if you want to append to it, you’ll probably want to insert a space before any other character tokens, otherwise you’ll probably create some SQL syntax error.You can just assume there will always be something in each of the clauses, and so remember to start each new string with a space, as in:
$join .= ' my_table
, or, you can always add a little line that only adds a space if you need to:That’s a stylistic thing more than anything else. The important bit to remember is: always leave a space BEFORE your string if you’re appending to a clause that already has some SQL in it!
Putting it together
The first rule of WordPress development is to try to use as much core functionality as you can. This is the best way to future proof your work. Suppose the core team decides that WordPress will now be using SQLite or Oracle or some other database language. Any hand-written mySQL may become invalid and break your plugin or theme! Better to let WP generate as much SQL as possible on its own, and just add the bits you need.
So first order of business is leveraging
WP_Query
to generate as much of your base query as possible. The exact method we use to do this depends largely on where this list of posts is supposed to appear. If it’s a sub-section of the page (not your main query) you would useget_posts()
; if it’s the main query, I suppose you could usequery_posts()
and be done with it, but the proper way to do it is to intercept the main query before it hits the database (and consumes server cycles) so use therequest
filter.Okay, so you’ve generated your query and the SQL is about to be created. Well, in fact, it has been created, just not sent to the database. By using the
posts_clauses
filter, you’re going to add your employee relationships table into the mix. Let’s call this table {$wpdb->prefix} . ‘user_relationship’, and it’s an intersection table. (By the way, I recommend that you genericize this table structure and turn it into a proper intersection table with the following fields: ‘relationship_id’, ‘user_id’, ‘related_user_id’, ‘relationship_type’; this is much more flexible and powerful… but I digress).If I understand what you want to do, you want to pass a Leader’s ID and then see only the posts by that Leader’s Followers. I hope I got that right. If it’s not right, you’ll have to take what I say and adapt it to your needs. I’ll stick with your table structure: we have a
leader_id
and afollower_id
. So the JOIN will be on{$wpdb->posts}.post_author
as a foreign key to the ‘follower_id’ on your ‘user_relationship’ table.I am answering this question extremely late and my apologies for the same. I had been way too busy with deadlines to attend to this.
A big thanks to @m0r7if3r and @kaiser in providing the base solutions that I could extend and implement in my application. This answer provides details on my adaptation of the solutions offered by @m0r7if3r and @kaiser.
First, let me explain why this question was asked in the first place. From the question and the comments thereof one could gather that I am trying to get WP_Query to pull posts by all users (leaders) that a given user (follower) follows. The relationship between the follower and the leader is stored in a custom table
follow
. The most common solution to this problem is to pull the user ID’s of the all leaders of a follower from the follow table and place it in an array. See below:Once you have the array of leaders you could pass it as an argument to WP_Query. See below:
The above solution is the simplest way of achieving my desired results. However, it is non-scalable. The moment you have a follower following tens and thousands of leaders the resultant array of leader ID’s would get extremely large and force your WordPress site to use 100MB – 250MB of memory on each page load and eventually crash the site. The solution to the problem is to run SQL query directly on the database and fetch relevant posts. That’s when @m0r7if3r’s solution came to rescue. Following @kaiser’s recommendation I set out to test both implementations. I imported around 47K users from a CSV file to register them on a fresh test install of WordPress. The install was running Twenty Eleven theme. Following this I ran a for loop to make about 50 users follow every other user. The difference in query time for both @kaiser and @m0r7if3r’s solution was staggering. @kaiser’s solution normally took about 2 to 5 secs for each query. The variation I presume happens as WordPress caches queries for later use. On the other hand @m0r7if3r’s solution demonstrated query time of 0.02 ms on average. For testing both solutions I had indexing ON for the leader_id column. Without indexing there was a dramatic increase in the query time.
The memory usage when using array based solution stood around 100-150 MB and dropped to 20 MB when running a direct SQL.
I hit a bump with @m0r7if3r’s solution when I needed to pass the follower ID to the posts_where filter function. Atleast, as per my knowledge WordPress allows no means of passing a variable to filer functions. You can use Global variables though, but I wanted to avoid globals. I ended up extending WP_Query to finally address the issue. So here is the final solution I implemented (based on @m0r7if3r ‘s solution).
Note: I eventually tried the above solution with 1.2 million entries in the follow table. The average query time stood around 0.060 ms.
You can do this with an entirely SQL solution using the
posts_where
filter. Here’s an example of that:I think there may be a way to do this with
JOIN
as well, but I cannot come up with it. I will keep playing with it and update the answer if I get it.Alternately, as @kaiser suggested, you can split it to two parts: getting the leaders and doing the query. I have a feeling this might be less efficient, but it’s certainly the more understandable way to go. You would have to test the efficiency for yourself to determine which method is better, as nested SQL queries can get quite slow.
FROM THE COMMENTS:
You should put the function in your
functions.php
and do theadd_filter()
right before thequery()
method ofWP_Query
is called. Immediately following that, you shouldremove_filter()
so that it does not affect the other queries.Template Tag
Just place both functions in your
functions.php
file. Then adjust the 1st function and add your custom table name. Then you need some try/error to get rid of the current user ID inside the resulting array (see comment).Inside the template
Here you can do whatever you want with your results.
Here´s the OPs Code from the comments, to add the first set of test users. I has to be modified to a real world example.
My answer to this â test:
I also have to state that the â above time tracking can´t be really measured, as it would also take the time to compute the loop together. Better would be to loop through the resulting set of IDs in a second loop.
further process here