laravel check amount if in between the records - php

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

Related

One-to-many Eloquent relation: Query to filter based on related table's column values

I have two tables that look like this:
stocks:
id | name | market_cap
1 | Microsoft | 5000
2 | Tesla | 2000
This table has a one-to-many relationship with a table titled stock_ratings:
id | stock_id | measure | value
12 | 1 | revenue | 30
13 | 1 | dividend| 5
14 | 2 | revenue | 10
15 | 2 | dividend| 0
Now, let's say a user wants to get all stocks with market_cap > 3000, skip the first 20 results, and get the next 20. This works:
$query = Stock::where('market_cap', '>', 3000);
$stocks = $query->skip(20)->take(20)->get();
However, the user needs to also be able to query on the stock_ratings. So, let's say, in addition to the conditions above, he wants to find all stocks that have a value > 20 for the revenue measure.
I have tried this, and it doesn't work. It returns the same list of stocks as it would without this additional condition.
$query = Stock::where('market_cap', '>', 3000);
$query = $query->with(['ratings' => function ($q) {
$q->where('stock_ratings.measure', 'revenue');
$q->where('stock_ratings.value', '>', 0);
}]);
$stocks = $query->skip(20)->take(20)->get();
This returns all stocks with market_cap > 3000 regardless of the value and measure in the stock_ratings table. It returns stocks that have a negative value in stock_ratings where measure = revenue and it also returns rows that have no related rows in stock_ratings whatsoever.
How do I resolve this?
When you apply a constraint in with it means the constraint is applied for which nested related records should be eager loaded and which should not be eager loaded.
However in your case you want to filter or apply constraint on the stocks based on values contained in related ratings.
So try this
$query = Stock::where('market_cap', '>', 3000);
$query = $query->whereHas('ratings', function ($q) {
$q->where('measure', 'revenue');
$q->where('value', '>', 0);
})-with('ratings');
$stocks = $query->skip(20)->take(20)->get();
Read more at Laravel docs: https://laravel.com/docs/8.x/eloquent-relationships#querying-relationship-existence

Create identification number in order and range delimited to registers

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

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'

MySQLi SUM function with pound/dollar sign?

Within my Admin panel, I would like to see all profits and sales by Weekly, Monthly, Yearly and Total.
So far I am able to display the transactions recorded in the ap_payment_logs database in to an advanced responsive table. Above this is where I am trying to display the profits for each margin: Weekly, Monthly Yearly and Total. By selecting and calculating the column: txn_amount.
This all works with normal numbers added to that column, although the way my site works, it adds a Pound sign (£) before the digits:
£10.00, £20.00, £50.00...
The SUM function doesn't seem to work with this Pound sign, so I am wondering if there is any simple way to perform the same calculations which would work for this? Here is my current code:
$result = $conn->query('SELECT SUM(txn_amount) AS value_sum FROM `ap_payment_logs` WHERE `txn_date` >= NOW() - INTERVAL 7 DAY');
$row = mysqli_fetch_assoc($result);
$sum = $row['value_sum'];
echo $sum;
Please Note: I cannot remove the addition of the £ sign to achieve my results.
Thanks in advance!
Having a Table like this:
create table dummy (
id int primary key auto_increment,
test varchar(10)
);
and these rows in the table:
mysql> select * from dummy;
+----+---------+
| id | test |
+----+---------+
| 1 | 10.0 |
| 2 | 14.0 |
| 3 | €14.0 |
+----+---------+
you could get the sum of the test values with the following select statement:
select sum(replace(test, '€', '')) from dummy;
Anyway like Mark Baker I would recommend to store your numeric colums in numeric fields as this has a couple of advantages (aggregates like sum are easier, sorting works...).

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.

Categories