MySQL join across two tables with serialized data, recursion? - mysql

I'm having some major issues trying to grab all the data I need for a SQL query. I'm still new with queries, so I will try to describe this as best as possible.
I am trying to do a cross query with the Wordpress plugin NextGen Gallery. Basically there are two tables nggalbum and nggallery. What I'm attempting to do is create a nested list of all albums and galleries.
The data in nggalbum contains columns id, name, slug, previewpic, albumdesc, sortorder, and pageid. The only values that I'm interested in are id, name, slug, and sortorder. The value for sortorder is serialized data that has the relationship data of this entry and all other album and gallery entries. For example: a:2:{i:0;s:2:"a2";i:1;s:2:"a6";} This basically means the current query item has two sub-albums (their corresponding id prefixed with an "a"): a2 and a6. If it has galleries, the number doesn't have an a prefix and is the ngggallery gid (will cover that in a second).
I use this to grab all the data from the nggalbum table:
$albums = $wpdb->get_results("SELECT * FROM $wpdb->nggalbum" , OBJECT_K );
foreach ($albums as $key => $value) {
$albums[$key]->id = 'a' . $key;
$albums[$key]->galleries = empty ($albums[$key]->sortorder) ? array() : (array) unserialize($albums[$key]->sortorder) ;
$albums[$key]->name = stripslashes( $albums[$key]->name );
$albums[$key]->albumdesc = stripslashes( $albums[$key]->albumdesc );
}
Sample data:
Array
(
[1] => stdClass Object
(
[id] => a1
[name] => Image Gallery
[slug] => image-gallery
[previewpic] => 0
[albumdesc] =>
[sortorder] => a:2:{i:0;s:2:"a2";i:1;s:2:"a6";}
[pageid] => 0
[galleries] => Array
(
[0] => a2
[1] => a6
)
)
[2] => stdClass Object
(
[id] => a2
[name] => ALBUM 1 - High res
[slug] => album-1-high-res
[previewpic] => 0
[albumdesc] =>
[sortorder] => a:2:{i:0;s:1:"2";i:1;s:1:"3";}
[pageid] => 0
[galleries] => Array
(
[0] => 2
[1] => 3
)
)
I add an a prefix to all of these id's because they are albums and I figured this may help later. Since I'm not sure what I'm doing, this may not be the case.
nggallery contains columns gid, name, slug, path, title, galdesc, pageid, previewpic, and author. The only relevant columns are gid, name, slug, path, and title.
I use this to grab all the data from the nggallery table:
$galleries = $wpdb->get_results( "SELECT SQL_CALC_FOUND_ROWS * FROM $wpdb->nggallery", OBJECT_K );
foreach ($galleries as $key => $value) {
$galleriesID[] = $key;
$galleries[$key]->counter = 0;
$galleries[$key]->title = stripslashes($galleries[$key]->title);
$galleries[$key]->galdesc = stripslashes($galleries[$key]->galdesc);
$galleries[$key]->abspath = WINABSPATH . $galleries[$key]->path;
}
Sample data:
Array
(
[2] => stdClass Object
(
[gid] => 2
[name] => new_collection
[slug] => new-collection
[path] => wp-content/gallery/new_collection
[title] => NEW COLLECTION
[galdesc] =>
[pageid] => 0
[previewpic] => 8
[author] => 1
[counter] => 0
[abspath] => /Applications/MAMP/htdocs/igal/wp-content/gallery/new_collection
)
[3] => stdClass Object
(
[gid] => 3
[name] => cha-collection
[slug] => cha-collection
[path] => wp-content/gallery/cha-collection
[title] => CHA COLLECTION
[galdesc] =>
[pageid] => 0
[previewpic] => 15
[author] => 1
[counter] => 0
[abspath] => /Applications/MAMP/htdocs/igal/wp-content/gallery/cha-collection
)
Now this is where I get stuck. I would really love to be able to write some code to parse through each album's galleries array and associate it with the corresponding albums and/or galleries from nggallery.
Eventually I would love to achieve a nested list of albums/galleries, such as:
(a1) [link] Title
(a2) [link] Title
1 [link] Title
2 [link] Title
3 [link] Title
(a3) [link] Title
1 [link] Title
[...]
I'm not entirely sure how to even start going about this. I tried looping some things through some foreach statements and have been largely unsuccessful. I would search google for this but I have no idea what this technique is even called.
I would REALLY like to understand how to do something like this, so if you could please shed any light upon this technique, I would be greatly indebted. Links to similar tutorials or just basic concepts would be very beneficial to me. I don't expect anyone to do all the code for me, but any step in the right direction would be greatly appreciated ( and if you want to do the code, with some steps, I won't argue of course ;) ).
Thanks so much ahead of time!
Tre

