Somewhat-Hierarchical Data With MySQL - php

I'm working with some somewhat hierarchical data for a work project and trying to find a more efficient way of dealing with it as my first attempt is probably dreadful in more ways than one. I've looked at a number of hierarchical data questions on this site so I know that with my structure it's nigh-impossible to get the information in a single query.
The table I'm querying from is on an AS/400 and each entry stores a single part of a single step, so if I had PartOne and three Components go into it there is an entry for each like:
PartOne ComponentOne, PartOne ComponentTwo, PartThree ComponentThree.
Its important to note if there are components for ComponentOne a subsequent row could contain:
ComponentOne SubComponentOne, ComponentOne SubComponentTwo.
With this in mind I'm trying to get all of the components in a tree-like structure for given finished parts, basically getting everything that goes into the final product.
I cannot create a flattened table for this, as what I'm trying to do is dynamically generate that flattened table. I do however have access to the list of finished parts. So my current algorithm goes like this
Fetch part number, query table for the components when that's the created part.
Take those components and query for each as the created part and get their components.
Repeat until query returns no entries.
In this case that's 7 queries deep. I'm wondering from anyone on the outside looking in if a better algorithm makes sense for this and also its been a while since I've done recursion but this seems reasonable for recursion at least in the creation of the queries. Would it be reasonable to look into creating a recursive function that passes back the query results from each level and somewhere in there store the information in an array/table/database entries?

Do you want a tree structure in php?
Then the next code sample might be interesting.
This builds a tree for records in the categories table starting with id -1000 as the root element, using only as many queries as the number of levels deep you want the information.
Table structure:
TABLE categories (
id INT NOT NULL PRIMARY KEY,
parent_id INT NULL,
name NVARCHAR(40) NOT NULL
)
PHP code:
class Bom {
public $Id;
public $Name;
public $Components = array();
function __construct( $id, $name ) {
$this->Id = $id;
$this->Name = $name;
}
}
$parentIds = array( -1000 ); // List of id's to lookup
$rootBom = new Bom( -1000, 'Root' );
$bomsById[ -1000 ][] = $rootBom; // For each id there can be multiple instances if we want separate instances of Bom for each time it occurs in the tree
$maxLevel = 0;
while ( count( $parentIds ) > 0
&& $maxLevel++ < 10
&& $result = $mysqli->query( "SELECT * FROM categories WHERE parent_id IN ( " . implode( ", ", $parentIds ) . " ) ORDER BY name" ) )
{
$parentIds = array(); // Clear the lookup list
$newBomsById = array();
while ( $row = $result->fetch_assoc() )
{
$boms = $bomsById[ $row[ 'parent_id' ] ];
if ( $boms )
{
foreach ( $boms as $bomToUpdate )
{
$compontentBom = new Bom( $row[ 'id' ], $row[ 'name' ] );
$bomToUpdate->Components[] = $compontentBom;
$newBomsById[ $compontentBom->Id ][] = $compontentBom;
}
$parentIds[] = $row[ 'id' ]; // Build new list of id's to lookup
}
}
$bomsById = $newBomsById;
}
echo '<!--
' . print_r( $rootBom, true ) . '
-->';

Related

Paginate Tree Array

I am looking at trying to build pagination method for an array. I have an array something like below. Before you suggest making the pagination work for sql query, I have already done so and it did work for a flat array but a requirement is having this multidimensional tree array.
array = (
item_id = 5,
parent_id = 0,
children = array(
array(
item_id = 20,
parent_id = 5,
children = array(
array(
item_id = 24,
parent_id = 20
),
array(
item_id = 24,
parent_id = 20
)
)
)
)
);
What methods that I can find don't seem to work with such an array since array_slice will only work on the first level of the array and doesn't take into consideration the children levels.
/*
$root =
[
'id' => 1,
'children' => [...]
]
$queue[] = $root
$visiteds[] = $root
while ($queue){
$current = array_shift($queue);
// do whatever with the current ex: echo $current."<br>"
if $current has children {
foreach child {
if ($child NOT in $visiteds) { // $child not visited before
$visiteds[] = $child // mark as visited
$queue[] = $child // add to queue
// do whatever with the child ex: echo $child ."<br>"
}
}
}
}
*/
Note: if you want to visit exactly same children as required (for example 2 exactly same child must be visited twice then, remove $visiteds related parts. In this case be careful that your graph structure must not have cycling.)
You may consider to read about Graph Theory, Breadth First Search algorithm, Depth First Search algorithm.

