I created a function inside a longer plug-in for shopware, which is supposed to create a random number for every row in the database that has a "NULL" value in the "vouchercode" column. Right now I replaced the for-loop condition with a fixed number, because I wanted to make sure the problem doesn't occur because of the for-loop condition.
The problem is, that the for-loop just has effect on the database once.
For instance: I have this table 's_plugin_tnev'. Inside of that table are 6 rows. 4 of these have "NULL" as value inside of the vouchercode column.
So as far as I understand my code. It should loop 5 times through the same table and every time update one of those "NULL"-value columns, meanwhile after every loop one of those "NULL"-value columns should be filled with a random number and therefore no longer be SELECTed nor UPDATEd by this for-loop.
Though as mentioned earlier this doesn't happen. The for loop just works once apparently.
Here is my code snippet:
public function generateCode()
{
//Repeat action 5 times
for($i = 0; $i <= 4; $i++)
{
$rand = 0;
//Creates 16 times a number and add it to the var
for ($i = 0; $i<15; $i++)
{
$rand .= mt_rand(0,9);
}
//On Checkoutcomplete add $rand to database table
$addInt = "UPDATE s_plugin_tnev SET vouchercode = $rand
WHERE vouchercode IS NULL
LIMIT 1";
$connect = Shopware()->Db()->query($addInt);
}
}
As you can see I use the DBAL Framework, because this is the best supported way by Shopware.
My idea would be that the mistake has something to do with the $connect variable or that DBAL is not communicating fast enough with the Database.
Maybe someone has more experience with DBAL and could help me out.
Thanks in advance,
Max K
You have two for loops with $i, so on your first iteration, at the end the $i value is 15 and the first loop is executed only once.
Try this instead :
public function generateCode()
{
//Repeat action 5 times
for($i = 0; $i <= 4; $i++)
{
$rand = 0;
//Creates 16 times a number and add it to the var
for ($j = 0; $j<15; $j++) // $j NOT $i <---
{
$rand .= mt_rand(0,9);
}
//On Checkoutcomplete add $rand to database table
$addInt = "UPDATE s_plugin_tnev SET vouchercode = $rand
WHERE vouchercode IS NULL
LIMIT 1";
$connect = Shopware()->Db()->query($addInt);
}
}
Related
$st = $this->db->prepare("SELECT * FROM invoices WHERE group_id=?");
$st->execute(array($id));
if($st->rowCount() >= 1){
foreach ($st as $row) {
$counter = $row["paymentAmount"];
$start = 1;
for($start; $start < $st->rowCount(); $start++) {
$counter = $counter + $row["paymentAmount"];
}
}
It actually print out $row["paymentAmount"] + $row["paymentAmount"] and so on, depending on how many $row["paymentAmount"] there is. But the problem is that the last output from $row["paymentAmount"] is 2500.
There is:
10000
10000
2500
And the result is: 7500
I want it to be: 22500
And if the last result is 3000 it shall be 23000. So what I simply need is this code to take every row from the database, just not the latest one.
Edit: I want it outside of the SQL query
You don't need PHP logic for something like this. The functionality is built right into SQL.
SELECT SUM(paymentAmount) FROM invoices WHERE group_id=?
You should let your database handle the sum unless you have a legitimate reason why it needs to be handled in PHP. The database is more efficient with this type of operation and you avoid a loop in PHP.
SELECT SUM(paymentAmount) AS TotalPaymentAmount FROM invoices WHERE group_id = ?
You can then change your PHP to return just one row:
$row = $st->fetch();
echo $row["TotalPaymentAmount"];
If you need to do this calculation outside of SQL, just change your loop:
if($st->rowCount() >= 1){
//init the counter to 0 before you loop through your rows
$counter = 0;
//the foreach will iterate over your result set and add the paymentAmount to $counter.
foreach ($st as $row) {
$counter += $row["paymentAmount"];
}
//echo results outside of the loop
echo $counter;
}
If you need to code this outside SQL on purpose (e.g. because you need to do further processing for each row), then I'd code this as follows:
if ($st->rowCount() >= 1) {
$counter = 0;
foreach ($st as $row) {
$counter += $row["paymentAmount"];
}
}
I am trying to delete (or create) a models associated objects when it is updated. Each bar has several taps. When you create a bar, these tap objects are created, and you can update that number and additional taps will be created or deleted as necessary.
Originally I just wanted to use pop like this:
$taps=$bar->taps;
if ( $taps_old < $taps_new){
for ($i = $taps_old; $i < $taps_new; $i++) {
$tap = Growlertap::create(['growlerstation_id' => $id]);
}
}
elseif ($taps_old > $taps_new) {
for ($i = $taps_new; $i < $taps_old; $i++) {
$taps->pop();
}
which doesn't work but doesn't give me an error. I know the if statement is working fine because the code below works:
elseif ($taps_old > $taps_new) {
for ($i = $taps_new; $i < $taps_old; $i++) {
Beertap::where('bar_id', '=', $id)->first()->delete();
}
}
This seems to not be the simplest way to write this. Is there a better way to write this?
By the way, for those wondering, this is in my update function in my controller.
pop() will remove the last item in your local collection, but it won't persist that change to the database.
Assuming it's a Beertap object, something like this should work:
Warning: untested code
...
elseif ($taps_old > $taps_new) {
for ($i = $taps_new; $i < $taps_old; $i++) {
$delete_me = $taps->pop();
$delete_me->delete();
}
}
or more succinctly: $taps->pop()->delete();
This should work Unfortunately this doesn't work because the DELETE statement doesn't support offsets.
Beertap::where('bar_id')->skip($taps_new)->delete();
So it skips as many taps as you want to keep and deletes the rest. You might want to use orderBy if it matters which rows get deleted.
Update
This should totally work now. First get all id's to delete from the collection and then delete them with one query
$idsToDelete = $taps->slice($taps_new)->modelKeys();
Beertap::destroy($idsToDelete);
Update 2
You can optimize the creation process as well (so that it's done in a single query)
$data = [];
for ($i = $taps_old; $i < $taps_new; $i++) {
$data[] = ['growlerstation_id' => $id];
}
Beertap::insert($data);
Note that you will loose the Eloquent features like automatic timestamps and model events when choosing to use insert().
I have a question that I have been stuck on for several hours now. I have played around with numerous types of for() and while() loops. I put them in different locations with different variables and ran different things, nothing worked..
My question:
Why is my program giving all users below the first one the same level? You can clearly see in the picture that Nicolas has much more XP. (5,000 xp is level 20, and if "Nic5" was first in the database then it would change the "Skill Level" to 20.
I know that the returned variable $lvl isn't changing for each player that loads and this is why each player is getting the first player's level.
Can anybody help me with this please?
Notes:
0 = a column that holds experience for a players skill level.
class calculatelevel:
class calculatelevel {
function level($skillnum)
{
$host = "*";
$user = "*";
$pass = "*";
$db = "*";
$con = new mysqli($host, $user, $pass, $db) or die($con->error);
$res = $con->query("SELECT `0` FROM hiscores");
$max = 99;
while($row = $res->fetch_assoc()) {
$xp = $row['0'];
// Find the appropriate level
for ($lvl = 1; $lvl < $max; $lvl++) //this for loop runs 99 times
{
if ($xp < $this->experience($lvl))//if players xp in skill is less than experience(level 1-99)
{
// Level found
$lvl -= 1;
break;
}
}
}
return $lvl;
}
public function experience($lvl)
{
$xp = 0;
for($x = 1; $x < $lvl; $x++)
{
$xp += floor($x + 300 * pow(2, ($x / 7)));
}
return floor($xp / 4);
}
}
Method that writes database information to page.
if($res->num_rows > 0) {
echo "<table>
<tr>
<td>Rank</td>
<td>Username</td>
<td>Skill Level</td>
<td>Total Exp</td> </tr>";
while($row = $res->fetch_assoc()) {
echo 'ran';
$calc = new calculatelevel();
$level = $calc->level(0);
echo '<tr>
<td>'.($count+1).'</td>
<td>'. htmlspecialchars($row['username']) .'</td>
<td>'.number_format($level).'</td>
<td>'.number_format($row['0']).'</td>
</tr>';
$count++;
}
}
Your while loop and for loop are structured incorrectly. You are looking at only the first row and returning the level for that row every time. You break the for loop when you find the level for that row's player, then immediately return that level. Result: it looks like everyone has the same level.
EDIT: Okay, here are a few more thoughts.
First, a column named 0 is asking for trouble, as Mike W pointed out. You say 0 is a table, but if that's the case, your SELECT statement doesn't make sense. The first thing I would try is changing the column name to something that isn't a number, like xp.
Second, you really should make only one database connection and use it throughout the entire request, if possible. Opening a new connection each time a particular function runs will tie up a server quickly.
Third, the obvious problem in your current code is this:
function level($skillnum) {
// other code here....
// Okay, you load a row's data into $row, with the idea that you will repeat this.
while($row = $res->fetch_assoc()) {
// $xp is set to the experience points for the user you just loaded
$xp = $row['0'];
// You now look at each level, starting at 1, to see if the
// user's xp is greater than the cutoff for that level
for ($lvl = 1; $lvl < $max; $lvl++) //this for loop runs 99 times
{
// If the user's xp is less than the cutoff...
if ($xp < $this->experience($lvl))//if players xp in skill is less than experience(level 1-99)
{
// ... then you go back down one level...
// Level found
$lvl -= 1;
// ... and quit the for loop!
break;
}
}
// okay, you're out of the for loop, so you go back to the while loop...
// a new row is loaded...
// and $xp and $lvl are both overwritten with that user's values
}
// So, the while loop has run once for each player...
// ... but you are only returning one player's level!
return $lvl;
}
Also, you have defined calculatelevel::level to require a parameter $skillnum, but you never use it in the code posted here.
I suspect there is another glitch in your real code causing it to return the first player's level, rather than the last player's level. It could be a problem with the 0 column name; that really should change.
I checked throught the existing topics. I have a fix for my problem but I know its not the right fix and I'm more interested making this work right, than creating a workaround it.
I have a project where I have 3 tables, diagnosis, visits, and treatments. People come in for a visit, they get a treatment, and the treatment is for a diagnosis.
For displaying this information on the page, I want to show the patient's diagnosis, then show the time they came in for a visit, that visit info can then be clicked on to show treatment info.
To do this a made this function in php:
<?
function returnTandV($dxid){
include("db.info.php");
$query = sprintf("SELECT treatments.*,visits.* FROM treatments LEFT JOIN visits ON
treatments.tid = visits.tid WHERE treatments.dxid = '%s' ORDER BY visits.dos DESC",
mysql_real_escape_string($dxid));
$result = mysql_query($query) or die("Failed because: ".mysql_error());
$num = mysql_num_rows($result);
for($i = 0; $i <= $num; ++$i) {
$v[$i] = mysql_fetch_array($result MYSQL_ASSOC);
++$i;
}
return $v;
}
?>
The function works and will display what I want which is all of the rows from both treatments and visits as 1 large assoc. array the problem is it always returns 1 less row than is actually in the database and I'm not sure why. There are 3 rows total, but msql_num_rows() will only show it as 2. My work around has been to just add 1 ($num = mysql_num_rows($result)+1;) but I would rather just have it be correct.
This section looks suspicious to me:
for($i = 0; $i <= $num; ++$i) {
$v[$i] = mysql_fetch_array($result MYSQL_ASSOC);
++$i;
}
You're incrementing i twice
You're going to $i <= $num when you most likely want $i < $num
This combination may be why you're getting unexpected results. Basically, you have three rows, but you're only asking for rows 0 and 2 (skipping row 1).
Programmers always count from 0. So, you are starting your loop at 0. If you end at 2, you have reached 3 rows.
Row0, Row1, Row2.
if $i = 0, and u increment it BEFORE adding something to the array, u skip the first row. increment $i AFTER the loop runs to start at 0 (first key).
For loops are not good for this: rather do:
$query=mysql_query(' --mysql --- ');
while ($row=mysql_fetch_array($query)){
$v[]=$row["dbcolumn"];
}
return $v for your function then.compact and neat. you can create an associative array, as long as the key name is unique (like primary ids).. $v["$priid"]=$row[1];
I am trying to loop through an array of usernames and for each username, execute a mysql_query on each.
<?php
for ($i = 0; $i < count($u); $i++)
{
$j = $i + 1;
$s = $database->checkUserPlayedRecent($u);
if (mysql_num_rows($s) > 0)
{
?>
<tr>
<?php
echo "<td>$j</td>";
?>
<?php
echo "<td>$u[$i]</td>";
?>
<?php
echo "<td>$p[$i]</td>";
?>
</tr>
<?
}
}
?>
As you can see, $u is each username.
I want each username to only appear in the echo statements if each one has num_rows > 0.
At the moment nothing is displaying.
But there should be results being returned!
Can someone help please.
The sql:
$q = "SELECT id FROM ".TBL_CONF_RESULTS." WHERE (home_user = '$u' OR away_user = '$u') AND date_submitted >= DATE_SUB(CURDATE(),INTERVAL 14 DAY)";
This line :
for ($i = 0; $i < count($u); $i++)
indicates that $u is an array -- and not a single name.
And $i is used to keep track of the current index in that array.
So, in your loop, you should be working with $u[$i] -- which will be the "current" line of the array :
$s = $database->checkUserPlayedRecent($u[$i]);
Note that you could probably rewrite your loop, using a foreach loop, like this :
foreach ($u as $currentPlayer) {
$s = $database->checkUserPlayedRecent($currentPlayer);
// ...
}
With foreach, no need to keep track of the current index -- makes code easier to write and understand, in my opinion ;-)
You should get in the habit of keeping count() outside of your conditional. You're count()ing the same array every time which is a waste of cycles.
$total = count($u);
for ($i=o; $i < $total; $i++) ...
I would definitely query these users all at once, especially since your query is abusing mysql_num_rows when you should be using the following sql:
select username, count(username) from user where username IN (your,array,of,usernames) group by username;
then your foreach loop would iterate over the results and you could reference each row without having to call yet another mysql_* method.
I'd be tempted to rewrite the query to accept the array of names and use an IN statement, so that you could execute the whole of your database activity in a single query rather than once for every entry in the array. It would almost certainly be faster.
If you show us the query that you're using in your checkUserPlayedRecent() method, we may be able to help with this.