I don't quite understand how albums and galleries are related to each other and what you want in the nested list. However, it looks to me like the problem is in the "sortorder" column which is doing too much. I suspect you are trying to express a many-to-many relationship between your tables in which case it might be cleaner to have a separate table that expresses that relationship. Once you've done that I think you can more easily query which nalbums and ngalleries relate to gallery. Does that help much?
Edit:
OK so I think I understand now. You are trying to create a hierarchy of albums and you want to print the whole hierarchy of albums including all the galleries that each album has. So, using your existing design I think you could do something like this:
PrintGallery( string galleryID )
{
//1 do a query that selects the gallery using the id (use a where clause)
//2 print gallery details like the name etc whatever you want to
}
PrintAlbum ( string albumID )
{
//1 do a query that selects the Album using the id (use a where clause)
//2 print Album details (name icon etc) but not the gallery array details.
if(galleries array length > 0 )
/* in case there are no galleries we don't want a hanging <li> with nothing in it */
echo "<ul>" /* This will ensure the items are nested properly */
foreach item currentItemID in galleries array
echo "<li>"
if (currentItemID is an Album)
PrintAlbum(currentItemID) /* recurse */
else /* assume it's a gallery are in the array */
PrintGallery(currentItemID)
echo "</ul>" /* end this level */
}

Related

CakePHP3 - creating multiple associations between same two records