Laravel "Trying to get property of non-object" Setting Array with Database Values

I'm Trying to solve this error i'm having with PHP, i'm not completely familiar with the Language, so it would be nice if you would help me out, I can't figure out this error.
I have this Code Here:
public function index() {
$counterino = ClientsJobs::all()->count();
$MasterArray = array();
/* Go Through All of the Records in the Client-Jobs Table and Resolve their columns to Desired Names */
for ($i = 1; $i <= $counterino; $i++ ) {
//Temporary Array for one Name-Resolved-Row of the Table.
$tempArray = array(
'id' => ClientsJobs::find( $i )->id, // id
'client_name' => ClientsJobs::find( $i )->clients->fname , // get the first name ( based on fk )
'job_name' => ClientsJobs::find( $i )->jobs->name, // get the name of the job ( based on fk )
'wage' => ClientsJobs::find( $i )->wage, // wage for the job
'productivity'=> ClientsJobs::find( $i )->producivity // productivity level for the job
);
$MasterArray[] = $tempArray; //add the row
}
return $MasterArray;
}
This code changes the names of the of the Columns in the ClientsJobs Junction Table.
public function up()
{
Schema::create('clients-jobs', function(Blueprint $table)
{
$table->increments('id')->unsigned();
$table->integer('client_id')->unsigned();
$table->foreign('client_id')->references('id')->on('clients');
$table->integer('job_id')->unsigned();
$table->foreign('job_id')->references('id')->on('jobs');
$table->decimal('wage', 4, 2);
$table->decimal('productivity', 5, 2); // 0.00 - 100.00 (PERCENT)
$table->timestamps();
});
}
The Jobs and Clients Table are very simple.
I am having the Error in the index() function I posted above, it says
'Trying to get property of non-object'
Starting on the Line
'client_name' => ClientsJobs::find( $i )->clients->fname,
It's also mad at me for the other parts of setting the array.
I have tested the individual functions I am using to set the array and they all work, fname should also return a string, I used dd() to get the value.
I have tried:
-Using FindorFail
-Setting the Array without the for loop and setting each element manually
-Dumping out multiple parts of the function to make sure it works( counterino, all of the functions for the array, .. )
My guess is that it has to do with the type-deduction of PHP, I actually only need a string array, but would still like to use the name mappings because I am going to be passing this a View I am using for some of my other stuff. The Code was actually working earlier, but I broke it somehow (adding a new record or running a composer update?) anyway, there's some serious voodoo going on.
Thanks in Advance for the help, I am working on this project for a Non-Profit Organization for free.
P.S. I am using Laravel 4.2, and Platform 2.0
First off, this is a horrible practice:
$tempArray = array(
'id' => ClientsJobs::find( $i )->id, // id
'client_name' => ClientsJobs::find( $i )->clients->fname , // get the first name ( based on fk )
'job_name' => ClientsJobs::find( $i )->jobs->name, // get the name of the job ( based on fk )
'wage' => ClientsJobs::find( $i )->wage, // wage for the job
'productivity'=> ClientsJobs::find( $i )->producivity // productivity level for the job
);
By calling ClientJobs::find($i) multiple times, you are doing multiple times the same lookup - either to your DB, or to your cache layer if you have one configured.
Secondly, the answer to your question depends on your ClientJobs model. For your example to work, it needs:
A valid clients relations, defined as follows:
public function clients()
{
return $this->hasOne(...);
}
clients also needs to be a valid 1:1 always existing relation. i.e. there must always be one client. If there isn't, you are susceptible to the error you just got (as the `clients̀ magic would end up being null)
The same applies to jobs.
In every case, it is better to make sure everything is set first. Check using the following:
$clientJob = ClientJobs::find($i);
if (!$clientJob->clients || $clientJob->jobs) throw new \RangeException("No client or job defined for ClientJob $i");
And then catch the exception at whichever level you prefer.
Best approach
public function index() {
$masterArray = array();
ClientsJobs::with('clients', 'jobs')->chunk(200, function($records) use (&$masterArray) {
foreach ($records as $record) {
$masterArray[] = array(
'id' => $record->id, // id
'client_name' => !empty($record->clients) ? $record->clients->fname : null,
'job_name' => !empty($record->jobs) ? $record->jobs->name : null,
'wage' => $record->wage,
'productivity'=> $record->productivity,
);
}
});
return $MasterArray;
}
Your Approach is very wrong
If you want to return an array you can do like this
$counterino = ClientsJobs::all()->toArray();
This will fetch all rows from the table and the toArray will convert the object into an array

