php infinite loop - php

This function gives me an infinite loop
function getCats($parent,$level){
// retrieve all children of $parent
$result = "";
$query = "SELECT title,parent_id from t_cats where parent_id = '$parent'";
if($rs = C_DB::fetchRecordset($query)){
while($row = C_DB::fetchRow($rs)){
$result .= str_repeat($parent,$level).$row['title']."\n";
getCats($row['parent_id'],$level+1);
}
}
return $result;
}
here is my db table
CREATE TABLE `db`.`t_cats` (
`ID` int(10) unsigned NOT NULL auto_increment,
`datasource_id` int(10) unsigned default '0',
`version` char(10) character set latin1 default 'edit',
`status` char(10) character set latin1 default 'new',
`modified_date` datetime default NULL,
`modified_by` int(10) unsigned default '0',
`title` char(255) character set latin1 default NULL,
`parent_id` int(11) default NULL,
PRIMARY KEY (`ID`),
KEY `idx_datasource_id` (`datasource_id`)
) ENGINE=MyISAM AUTO_INCREMENT=50 DEFAULT CHARSET=utf8;
I just want to be able to get my list of categories recursive.
But what am i doing wrong?
EDIT:
function getCats($parent,$level){
// retrieve all children of $parent
$result ="";
$query = "SELECT title,parent_id from t_cats where parent_id = '$parent'";
if($rs = C_DB::fetchRecordset($query)){
while($row = C_DB::fetchRow($rs)){
$result.= str_repeat($parent,$level).$row['title']."\n";
getCats($row['id'],$level + 1 );
}
}
return $result;
}

This line looks wrong:
getCats($row['parent_id'],$level+1);
You should be calling it with the current child ID - at the moment you're calling it with the same ID over and over. Something like this (you need to select the id from your table):
getCats($row['id'], $level + 1);
Edit: you need to update your query to select id:
$query = "SELECT id, title, parent_id from t_cats where parent_id = '$parent' AND id != parent_id";
I've also added a bit to stop it getting into a loop if an item is its own parent.

I found this SitePoint article on "Storing Hierarchical Data in a Database" very helpful. It's all PHP examples, and it will improve the performance of what you're trying to do dramatically.

Maybe one of the items in the db has itself as parent?

I don't know C_DB, but I'd bet that the $rs returned by fetchrecordset is a reference, which means that every invocation of getCats is using the same $rs. Exactly what it will do then is unpredictable, depending on how fetchRow is implemented.
If you want to do this (and recursive closures are a pain in SQL, I know), you should open a new connection inside getCats. and be using a separate connection for each access.

correct answer provided by greg ...
2 side notes:
in the case of infinite loops, track recursion depth (you can conveniently use $level here) or overall invocation count (if you are lazy, since this is a oneliner accessing a global counter), and terminate recursion with an exception, when it reaches a maximum value (in general, 10 is already enough to see the problem, but of course that may vary) ... and then get some debug output ... for example $query or something like "calling getCats($parent,$level)" ... would've shown you the problem in no time in this case ... :)
you should minimize the amount of queries ... traversing a tree like that is quite inefficient ... especially, if the database is on another machine ...
greetz
back2dos

Erm shouldnt it be:
$query = "SELECT title,parent_id from t_cats where id = '$parent'";
And not:
$query = "SELECT title,parent_id from t_cats where parent_id = '$parent'";

Related

MySQL procedures - incrementally recalculate rows

