MySQL complicated query. Dynamically Update rows from a table based on another - php

Above is a scheme I drew. There's an ideal case, when content.user are grouped. But usually they are not grouped.
What i meant in this scheme is:
At the first step, I select users.monetos WHERE users.id = content.user
At the second step, I decrement users.monetos with every content.cpc value (2.1 , 2.2)
When simulating this:
select content.user (9)
select users.monetos Where users.id=content.users (15)
So we have 15 value for users.monetos for users.id=9, now we go back to content table
and:
decrement 15 value with 8 (content.cpc) (15-8=7 > 0 -> go to step 2)
decrement 7 (result from previous step) with 10 (content.cpc) (7-10=-3 <0 -> update
content set active='0' where content.id= (current id when a negative result obtained) )
And like this for every content.user
More extended - I want to select contet.* rows which have content.active = 1 (n). Having this data, SELECT users.monetos WHERE users.id=content.user from previous query.
And now, by maximum (n) steps I decrement users.monetos value by content.cpc value
and on the moment when **users.monetos=0 or less than 0, i want to update content and SET active='0'**
By words, I want like to share users.monetos amount to each content entry (content.cpc for each). And there's no more users.monetos make the current content
entry inactive. And do this vor every content.user
What i'he done at this moment is shown below. I now it looks really bad, but I already don't know what to do.
$query = "select content.id, content.cpc, conent.user, content.active from content a
join users b on a.user=b.id
group by b.id where a.active='1'";
/** cycle each user **/
foreach($rows = $connector->fetchArray($query) as $row ) {
$monetos = $row['monetos'];
$query = "select id, cpc from content where user={$row['id']}";
/** cycle each users content **/
foreach($contents = $connector->fetchArray($query) as $content) {
echo $monetos;
$monetos -= $content['cpc'];
if($monetos <= 0) {
$disable[] = $content['id'];
}
}
if( isset($disable) ) {
$connector->query("update content set active='0' where id in(".implode(',',$disable).")");
}
}

You can try this code:
$query = "select * from users";
foreach($rows = $connector->fetchArray($query) as $row ) {
$a='';
$query = "select * from content where user='{$row[id]}' and active='1' ";
foreach($contents = $connector->fetchArray($query) as $content ) {
$a++;
$a_min=$a-1;
$test[$a]=$row[monetos]-$content[cpc];
if($a==2){
if($test[$a_min] > 0){
$test2=$test[$a_min]-$content[cpc];
if($test2 < 0){
$connector->query("update content set active='0' where id='$content[id]'");
}
}
}
}
}

Related

PhP/MySQL: how to dynamically change my (large and always changing) database