PHP MySQL building a 3 Tier multi dimensional array

So I have my query, its returning results as expect all is swell, except today my designer through in a wrench. Which seems to be throwing me off my game a bit, maybe its cause Im to tired who knows, anyway..
I am to create a 3 tier array
primary category, sub category (which can have multiples per primary), and the item list per sub category which could be 1 to 100 items.
I've tried foreach, while, for loops. All typically starting with $final = array(); then the loop below that.
trying to build arrays like:
$final[$row['primary]][$row['sub']][] = $row['item]
$final[$row['primary]][$row['sub']] = $row['item]
I've tried defining them each as there own array to use array_push() on. And various other tactics and I am failing horribly. I need a fresh minded person to help me out here. From what type of loop would best suit my need to how I can construct my array(s) to build out according to plan.
The Desired outcome would be
array(
primary = array
(
sub = array
(
itemA,
itemB,
itemC
),
sub = array
(
itemA,
itemB,
itemC
),
),
primary = array
(
sub = array
(
itemA,
itemB,
itemC
),
sub = array
(
itemA,
itemB,
itemC
),
),
)
Something like this during treatment of your request :
if (!array_key_exists($row['primary'], $final)) {
$final[$row['primary']] = array();
}
if (!array_key_exists($row['sub'], $final[$row['primary']])) {
$final[$row['primary']][$row['sub']] = array();
}
$final[$row['primary']][$row['sub']][] = $row['item'];
Something like this....
$final =
array(
'Primary1'=>array(
'Sub1'=>array("Item1", "Item2"),
'Sub2'=>array("Item3", "Item4")
),
'Primary2'=>array(
'Sub3'=>array("Item5", "Item6"),
'Sub4'=>array("Item7", "Item8")
),
);
You can do it using array_push but it's not that easy since you really want an associative array and array_push doesn't work well with keys. You could certainly use it to add items to your sub-elements
array_push($final['Primary1']['Sub1'], "Some New Item");
If I understand you correctly, you want to fetch a couple of db relations into an PHP Array.
This is some example code how you can resolve that:
<?php
$output = array();
$i = 0;
// DB Query
while($categories) { // $categories is an db result
$output[$i] = $categories;
$ii = 0;
// DB Query
while($subcategories) { // $subcategories is an db result
$output[$i]['subcategories'][$ii] = $subcategories;
$iii = 0;
// DB Query
while($items) { // $items is an db result
$output[$i]['subcategories'][$ii]['items'][$iii] = $items;
$iii++;
}
$ii++;
}
$i++;
}
print_r($output);
?>

Return all page_names in WordPress, without excess?

I need to retrieve an array of every page_name registered in WordPress. I've got two issues, I can do a get_pages() and such, but it literally pulls every freakin thing about each page including their content. Totally unnecessary overhead when all I need is page_name for each.
The other is that I'd like to do it with a built in method if possible, as this is for an in-house plugin and we'd like to keep it compatible with the mainline. (worst case will just access the DB directly and get them) I know you can include/exclude in the get_pages() call, but I haven't figured out if it is possible to exclude retrieving everything but one, instead of the opposite.
It needs to be dynamic in that it cannot have any hard-coded strings i.e. know anything about the pages themselves or what they're called. Also no extra junk like it being in an unordered list or something. Straight up array, no levels needed. (sub-pages are listed same as primary)
Any ideas guys? I've searched and searched..but the documentation is retarded for these types of things as you guys probably know.
Thanks.
Example of what I'd like in the end or similar:
Array
(
[0] => stdClass Object
(
[page_name] => 'page1'
)
[1] => stdClass Object
(
[page_name] => 'page2'
)
[2] => stdClass Object
(
[page_name] => 'page3'
)
[3] => stdClass Object
(
[page_name] => 'page4'
)
)
To limit the fields returned, you can set up a filter. In your themes functions.php file, or a plugin, try
add_filter( 'get_pages', 'get_pages_filter' );
function get_pages_filter( $res ){
$res = array_map( 'get_pages_title', $res );
return $res;
}
function get_pages_title( $item ){
return (object) array( 'page_name' => $item->post_name );
}
$pages = get_pages();
var_dump( $pages );
I checked get_pages() source code and there's no way you can limit what's being requested from a database. Pages are fetched at lines 3177-3184, and as you can see, there's a hardcoded SELECT * FROM query.
If anyone has a clean solution, please chime in. For now I'm just going to use WordPress's built in DB connection and grab them manually.
For the curious...I just threw this together quick...probably not the best..
/**
* get_wpdb_values()
*
* DESC:
*
* Allows you to make a WP Database connection
* and return an Array => Object of what ever
* values you need from a table.
*
* Was made for the purpose of returning a page
* list, but since you pass it the field you
* want returned, along with with optional filters
* it can easily be used for other purposes.
*
* USAGE:
*
* array get_wpdb_values ( string $table [, string $field [, array $filters ]] )
*
* PARAMETERS:
*
* ----------|-----------------------------------------------------------------
* $table | Required table you want to return values from.
* | DO NOT INCLUDE THE WP PREFIX! (e.g. use 'posts' not 'wp_posts'
* ----------|-----------------------------------------------------------------
* $field | Optional field name you want returned. (Default returns * all)
* ----------|-----------------------------------------------------------------
* $filters | Optional filtering passed as field => value array.
* ----------|-----------------------------------------------------------------
*/
function get_wpdb_values( $table = null,
$field = "*",
$filters = null )
{
// Get access to the
// WordPress Database
// class in this scope
global $wpdb;
// If we weren't passed any
// arguments, get out quick
if(is_null($table) || empty($table))
return false;
// Add optional filters if
// they were passed in
if(!is_null($filters))
{
// Counter is so we can tell
// if there is more then one
// filter so we can add the
// AND separator in the
// SQL query
$WHERE = "WHERE ";
$counter = 0;
foreach ($filters as $key => &$value)
{
$counter++;
// If we're on the second or more
// pair, we add the AND to chain
// conditional WHERE's
$AND = ($counter >= 2) ? " AND" : null;
// Append to existing WHERE
// statement
$WHERE .= "$AND $key = '$value' ";
}
}
else
{
// No filters passed
$WHERE = null;
}
// Get WordPress formatted
// table name
$wp_table = $wpdb->$table;
// Putting it all together
$query = "SELECT $field FROM $wp_table $WHERE ";
// Make actual DB call
// through the built in
// MySQL interface for
// WordPress to at least
// attempt to remain
// compatible with mainline
return $wpdb->get_results($query);
}

User reporting Library

I would like to build (or find) a php reporting library that I can use to allow my users to report any type of content as bad. Actually, if they could report content as good (which I guess would mean a rating system) that would be even better. So if anyone knows of a library like this I would love to hear about it.
Anyway, this is how I imagine the database working and I would like to know if this sounds correct.
ID - ID of row
USER_ID - ID of the user voting/reporting
ITEM_ID - UNIQUE table row ID of the item reported (post, other user, link, etc...)
TYPE - the name of the table this pertains to (posts, users, links, etc...)
DATE - datetime of report
VALUE - I guess this could be an UP vote or DOWN vote
By including the [TYPE] column I can use this voting system across all content types on my site instead of just one (like forum posts). By storing the user_id I can be sure that only one vote/report is cast per item per user.
Well, I went ahead and built it like I said. Frank was right, it wasn't too hard. Here is the code incase anyone else wants to use it on a MVC that supports AR styled DB calls like CodeIgniter or MicroMVC:
/*
* Multi-table user voting model
*/
class votes extends model {
public static $table = 'votes';
function cast( $user_id = NULL, $item_id = NULL, $type = NULL, $vote = TRUE) {
$where = array(
'user_id' => $user_id,
'item_id' => $item_id,
'type' => $type
);
//Try to fetch this row
$row = $this->db->get_where(self::$table, $where );
//Look for an existing vote row
if( $row && ! empty($row[0]) ) {
//Fetch row
$row = $row[0];
//If they already voted this way... then we're done!
if( $row->vote == $vote) {
return;
}
//Else update the row to the new vote
$row->vote = $vote;
$row->date = convert_time(time());
$this->db->update( self::$table, $row, array('id' => $row->id) );
return;
}
//Else create a new vote for this item
$row = array(
'user_id' => $user_id,
'item_id' => $item_id,
'type' => $type,
'date' => convert_time(time()),
'vote' => $vote,
);
$this->db->insert( self::$table, $row);
}
}

Categories