PHP- Query MySQLi results nearest a given number - php

I am trying to search for an invoice by the amount. So, I would like to search all invoices +/- 10% of the amount searched, and order by the result closest to the given number:
$search = 100.00
$lower = $search * 0.9; // 90
$higher = $search * 1.1 // 110
$results = $db->select("SELECT ID from `invoices` WHERE Amount >= `$lower` && Amount >= `$higher`");
So, I am not sure how to order these. Let's say this query gives me the following results:
108, 99, 100, 103, 92
I want to order the results, starting with the actual number searched (since it's an exact match), and working out from there, so:
100, 99, 103, 92, 108

You could do this as follows:
$search = 100.00
$deviation = 0.10;
$results = $db->select("
SELECT ID, Amount, ABS(1 - Amount/$search) deviation
FROM invoices
WHERE ABS(1 - Amount/$search) <= $deviation
ORDER BY ABS(1 - Amount/$search)
");
Output is:
+----+--------+-----------+
| id | Amount | deviation |
+----+--------+-----------+
| 3 | 100 | 0 |
| 2 | 99 | 0.01 |
| 4 | 103 | 0.03 |
| 1 | 108 | 0.08 |
| 5 | 92 | 0.08 |
+----+--------+-----------+
Here is an SQL fiddle
This way you let SQL calculate the deviation, by dividing the actual amount by the "perfect" amount ($search). This will be 1 for a perfect match. By subtracting this from 1, the perfect match is represented by the value 0. Any deviation is non-zero. By taking the absolute value of that, you get the exact deviation as a fractional number (representing a percentage), like for example 0.02 (which is 2%).
By comparing this deviation to a given maximum deviation ($deviation), you get what you need. Of course, ordering is then easily done on this calculated deviation.

Try this:
$search = 100.00
$lower = $search * 0.9; // 90
$higher = $search * 1.1 // 110
$results = $db->select("SELECT ID from `invoices`
WHERE Amount >= `$lower` && Amount <= `$higher`
ORDER BY ABS(Amount - $search)
");
The ABS function returns the absolute value of its argument (=> it basically removes the minus from negative numbers). Therefore ABS(Amount - $search) returns the distance from the $search value.
Besides that you should consider using prepared statements. Otherwise your application could be vulnerable to sql injection.

Related

Calculate Average between MySQL results

I need to calculate the avg days between date of sales:
My DB is like this:
id | customer | creation_date | payment_date
1 | 234 | 2017/07/6 | 2017/07/8
34 | 234 | 2017/08/4 | 2017/08/10
53 | 234 | 2017/09/15 | 2017/09/17
67 | 234 | 2017/10/1 | 2017/07/6
So I need to calculate de difference of days (creation_date) between Order 1 and Order 34, Order 34 and Order 53, Order 53 and Order 67, etc...
and calculate an AVG of days depending the number of results.
So I know how to calculate the difference of days between 2 dates using this small script:
$seconds=strtotime($date1) - strtotime($date2);
$difference=intval($seconds/60/60/24);
$positive = $difference * -1;
but I don´t know how to take the date of the las result and compare it with the next result.
Please someone who can help me with this enigma. Thanks!
I could be misunderstanding what you are looking for, but I would think something like this should work
(TO_DAYS(MAX(creation_date))-TO_DAYS(MIN(creation_date))) / (COUNT(1)-1)
This will get you the total days between the first and last; and divide by the number of "spaces" between orders.
Edit: ....and if you wanted to treat orders on the same date as a single order, you can just change COUNT(1) to COUNT(DISTINCT creation_date).
...all this assumes the db designer was sane and actually used DATE data types for date values.
To summarize, the average of the span sizes should be the same as the total span divided by the number of spans.
You can keep track of the previous result using a variable outside of the loop to get your MySQL table and then run the loop through the rows of the table:
$last_positive = 0;
while ($row = $result->fetch_assoc()){
$date1 = $row['creation_date'];
$date2 = $row['payment_date'];
$seconds=strtotime($date1) - strtotime($date2);
$difference=intval($seconds/60/60/24);
$positive = abs($difference);
//DO SOME COMPARISON HERE
echo($last_positive >= $positive);
$last_positive = $positive;
}
I'd also suggest using abs to get the absolute value instead of multiplying by -1.
SOLVED WITH THIS:
SELECT DATEDIFF(MAX(creation_date), MIN(creation_date)) / (COUNT(creation_date) - 1) AS SaleAverage FROM table WHERE customer = '$customer'

Advertisement System Tips

I am creating an advertisement system which shows the highest bidder's ads more frequently.
Here is an example of the table structure I am using, but simplified...
+----+----------+------------------------+----------------------+-----+
| id | name | image | destination | bid |
+----+----------+------------------------+----------------------+-----+
| 1 | abc, co | htt.../blah | htt...djkd.com/ | 3 |
+----+----------+------------------------+----------------------+-----+
| 2 | facebook | htt.../blah | htt...djkd.com/ | 200 |
+----+----------+------------------------+----------------------+-----+
| 3 | google | htt.../blah | htt...djkd.com/ | 78 |
+----+----------+------------------------+----------------------+-----+
Now, right now I am selecting the values from the database and then inserting them into an array and picking one out by random similar to the following:
$ads_array = [];
$ads = Ad::where("active", "=", 1)->orderBy("price", "DESC");
if ($ads->count() > 0) {
$current = 0;
foreach ($ads->get() as $ad) {
for ($i = 0; $i <= ($ad->price == 0 ? 1 : $ad->price); $i++) {
$ads_array[$current] = $ad->id;
$current++;
}
}
$random = rand(0,$current-1);
$ad = Ad::where("id", "=", $ads_array[$random])->first();
...
}
So, essentially what this is doing is, it is inserting the advert's ID into an array 1*$bid times. This is very inefficient, sadly (for obvious reasons), but it was the best way I could think of doing this.
Is there a better way of picking out a random ad from my database; while still giving the higher bidders a higher probability of being shown?
Looks like this might do the trick (but all the credit go to this guy in the comments)
SELECT ads.*
FROM ads
ORDER BY -log(1.0 - rand()) / ads.bid
LIMIT 1
A script to test this :
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;', 'test', 'test');
$times = array();
// repeat a lot to have real values
for ($i = 0; $i < 10000; $i++) {
$stmt = $pdo->query('SELECT ads.* FROM ads ORDER BY -log(1.0 - rand()) / bid LIMIT 1');
$bid = $stmt->fetch()['bid'];
if (isset($times[$bid])) {
$times[$bid] += 1;
} else {
$times[$bid] = 1;
}
}
// echoes the number of times one bid is represented
var_dump($times);
The figures that comes to me out of that test are pretty good :
// key is the bid, value is the number of times this bid is represented
array (size=3)
200 => int 7106
78 => int 2772
3 => int 122
Further reference on mathematical explanation
Many important univariate distributions can be sampled by inversion using simple closed form expressions. Some of the most useful ones are listed here.
Example 4.1 (Exponential distribution). The standard exponential distribution has density f(x) = e−x on x > 0. If X has this distribution, then E(X) = 1, and we write X ∼ Exp(1). The cumulative distribution function is F(x) = P(X 􏰀 x) = 1 − e−x, with F−1(u) = −log(1 − u). Therefore taking X = − log(1 − U ) for U ∼ U(0, 1), generates standard exponential random variables. Complementary inversion uses X = − log(U ).
The exponential distribution with rate λ > 0 (and mean θ = 1/λ) has PDF λexp(−λx) for 0 􏰀 x < ∞. If X has this distribution, then we write X ∼ Exp(1)/λ or equivalently X ∼ θExp(1), depending on whether the problem is more naturally formulated in terms of the rate λ or mean θ. We may generate X by taking X = − log(1 − U )/λ.
coming from http://statweb.stanford.edu/~owen/mc/Ch-nonunifrng.pdf

Splitting website traffic by percentage

I need to split up traffic to multiple sources based on an assigned percentage. I figure I need a log table like this:
Table:
+--------+------+----------------------+
| Source | hits | allocated percentage |
+--------+------+----------------------+
| path1 | 50 | 50 |
| path2 | 40 | 40 |
| path3 | 10 | 10 |
+--------+------+----------------------+
I figure the logic needs to loop through all the paths and calculate the current percentage and then determine which one is furthest from the "allocated percentage" and then update the table hits=hits+1. I'm having trouble with the last compare part.
$overall_hits = $db->getall('Select sum(total_hits) from table');
$source = $db->getall('Select * from table');
foreach($source as $row){
$current_percentage = ($row['total_hits']/$overall_hits)*100;
//how should I compare? what if they are equal?
if($current_percentage < $row['allocated_percentaged'])
{
$chosen_path = $row['source'];
$db->sql("Update table set total_hits=total_hits+1 where source='".$chosen_path."'");
break;
}else{
continue;
}
}
Am I even on the right track here?
Presuming I understand what you're trying to do, you can do all of the logic checks in your SQL.
Using the following data as an example:
CREATE TABLE t (
source TEXT,
hits INT,
percentage INT
);
INSERT INTO t (source, hits, percentage)
VALUES
('path1', 41, 50),
('path2', 27, 40),
('path3', 3, 10)
You can simply run a query against the entire table, to calculate what percentage each of the paths is at:
SELECT
source,
hits,
percentage,
(hits / percentage) * 100
AS current
FROM t
ORDER BY current ASC;
Which will give you the following results
SOURCE HITS PERCENTAGE CURRENT
path1 3 10 30
path2 27 40 67.5
path3 41 50 82
You can then simply add LIMIT 1 to the end of your query, to only obtain 1 result. This will give you the path with the lowest number of hits : allocated ratio.
SOURCE HITS PERCENTAGE CURRENT
path1 3 10 30
You can see it in action on SQLFiddle here.

Integer division in PHP produces zero - how to cast it to float?

In a Russian card game I'm trying to keep statistics of how often a player swears (says "bad words" - and we do have a lot of them in Russian language) in the following PostgreSQL table:
# select * from pref_chat order by swear desc;
id | swear | lines
-------------------------+-------+-------
OK194281930260 | 3 | 153
OK350321778615 | 2 | 127
DE12770 | 2 | 339
OK122898831181 | 2 | 63
OK349829847011 | 2 | 126
OK215240745969 | 1 | 66
OK459742722980 | 1 | 96
And I need to generate an integer number of this data - between 1 and 100 (overflowing is okay) - so that I can create the following "swear'o'meter:
So I'm trying (with PHP 5.3 at CentOS 6.2):
$sth = $db->prepare('select swear, lines from pref_chat where id=?');
$sth->execute(array($id));
if ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
$quotient = 100 * floor(20 * $row['swear'] / (1 + $row['lines']));
print('<p><img src="https://chart.googleapis.com/chart?chs=700x100&cht=gom&chco=00FF00,FFFF00,FF0000&chxt=x&chxl=0:|Swearometer&chd=t:' . $quotient . '" width=700 height=100 alt="Swearometer"></p>)';
}
Unfortunately I get zero - because PHP is probably doing an "integer division".
I've tried to prepend floor() to $row['swear'] and $row['lines'] to "cast" them to float - but this didn't help.
UPDATE:
Sorry, I had a typo in my original question... The $quotient is really 0, I print it out. I've also tried the following, but still zero:
$quot = 100 * floor(20 * $row['swear'] / (.1 + $row['lines']));
Well, $row['swear'] / (1 + $row['lines']) would always be < .05 given the numbers you list. Therefore, when you multiply by 20 and then floor you will very correctly get 0.
#AlexanderFarber: No, sorry integer division in PHP is not giving zero may be you are missing something.Just use your usual division.

php - map a value using fromRange and toRange?

I'm trying to figure out how to map a number between 1 and 1000 to a number between 1 and 5.
For example:
I have a database of 1000 records and I want to assign an id number between 1 and 5 to each record. I don't want it to be random, thats easy enough with rand(1,5).
In the Arduino language it has a function that I'm hoping PHP has:
result = map(value, fromLow, fromHigh, toLow, toHigh)
The purpose of this is I don't want to store that mapped value in the database, I need a php function that I can call and if say the db record is 100 no matter how often the function is called it will always return the same mapped value.
Thanks!
The function you're looking for maps ranges by using different scales. So that's easy to do:
function map($value, $fromLow, $fromHigh, $toLow, $toHigh) {
$fromRange = $fromHigh - $fromLow;
$toRange = $toHigh - $toLow;
$scaleFactor = $toRange / $fromRange;
// Re-zero the value within the from range
$tmpValue = $value - $fromLow;
// Rescale the value to the to range
$tmpValue *= $scaleFactor;
// Re-zero back to the to range
return $tmpValue + $toLow;
}
So basically, it'll re-base the number within the range. Now, note that there is no error checking if value is within either range. The reason is that it maps scales, not ranges. So you can use it for base conversion:
$feet = map($inches, 0, 12, 0, 1);
And you can map "ranges" as well since it re-bases the number (moves it along the number line):
5 == map(15, 10, 20, 0, 10);
So for from range (0, 1000) and to range (0, 5), the following table will hold true:
-200 | -1
0 | 0
1 | 0.005
100 | 0.5
200 | 1
400 | 2
600 | 3
800 | 4
1000 | 5
2000 | 10
3000 | 15
And to show the re-basing, if we map (0, 1000) to (5, 10):
-200 | 4
0 | 5
1 | 5.005
100 | 5.5
200 | 6
400 | 7
600 | 8
800 | 9
1000 | 10
2000 | 15
3000 | 20
Have you considered: $mappedValue = ($value % 5) + 1;? Will return the remainder after dividing the value by 5 (i.e. 0-4) then adds one.

Categories