PHP MySQL - An attempt at recursive functions - php

I'm trying out my first recursive function (at least I think I am!) and it only half works. First, the code:
function check_title($i,$title) {
$q=mysql_query("SELECT Title FROM posts WHERE Title = '$title'");
$num=mysql_num_rows($q);
if($num==0) {
return $title;
}else {
$title=$title.' ('.$i++.')';
check_title($i,$title);
}
}
What I'm doing is taking a string (title) and checking if that title exists in the db already. If it does, I want to append a number to the newer of the duplicates (e.g. 'I Am A Title' becomes 'I Am A Title-2'). I then need to run the function again to check this new version of my title, and increase the appended value as required ('I Am A Title-3'). Once no duplication is discovered, return the Title in its acceptable form.
It works when no duplication is found (the easy bit), but fails when duplication is found. Instead of appending a number, the entire title variable is emptied.
Any help would by greatly appreciated!

As Mchl stated, the empty title is due to a lack of return in the else branch.
However, there is a problem with the function as it does not do what you intend. Currently, your function is building $title as 'Title-1-2-3-4-etc' the way you currently append the number to the title and check again. Instead of passing a modified title on the recursed call you should just pass the base title. Then, for the query, modify the title.
function check_title($title, $i = 0) {
$qtitle = $title . ($i == 0 ? '' : "-$i");
$q=mysql_query("SELECT Title FROM posts WHERE Title = '$qtitle'");
$num=mysql_num_rows($q);
if($num==0) {
return $title . ($i == 0 ? '' : "-$i");
}else {
return check_title(++$i,$title);
}
}
PS, I also changed the order of parameters that way your initial call doesn't need to specify 0.
$title = check_title($title);
PPS, I should mention this is a solution to do it via recursion. However, a recursive solution is not the proper solution here as it needlessly makes return trips to the DB. Instead, you should use an sql query that selects all titles LIKE "$title%" Order by title asc. Then, iterate through each result and do a regex comparison with the title to see if it matches a pattern <title>|<title>-<#>. If it does you increment a duplicate counter. At the end you spit out the title with an appended counter value. I'll leave that solution as an exercise for the original poster.

Use a loop instead...
$record_exists = true;
$title_base = "I Am A Title";
$title = $title_base;
$i = 0;
while($record_exists) {
$q=mysql_query("SELECT Title FROM posts WHERE Title = '$title'");
$num=mysql_num_rows($q);
if($num==0) {
$record_exists = false;
// Exit the loop.
}
else {
$i++;
$title = $title_base . "-" . $i;
}
}
echo $title; // last existing title
However, optimally you'd do more work with a single SQL query and iterate the result, saving a lot of trips to and from the database.
And just for fun...
$title_base = "I Am A Title";
$title = $title_base;
for ($i=1, $num=1; $num != 0; $i++)
{
$q=mysql_query("SELECT Title FROM posts WHERE Title = '$title'");
$num=mysql_num_rows($q);
$title = $title_base . "-" . $i;
}
echo $title; // next title in sequence (doesn't yet exist in the db)

You lack a return in else branch.
Recursion is not the best idea for this application. Hint: do a query like this SELECT MAX(Title) FROM posts WHERE Title LIKE '$title%');

Your recursive function is fine except for 2 things:
The original title isn't maintained between recursive calls. Hence each time $title = $title . ' (' . $i++ . ')' runs, another parenthesis is appended to the title, like "abc", "abc (1)", "abc (1) (2)" and so on.
You are returning $title when no more matches are found but no title is returned in the ELSE. It is important to do so. When the execution reaches the IF, it returns the title but the returned title is not assigned anywhere and hence is lost.
Here is the revised code:
$orgTitle = 'I am a title';
function check_title($i, $title = '') {
global $orgTitle;
$q = mysql_query("SELECT Title FROM posts WHERE Title = '$title'");
$num = mysql_num_rows($q);
if ($num == 0) {
return $title;
} else {
$title = $orgTitle . ' (' . ++$i .')';
return check_title($i, $title);
}
}
echo check_title(0, $orgTitle);
Note the addition of new variable $orgTitle. I've replaced it in the assignment statement inside the ELSE. This does the fix for point 1 above.
Also note the return added before check_title call in the ELSE. This solves point 2.
Hope it makes sense!
Add-on: Recursions are confusing, logically complex and tricky to debug. Also, recursive calls consume more memory (not in case of simple operations like your example) because the compiler/interpreter had to maintain the state variables for all steps in a recursion.