Scenario
I have a MySQL database with 10.000 rows. Setup of the database:
ID UniqueKey Name Url Score ItemValue
1 5Zvr3 Google google.com 13 X
2 46cfG Radio radio.com -20 X
3 2fg64 Yahoo yahoo.com 5 X
.... etc etc etc
As you can see, each item has a score. The score is constantly changing. Google may have a score of 13 now, but tomorrow it may be 80, or -50.
What I want:
I want to create a system that creates a hierarchy in my current database, based on the score of the items. Right now I'm thinking about percentile ranks, meaning that the highest scoring items will be close to 100%, and the lowest scoring items will be close to 0%. For this I created some code that will try to achieve what is shown here: http://www.psychstat.missouristate.edu/introbook/sbk14m.htm
This is my code:
$sql = "SELECT * FROM database order by Score";
$result = $conn->query($sql);
$count = 0;
while ($row = $result->fetch_assoc()) {
$woow = $row['Score'];
$sql = "SELECT * FROM database WHERE Score = $woow";
$resultnew = $conn->query($sql);
$somanythesame = $resultnew->num_rows;
$itemPercentile = ( ($count/$result->num_rows + 0.5*$somanythesame/$result->num_rows) * 100 );
$rowID = $row['ID'];
$sql2 = "UPDATE database SET itemValue = $itemPercentile WHERE ID = $rowID";
$conn->query($sql2);
$count++;
}
This works, but for one problem it does not: There are many items in my database, many with the same score. To illustrate my problem, here is a very simple 10-row database with only the Scores:
Scores
-10
0
0
0
10
20
20
30
40
50
The problem with my code is that it doesn't give the same percentile for the items with the same Score, because it takes in account all previous rows for the calculation, including the ones with the same Score.
So, for the 2nd, 3rd and 4th item with a Score of 0, it should be like this: (1/10 + 0.5*1/10) * 100. Problem is, that for the 3rd item it will do (2/10 + 0.5*1/10) * 100 and the 4th item it will do (3/10 + 0.5*1/10) * 100.
Then, for the 5th item with a score of 10, it should do (4/10 + 0.5*1/10) * 100. This is going well; only not for the items with te same score.
I'm not sure if I explained this well, I find it hard to put my problem in the right words. If you have any questions, let me know! Thank you for your time :)
You need to maintain an "identical count" ($icount) variable that tracks the number of items with an identical score and a "current score" ($score) that tracks the current score.
$icount = 0;
$score = null;
Increment $icount instead of $count when $woow == $score (identical value check). Otherwise, add it to your $count and increment, and then reset the $icount value to 0.
if ($woow == $score) {
$icount++;
} else {
$count += $icount + 1;
$icount = 0;
}
Finally, set your $score value to the latest $woow for testing in the next iteration of the loop:
$score = $woow;
This will allow items with the same Score to have the same $count value, while incrementing an additional $icount times when a new $score is found.
Your final code will look like this:
$sql = "SELECT * FROM database order by Score";
$result = $conn->query($sql);
$count = 0;
$icount = 0;
$score = null;
while ($row = $result->fetch_assoc()) {
$woow = $row['Score'];
$sql = "SELECT * FROM database WHERE Score = $woow";
$resultnew = $conn->query($sql);
$somanythesame = $resultnew->num_rows;
$itemPercentile = ( ($count/$result->num_rows + 0.5*$somanythesame/$result->num_rows) * 100 );
$rowID = $row['ID'];
$sql2 = "UPDATE database SET itemValue = $itemPercentile WHERE ID = $rowID";
$conn->query($sql2);
if ($woow == $score) {
$icount++;
} else {
$count += $icount + 1;
$icount = 0;
}
$score = $woow;
}
You can change $sql query:
$sql = "SELECT *,count(*) FROM database group by Score order by Score";
In this case, you fetch score with counts and no more select needed in the while loop.
Even you can select Percentile in MySQL query:
Select t2.* , #fb as N , ((t2.fb1 + 0.5 * t2.fw)/#fb*100) as percentile from (
Select t1.* , (#fb := #fb + t1.fw) as fb1 from (
Select score,count(*) as fw From tablename group by score order by score ASC
) as t1
) as t2
I think this query returns most of columns which you may needs to check results.

Updating query to reflect normalized database

