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.
Related
I have a weight calculated, and a database table that contains the range of amounts, now I want to check if the calculated weight is between the ranges in my table.
I want to check the amount if it's between in one of the records in the table.
For example the calculated weight is 505kg it will find it on the table if it's between weight_from & weight_to, so it will return the 120 amount because 505kg is between 500 and 1000
Shipping Fee Table
+-----+---------------+--------------+-------------+
| id | weight_from | weight_to | amount |
+-----+---------------+--------------+-------------+
| 1 | 0.5 | 100 | 100 |
| 2 | 500 | 1000 | 120 |
| 3 | 1000 | 3000 | 180 |
+-----+---------------+--------------+-------------+
I found something like this, but it is not like what I'm trying to do, because it pass a number, and not getting the value of database table.
Code for reference only
$fee = Fee::whereBetween('column', [1, 150])->first();
whereBetween() is for finding values in a range within a single column. Since you are checking between two different columns, the checks needs to be explicit.
$weight = 505;
$fee = Fee::where('weight_from', '<=', $weight)
->where('weight_to', '>=', $weight)
->orderBy('weight_from')
->first();
if ($fee) {
// Do something with $fee->amount;
}
You shouldn't use <, the ranges should be inclusive. The reason for this, contrary to the other answer that states "use > for the upper-bound if the next range starts from the same value", is that the range may not always have the same upper-limit as the start-limit of your next batch (for example, the sample data provided has a range from 0.5 to 100, but none from 100-500). If it doesn't, then suddenly you're missing a value. Its better to use orderBy() to find the correct record. Combine that with first() and you only get one record.
I got it now, I want to share this simple yet helpful solution. This kind of logic is so confusing.
Code
$weight = 505;
$fee = Fee::where('weight_from', '<=', $weight)
->where('weight_to', '>', $weight)
->first();
if ($fee) {
// Do something with $fee->amount;
}
The weight_from should be less (or equal) to the calculated weight, and weight_to should be greater than the calculated weight
Also, use > for the upper-bound if the next range starts from the same value.
Thanks to tykus and MichalOravec of Laracasts for helping me
A have a list with many full names (>20000) and it increases with each new registration. I need create a seven digits identification number to every register in alphabetical order, so that the conversion start in 0100000 and finish in 9999999. This number must be based on the full name and your order.
When adding new names and that they are merged in the existing base, also generate new numbers merged too.
I have not yet been able to develop a good algorithm that solves this. Then I'll need to create a PHP script for this.
It is a conversion of names to numbers, but with a defined range.
For example
Anthony Felder : 0.459.789
Bianca Mall : 0.989.432
Danton Bishop : 2.986.999
Mario Cortez: 7.883.120
Paul Rudd: 8.788.454
Zeta Jones: 9.987.001
A new name inserted:
Augustus Novell : 0.589.223
Frederic Francis Ford Copolla : 3.765.453
You are going to run into problems, because eventually you are adding to many records that August zzzzzperson will get number 0.989.432 and that already exists.
If you don't expect TOO many new people being added, what you could do:
If Augustus Novell is added to your database - find out between which two names he should be placed (alphabetically).
Anthony Felder : 0.459.789
Bianca Mall : 0.989.432
Grab their numbers and get a number right in the middle of the two:
roundUp((0.459.789 + 0.989.432) / 2) = 0.724.611
As long as you leave a significant gap between each record at the start. In this example with this gap you can only do this 20 times when you keep adding a new name between Anthony Felder and the latest added name. Increasing the gap, increases the amount of times you can do this. But you have to DOUBLE the gap, just to get one additional name in there.
The limit of 20 is only if keep using the same name 20 times as the upper or lower limit. Would love to hear if there is a smarter algorithm, but I doubt it, without rebuilding indices. Taking the middle of two numbers makes sure you always have the biggest gap between two numbers. (not taking predictive models into account).
I don't like my solution of taking the average, but I think it may be the best solution. In other words, unless someone comes up with a better algo, I would try to sort your situation differently. For example, letting go of the need to make the numbers sequential to the alphabetical order of the names (I wonder why this is needed anyway)
EDIT: One other option. Map their name to a number
a = 01, b = 02, c = 03... z = 26, space = 27
Optional, space is a dot, but you can also put a dot every 3 letters (6 numbers)
That means 2 people with the same name would get the same number. You can solve this by having the first two numbers telling you which person it is.
So the first Anthony Felder would start with 01, second Anthony Felder with 02, third Anthony Felder with 03 etc and then start mapping the A (=01).
You have to define how to deal with other characters such as é .
This leads to numbers with variable lengths
This can lead to LONG numbers.
The limit is 99 people with the same name (or 100 if you start with 00)
Actually we can create your idea to code
but it need more time (You say that there are 20000 records)
$value = 100000;
$n= 100000 + $total_db_row; //find total name count from your db table
for( $i=100000; $i < $n; $i++ ) {
$a = str_pad($i, 7, '0', STR_PAD_LEFT);
$a = substr_replace( $a, '.', 1, 0 );
$id[] = substr_replace( $a, '.', 5, 0 );
}
//var_dump($id);
then ,after each insertion you should do...
mysql> SELECT * FROM ordered_names;
+----+-----------+----------+------------+
| id | firstname | lastname | sort_order |
+----+-----------+----------+------------+
| 1 | pamela | edwards | NULL |
| 2 | rolando | edwards | NULL |
| 3 | diamond | edwards | NULL |
+----+-----------+----------+------------+
3 rows in set (0.00 sec)
mysql>
Next, let's populate the sort order column:
SET #x = 0;
UPDATE ordered_names SET sort_order = (#x:=#x+1) ORDER BY lastname,firstname;
SELECT * FROM ordered_names;
Let's run that:
mysql> SET #x = 0;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE ordered_names SET sort_order = (#x:=#x+1) ORDER BY lastname,firstname;
Query OK, 3 rows affected (0.00 sec)
Rows matched: 3 Changed: 3 Warnings: 0
mysql> SELECT * FROM ordered_names;
+----+-----------+----------+------------+
| id | firstname | lastname | sort_order |
+----+-----------+----------+------------+
| 1 | pamela | edwards | 2 |
| 2 | rolando | edwards | 3 |
| 3 | diamond | edwards | 1 |
+----+-----------+----------+------------+
3 rows in set (0.00 sec)
Then update php created array with where condition 'sort_order'.
foreach($i=0; $i < count($id); i++ )
{
$sql = "UPDATE ordered_names SET sort_order=.$id[$i]. WHERE sort_order=.$i.";
$db->query($sql);
}
it will update sort_order col with 0.100.100 to .....
But i repeat it need more execution time for larger records
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'
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.
I want to achieve something like this using php and mysql
if the customer has an account with rewards and wants to spend rewards, how do i update the table so that it will going to subtract the spent reward to the table.
Definitely it will going to get the sum of reward by customer_id then subtract the spent reward. IF the first row(reward) is less than the spent value, it will going to subtract all then go to next row get the difference from previous result until the value of spent is equal to 0.
sample:
spent = 60
id_customer = 2
I have a table like this
id | id_customer | reward
1 | 2 | 50
2 | 2 | 20
3 | 3 | 100
4 | 4 | 5
the result should be something like this:
1st row: 50(value of first row) - 60 = 0 (with remaining 10)
2nd row: 20(value of 2nd row) - 10 (remaining points from first row) = 0
id | id_customer | reward
1 | 2 | 0
2 | 2 | 10
3 | 3 | 100
4 | 4 | 5
Hope that makes sense. Thanks
This is the logic of my solution (of course, maybe more than this one and a better ones):
Get the set of rows for that customer (id_customer = 2) and loop through the rows returned.
In each iteration, compare the value of field reward against the amount you like to subtract (60).
If the actual value is >= 60, update that row and exit. If not, update it with 0, update the remain value (60 - row value) and go to the next item in the iteration doing the same action.
In MySQL, I think the best way to do this is with variables. This should work:
declare #spent := 60;
update tablelikethis
set reward = (case when #spent = 0 then reward
when #spent >= reward
then (case when (#tmp := #spent) is null then NULL
when (#spent := #spent - reward) is null then NULL
else 0
end)
else (case when (#tmp := #spent) is null then NULL
when (#spent := 0) is null then NULL
else reward - #tmp
end)
end)
where id_customer = 2
order by id;
MySQL makes this a little hard to do in a single update, because you cannot use order by with a join. The variable version just has to deal with logic on whether the amount remaining for the reward is bigger or less than the amount remaining being spent.
I'd structure your database in a different way. You could create a column called "reward_points" in the customer table, and have a separate reward table. The structure is:
REWARD_TABLE
----------------------------------
reward_id | customer_id | reward
----------------------------------
1 | 2 | 50
2 | 2 | 20
3 | 3 | 100
4 | 4 | 5
CUSTOMER_TABLE
-------------------------------------------------
customer_id | name | reward_points
-------------------------------------------------
1 | Eddard Stark | 0
2 | Jaime Lannister | 70
3 | Joffrey Baratheon | 100
4 | Theon Greyjoy | 5
Then you could just update the CUSTOMER_TABLE with the new value. You could keep the REWARD_TABLE as a 'reward history'. Even better... upon purchase, you could add a negative transaction to the REWARD_TABLE, so when you would do a SELECT SUM(reward) asRewardFROM reward_table WHERE customer_id = 2 GROUP BY customer_id, it would count all the negative transactions as well, resulting in something, which is close to your concept.