In order to minimise MySql interactions I'd recommend something similar to the following.
function checkTitle($title)
{
/*Return all iterations of the title*/
$res = mysql_query("SELECT COUNT(title) AS titleCount FROM posts
WHERE SUBSTR(title, 1,". strlen($title) .") = '$title' ");
/*Return the incremented title*/
return $title. (mysql_result($res, 0, "titleCount") + 1);
}
Example:
mysql> select title from posts;
+----------+
| title |
+----------+
| firefox1 |
| firefox2 |
| shoe |
| firefox3 |
+----------+
4 rows in set (0.00 sec)
mysql> SELECT COUNT(title) AS titleCount FROM posts WHERE SUBSTR(title, 1,7) = 'firefox' ;
+------------+
| titleCount |
+------------+
| 3 |
+------------+
1 row in set (0.00 sec)
mysql>
---- Follow up test
Test table structure.
mysql>SHOW COLUMNS FROM posts;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| title | varchar(12) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
/*Test code and output*/
function checkTitle($title)
{
/*Return all iterations of the title*/
$res = mysql_query("SELECT COUNT(title) AS titleCount FROM posts
WHERE SUBSTR(title, 1,". strlen($title) .") = '$title' ");
/*Return the incremented title*/
return $title. (mysql_result($res, 0, "titleCount") + 1);
}
mysql_connect("localhost","root", "password");
mysql_select_db("test");
echo checkTitle("firefox");
Output: firefox4

Related

Count how much specific words are in a database row

I have two lists of words and a database with more than thousand news articles.
I want to count how many of the words from the lists $badwords and $goodwords are in each article in my database. Next, I would like to save the two outcomes (of $badwords and $goodwords) per row in the columns badwords and goodwords. I will run this script with a cronjob.
My current table structure The last two rows are empty
TABLE news
-----------------
|ID|newstitle|newscontent|badwords|goodwords|
|1| Rain in London | It is horrible depressive weather in this nice city. | EMPTY | EMPTY |
|2| France wins the WorldCup | The player made a great goal. | EMPTY | EMPTY |
My desired table structure
The number of $badwords and $goodwords in the last two columns
TABLE news
-----------------
|ID|newstitle|newscontent|badwords|goodwords|
|1| Rain in London | It is horrible depressive weather in this nice city. | 2 | 1 |
|2| France wins the WorldCup | The player made a great goal. | 0 | 1 |
My current PHP code
<?php
//the wordlists
$badwords = "depressive horrible";
$goodwords = "great";
//connection to the database
$servername = "localhost";
$username = "user";
$password = "pass";
$dbname = "db";
$conn = new mysqli($servername, $username, $password, $dbname);
// here is my sql query
$sql = " UPDATE news
set badwords = (SELECT count (*) from news
where newscontent LIKE '.%$badwords%.')";
//close the connection
$conn->close();
?>
If I understand your question correctly you want to check if a certain wordlist exists in your database. In that case you're looking for a query like so (also use escaping in your queries depending on the db-class you use, e.g. mysqli_real_escape_string()):
SELECT COUNT(*) AS `count`
,`newscontent`
FROM `news`
WHERE `newscontent` = '" . $wordlist . "'
If you want to display how many times each wordlist exists in your database this is what you need:
SELECT COUNT(*) AS `count`
,`newscontent`
FROM `news`
GROUP BY `newscontent`
If you want to display how many strings you have for a given number of words this is what you're looking for:
<?php
$sql = new mysqli($host, $user, $password, $database);
$query = $sql->query('select * from `news`');
$summary = [];
while($record = $query->fetch_object()) {
$summary[count(explode(' ', $record->newscontent))]++;
}
echo '<pre>';
print_r($summary);
echo '</pre>';
If none of the above are what you're looking for than, after 4 times of reading your question, I literally have no clue what you're after.
Updated answer
Since you've updated your question I understand what you want. See the updated answer below.
<?php
// your db connection ...
// array with good and bad words
$good = [
'awesome',
'neat',
'fantastic',
'great',
// and so on
];
$bad = [
'horrible',
'worst',
'bad',
'terrific',
// and so on
];
// if you keep using your string approach you can set $good and $bad with $good = explode(' ', $goodwords); and $bad = explode(' ', $badwords);
// fetch the record you need
$query = $sql->query('select * from `news` where `ID` = 1'); // insert parameter for your ID here instead of just 1
$newsitem = $query->fetch_object();
// set up good and bad word counters
$totalGood = 0;
$totalBad = 0;
// check how many times each word is mentioned in newscontent
foreach($good as $word) {
// add spaces arround the word to make sure the full word is matched, not a part
$totalGood += substr_count($newsitem->newscontent, ' ' . $word . ' ');
}
// check how many times each word is mentioned in newscontent
foreach($bad as $word) {
// add spaces arround the word to make sure the full word is matched, not a part
$totalBad += substr_count($newsitem->newscontent, ' ' . $word . ' ');
}
// update the record
$sql->query("
update `news`
set `badwords` = " . $totalBad . ",
`goodword` = " . $totalGood . "
where `ID` = " . $newsitem->ID);
An interesting thing on text interpretation remains sarcasm. How would you deal with something like "Well, the weather in England is great again - as usual!" ;) Good luck though!

Count MySQL rows with values

I want to count how many rows in my MySQL database have two certain values. My table is set up like this:
|---------------------|
| ids |
|---------------------|
|source_id | target_id|
|----------|----------|
| 2 | 6|
| 2 | 6|
| 3 | 4|
|---------------------|
I want to count how many rows have the source_id = 2 and target_id = 6
I tried this statement:
<?php
$prep_stmt = "SELECT source_id FROM ids WHERE source_id = 2 AND target_id = 6";
if (!$result = $mysqli->query($prep_stmt)) {
die("Failed");
} else {
$num_rows = $result->num_rows;
echo $num_rows;
}
?>
However, the PHP file ceases to function after the third line.
Your code looks a bit weird. If you want to use prepared statements, that's working totally differentely:
<?php
$stmt = $mysqli->prepare("SELECT COUNT(*) FROM `ids` WHERE `source_id` = ? AND `target_id` = ?");
$stmt->bind_param("ii", $source_id, $target_id);
$stmt->execute();
$stmt->bind_result($count);
$stmt->fetch();
$stmt->close();
echo $count;
And without prepared statements.
<?php
echo $mysqli->query("SELECT COUNT(*) FROM `ids` WHERE `source_id` = 2 AND `target_id` = 6");
And as a last note, if you asign anything within a condition be sure to enclose it in brackets:
<?php
function fn() {
return "something";
}
if (($foo = fn())) {
// The condition is true if $foo isset, or in other words not null after the function was called.
}
if (!($foo = fn())) {}
if (($foo = fn()) === null) {}
// ...
SELECT COUNT(*) FROM ids WHERE source_id=2 AND target_id=6
SELECT COUNT(*) FROM ids WHERE source_id = 2 AND target_id = 6";
will give you the number of entries corresponding to what you want.
(it will give one row with 1 column, containing the number of lines corresponding to the where close)

Include corresponding data from table to json from another table

I have two tables-food and tags. Each row from food has corresponding tags.
I want to output each row with those tags, ie:
table food:
id | name
1 | bread
2 | meat
table tags:
reference_id | tag
1 | bakery
1 | wheat
2 | cow
desired output is:
{"results":
[{"id":"1","name":"bread","tags":["bakery","wheat"]},
{"id":"2","name":"meat","tags":["cow"]}]
}
So far I have this:
$db = getConnection();
$stmt = $db->query($sql);//get every column from every food
$food = $stmt->fetchAll(PDO::FETCH_OBJ);
$tagsSql="select id_reference,tag FROM tags T,food F WHERE F.id=T.food_id_reference";
$stmt = $db->query($tagsSql);
$tags=$stmt->fetchAll(PDO::FETCH_OBJ);
echo '{"results":' . json_encode($food) . '}';
I was thinking about cycling through every food and ever tag and find matching pairs, but it seems pretty heavyweight to me (considering the fact, that I could have thousands of rows). Do you have any suggestions?
Untested but I think something like this should work for you
$db = getConnection();
$stmt = $db->query($sql);//get every column from every food
$tagsSql="select F.id as id, F.name as name, group_concat(T.tag SEPARATOR ',') as tags FROM tags T,feeds F WHERE F.id=T.feed_id_reference group by feed_id_reference";
$stmt = $db->query($tagsSql);
for($x = 0; $x < count($tags); $x++){
$tags[$x]->{"tags"} = explode(",", $tags[$x]->{"tags"});
echo '{"results":' . json_encode($tags) . '}';
}

How can I use recursion to retrieve parent nodes?

I have created a recursive function to get parent products of a product. I'm almost sure I'm nearly there, but need help snapping out of recursion.
The result I am looking for is something like:
Product 1 (id:1, parent:none)
Product 2 (id:2, parent:1)
--- --- Product 3 (id:3, parent:2)
--- --- Product 4 (id:4, parent:2)
--- Product 5 (id:5, parent:1)
--- --- Product 6 (id:6, parent:5)
--- --- Product 7 (id:7, parent:5)
My updated function is as follows:
function get_parents ($pid, $found = array()) {
array_push ($found, $pid);
$sql = "SELECT * FROM products WHERE child_id = '$pid'";
$result = mysql_query($sql) or die ($sql);
if(mysql_num_rows($result)){
while($row = mysql_fetch_assoc($result)){
$found[] = get_parents($row['pid'], $found);
}
}
return $found;
}
I call it using a simple:
$parents = get_parents($pid);
The problem I am having is that when I run it, it creates an infinite loop, which doesn't break.
I don't want to get done for spamming so I have saved the result of my array to a text file, which can be seen here http://vasa.co/array.txt
Any help would be seriously appreciated :-)
Hmm.. judging by your structure of your DB, it would seem that something is amiss unless I'm missing something
The statement
$sql = "SELECT * FROM products WHERE child_id = '$pid'";
Tells me that for each product, you are storing the ID of the child. Typically, in a tree based structure, it's the reverse, you store the parent ID not the child - unless you want a child node to have many parents. If that is the case, then the function could easily run into problems. Consider the following:
| ID | Child_ID |
+----+----------+
| 1 | 2 |
| 2 | 1 |
This would cause an infinite loop. If you store the parent_id, then by that nature, you are encoding the graph to be hierarchical. Since every product has A parent, then the logic can be written recursively.
The could then be written as such?
function get_parents ($pid, $found = array()) {
array_push ($found, $pid);
$sql = "SELECT * FROM products WHERE id = '$pid'";
$result = mysql_query($sql) or die ($sql);
if(mysql_num_rows($result)){
while($row = mysql_fetch_assoc($result)){
$found[] = get_parents($row['parent_id'], $found);
}
}
return $found;
}

PHP Tree Traversal For Sub Forums Within Sub Forums

I have been developing my own forum for about a week now and I am almost done with all of the code, however, I am stuck on one single issue that I have not been able to figure out.
Well, simply said I have sub forums that can be within any amount of other sub forums.
How would I create a path dynamically to any of those sub forums on the spot with PHP.
After the path is created I would use it within href's and other things.
I am guessing I would somehow need to traverse the database based on a ID column and another column that would link one sub forum to another sub forum.
Let's assume that my database table looks like this:
ID | Name | Link |
---+-------------+-------
1 | Forum-One | Top |
2 | Forum-Two | 1 |
3 | Forum-Three | 2 |
4 | Forum-Four | 2 |
5 | Forum-Five | 3 |
6 | Forum-Six | 3 |
How would I go about doing this - or is there something else that must be done instead?
I hope I was clear enough for everyone to understand.
EDIT:
include("inc/config.php");
function generateBreadcrumb($startingID){
$result = mysql_query("SELECT * FROM temp_table WHERE ID='$startingID'");
while($row = mysql_fetch_array($result))
{
$db_id=$row['ID'];
$db_name=$row['Name'];
}
if($db_id!='Top'){
return generateBreadCrumb($db_id);
} else {
return $db_name;
}
}
$startID='6';
echo generateBreadcrumb($startID);
First you need a terminating condition. So set your top level Forum[link] to null, or 'top', or something. Then its simply a matter of using a recursive function put your bread-crumb together.
So lets assume you wanted to go to display the breadcrumb to Forum-One:Forum-Three:Forum-Six , better known as Forum-Six.
Example code:
<?php
$yourForumId = 6; // replace this dynamically with your forum;
$breadcrumb = generateBreadcrumb($yourForum);
function generateBreadcrumb($startingForumId){
$sql= "SELECT Name ,link FROM Forums WHERE ID = ".$startingForumId;
//run your $sql however you do to get results
//assuming you get associative arrays back
if($res['link'] != 'top'){
return generateBreadCrumb($res['link']).":".$res['Name'];
} else {
return $res['Name'];
}
}
echo $breadcrumb;
?>
It's recursion, which if you're new to it may seem complicated, but I hope that helps!
EDIT: here's your code with the needed edit...
include("inc/config.php");
function generateBreadcrumb($startingID){
$result = mysql_query("SELECT * FROM temp_table WHERE ID='$startingID'");
$row = mysql_fetch_array($result);
$db_id=$row['link'];
$db_name=$row['Name'];
if($db_id!='Top'){
return generateBreadCrumb($db_id).":".$db_name;
} else {
return $db_name;
}
}
$startID='6';
echo generateBreadcrumb($startID);

Categories