I am trying to make an article website, but I want to normalize my current database. I have four tables each labeling a category except for one. The four tables are: Entertainment, Lifestyle, Science, and Articles. Articles is the combination of all the entries of Entertainment, Lifestyle, and Science. I want to delete Entertainment, Lifestyle, and Science and just leave the Articles table, thus saving space and increasing performance. The only problem I am facing though is with a query that generates random articles by getting the last ID of the table, and then getting a random number between 6 and the max ID.
All my tables have the following structure: id (unique), category (the type of article this is, i.e Entertainment), title (title of the article), image (image URL of the article), link (URL of the article), Counter (number of views this article has), and dateStamp (the date the article was published). Here's the query I am trying to update based on my normalized database.
<?php
//SELECT the MAX id from the Entertainment table
$MAX_ID = $db->query("SELECT MAX(id) FROM Entertainment");
//Get Max id value
$MAX_ID = $MAX_ID->fetch_array();
$MAX_ID = $MAX_ID[0];
//Create random number variable
$RAND_NUM;
//If the Max ID is less than 6, make $MAX_ID the $RAND_NUM
if ($MAX_ID < 6) {
$RAND_NUM = $MAX_ID;
}
//Else get a random value between 6 and the Max ID
else {
$RAND_NUM = mt_rand(6, $MAX_ID);
}
//Grab 6 articles by descending from the random number, example: If $RAND_NUM is 7, get all entries from 7-2
$resultSet = $db->query("SELECT * FROM Entertainment WHERE id <= $RAND_NUM ORDER BY id DESC LIMIT 6");
if ($resultSet->num_rows != 0) {
//Booleans to check where we are at in the print off
$conditional = true;
$conditional2 = true;
echo "<div class='row'>";
while ($rows = $resultSet->fetch_assoc()) {
$image = $rows["image"];
$title = $rows["title"];
$link = $rows["link"];
$count = number_format($rows["Counter"]);
//Print off these articles on the left
if ($conditional == true && $conditional2 == true) {
echo "<div class='left'><a href='$link'><img src=$image><p>$title</p></a><p id='article-views'><span class='glyphicon glyphicon-fire'></span> $count Views</p></div>";
$conditional2 = false;
}
//Print off these articles on the right
else {
echo "<div class='right'><a href='$link'><img src=$image><p>$title</p></a><p id='article-views'><span class='glyphicon glyphicon-fire'></span> $count Views</p></div><hr>";
$conditional2 = true;
}
}
echo "</div>";
}
?>
How should I change/update this query to make it so that I get random Entertainment articles from the Articles table? I am open to any changes that improve the performance of the query as well.
Instead of selecting a random ending ID, just randomize all the rows and select 6 from them.
SELECT *
FROM Articles
WHERE category = 'Entertainment'
ORDER BY RAND()
LIMIT 6
If you need to avoid ORDER BY RAND() because it doesn't perform well enough, see How can i optimize MySQL's ORDER BY RAND() function?

How could I execute this basic table query in PHP?

Suppose I have a table TABLE:
NAME ID ...
m -1 ...
f -1 ...
g -1 ...
b -1 ...
z -1 ...
And I want to turn it into:
NAME ID ...
f 1 ...
g 2 ...
m 3 ...
b -1 ...
z -1 ...
You probably get the idea:
select the first 3 rows from the original table (preserving order)
order selected rows by the NAME column.
update selected rows' IDs with their position in the new table (keeping the remaining unselected rows in their original positions).
So (m, f, g) got sorted to (f, g, m) and (b, z) remained (b, z).
Here's how I am trying to do it in PHP:
$count = 0;
$query = "UPDATE TABLE SET ID = $count:= $count + 1 ORDER by NAME DESC LIMIT 3";
mysqli_query($con, $query);
But I don't think I can just go ahead and increment a counter and store its value like that. Any advice?
You can try this :
$limit = 3;
for($count = 0 ; $count < $limit;$count++ ){
$query = "UPDATE TABLE SET ID = $count + 1 WHERE ID = '-1' ORDER by NAME DESC";
mysqli_query($con, $query);
}
$query = "UPDATE TABLE SET ID = '-1' WHERE ID > $limit ORDER by NAME DESC";
mysqli_query($con, $query);
In the above logic :
In the final loop, all the IDs are set to $limit
However the update command outisde the loop will set back IDs to -1 again
First, you can quickly query for the first 3 rows in the table and get the name property only and assign the value in an array.
$sql = "select name from table order by name limit 3"
$query = $mysqli->query($sql);
Now let's construct a helper array:
while ($row = $mysqli->fetch_assoc()) {
$a[] = $row['name'];
}
Now just structure the queries:
foreach($a as $id => $name) {
$query = "update table set id={$id+1} where name='$name' limit 1";
// execute the query
}
Note that I assume that the name is unique so I added the limit 1 directive to tell it stop looking for rows to update once it has found a row.
Also, don't forget that array keys are counting starting from 0, hence we are adding 1 to the $id in the loop.
There may be more elegant solutions but this one is rather easy to understand and use.
In MySQL:
SET #row_number = 0;
update TABLE d
join
(
select
NAME,
#row_number:=#row_number+1 as ID,
from
(select NAME from TABLE limit 3) t
order by
NAME asc
) s on s.NAME = d.NAME
set d.ID = s.ID;
SQLFiddle: http://sqlfiddle.com/#!9/dffecf/1
This assumes NAME is your unique key, otherwise likely best to replace with an Identity column in your table and use that for the update.
This approach may require some syntax changes depending on your DB engine. By doing this in SQL, we only make one pass at the DB. Not a huge deal to iterate in multiple passes with PHP if you're only updating three records, but if it was a 1000, etc.

