I've been banging my head hard over this problem for the last 2-3 days trying to see the problem from as many different angles as possible but to no avail. I'm turning to the SO community for extra perspectives. Below is the code I have which prints all 9 product plans. I'm wanting to find and print the plan with pricing equals or closest to a given user input. How can I do this?
//arrays of productnames
$productnames=array(1=>"Beginner","Advanced","Expert");
//arrays of productlevels
$productlevels=array(1=>"Bronze","Silver","Gold");
//Get The Length of Product Name Array
$planname_array_length=count($productnames);
//Get The Length of Product Level Array
$planlevel_array_length=count($productlevels);
for ($prn=1; $prn <= $planname_array_length; $prn++) {//loop to create plan name indicators
for ($prl=1; $prl <= $planlevel_array_length; $prl++) {//loop to create plan level indicators
$getpoductsql = " SELECT name, level,productNameId,productLevelId,finalProductPrice
FROM (
SELECT wspn.productName AS name, wspl.productLevel AS level, wsp.productNameId AS productNameId, wsp.productPlanLevel AS productLevelId,
ROUND(SUM(`Price`) * 1.12) AS finalProductPrice,
FROM `products` ws
left join product_plan wsp on wsp.productId = ws.wsid
left join product_plan_level wspl on wsp.productPlanLevel = wspl.wsplid
left join product_plan_name wspn on wspn.wspnid = wsp.productNameId
WHERE wspn.productName = '$planname_array_length[$pn]' AND wspl.productLevel = '$planlevel_array_length[$pl]'
)
AS x ORDER BY ABS(finalProductPrice - $compareprice)"
$resultproducts = $conn->query($getpoductsql);
$prodArray = mysqli_fetch_array($resultproducts);
//print array of each plan
$resultArr = array('planNameID' => $prodArray['planNameId'],
'planName' => $prodArray['name'],
'planLevelID' => $prodArray['planLevelId'],
'planLevelName' => $prodArray['level'],
'planPrice' => $prodArray['finalProductPrice'];
//print arrays of products
echo json_encode($resultArr);
}
}
This will output 9 plans as follow :
{"planNameID":"1","productName":"Beginner","productLevelID":"1","productLevelName":"Bronze","productPrice":"15"}
Rather than performing a separate query for each product name and product level, do them all in one query, and let MySQL find the one with the closest price.
$getpoductsql = " SELECT name, level,productNameId,productLevelId,finalProductPrice
FROM (
SELECT wspn.productName AS name, wspl.productLevel AS level, wsp.productNameId AS productNameId, wsp.productPlanLevel AS productLevelId,
ROUND(SUM(`Price`) * 1.12) AS finalProductPrice,
FROM `products` ws
left join product_plan wsp on wsp.productId = ws.wsid
left join product_plan_level wspl on wsp.productPlanLevel = wspl.wsplid
left join product_plan_name wspn on wspn.wspnid = wsp.productNameId
WHERE wspn.productName IN ('Beginner', 'Advanced', 'Expert') AND wspl.productLevel IN ('Bronze', 'Silver', 'Gold')
GROUP BY productNameId, productLevelId
)
AS x ORDER BY ABS(finalProductPrice - $compareprice)"
forgive my formatting, I'm on mobile
Like Amr Berag said above, your result should be the first row returned from your query.
If you have a table like this:
ID value
---- ------
A 7
B 12
C 23
...
You can then SELECT from this table to find the closest to some value, like so:
(Assume your desired value is $VALUE)
SELECT id, value, ABS(value - $VALUE) AS diff
FROM your_table
ORDER BY diff ASC
This will return something like this (say $VALUE is 10):
id value diff
-- ------ ----
B 12 2
A 7 3
C 23 13
...
You can just pick the first row.
You may also be able to add a WHERE clause to only select the row with the least difference using the MIN function:
SELECT id, value, ABS(value - $VALUE) AS diff
FROM your_table
WHERE diff = MIN(diff)
The way you are doing it will produce invalid json, do it like this:
$result=array();
for ($prn=1; $prn <= $planname_array_length; $prn++) {
for ($prl=1; $prl <= $planlevel_array_length; $prl++) {
. . . // the other code
//print array of each plan
$resultArr = array('planNameID' => $prodArray['planNameId'],
'planName' => $prodArray['name'], 'planLevelID' => $prodArray['planLevelId'],
'planLevelName' => $prodArray['level'],
'planPrice' => $prodArray['finalProductPrice'];
//print arrays of products
$resul[]=$resultArr;
}//loop1
}//loop2
echo json_encode($result);
you should also add the limit 1 and do the rest in JS in the front end
Related
I have a strange issue happening. I've got a table where I want to order the number of times a product was sold. I'll post the query, but the issue is inside the while loop.
Query:
$cat = $db->query("SELECT *, COUNT(id_produto) as quantos FROM produtos p JOIN pedidos m ON p.id_prod = m.id_produto GROUP BY id_produto ORDER BY quantos +0 DESC");
$cat->execute();
Now the loop:
while($res_cat = $cat->fetch(PDO::FETCH_ASSOC)){
$quantidade_ok = $res_cat['quantos'] * $res_cat['qtde'];
$quanti = array($quantidade_ok);
rsort($quanti);
foreach($quanti as $ord){
echo $ord."<br>";
}
The output is:
40
50
4
1
3
2
10
But I want it to be:
50
40
10
4
3
2
1
I'll be happy for any help.
You're sorting within a loop (so your sorting already happened), and you're sorting an array with just 1 value $quanti, so your sort does nothing.
You have 2 ways to approach this properly: edit your query to actually sort how you wish, or sort the PHP array before looping it.
Option 1: Edit your query
Based on your code it seems clear that you wish to sort by the product of quantos times qtde. So you can simply edit your query as follows:
SELECT *, COUNT(id_produto) as quantos
FROM produtos p JOIN pedidos m ON p.id_prod = m.id_produto
GROUP BY id_produto
ORDER BY (quantos*qtde) DESC
Option 2: Sort via PHP
If you prefer to sort via PHP as you don't want to change your query, you can simply populate a temporary array, the product of quantos times qtde as key and then use krsort to sort the array.
In code:
$array = [];
while ($res_cat = $cat->fetch(PDO::FETCH_ASSOC)) {
$key = $res_cat['quantos'] * $res_cat['qtde'];
$array[$key] = $res_cat;
}
krsort($array);
foreach ($array as $ord => $res_cat) {
echo $ord."<br>";
}
You overwrite $quanti each time so it always has a single value.
Try this:
$quanti = [];
while($res_cat = $cat->fetch(PDO::FETCH_ASSOC)) {
$quantidade_ok = $res_cat['quantos'] * $res_cat['qtde'];
$quanti[] = $quantidade_ok;
}
rsort($quanti);
foreach($quanti as $ord){
echo $ord."<br>";
}
I have an addresses table in my MYSQL database with the following structure:
The first column ID, is a primary, auto-increment column.
The second column Name is varchar.
The third column contains address (text), filled by user.
The forth column contains address slug, which is basically the address (Third Column) in lower case and without any special characters.
The last column contains the creation date of the record.
I wish to display all the records and highlight the possible duplicates, based on the address/address slug.
In this case, the duplicates are as follows:
Record 1 and Record 2
Record 3 and Record 6
Is there a way to partially match a string in MYSQL or PHP, to achieve the above results?
FYI: I have gone through SPHINX PHP, SQL FULLTEXT SEARCHES etc.
I have been struggling over 2 weeks, but couldn't find any optimal solution.
Any ideas, suggestions, solutions are welcome.
Since laravel was tagged initially, later removed, I thought the strategy can still help.
This is the given list:
$lists = [
[
'id' => 1,
'text' => '2693 Edgewood Road Exit',
],
[
'id' => 2,
'text' => '4408 Cost 4657 Avenue',
],
[
'id' => 3,
'text' => '2693 Mapleview Road',
],
[
'id' => 4,
'text' => '4657 Cost Edgewood Avenue',
],
[
'id' => 5,
'text' => '4408 Mapleview Drive Road',
]
];
Goal is to find repetitive/duplicate texts from each.
Since finding duplication of ONE word is not a real scenario, I thought of finding the duplication with TWO words with all the combinations possible.
$combinations = [];
foreach ($lists as $list) {
$insideCombo = [];
$insideText = explode(' ', $list['text']);
$length = count($insideText);
for ($i = 0; $i < $length; $i++) {
for ($j = $i + 1; $j < $length; $j++) {
if (isset($insideText[$j])) {
$insideCombo[] = $insideText[$i] . ' ' . $insideText[$j];
}
}
}
$combinations[$list['id']] = $insideCombo;
}
This is gonna return
// for '2693 Edgewood Road Exit'
1 => array:6 [
0 => "2693 Edgewood"
1 => "2693 Road"
2 => "2693 Exit"
3 => "Edgewood Road"
4 => "Edgewood Exit"
5 => "Road Exit"
]
Now, we loop again to compare the possible repetition. Here, we leverage Laravel's Str::containsAll()
$copyCat = [];
foreach ($lists as $list) {
foreach ($combinations as $comboKey => $combination) {
/* no need to compare the text with itself &&
* to avoid duplication of '4 to 2' if '2 to 4' is already mentioned
*/
if ($list['id'] != $comboKey && $list['id'] < $comboKey) {
foreach ($combination as $row) {
if (Str::containsAll($list['text'], explode(' ', $row))) {
$copyCat[] = $list['id'] . ' matches with ' . $comboKey . ' with "' . $row . '"';
}
}
}
}
}
Final Response of $copyCat
array:5 [
0 => "1 matches with 3 with [2693 Road]"
1 => "2 matches with 4 with [4657 Cost]"
2 => "2 matches with 4 with [4657 Avenue]"
3 => "2 matches with 4 with [Cost Avenue]"
4 => "3 matches with 5 with [Mapleview Road]"
]
Keep me posted in the comments below. Cheers!
Make an empty duplicate of the table - e.g. mytable_to_update.
Run a few queries to find out duplicates.
Start with populating the newly created table with non-duplicates. Initial query:
SELECT SUBSTRING_INDEX(Name,' ',1),COUNT(*)
FROM mytable_to_update
GROUP BY SUBSTRING_INDEX(Name,' ',1) HAVING COUNT(*) = 1;
The SUBSTRING_INDEX will capture the first string before space (' '). In the example, Sam Mcarthy will become Sam only. Then using that to group and count how many name occurrences it has. HAVING COUNT(*) = 1 will only show any name occurring once. But that might as well return nothing if there's a name like Joe and Joe John but the two are actually a different person with different addresses (since the first query only group by the first name occurring). Therefore, we need to add address comparison in the mix.
Add the same function to the Address column like this:
SELECT SUBSTRING_INDEX(Name,' ',1),
SUBSTRING_INDEX(Address,' ',1), /*we take the first string in the address*/
COUNT(*)
FROM mytable_to_update
GROUP BY SUBSTRING_INDEX(Name,' ',1),
SUBSTRING_INDEX(Address,' ',1) /*then add group by for the address*/
HAVING COUNT(*) = 1;
Similarly, we take only the first string occurrence from the address. So let's say for example there are two data that looks like this, Joe, 12 Street.. and Joe John, 12 St. .., what will happen is the query above will (given the SUBSTRING_INDEX function) take only the first string occurrence; Joe, 12 , which will return the count value as 2. That means both data (Joe, 12 Street.. and Joe John, 12 St. ..) are considered as duplicates and will not show in the query results.
Change the query to list out all non-duplicates ID to be inserted into mytable_to_update table:
INSERT INTO mytable_to_update
SELECT * FROM mytable WHERE ID IN
(SELECT GROUP_CONCAT(ID) /*replace everything else in the select with just `ID`*/
FROM mytable
GROUP BY SUBSTRING_INDEX(Name,' ',1),
SUBSTRING_INDEX(Address,' ',1)
HAVING COUNT(*) = 1) ;
Note: I'm using GROUP_CONCAT(ID) because of incompatibility of sql_mode=only_full_group_by - if it's being set. Of course the result could be different (like '1,2' or '1,,,,,') but since we're only looking at any count=1, it shouldn't have a problem as it will only return 1 value. I've tested with ANY_VALUE it also return similar results.
Now you have all the non-duplicates inside the mytable_to_update table. the next step is to search for duplicates and insert the ones that you only want. This is merely a suggestion/assumption of what you might want and it's not 100% accurate due to the nature of the data value that we're comparing.
The query is similarly structured and changed only in a few places, for example:
SELECT GROUP_CONCAT(ID), /*add GROUP_CONCAT to list all the duplicates group by the first name & address string.*/
Name,
Address,
COUNT(*)
FROM mytable
GROUP BY SUBSTRING_INDEX(Name,' ',1),
SUBSTRING_INDEX(Address,' ',1)
HAVING COUNT(*) > 1; /*Change '= 1' to '> 1' to get any records with more than 1 count.*/
Using GROUP_CONCAT to generate a comma separated list of ID that has possible duplicates.
Then add GROUP_CONCAT over all the columns listed with identical ORDER BY so every columns will be ordering by the same thing.
SELECT GROUP_CONCAT(ID ORDER BY ID), /*add ORDER BY*/
GROUP_CONCAT(Name ORDER BY ID),
GROUP_CONCAT(Address ORDER BY ID),
COUNT(*)
FROM mytable
GROUP BY SUBSTRING_INDEX(Name,' ',1),
SUBSTRING_INDEX(Address,' ',1)
HAVING COUNT(*) > 1;
With this you go over the values it returned for any of the duplicates and compare it side by side. That way you can decide to omit any ID that you don't want to appear in the list by adding WHERE ID NOT IN(1,3 ...) etc.
Once you've finalized which ID you want to keep, you can do something like this:
INSERT INTO mytable_to_update
SELECT * FROM mytable WHERE ID IN
(SELECT SUBSTRING_INDEX(GROUP_CONCAT(ID ORDER BY ID),',',1)
/*assuming that you only want the first ID in the set, do SUBSTRING_INDEX to separate the first ID*/
FROM mytable
GROUP BY SUBSTRING_INDEX(Name,' ',1),
SUBSTRING_INDEX(Address,' ',1)
HAVING COUNT(*) > 1);
Now you'll have a table (mytable_to_update) that might probably have all non-duplicates. In case some of the data in the mytable_to_update are not what you want, you can just remove it or in case there are some data you think is not a duplicate, you can insert it. It's pretty much a manual process afterwards; well, even with the queries, only yourself can determine whether the processes/data are correct.
Here's a fiddle: https://www.db-fiddle.com/f/6Dfrn78mqZbGTwZs3U9Vhi/0
I have a Part Management system I've created in PHP with MySQL. What I'm trying to create is something that will generate the next Part Number for me. All part numbers start with a 3 letter prefix (which is determined by the product family/category) followed by their number.
For example 'ABC001'
What I have below is something that I'd like to use to determine what the next number is having already 'ABC001', 'ABC002' & 'ABC003' so I would like it to recognize what the next number is by querying until the query comes back false because that product number doesn't exist yet.
$abc_query = "SELECT * FROM products WHERE id LIKE 'ABC%'";
$abc_result = $mysqli2->query($abc_query);
while($row = $abc_result->fetch_assoc()) {
$rowid = $row["id"];
$pnumber = substr($rowid, 3, 3);
echo $pnumber. '<br/>';
$int = (int)$pnumber;
$abc_query2 = "SELECT * FROM products WHERE id 'ABC" . sprintf('%03s', $int);
for ($abc_query2 = true; $abc_query2 = false; $int++){
echo $int;
}$abc_nextnumber = $int +1;
}
$abc_newnumber = 'ABC' . sprintf('%03s', $abc_nextnumber);
echo $abc_newnumber;
The result I get is
001
002
003
005
ABC006
However the result should be..
001
002
003
ABC004
code update I've updated the code but it doesn't seem to stop at ABC004 if I have an 005. It will go to 006.
You should have the db do this instead of your app:
select t.id_prfx, max(t.id_num) as latest_num from
(select substring(id, 1, 3) as id_prfx,
cast(substring(id,4) as integer) as id_num) t
group by id_prfx
This will give you a result table where you get the highest part number for each prefix.
If you really really only want prefixes of 'ABC' then:
select max(cast(substring(id,4) as integer)) as max_num from table
where id LIKE 'ABC%'
Could you try this query?
SELECT MAX(SUBSTR(id, 4)) as last_id FROM products WHERE SUBSTR(id, 1, 3)='ABC'
EDİT:
products TABLE
==============
ABC001
ABC002
ABC003
ABC005
==============
We want to find 4 in products table.
SELECT SUBSTR(t1.id, 4) + 1 as POSSIBLE_MIN_ID
FROM products t1
WHERE NOT EXISTS (
SELECT *
FROM products t2
WHERE SUBSTR(id, 1, 3)='ABC' AND SUBSTR(t2.id, 4) = SUBSTR(t1.id, 4) + 1
) LIMIT 1
RESULT:
POSSIBLE_MIN_ID : 4
If anyone knows how I can have it add automatic zeros to the into the query (as it will be different amount of 0s once it gets to 'ABC011') instead of typing them in that would also be very helpful.
Here's how to automatically handle the prepended zeroes.
$sql3 = "SELECT * FROM products WHERE id 'ABC" . sprintf('%03s', $int);
I have a history table which contains changes made to data and the date the change was made - the history are the old values and the date the change was made.
There is then a main table which contains the current values.
The actual table layouts are like:
history
id user_id colour change_date
1 1 Red 2016-01-01
2 1 Blue 2016-04-05
3 1 Green 2016-08-05
and then:
current
user_id colour entry_date
1 Yellow 2015-10-14
I want to try and get a chronological list of all the values so the output for user_id 1 would look like:
2015-10-14 Red
2016-01-01 Blue
2016-04-05 Green
2016-08-05 Yellow
At the moment I am taking each user in the user table and checking if they have a history value - if they do not then the output simply is:
2015-10-14 Yellow
However, when they do have a history I am having to start with the entry date and assign the first colour history to that and storing in an array.
Then I take the first history date and get the second history value (via another query) and store that in the array and loop and so on - there are 150k users and each one can have 20 or 30 changes and it is horribly inefficient!
I would like to find a more efficient way of doing this either in PHP or MySQL - can anyone help?
Firstly, I am unsure what connection class you have decided to use but for this, I'll provide a class I wrote to query which uses PDO.
class Entity extends PDO {
public function __construct(
) {
try {
parent::__construct(
'mysql:host=localhost;dbname=example',
'db_user',
'db_pass'
);
} catch (PDOException $ex) {
die($ex->getMessage());
}
}
public function query(
$statement,
$values = array()
) {
$stmp = parent::Prepare($statement);
$stmp->execute($values);
return $stmp;
}
}
Now, assuming you already know the user_id you can begin to now query through the database to retrieve the change logs.
$con = new Entity();
$user_id_example = 1;
$sql = 'SELECT colour, change_date FROM history WHERE user_id = ? ORDER BY change_date ASC';
var_dump($con
->query($sql, [$user_id_example])
->fetchAll()
);
Update: If you're trying to get the newest change log you can add an LIMIT to your query
$sql = 'SELECT colour, change_date FROM history WHERE user_id = ? ORDER BY change_date ASC LIMIT 1';
Update: Note, if you want to bring results out of both table current and history, you can use an INNER JOIN
$sql = 'SELECT colour, change_date FROM history h INNER JOIN current c ON h.change_date = c.change_date WHERE user_id = ? ORDER BY change_date ASC';
I propose you two querys, in the first one you will have the colours ordered and in the second one you will have the dates ordered, so, in php you will have two arrays with the data and only remains to put together the data.
There is the code, only you have to set the conection to the database
$sqlColour = "select c.user_id, u.colour from current as c inner join ( select user_id, colour from history union all select user_id , colour from current ) u on c.user_id=u.user_id";
$sqlDate = "select c.user_id, u.date from current as c inner join ( select user_id, entry_date as date from current union all select user_id , change_date as date from history ) u on c.user_id=u.user_id";
$stmt = $db->query($sqlColour);
$colours = $stmt->fetchAll();
$stmt = $db->query($sqlDate);
$dates = $stmt->fetchAll();
$answer = array();
for ($i = 0; $i < count($colours); $i++) {
$answer[] = array("date" => $dates[$i]['date'], "colour" => $colours[$i]['colour'], "user_id" => $colours[$i]['user_id']);
}
echo $answer;
Im trying to find a better way to return 2 tables at once.
My first table is:
[ID] [area]
1 13,12,15
6 18,17,13
and the second table is:
[areaname] [singlearea]
textOf12 12
textOf18 18
textOf15 15
Now, I need to return for each [ID] hits area names, for example:
For the ID: 1, I need the following array: (textOf12,textOf15)
and for the ID 6 I need: (textOf18) only.
This is what i have for now (I don't think its a nice code):
$getall = "SELECT * FROM table1";
$resultfull = mysql_query($getall);
while ($res = mysql_fetch_assoc($resultfull))
{
$uarray = array();
$sqlarea = explode(",", $res['area']);
foreach($sqlarea as $userarea)
{
$areaarray = runquery("SELECT areaname From table2 WHERE singlearea = '".$userarea."'");
$value = mysql_fetch_object($areaarray);
array_push($uarray,$value->areaname);
}
var_dump($uarray);
any suggestions?
Thank you very much!
Comma separated ID list and ID value pretty good matching using like:
select t1.id, t2.areaname
from table1 t1, table2 t2
where concat(',', t1.area, ',') like concat('%,', t2.singlearea, ',%')
However It's recommended to use additional link table!