I have a quite trivial task of calculating budget entries (income/outcome/balance). There can be thousands of entries and I can change any of them in the middle. As the result, all later entries balance must be recalculated.
Right now I am doing it in PHP by iterating through array of all entries, and updating rows that changed. It takes too much time that way - my server stops responding for several minutes.
I suppose that it happens because PHP calls MySQL for every entry update, though for PHP itself this task of array iteration and recalculation is pretty cheap. I think that there must be a way to throw this task at MySQL, so it does the iteration/recalculation/update itself, which might be cheap as well.
I am not an expert in MySQL at all, but I heard that there are stored procedures that might be the cure.
Here is my MySQL (5.5.33) table:
CREATE TABLE `entry` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`date` date DEFAULT NULL,
`is_income` tinyint(1) NOT NULL DEFAULT '0',
`income` decimal(20,2) DEFAULT NULL,
`outcome` decimal(20,2) DEFAULT NULL,
`balance` decimal(20,2) DEFAULT NULL,
PRIMARY KEY (`id`)
)
Here is my PHP (5.3.27):
//...
//$DB is a class for operating DB
$entries = $DB->get_all('entry'); //retrieves all entries from 'entry' table, sorted by date
$balance = 0;
foreach ($entries as $e) {
if ($e['is_income']) {
$balance += $e['income'];
} else {
$balance -= $e['outcome'];
}
if ($balance <> $e['balance']) {
$e1 = $e;
$e1['balance'] = $balance;
$DB->update('entry', $e1); //update the row by doing query('UPDATE `entry` ... WHERE id=' . $entry_id);
}
}
Can you point me the right direction? Thanks!
I think you can do this in a single SQL UPDATE query, no procedure needed.
UPDATE entry AS e1
JOIN (SELECT * FROM entry ORDER BY date) AS e2 ON e1.id = e2.id
CROSS JOIN (SELECT #balance := 0) AS var
SET e1.balance = (#balance := #balance + IF(e2.is_income, e2.income, -e2.outcome))
The user variable #balance serves the same purpose as the PHP variable $balance. The subquery is needed because MySQL doesn't allow use of ORDER BY in a multi-table UPDATE query, so you need to join with a subquery that produces the IDs in date order.
The "proper" way is to do the summation when displaying the report, and not store it in the table.
For only "thousands", it should not be a performance problem.

PHP mysqli - updating value of enum field results in empty string, but same query works when run in PHPMyAdmin

The query:
UPDATE caption_queue SET status = 'Conversion Completed' WHERE tpi_id = '3130'
As stated in the title, when I run this in PHP, the value is set to an empty string. However, when the exact same query is run directly in MySQL, it works correctly.
On top of that, I'm only getting this behavior on a single enum value: 'Conversion Completed'. When updating with other values (most of which also contain spaces), there is no problem.
Actual PHP code for those interested:
$sql = "UPDATE caption_queue SET status = 'Conversion Completed' WHERE tpi_id = '$tpi_id'";
$val = mysqli_query($link, $sql);
//$link comes from somewhere else, but we use it extensively throughout our website
Table definition:
CREATE TABLE IF NOT EXISTS `caption_queue` (
`tpi_id` int(11) NOT NULL,
`pid` int(6) DEFAULT NULL,
`conversion_began` datetime DEFAULT NULL,
`yt_caption_id` varchar(50) DEFAULT NULL,
`yt_video_id` varchar(50) DEFAULT NULL,
`status` enum('Pending Conversion','Converting','Conversion Completed','Pending Upload','Video Processing','Video Processed','Uploading Transcription','Caption Syncing','Caption Synced','Caption Downloading','Caption Ready') DEFAULT 'Pending Conversion'
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
if you are using MySQLI and your database has enums you need to find the place of your value in order to update your database because it does not accept new strings!
here is an example of the column of my database configuration!
status enum('active', 'inactive', 'banned')
if you want to update these values convert the values into numbers for example active = 1, inactive = 2, banned = 3
from PHP we are able to do the following
$query = 'UPDATE '.$this->table.' SET status = :status'
$stmt = $this->conn->prepare($query);
if($this->status == 'active')
{
$finalStatus = 1;
}
if($this->status == 'inactive')
{
$finalStatus = 2;
}
if($this->status == 'banned')
{
$finalStatus = 3;
}
$stmt->bindParam(':status', $finalStatus);
$stmt->execute();
and this will save your day!
this code was used as an example to provide a full solution to this issue!
Thanks.
I think you'll find it should work if you put the column called status in back ticks.
$query="UPDATE caption_queue SET `status` = 'Conversion Completed' WHERE tpi_id = '3130'";
I found a workaround. By using strict mode:
SET SESSION sql_mode = 'STRICT_ALL_TABLES'
I'm able to update the field with no issues. Seems like some kind of issue with mysqli.

IF and ELSE IF statements within a WHILE loop not working properly

I'm trying to create notification system for my community website, am trying to use a while loop to get data, when ever a condition in the if statement is met within the while loop, it should display/print data to the page. For some reason it's only displaying one result, dunno why.
The structure of my database:
CREATE TABLE IF NOT EXISTS `notifications` (
`notification_id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`to_id` int(11) NOT NULL,
`notification_identifier` enum('1','2','3','4','5','6') NOT NULL,
`notify_id` int(11) NOT NULL,
`opened` enum('0','1') NOT NULL,
`timestamp` datetime NOT NULL,
PRIMARY KEY (`notification_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
The notification_identifier tell me what type of notification it is(e.g. profile comment, status update, likes) and the notify_id tells me id of each specific table i need to check with.
My code:
<?
$DisplayNotification ="";
$unread = "0";
$mynotify = mysql_query("SELECT * FROM notifications WHERE to_id='$logOptions_id' AND opened='$unread'") or die (mysql_error());
$notify_Count = mysql_num_rows($mynotify);
if($notify_Count>0){
while($row = mysql_fetch_array($mynotify)){
$notification_id = $row["notification_id"];
$memb_id = $row["user_id"];
$identifier = $row["notification_identifier"];
$notify_id =$row["notify_id"];
$timestamp = $row["timestamp"];
$convertedTime = ($myObject -> convert_datetime($timestamp));
$when_notify = ($myObject -> makeAgo($convertedTime));
if($identifier == 1){// condition 1
$DisplayNotification ='user added you as a friend';
}else if ($identifier == 2) {//condition 2
$DisplayNotification ='user commented on your post';
}
}
}else{// End of $notify
$DisplayNotification ='You have no new notifications.';
}
?>
any help appreciated
Where is $DisplayNotification actually displayed? It's certainly not within the body of your loop.
Each time through the loop you assign $DisplayNotification a new value, which of course replaces the old value. By the time you get done, no matter what's happened, the most recent change is the only one left.
Most likely I suspect you meant to do something like
$DisplayNotification .= "User added you as a friend\n";
The .= will continue adding new text to the same variable throughout the loop.
Or perhaps you could use an array, in which case you'd do
$DisplayNotifications[] = "User added you as a friend";
Then you could display all the items at the end however you'd like.
It looks like you run the while statement fully before actually dumping the variable $DisplayNotification. If that is the case, you're just switching values on the variable during the loop. You either need to store the values to be dumped inside an Array or just dump them inside the loop.

JQuery & PHP Star Rating simplified

EDIT: The plugin in question is located here.
PHP beginner here using a JQuery Star Rating snippet and have gotten it to work perfectly. My problem is that it is currently configured to count and display the average of many ratings (for public applications). I'm trying to simplify the plugin so that it allows one to set a personal rating (as if rating your own songs in iTunes). The user may update their rating, but no partial stars would ever exist. I've broken the plugin many times trying to get it working, but to no avail. The mysql database exists as follows:
CREATE TABLE IF NOT EXISTS `pd_total_vote` (
`id` int(11) NOT NULL auto_increment,
`desc` varchar(50) NOT NULL,
`counter` int(8) NOT NULL default '0',
`value` int(8) NOT NULL default '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
If I can get it working the way I imagine, I wouldn't require both the counter and value columns, simply a single INT column that holds a value between 1 and 5. Currently counter accumulates the number of votes, while value aggregates the ratings. The stars are then displayed using (value/counter)*20 (as a percentage). The PHP is below (original):
<?php
// connect to database
$dbh=mysql_connect ("localhost", "user", "pass") or die ('Cannot connect to the database');
mysql_select_db ("thenally_pd",$dbh);
if($_GET['do']=='rate'){
rate($_GET['id']);
}else if($_GET['do']=='getrate'){
// get rating
getRating($_GET['id']);
}
// get data from table
function fetchStar(){
$sql = "select * from `pd_total_vote`";
$result=#mysql_query($sql);
while($rs = #mysql_fetch_array($result,MYSQL_ASSOC)){
$arr_data[] = $rs;
}
return $arr_data;
}
// function to retrieve
function getRating($id){
$sql= "select * from `pd_total_vote` where id='".$id."' ";
$result=#mysql_query($sql);
$rs=#mysql_fetch_array($result);
// set width of star
$rating = (#round($rs[value] / $rs[counter],1)) * 20;
echo $rating;
}
// function to set rating
function rate($id){
$text = strip_tags($_GET['rating']);
$update = "update `pd_total_vote` set counter = counter + 1, value = value + ".$_GET['rating']." where id='".$id."' ";
$result = #mysql_query($update);
}
?>
Thanks for a point in the right direction,
Mike
I am unsure as I have no access to the rating system you are using yet just glancing at what you have I guess you could keep the counter set to 1 (if removing it breaks the jQuery Rating System) and have the value updated by the person so when you fetch it they only see their value (make sure value can't go above 5). That way if the value is set to 5 then it will show 5 because it isn't finding other ratings.... (based on my understanding) You will also have to add a user id so you know which persons rating to fetch (since you want it personal). This depends on how dependent the application is a specific database design.

Recursive Function To Create Array

i use kohana framework and i am trying to code recursive function to create category tree.
My Categories Table
id int(11) NO PRI NULL auto_increment
name varchar(50) NO NULL
parent_id int(11) NO NULL
projects_count int(11) NO NULL
My Example Which Is Not Work
public static function category_list($parent_id = 0)
{
$result = Database::instance()->query('
SELECT name, projects_count
FROM project_categories
WHERE parent_id = ?',
array($parent_id)
);
$project_categories = array();
foreach($result as $row)
{
$project_categories[] = $row;
Project_Categories_Model::factory()->category_list($parent_id + 1);
}
return $project_categories;
}
Using this kind of hierarchical data implementation is highly non-optimal, because to get every subcategory you need do a separate query to the database. Like here you want to create recursion function.
If you still can change your table architecture please check Managing Hierarchical Data in MySQL.
This article describes a solution, how to fetch the whole hierarchy in one time query, so the recursive function will not be necessary.

Categories