Update a table step by step based on another table

Above is a scheme i drew. There's an ideal case, when content.user are groupped. But usually they are not groupped.
What i meant in this scheme is:
At the first step, i select users.monetos WHERE users.id = content.user
At the second step, i decrement users.monetos with every content.cpc value (2.1 , 2.2)
When simulating this:
select content.user (9)
select users.monetos Where users.id=content.users (15)
So we have 15 value for users.monetos for users.id=9, now we go back to content table
and:
decrement 15 value with 8 (content.cpc) (15-8=7 > 0 -> go to step 2)
decrement 7 (result from previous step) with 10 (content.cpc) (7-10=-3 <0 -> update
content set active='0' where content.id= (current id when a negative result obtained) )
And like this for every content.user
More extended - I want to select contet.* rows which have content.active = 1 (n). Having this data, SELECT users.monetos WHERE users.id=content.user from previous query.
And now, by maximum (n) steps i decrement users.monetos value by content.cpc value
and on the moment when **users.monetos=0 or less than 0, i want to update content and SET active='0'**
By words, i want like to share users.monetos amount to each content entry (content.cpc for each). And there's no more users.monetos make the current content
entry inactive. And do this vor every content.user
What i'he done at this moment is shown below. I now it looks really bad, but i already don't know what to do. Count on you guys. Thank you.
$query = "select content.id, content.cpc, conent.user, content.active from content a
join users b on a.user=b.id
group by b.id where a.active='1'";
/** cycle each user **/
foreach($rows = $connector->fetchArray($query) as $row ) {
$monetos = $row['monetos'];
$query = "select id, cpc from content where user={$row['id']}";
/** cycle each users content **/
foreach($contents = $connector->fetchArray($query) as $content) {
echo $monetos;
$monetos -= $content['cpc'];
if($monetos <= 0) {
$disable[] = $content['id'];
}
}
if( isset($disable) ) {
$connector->query("update content set active='0' where id in(".implode(',',$disable).")");
}
}
By using GROUP_CONCAT we group the IDs and CPCs separated by a comma for later use and with GROUP BY the user ID we will get a single row of result per user.
On the foreach we deduce each CPC from the MONETOS and from there we set who needs to be disabled to the $to_disable array that is later used to disable all the id's needed to.
$query = "SELECT b.id AS user_id,
b.monetos,
GROUP_CONCAT(a.id ORDER BY a.id DESC) AS content_ids,
GROUP_CONCAT(a.cpc ORDER BY a.id DESC) AS cpc,
FROM content a
JOIN users b
ON a.user = b.id
GROUP BY b.id";
$to_disable = array();
$to_enable = array();
foreach($rows = $connector->fetchArray($query) as $row)
{
$monetos = $row['monetos'];
$data = array_combine(explode(',',$row['content_ids']), explode(',',$row['cpc']));
echo "USER {$row['user_id']} currently have {$monetos}!<br>\n";
foreach ($data as $content_id => $cpc)
{
$monetos -= $cpc;
echo "USER {$row['user_id']} after CONTENT {$content_id} now have {$monetos}!<br>\n";
if ($monetos <= 0)
{
echo "USER {$row['user_id']} should have the CONTENT {$content_id} disabled!<br>\n";
$to_disable[] = $content_id;
}
else
{
echo "USER {$row['user_id']} should have the CONTENT {$content_id} enabled!<br>\n";
$to_enable[] = $content_id;
}
}
echo "<br>\n";
}
if (sizeof($to_disable) > 0)
{
$connector->query("UPDATE content
SET active = 0
WHERE id IN (".implode(',',$to_disable).")");
}
echo "UPDATE content SET active = 0 WHERE id IN (".implode(',',$to_disable).")<br>\n";
if (sizeof($to_enable) > 0)
{
$connector->query("UPDATE content
SET active = 1
WHERE id IN (".implode(',',$to_enable).")");
}
echo "UPDATE content SET active = 0 WHERE id IN (".implode(',',$to_enable).")";
Using your SQL dump this is what I get:
USER 9 currently have 15!
USER 9 after CONTENT 16 now have 10!
USER 9 after CONTENT 30 now have 5!
USER 9 after CONTENT 17 now have 4!
USER 9 after CONTENT 31 now have -1!
USER 9 should have the CONTENT 31 disabled!
USER 9 after CONTENT 18 now have -4!
USER 9 should have the CONTENT 18 disabled!
USER 9 after CONTENT 32 now have -9!
USER 9 should have the CONTENT 32 disabled!
USER 9 after CONTENT 20 now have -13!
USER 9 should have the CONTENT 20 disabled!
USER 9 after CONTENT 33 now have -18!
USER 9 should have the CONTENT 33 disabled!
USER 9 after CONTENT 21 now have -22!
USER 9 should have the CONTENT 21 disabled!
USER 9 after CONTENT 34 now have -26!
USER 9 should have the CONTENT 34 disabled!
USER 9 after CONTENT 22 now have -31!
USER 9 should have the CONTENT 22 disabled!
USER 9 after CONTENT 24 now have -36!
USER 9 should have the CONTENT 24 disabled!
USER 9 after CONTENT 26 now have -41!
USER 9 should have the CONTENT 26 disabled!
USER 9 after CONTENT 29 now have -45!
USER 9 should have the CONTENT 29 disabled!
USER 10 after CONTENT 28 now have 95!
USER 11 after CONTENT 27 now have -4!
USER 11 should have the CONTENT 27 disabled!
And the UPDATE result:
UPDATE content SET active = 0 WHERE id IN (31,18,32,20,33,21,34,22,24,26,29,27)
And here is the sample code used to read the data as is:
<?php
// Your database info
$db_host = '';
$db_user = '';
$db_pass = '';
$db_name = '';
$con = new PDO("mysql:host={$db_host};dbname={$db_name}", $db_user, $db_pass);
$con->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = "SELECT b.id AS user_id,
b.monetos,
GROUP_CONCAT(a.id ORDER BY a.id DESC) AS content_ids,
GROUP_CONCAT(a.cpc ORDER BY a.id DESC) AS cpc
FROM content a
JOIN users b
ON a.user = b.id
GROUP BY b.id";
$result = $con->prepare($sql);
$result->execute();
if ($result->rowCount() == 0)
{
die('No data found...');
}
$to_disable = array();
$to_enable = array();
foreach($result->fetchALL(PDO::FETCH_ASSOC) as $row)
{
$monetos = $row['monetos'];
$data = array_combine(explode(',',$row['content_ids']), explode(',',$row['cpc']));
echo "USER {$row['user_id']} currently have {$monetos}!<br>\n";
foreach ($data as $content_id => $cpc)
{
$monetos -= $cpc;
echo "USER {$row['user_id']} after CONTENT {$content_id} now have {$monetos}!<br>\n";
if ($monetos <= 0)
{
echo "USER {$row['user_id']} should have the CONTENT {$content_id} disabled!<br>\n";
$to_disable[] = $content_id;
}
else
{
echo "USER {$row['user_id']} should have the CONTENT {$content_id} enabled!<br>\n";
$to_enable[] = $content_id;
}
}
echo "<br>\n";
}
if (sizeof($to_disable) > 0)
{
$ids = implode(',',$to_disable);
$sql = "UPDATE content
SET active = 0
WHERE id IN ({$ids})";
$disable = $con->prepare($sql);
$disable->execute();
echo "UPDATE content SET active = 0 WHERE id IN ({$ids})<br>\n";
}
else
{
echo "Nothing was disabled...<br>\n";
}
if (sizeof($to_enable) > 0)
{
$ids = implode(',',$to_enable);
$sql = "UPDATE content
SET active = 1
WHERE id IN ({$ids})";
$enable = $con->prepare($sql);
$enable->execute();
echo "UPDATE content SET active = 1 WHERE id IN ({$ids})";
}
else
{
echo "Nothing was enabled...";
}
$con = NULL;
Can you try two UPDATE statements?
UPDATE
users u JOIN
( SELECT user, SUM(cpc) AS cpc FROM content GROUP BY user) as c ON (u.id=c.user)
SET u.monetos = u.monetos - c.cpc;
UPDATE content AS c
SET c.active = 0
WHERE
(SELECT u.monetos FROM users u WHERE c.user = u.id) <= 0;
I didn't get if users.monetos was allowed to go negative or not. If not you can add an IF() check to the SET u.monetos
SQL Fiddle

Update a mysql table based on another. Complicated query

Short about my tables: I have 2 tables: content, users
content structure is something like:
id | title | cpc | user | active
users is:
id | user | monetos
Idea - I want to select contet.* rows which have content.active = 1 (n). Having this data, SELECT users.monetos WHERE users.id=content.user from previous query.
And now, by maximum (n) steps i decrement users.monetos value by content.cpc value
and on the moment when *users.monetos=0 or less than 0, i want to update content and SET active='0'*
By words, i want like to share users.monetos amount to each content entry (content.cpc for each). And there's no more users.monetos make the current content
entry inactive. And do this vor every content.user
What i'he done at this moment is shown below. I now it looks really bad, but i already don't know what to do. Count on you guys. Thank you.
$kak2 = array();
$rs16 = $connector->query("SELECT user FROM content WHERE active='1'");
while($rw16 = $connector->fetchArray($rs16))
{
$users_ids[] = $rw16['user'];
}
$user_info2 = $connector->fetchArray("SELECT monetos,id FROM users WHERE id IN (".implode(',',$users_ids).")");
while($user_info = $connector->fetchArray($user_info2))
{
$current_entry_info2 = $connector->query("SELECT cpc,id FROM content WHERE user='$user_info[id]' ORDER BY date DESC");
while ($current_entry_info = $connector->fetchArray($current_entry_info2))
{
$user_info['monetos']= $user_info['monetos'] - $current_entry_info['cpc'];
if($user_info['monetos'] = 0)
{
$updt = $connector->query("UPDATE content SET active='0' WHERE id='$current_entry_info[id]' LIMIT 1");
}
}
}
I think i have got the idea of what you are trying to do so have contructed a small code snippit which i think should do what you are after.
note this is untested code
/** get the cost of all content for all users **/
$query = "select b.id, sum(a.cpc) as cpc from content a
join users b on a.user= b.id
group by b.id where a.active = 1"
/** cycle each user **/
foreach($rows = $connector->fetchArray($query) as $row ) {
$menotos = $row['cpc'];
$query = "select id, cpc from content where user={$row['id']}"
/** cycle each users content **/
foreach($contents = $connector->fetchArray($query) as $content) {
$menotos -= $content['cpc'];
if($menotos <= 0) {
$disable[] = $content['id'];
}
}
if( isset($disable) ) {
$connector->query("update content set active=0 where id in(" . implode(',', $disable) . ")";
}
}

Categories