I have a many-to-many relationship between Invoices and Items. An Item can appear multiple times in the same Invoice, but with different amounts (e.g. when the same service is used several times during one month).
I was hoping I could create this association by including the following in the 'items' element of the invoice when saving (see https://book.cakephp.org/3/en/orm/saving-data.html#saving-belongstomany-associations):
Array
(
[0] => Array
(
[id] => 1
[_joinData] => Array
(
[amount] => 5338.29
)
)
[1] => Array
(
[id] => 1
[_joinData] => Array
(
[amount] => 5988.53
)
)
[2] => Array
(
[id] => 1
[_joinData] => Array
(
[amount] => 6023.40
)
)
)
In the example above, the result I'm hoping for is that three rows are created in the join table invoices_items. The invoice is saved correctly, but only one row is created in the join table.
One both associations I tried setting saveStrategy to append (I wasn't sure what this does), but this didn't help:
$this->belongsToMany('Invoices', [
'saveStrategy' => 'append'
]);
Is it possible to achieve this behaviour out of the box, or do I need to create something more custom, like a new model to specifically keep track of these relationships? Please let me know if more code would help to clarify what I'm trying to achieve.
Thanks!
The answer seems to be that no, this type of mass assignment isn't possible.
The solution I arrived at is to loop through each item I want to associate with the freshly saved invoice and call link() for each. I don't know what the performance hit is for this but for my purposes it works as this operation in my case happens relatively rarely.
// Build an array with an element per item
...
$itemsJoinData[] = [
'item' => $item,
'_joinData' => [
'amount' => $amount
]
];
...
Once the invoice is successfully saved I attach the items with their respective amount.
// Join up the invoice with the items
foreach($itemsJoinData as $itemToJoin) {
$itemToJoin['item']->_joinData = new Entity(['amount' => $itemToJoin['_joinData']['amount']], ['markNew' => true]);
$this->Invoices->Items->link( $invoice, [$itemToJoin['item']] );
}

Is there a name for storing data where each line either has a semi-colon or curly braces?

I am trying to understand how a Wordpress plugin works with data, when I pull it from MySQL it comes out like this:
a:1:{s:9:"home-team";a:6:{s:2:"id";s:9:"home-team";s:4:"slug";s:9:"home-team";s:4:"type";s:6:"select";s:4:"name";s:9:"Home Team";s:11:"description";s:0:"";s:4:"data";a:4:{s:7:"options";a:3:{s:60:"wpcf-fields-select-option-3892e2c3ad45e24dc7f47ff2ba880c33-2";a:2:{s:5:"title";s:13:"Chicago Bears";s:5:"value";s:1:"1";}s:60:"wpcf-fields-select-option-09fbd82bfa4142df6439c8e15d96dbfc-1";a:2:{s:5:"title";s:15:"New York Giants";s:5:"value";s:1:"2";}s:60:"wpcf-fields-select-option-7c7df972f933545b37c41ca249c686b4-1";a:2:{s:5:"title";s:15:"Oakland Raiders";s:5:"value";s:1:"3";}}s:8:"validate";a:1:{s:8:"required";a:3:{s:6:"active";s:1:"1";s:5:"value";s:4:"true";s:7:"message";s:22:"This Field is required";}}s:19:"conditional_display";a:2:{s:8:"relation";s:3:"AND";s:6:"custom";s:0:"";}s:16:"disabled_by_type";i:0;}}}
Is there a name for the way this is stored? To me it looks a little bit like JSON, but of course this is not JavaScript. Also, is there a way to clean it up easily (by using an online tool), so the first few lines would look like this:
a:1: {
s:9:"home-team";
a:6: {
s:2:"id";
s:9:"home-team";
s:4:"slug";
s:9:"home-team";
s:4:"type";
s:6:"select";
s:4:"name";
etc.. etc.. etc...
That's the PHP serialize format.
See : http://php.net/manual/en/function.serialize.php
Not sure how to get the formatted version exactly like you have it (but you could probably put that together easily), but here is another way to get an idea of what is in the serialized string:
$test_string= 'a:1:{s:9:"home-team";a:6:{s:2:"id";s:9:"home-team";s:4:"slug";s:9:"home-team";s:4:"type";s:6:"select";s:4:"name";s:9:"Home Team";s:11:"description";s:0:"";s:4:"data";a:4:{s:7:"options";a:3:{s:60:"wpcf-fields-select-option-3892e2c3ad45e24dc7f47ff2ba880c33-2";a:2:{s:5:"title";s:13:"Chicago Bears";s:5:"value";s:1:"1";}s:60:"wpcf-fields-select-option-09fbd82bfa4142df6439c8e15d96dbfc-1";a:2:{s:5:"title";s:15:"New York Giants";s:5:"value";s:1:"2";}s:60:"wpcf-fields-select-option-7c7df972f933545b37c41ca249c686b4-1";a:2:{s:5:"title";s:15:"Oakland Raiders";s:5:"value";s:1:"3";}}s:8:"validate";a:1:{s:8:"required";a:3:{s:6:"active";s:1:"1";s:5:"value";s:4:"true";s:7:"message";s:22:"This Field is required";}}s:19:"conditional_display";a:2:{s:8:"relation";s:3:"AND";s:6:"custom";s:0:"";}s:16:"disabled_by_type";i:0;}}}';
$unser = unserialize( $test_string);
print_r ( $unser );
Which will display:
Array
(
[home-team] => Array
(
[id] => home-team
[slug] => home-team
[type] => select
[name] => Home Team
[description] =>
[data] => Array
(
[options] => Array
(
[wpcf-fields-select-option-3892e2c3ad45e24dc7f47ff2ba880c33-2] => Array
(
[title] => Chicago Bears
[value] => 1
)
[wpcf-fields-select-option-09fbd82bfa4142df6439c8e15d96dbfc-1] => Array
(
[title] => New York Giants
[value] => 2
)
[wpcf-fields-select-option-7c7df972f933545b37c41ca249c686b4-1] => Array
(
[title] => Oakland Raiders
[value] => 3
)
)
[validate] => Array
(
[required] => Array
(
[active] => 1
[value] => true
[message] => This Field is required
)
)
[conditional_display] => Array
(
[relation] => AND
[custom] =>
)
[disabled_by_type] => 0
)
)
)
That's the way WordPress stores arrays and objects in the database. From the article WordPress serializes options and meta for you
In the most basic use case, serialization is a way to store arrays and objects directly in the database, which can only store numbers, text, and dates. Serialization takes an array and turns it into a serialized string. For example:
$data = array( 'apple', 'banana', 'orange' );
echo serialize( $data );
// Result is a string we can unserialize into an array:
// a:3:{i:0;s:5:"apple";i:1;s:6:"banana";i:2;s:6:"orange";}
WordPress has a few helper functions that we use instead of serialize() and unserialize() — maybe_serialize() and maybe_unserialize(). The first only serializes data that needs to be serialized — arrays and objects — and the second only unserializes data that is already serialized. (We have a lot of handy functions like these.)
You mention "when I pull it from MySQL it comes out like this", so you're probably not using the bundled functions to pull the data, like get_option(), get_post_meta() and get_user_meta(). Those functions take care of unserializing the data.
Worth noting that you should use a tool like WordPress (and others) Search and Replace Tool to search/replace inside the database. As it takes care of replacing strings inside serialized data.
You cannot simply change:
a:3:{i:0;s:5:"apple";i:1;s:6:"banana";i:2;s:6:"orange";}
to
a:3:{i:0;s:5:"grapefruit";i:1;s:6:"banana";i:2;s:6:"orange";}
Because it should be s:10:"grapefruit";, being 10 the number of characters in the string.

CakePHP sql-query

I have the following tables:
- restaurants (restaurant.id, restaurant.name)
- menus (menu.id, menu.name, menu.active, menu.restaurant_id)
I want to have a list with all restaurants with the active menus (menu.active = true):
- restaurant2
- menu1
- menu4
- restaurant5
-menu3
- restaurant19
- menu34
- menu33
My first idea was something like this:
$options['contain'] = array(
'Menu' => array(
'conditions' => $menuParams //array('Menu.active' => '1') //$menuParams
)
);
This doen't work becaus all restaurants will be listed. I want to have only restaurants with active menus.
Next idea: using join
$options['joins'] = array(
array('table' => 'menus',
'alias' => 'Menu',
'type' => 'RIGHT',
'conditions' => array(
'Menu.restaurant_id = Restaurant.id',
)
)
);
Not good, because, I don't have the ordered list I want. I need the menus grouped by the restaurant. Look above.
Is it the right way to make a join with restaurants and menus(active = true) and then using the contain to get the ordered list? I think that could work but I think also there is an easier way, right?
Any help is welcome! Thank you.
If you are using the ORM machinery from CakePHP, you should have a Restaurant model and a Menu model to describe each table. In the Restaurant model, you should have a $hasMany = "Menu" field, and in menu a $belongsTo = "Restaurant" field (assuming the model names are Menu and Restaurant).
From that point, doing queries using ORM is fairly straightforward:
$this->Restaurant->recursive = 1; // grab the menus
$conditions = array('Menu.active' => '1'); // restrict to active menus only
$this->Restaurant->find('all', array('conditions' => $conditions));
The above in the ad-hoc method of the Restaurant controller should retrieve the rows as an array of Restaurant objects, each bundled with an array of active Menu.
Now I found the easy and clean solution for my concern! Yeah!
First I had to unbind the bindings and then I had to make a new binding with the condition. It works like a charm. Here is the code:
$this->unbindModel(array('hasMany' => array('Menu')));
$this->bindModel(array('hasMany'=>array(
'Menu'=>array(
'foreignKey' => 'restaurant_id',
'conditions' => array(
'Menu.active' => 1
)
)
)));
I thank you all for your answers!

Need some help with the conditions in CakePHP!

I have three models linked in this manner: Item->Order->Payment
Order hasMany Item
Order hasOne Payment
Now, I am paginating Items and want to add a condition in it to find only items of that order which has payment of a particular id. I hope that makes sense :P
I added the condition as:
array('Payment.id'=>$id)
but it doesn't work. Obviously cause Payment is not associated with Item.
So, how can I go about this?
I am new to cakephp, maybe I am completily wrong but as I understand it you can use other models in your controller with the $uses variable. First make a query on payment model to get your order id, than you can use this id to find the corresponding items.
$uses=array('Item','Order','Payment');
$order_id=$this->Payment->find('first',array('fields'=>'order_id','conditions'=>array('id'=>$payment_id)));
$items=$this->Item->find('all',array('conditions'=>array('order_id'=>$order_id)));
I hope it help.
Why don't you add a condition:
array('Order.payment_id'=>$id)
I think this should work.
If you specify that you want two levels of recursion this should work. Im assuming you have
in Payment.php
//recursion level 1
var $belongsTo = array('Order');
in Order.php
//recursion level 2
var $hasMany = array('Items')
You are right that for paginate to work you must query the model you wish to page and sort the lists by.
in PaymentController.php
//Query two levels deep, so the $payment['Order']['Item'][0-n] will be present
var $paginate = array('recursive' => 2);
Note this method does generate another query for each row to retrieve items.
Make sure the debug level in app/config/core.php is set to 2 to see the database calls.
1) You can use Containable behaviour, in which case you need to put this in your Item model:
var $actsAs = array('Containable');
and this into your Items controller:
$items = $this->Item->find('all'
, array (
'contain' => array('Order' => array('Payment'))
, 'conditions' => array('Payment.id' => $paymentId)
)
)
However I suspect that that will do a left join onto the Payments table (as its a hasMany relationship). So you won't filter Items in any way.
2) If you can't get contains to work then I often use explict joins (read this bakery article by nate on joins) in my find queries. So in your Items controller you'd have:
$items = $this->Item->find('all'
, array (
, 'joins' => array(
array(
'table' => 'payments'
, 'alias' => 'Payment'
, 'type' => 'INNER'
, 'conditions' => array(
'Option.id = Payment.option_id'
)
)
)
, 'conditions' => array('Payment.id' => $paymentId)
)
)
You may also need to specify the join onto the options table.

cakephp COUNT items per month in a year

How do you use cakephp to count, for example the number of posts, made every month in a year?
Preferably using Model->find('count') and get the data in an array.
I just did something similar, using only CakePHP (no direct queries). It works in CakePHP 2, haven't tested in 1.x.
The code for your example would be something like this:
$params = array(
'recursive' => -1,
'fields' => array('id', 'MONTH(created)')
'group' => array('YEAR(created)', 'MONTH(created)')
);
$numberOfPosts = $this->Model->find('count', $params);
This comes close
Query
$data = $this->Post->query("SELECT COUNT(id),MONTH(created) FROM posts GROUP BY YEAR(created), MONTH(created);");
Return
Array
(
[0] => Array
(
[0] => Array
(
[COUNT(id)] => 1
[MONTH(created)] => 3
)
)
[1] => Array
(
[0] => Array
(
[COUNT(id)] => 2
[MONTH(created)] => 4
)
)
)
When using cake, I prefer to stay as close to the framework as possible. This means that I try to avoid writing queries directly in the controllers because this results in the model code being everywhere. Therefore I recommend one of two solutions
1: (and what I do with more complicated stuff): Create a view for the calculation that you want to do and create a model to match.
2: Use a query as mentioned before, but put it in the model class, not the application class.