wordpress ajax relationship query - sql

Using the relationship field from Advanced Custom Fields I link artists to events. Both these artist and events are custom post types. For each event, the post IDs of the related artists are stored in an array as a custom meta field (lineup_artists).
On each event page I list all the artists. When you click on an artist, I'd like to show all the events where you can find this artist (through an AJAX call which shows the results in a bootstrap modal). I've tested the AJAX call and it's working, but there's something wrong with the query (takes very long to complete).
In my function I have:
$ArtistID = $_POST['ArtistID']; // Gets the ArtistID from the AJAX call
$meta_query = array(
'key' => 'lineup_artists',
'value' => '"' . $ArtistID .'"',
'compare' => 'LIKE'
);
$args = array(
'post_type' => 'events',
'meta_query' => array($meta_query),
'posts_per_page' => 5,
'post_status' => 'publish',
);
If I dump the results of wp_query, I get the following sql query:
SELECT SQL_CALC_FOUND_ROWS yt_posts.ID FROM yt_posts
INNER JOIN yt_postmeta ON (yt_posts.ID = yt_postmeta.post_id)
WHERE 1=1
AND yt_posts.post_type = 'events'
AND (yt_posts.post_status = 'publish')
AND ( (yt_postmeta.meta_key = 'lineup_artists' AND CAST(yt_postmeta.meta_value AS CHAR) LIKE '%\"17497\"%') )
GROUP BY yt_posts.ID ORDER BY yt_posts.post_date DESC LIMIT 0, 5
When I paste this query in phpmyadmin it takes very long to complete (I've never seen it finish because it takes so long).
Is this because the artist IDs are stored as an array? Someone told me that this is not very efficient and that these relations should be stored in a separate table (which I don't know how to do). Is there something wrong with my query or is this a very inefficient way of querying relations?

Related

Joining multiple tables together with eloquent and eager loading in laravel [implementing filter]

What I have already and what I'm trying to do:
I have a website that lets users make posts advertising themselves to other users. Now, I'm trying to implement a filter for these posts but filter by values in the users table, posts table and three others. This is where I'm running into some trouble.
Code that works, as is, no filter:
$posts = Post::with('user','lookingfors','playstyles', 'postcomments')->orderBy('id', 'DESC')->paginate(10);
return View::make('posts/index', compact('posts'));
This is what I've tried:
$posts = User::leftjoin('posts', function($join)use($region){
$join->on('users.id', '=', 'posts.user_id');
$join->on('playstyle_post.post_id', '=', 'posts.id'); // join pivot, then join pivot to playstyles
$join->on('playstyle_post.playstyle_id', '=', 'playstyles.id');
//$join->on;
})->where('users.region_id', 'like', $region)->get();
This one keeps bringing up this error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column
'playstyle_post.post_id' in 'on clause' (SQL: select * from users
left join posts on users.id = posts.user_id and
playstyle_post.post_id = posts.id and
playstyle_post.playstyle_id = playstyles.id where
users.region_id like ?) (Bindings: array ( 0 => 6, ))
Also:
$posts = Post::with('lookingfors', 'playstyles', 'postcomments')
->leftjoin('users', 'posts.user_id', '=', 'users.id')
//->join('playstyle_post', 'posts.id', '=', 'playstyle_post.post_id')
->paginate(10);
This one is odd ( well for me ), when 'leftjoin' is commented out obviously it works just as before, but when I have it as it is now it displays the view as it should and even pulls all the right values from the users table but does not display any of the information thats being requested through the 'with'.
I've tried a couple other things in the last two weeks, but I've already thrown them out when they didn't work. This has been kicking my ass for the better half of a month and I can't wrap my head around why I can't get it to work.
So really, my trouble right now is just trying to join the tables and returning it to the view in an eloquent fashion so I can keep treating it the same. The filter itself will be a problem of its own, but getting the joins to work has proved to be the hard part.
Instead of passing a bunch of relationship names to the with() function, try passing an array, setting keys as the names of the relationships and the values as callback functions and pass in the query for actual editing. Also will have to use use ($region) where needed. Hopefully you get the idea.
I put in some examples for adding filters on each relationship. Because you are working locally with each one, you shouldn't have to worry about prefixing them with table names or aliases.
$posts = Post::with(array(
'user' => function($query) use ($region)
{
// User constraints here
$query->where('status', 'A');
$query->where('deleted', '0');
},
'lookingfors' => function($query)
{
// lookingfors constraints here
$query->where('name', 'somelookingforname');
},
'playstyles' => function($query)
{
// playstles constraints here
$query->where('name', 'someplaystylesname');
},
'postcomments' => function($query)
{
// Some postcomments constraints here
$query->where('name', 'somepostcommentsname');
}))
->orderBy('id', 'DESC')
->paginate(10);
Looking at your attempt at left joins though, I don't know if these relationships are correct. My example assumes all the relationships can relate directly back to the user table. Otherwise, you will need to change the keys on the array to match how the relationships should be.
For example, if trying to relate to the playstyles table, but the relationship between playstyles and posts doesn't exist because you need to go through the users table first, you would just change 'playstyles' to 'users.playstyles'.
If you post your migrations, I could help you further.

Rails ActiveRecord Join Query With conditions

I have following SQL Query:
SELECT campaigns.* , campaign_countries.points, offers.image
FROM campaigns
JOIN campaign_countries ON campaigns.id = campaign_countries.campaign_id
JOIN countries ON campaign_countries.country_id = countries.id
JOIN offers ON campaigns.offer_id = offers.id
WHERE countries.code = 'US'
This works perfectly well. I want its rails active record version some thing like:
Campaign.includes(campaign_countries: :country).where(countries: {code: "US"})
Above code runs more or less correct query (did not try to include offers table), issue is returned result is collection of Campaign objects so obviously it does not include Points
My tables are:
campaigns --HAS_MANY--< campaign_countries --BELONGS_TO--< countries
campaigns --BELONGS_TO--> offers
Any suggestions to write AR version of this SQL? I don't want to use SQL statement in my code.
I some how got this working without SQL but surely its poor man's solution:
in my controller I have:
campaigns = Campaign.includes(campaign_countries: :country).where(countries: {code: country.to_s})
render :json => campaigns.to_json(:country => country)
in campaign model:
def points_for_country country
CampaignCountry.joins(:campaign, :country).where(countries: {code: country}, campaigns: {id: self.id}).first
end
def as_json options={}
json = {
id: id,
cid: cid,
name: name,
offer: offer,
points_details: options[:country] ? points_for_country(options[:country]) : ""
}
end
and in campaign_countries model:
def as_json options={}
json = {
face_value: face_value,
actual_value: actual_value,
points: points
}
end
Why this is not good solution? because it invokes too many queries:
1. It invokes query when first join is performed to get list of campaigns specific to country
2. For each campaign found in first query it will invoke one more query on campaign_countries table to get Points for that campaign and country.
This is bad, Bad and BAD solution. Any suggestions to improve this?
If You have campaign, You can use campaign.campaign_countries to get associated campaign_countries and just get points from them.
> campaign.campaign_countries.map(&:points)
=> [1,2,3,4,5]
Similarly You will be able to get image from offers relation.
EDIT:
Ok, I guess now I know what's going on. You can use joins with select to get object with attached fields from join tables.
cs = Campaign.joins(campaign_countries: :country).joins(:offers).select('campaigns.*, campaign_countries.points, offers.image').where(countries: {code: "US"})
You can than reference additional fields by their name on Campaign object
cs.first.points
cs.first.image
But be sure, that additional column names do not overlap with some primary table fields or object methods.
EDIT 2:
After some more research I came to conclusion that my first version was actually correct for this case. I will use my own console as example.
> u = User.includes(:orders => :cart).where(:carts => { :id => [5168, 5167] }).first
> u.orders.length # no query is performed
=> 2
> u.orders.count # count query is performed
=> 5
So when You use includes with condition on country, in campaign_countries are stored only campaign_countries that fulfill Your condition.
Try this:
Campaign.joins( [{ :campaign_countries => :countries}, :offers]).where('`countries`.`code` = ?', "US")

CakePHP SQL-query count

I have a problem concerning CakePHP SQL-queries. I need to fetch products from the database where shop_id is given and then count the products. All I need is Product.url and its count.
This will do the trick in plain SQL:
SELECT url,COUNT(*) as count FROM products GROUP BY url ORDER BY count DESC;
This one I used to get all products relating to shops:
$this->Product->find('all', array('conditions' => array('Product.shop_id'=>$id)));
That works correctly, but I need to convert that SQL-query above to CakePHP.
I tried something like this:
$this->Product->find('count', array('conditions' => array('Product.shop_id'=>$id),
'fields'=>array('Product.url','Product.id'),
'order'=>'count DESC',
'group'=>'Product.url'));
That returns only an int. But if I run that SLQ-query presented above in mysql server, I get two columns: url and count. How do I get the same results with CakePHP?
You can try this:
$data = $this->Post->query(
"SELECT COUNT(id),MONTH(created) FROM posts GROUP BY YEAR(created), MONTH(created);"
);
The most easiest way to do this:
$this->Product->query("SELECT url,COUNT(*) as count FROM products GROUP BY url ORDER BY count DESC;");
...at least for me.
Try the following code:
$this->Product->virtualFields['CNT_PRODUCTS'] = 0;
$this->Product->find('count', array('conditions' => array('Product.shop_id' => $id),
'fields' => array('Product.id', 'Product.url', 'count(*) as CNT_PRODUCTS'),
'order' => array('CNT_PRODUCTS' => 'DESC'),
'group' => 'Product.url'));

Yii order by another table CActiveDataProvider

I am a bit stumped on this. Basically I have two tables:
Page:
id
name
Points:
id-
pageid
points
I am looking to get the records from the Page table, and sort it by the amount of points it has in the Points table (the points field)
Currently I have:
$dataProvider=new CActiveDataProvider('Page',array(
'criteria'=>array(
'condition'=>"active = 1 AND userid IN (".$ids.")",
'order'=>"???",
),
'pagination'=>array(
'pageSize'=>30,
),
));
I just don't know how to sort it by the Points table value for the relevant record
I have set up a relation for the Page/Points tables like so:
(in the Page model)
'pagepoints' => array(self::HAS_ONE, 'Points', 'pageid'),
Thanks
You need to do two things:
Add the pagepoints relation to the with part of the query criteria
Reference the column you want to sort by in the order part of the criteria
I 've marked the lines where this happens in the code below:
$dataProvider = new CActiveDataProvider('Page', array(
'criteria'=>array(
'with' => array('pagepoints'), // #1
'condition' => 'active = 1 AND userid IN ('.$ids.')',
'order' => 'pagepoints.points', // #2
),
'pagination'=>array(
'pageSize'=>30,
),
));
What you need to know to understand how this works is that when Yii builds the SQL query (which is a LEFT OUTER JOIN to the Points table), it uses the name you gave to the relation in the Page model (you give the definition for this, it's pagepoints) to alias the joined table. In other words, the query looks like:
SELECT ... FROM Page ... LEFT OUTER JOIN `Points` `pagepoints` ...
It follows that the correct specification for the sort order is pagepoints.points: pagepoints is the table alias, and points is the column in that table.
Try the following
$dataProvider=new CActiveDataProvider('Page',array(
'criteria'=>array(
'with'=>array('pagepoints'),
'condition'=>"active = 1 AND userid IN (".$ids.")",
'order'=>"t.points DESC",
),
'pagination'=>array(
'pageSize'=>30,
),
));
this is the sql you want to generate:
select * from page inner join points
on page.id = points.page_id order by
points.points desc

Bug in Kohana 3 ORM?

Sorry to ask all these questions about Kohana. They usually get ignored. I think I just found a bug. I'm making a join between two tables that are not directly related.
$results = ORM::factory('foo')->join("bar")->on("foo.foreign_id", "=", "bar.id");
This generates a query that does not resolve the table names explicitly:
SELECT * FROM `foo` JOIN `bar` ON (`foo`.`foreign_id` = `bar`.`id`)
Which gives (in phpMyAdmin) a table that looks like this:
id time foreign_id blah_int id baz
4 1291851245 3 0 3 52501504
Notice there are two id columns, one for the foo table and one for bar. This is a real problem. Because now, in my results, if I loop through...
foreach ($results as $result) {
echo $result->id; // prints 3!!!
}
Because my results should be foo objects, I expect to get an id of 4, but it's giving me 3 because of the join. Is this a bug in the ORM library? Should I be using a different method to restrict my results from the query? I really don't want to do two separate queries where I load all the bars id's, and then load my foos that way, but it looks like I have to.
You have to use the Database object to build raw queries, not ORM, like this:
$results = DB::select()->from('foo')->join('bar')->on("foo.foreign_id", "=", "bar.id")->execute();
You will need to specific some column aliases however to make your query work unless you use ORM as it was intended.
Using ORM
If you want to use ORM, you need to define the relationships in your model. You mention that they share a relationship with another table so in your case you could use a has many through relationship like this:
protected $_has_many = array(
'bars' => array('model' => 'bar', 'through' => 'other_table', 'foreign_key' => 'foreign_id'),
);
Although your example as given suggests that a straight has_many relationship would work:
protected $_has_many = array(
'bars' => array('model' => 'bar','foreign_key' => 'foreign_id'),
);
This would allow you to access all of the bars using a statement like
$bars = $results->bars->find_all();
foreach($bars as $bar)
{
echo $bar->id; // should echo 4, assuming one record in bars with id 4
}
The Kohana 3.1 ORM Reference Guide is good place to start if you want to learn more about ORM and relationships
Using the Kohana database object and query builder
If you prefer ad hoc queries and are doing joins using the query builder you will likely have colliding column names regardless if you are using Kohana or just raw queries (pop "SELECT * FROM foo JOIN bar ON (foo.foreign_id = bar.id)" into MySQL and you will get the exact same result).
Kohana, just like MySQL allows you to define column aliases for precisely this reason. (See here for more information)
Rewrite your query as follows:
$results = DB::select('id', 'time', 'foreign_id', array('bar.id', 'bar_id'), 'baz')->from('foo')->join("bar")->on("foo.foreign_id", "=", "bar.id")->execute();
This will return:
id time foreign_id blah_int bar_id baz
4 1291851245 3 0 3 52501504