I am trying to insert the data of a query, currently I am doing it with the eloquent ORM of laravel, but it takes a long time since there are approximately 120k of records, so I would like to know if they can help me as it can be done in the same query
This is the query:
$var = DB::select("
select selrs.*, sim.emin, sim.emax
from (select rac.*, sup.zona, sup.sitio, sup.manejo
from datos_rendim_actual rac
left join super sup on (sup.codigo = (rac.fundo::int8 * 1000000 + rac.rodal::int8 ))) selrs
left join sitman sim on (sim.sitio = selrs.sitio and sim.manejo = selrs.manejo)
where selrs.edad >= sim.emin and selrs.edad <= sim.emax
");
This is the dd of $var
array:123921 [▼
0 => {#813 ▼
+"id": 255214
+"fundo": 101
+"rodal": 196826
+"codigo": null
+"edad": 10
+"densidad": "1019"
+"vol_prod1": "0"
+"vol_prod2": "113.95"
+"created_at": null
+"updated_at": null
+"zona": 5
+"sitio": 1
+"manejo": 7
+"emin": 10
+"emax": 20
}
So This is how I insert them:
foreach ($var as $lista) {
$rendimA = new RendimActual;
$rendimA->codigo = $lista->fundo.$lista->rodal;
$rendimA->edad = $lista->edad;
$rendimA->densidad = $lista->densidad;
$rendimA->vol_prod1 = $lista->vol_prod1;
$rendimA->vol_prod2 = $lista->vol_prod2;
$rendimA->vol_prod3 = $lista->vol_prod3;
$rendimA->save();
}
The fields that I have to fill are in the rendim_actual table and are the following:
codigo = concat(fundo, rodal) from $var
edad= from $var
densidad = from $var
vol_prod1 to n (actually there are 36 but as an example just leave 3) from $var
in terms of time insert by Eloquent takes about 15 minutes, I hope you can help me, ty
I am using laravel-5.8 and postgresql
I don't have much experience dealing with large databases, but in your solution you will be making 120k separate inserts, which will not be efficient.
If the records in your $var array are already in the correct format from your table you can try using the query builder's insert method.
// Not certain what your table name is
DB::table('rendim_actual')->insert($var);
DB::commit();
If the $var array becomes soo big that you cannot insert all data in one insert, you can look into this SO Question about chunking inserts.
What you are currently doing is running an insert query for each RendimActual. What you should be doing is bulk insert via ::insert()
insert()
This will insert 1 query with 120K values
$insert = [];
foreach ($var as $lista) {
$insert[] = [
'codigo' => $lista->fundo . $lista->rodal,
'edad' => $lista->edad,
'densidad' => $lista->densidad,
'vol_prod1' => $lista->vol_prod1,
'vol_prod2' => $lista->vol_prod2,
'vol_prod3' => $lista->vol_prod3
];
}
RendimActual::insert($insert);
chunk()
Since you are inserting 120K values inside 1 query, there's a chance of consuming a lot of memory. So, it better to chunk() the data if 2000 rows and insert those 2K first. Even though you will be running 120K/2K = 60 queries ... at least this way each query will not be consuming memory compared to 1 big (120K) insert query.
foreach (array_chunk($insert, 2000) as $inst) {
RendimActual::insert($inst);
}
Keep me posted in the comments below. Cheers!
Related
This question already has answers here:
Strict Standards: mysqli_next_result() error with mysqli_multi_query
(3 answers)
Closed 5 years ago.
I'm trying to display data to bootstrap datatable (4 columns) using php and ajax from mysqli_multi_query().
I could not get the 2nd query working with UNION or INNER JOIN.
I'm no programmer and managed to, almost, get the correct result, but for some reason the 1st query returns "null" value.
How can I change that?
Here is a log sample and my code:
PHP Notice: Undefined offset: 2 on line 26
PHP Notice: Undefined offset: 3 on line 27
{"sEcho":1,"iTotalRecords":1,"iTotalDisplayRecords":1,"aaData":{"CommonInverterData":[{"Date":null,"Generated":null,"Export":"0.9921563111569116","Import":"1.8864974578334937"}
/* Database connection start */
include ('db.php');
$conn = mysqli_connect($hn, $un, $pw, $db) or die("Connection failed: " . mysqli_connect_error());
/* Database connection end */
$sql = "SELECT m.`date`, `day_energy`
FROM `CommonInverterData`
JOIN ( SELECT `date`, MAX(`time`) 'maxtime'
FROM `CommonInverterData`
GROUP BY `date`) m
ON m.maxtime = `CommonInverterData`.`time`
AND m.`date` = `CommonInverterData`.`date`;";
$sql .= "SELECT ABS(SUM((CASE WHEN `P_Grid`<0 THEN `P_Grid` ELSE 0 END) / 60000 )) as 'Export', SUM((CASE WHEN `P_Grid`>=0 THEN `P_Grid` ELSE 0 END) / 60000 ) as 'Import' FROM `PowerFlowRealtimeData` GROUP BY `date`;";
if (mysqli_multi_query($conn, $sql) or die(mysqli_error($conn))) {
do {
if ($result=mysqli_store_result($conn)) {
$data = array();
while( $rows = mysqli_fetch_row($result) ) {
$data[] = array(
'Date' => $rows[2],
'Generated' => $rows[3],
'Export' => $rows[0],
'Import' => $rows[1],
);
} mysqli_free_result($result);
}
} while (mysqli_next_result($conn));
}
mysqli_close($conn);
$return = array(
"sEcho" => 1,
"iTotalRecords" => count($data),
"iTotalDisplayRecords" => count($data),
"aaData"=>$data);
echo json_encode($return);
Do not mess mess with mysqli_multi_query().
Run one query, collect results into array.
Run the other query, collect results into array.
combine the results in one simple loop.
PROBLEM SOLVED. With simplest tools available.
Whereas the most sensible solution would be using the actual JOIN. For whish you should have been asking an SQL question.
You are not checking for mysqli_more_results().
You are trying to access 4 elements in each result set, despite only two being offered from each query.
I've blended your snippet with a generalized code block that I built a while ago that helps people to understand and debug mysqli_multi_query(). If there is still something wrong after implementing this, this code block should help you to isolate the cause.
$sql=["SELECT m.`date`, `day_energy`
FROM `CommonInverterData`
JOIN ( SELECT `date`, MAX(`time`) 'maxtime'
FROM `CommonInverterData`
GROUP BY `date`) m
ON m.maxtime = `CommonInverterData`.`time`
AND m.`date` = `CommonInverterData`.`date`",
"SELECT ABS(SUM((CASE WHEN `P_Grid`<0 THEN `P_Grid` ELSE 0 END) / 60000 )) as 'Export',
SUM((CASE WHEN `P_Grid`>=0 THEN `P_Grid` ELSE 0 END) / 60000 ) as 'Import'
FROM `PowerFlowRealtimeData` GROUP BY `date`"
];
if(mysqli_multi_query($conn,implode(';',$sql))){
do{
// echo "<br><br>",key($sql),": ",current($sql); // display key:value # pointer
if($result=mysqli_store_result($conn)){ // if a result set
while($rows=mysqli_fetch_row($result)){
// echo "<br>Cols: {$rows[0]}, {$rows[1]}";
if(key($sql)===0){ (first query)
$tmp1[]=['Date'=>$rows[0],'Generated'=>$rows[1]];
}else{ // key($sql)===1 (second query)
$tmp2[]=['Export'=>$rows[0],'Import'=>$rows[1]];
}
}
mysqli_free_result($result);
}
// echo "<br>Rows = ",mysqli_affected_rows($conn); // acts like num_rows on SELECTs
} while(next($sql) && mysqli_more_results($conn) && mysqli_next_result($conn));
}
if($mysqli_error=mysqli_error($conn)){
echo "<br><br>",key($sql),": ",current($sql),"Syntax Error:<br>$mysqli_error"; // display array pointer key:value
}
mysqli_close($conn);
$data=array_replace_recursive($tmp1,$tmp2); // blend the temporary arrays together
$return = array(
"sEcho" => 1,
"iTotalRecords" => count($data),
"iTotalDisplayRecords" => count($data),
"aaData"=>$data);
echo json_encode($return);
The truth is: mysqli_multi_query() is not best practice for this case. If this was my task/project, I'd be trying to merge the queries into a single call (and avoid calling mysqli_multi_query() entirely).
Here is a link where I advise about what can be done with a UNION query: https://stackoverflow.com/a/34758622/2943403 If you go this route, just remember you will still be producing 2 columns per row (not 4 columns).
As YourCommonSense suggests JOIN is going to be best practice as you'll be able to produce 4 columns per row and simplify your resultset processing. To repeat YCS, this merits a new question with separate/additional details about your database tables and desired output. If you want to receive a high quality response on how to write the query, be sure to include a sqlfiddle of all tables involved and explain the table relationships.
Very basic question but couldn´t find a solution.
In a table with some values I try to store in 1 column a array (int[]) and retrieve it. The storing and searching works fine but if I select it I get it as string in php.
Table week
col id (int) = 1
col days (int[]) = {1,1,1,1,1,0,0}
PHP
$query = SELECT id, days, manyother FROM week //array_to_json(days) does the same result
$pdo->setAttribute( PDO::ATTR_CASE, PDO::CASE_NATURAL );
$result = $pdo->query($query);
$test = $result->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($test)
Returns:
id: 1
days: "[1,1,1,1,1,0,0]"
manyother: ""
I´m sure I miss just something with json_encode/decode
Edit: its not affected by json_encode, I debuged it and before it returns the value like '[1,1,1,1,1,0,0]'.
Edit 2: Found a solution which works but increases the loading time 15* times :D So the question is still open but I have a workaround for the moment.
foreach($test as $key => $value){
$test[$key]['days'] = json_decode($value['days']);
}
when selecting array, you can hack the way array is displayed:
t=# select json_build_array(array[1,2,3,4])->0;
?column?
-----------
[1,2,3,4]
(1 row)
It should be easily evaluated by php then with eval. Or even json_agg on whole data set and then eval:
t=# select json_agg(s162) from s162;
json_agg
[{"i":0,"a":[1,2,3,4]},
{"i":1,"a":[1,4]}]
(1 row)
Time: 0.281 ms
I have a MySQL database with a backend: PHP.
In one table I have to insert 60,000 rows.
We made a query into my PHP that returns 1,000 rows. For each rows we have to insert 60 rows. We thought make a loop but we don't know if is the best practices that way.
The part of the code that insert the data is:
$turnos = $db->query("SELECT * FROM turno t
WHERE t.canchaId = :cancha
AND t.fecha BETWEEN :fechaInicio AND :fechaFin
AND t.nombre LIKE :cadena
ORDER BY t.fecha,t.hora ASC",
array("cancha" => $cancha["idCancha"], "fechaInicio" => $fechaInicio, "fechaFin" => $fechaFin, "cadena" => "%turno fijo%"));
foreach($turnos as $turno) {
//turnos have 1000 rows
$fecha = date_create($turno["fecha"]);
date_add($fecha, date_interval_create_from_date_string('7 days'));
$anioAuxiliar = 2017;
while(2017 == $anioAuxiliar){
//60 times
$data1 = turno[data1];
$data2 = turno[data2];
...
$fechaAGuardar = (String) $fecha->format('Y-m-d');
$result = $db->query("INSERT INTO turno(fechaAGuardar, data2, data3, data4, data5, data6, data7, data8) VALUES(:fechaAGuardar, :data2, :data3, :data4, :data5, :data6, :data7, :data8)",
array("fechaAGuardar" => $fechaAGuardar, "data2" => $data2, "data3" => $data3, "data4" => $data4, "data5" => $data5, "data6" => $data6, "data7" => $data7, "data8" => $data8));
date_add($fecha, date_interval_create_from_date_string('7 days'));
$anioAuxiliar = (int) $fecha->format("Y");
}
$cantidad_turnos = $cantidad_turnos + 1;
}
This php is into a hosting with phpmyadmin.
So my questions are:
This is the best way to insert 60,000 rows?
Shall we considerer take into account another constraint? (eg: phpmyadmin don't allow you insert that amount of rows)
Thanks for helping me, Any suggestions are welcome
//EDIT//
The inserts data change, we have to insert datetime, and for each loop we have to add 7 day the last date inserted. So we can't use insert with select
As a bunch of fellows described in the comments, INSERT/SELECT is the way to go if this data is in the same server/database. There's no need to use PHP at all. Your year comment can be handled with DATE_ADD.
Anyway, if there is any other requirement and you can't use PHP, consider using Bulk Data Loading.
Analysing your code, the MOST IMPORTANT TIP would be: don't use multiple INSERT INTO TABLE expressions. Each INSERT INTO will cause a round trip do the database and things will get really slow. Instead of it, concat multiple values with one INSERT INTO (example from the link):
INSERT INTO yourtable VALUES (1,2), (5,5), ...;
Good luck!
Unfortunately I can't show you the code but I can give you an idea of what it looks like, what it does and what the problem I have...
<?php
include(db.php);
include(tools.php);
$c = new GetDB(); // Connection to DB
$t = new Tools(); // Classes to clean, prevent XSS and others
if(isset($_POST['var'])){
$nv = json_decode($_POST['var'])
foreach($nv as $k) {
$id = $t->clean($k->id);
// ... goes on for about 10 keys
// this might seems redundant or insufficient
$id = $c->real_escape_string($id);
// ... goes on for the rest of keys...
$q = $c->query("SELECT * FROM table WHERE id = '$id'");
$r = $q->fetch_row();
if ($r[1] > 0) {
// Item exist in DB then just UPDATE
$q1 = $c->query(UPDATE TABLE1);
$q4 = $c->query(UPDATE TABLE2);
if ($x == 1) {
$q2 = $c->query(SELECT);
$rq = $q2->fetch_row();
if ($rq[0] > 0) {
// Item already in table just update
$q3 = $c->query(UPDATE TABLE3);
} else {
// Item not in table then INSERT
$q3 = $c->query(INSERT TABLE3);
}
}
} else {
// Item not in DB then Insert
$q1 = $c->query(INSERT TABLE1);
$q4 = $c->query(INSERT TABLE2);
$q3 = $c->query(INSERT TABLE4);
if($x == 1) {
$q5 = $c->query(INSERT TABLE3);
}
}
}
}
As you can see is a very basic INSERT, UPDATE tables script, so before we release to full production we did some test to see that the script is working as it should, and the "result" where excellent...
So, we ran this code against 100 requests, everything when just fine... less than 1.7seconds for the 100 requests... but then we saw the amount of data that needed to be send/post it was a jaw drop for me... over 20K items it takes about 3 to 5min to send the post but the script always crash the "data" is an array in json
array (
[0] => array (
[id] => 1,
[val2] => 1,
[val3] => 1,
[val4] => 1,
[val5] => 1,
[val6] => 1,
[val7] => 1,
[val8] => 1,
[val8] => 1,
[val9] => 1,
[val10] => 1
),
[1] => array (
[id] => 2,
[val2] => 2,
[val3] => 2,
[val4] => 2,
[val5] => 2,
[val6] => 2,
[val7] => 2,
[val8] => 2,
[val8] => 2,
[val9] => 2,
[val10] => 2
),
//... about 10 to 20K depend on the day and time
)
but in json... any way, sending this information is not a problem, like I said it can take about 3 to 5mins the problem is the code that does the job receiving the data and do the queries... in a normal shared hosting we get a 503 error which by doing a debug it turn out to be a time out, so for our VPS we can increment the max_execution_time to whatever we need to, to process 10K+ it takes about 1hr in our VPS, but in a shared hosting we can't use max_execution_time... So I ask the other developer the one that is sending the information that instead of sending 10K+ in one blow to send a batch of 1K and let it rest for a second then send another batch..and so on ... so far I haven't got any answer... so I was thinking to do the "pause" on my end, say, after process 1K of items wait for a sec then continue but I don't see it as efficient as receiving the data in batches... how would you solve this?
Sorry, I don't have enough reputation to comment everywhere, yet, so I have to write this in an answer. I would recommend zedfoxus' method of batch processing above. In addition, I would highly recommend figuring out a way of processing those queries faster. Keep in mind that every single PHP function call, etc. gets multiplied by every row of data. Here are just a couple of the ways you might be able to get better performance:
Use prepared statements. This will allow MySQL to cache the memory operation for each successive query. This is really important.
If you use prepared statements, then you can drop the $c->real_escape_string() calls. I would also scratch my head to see what you can safely leave out of the $t->clean() method.
Next I would evaluate the performance of evaluating every single row individually. I'd have to benchmark it to be sure, but I think running a few PHP statements beforehand will be faster than making umpteen unnecessary MySQL SELECT and UPDATE calls. MySQL is much faster when inserting multiple rows at a time. If you expect multiple rows of your input to be changing the same row in the database, then you might want to consider the following:
a. Think about creating a temporary, precompiled array (depending on memory usage involved) that stores the unique rows of data. I would also consider doing the same for the secondary TABLE3. This would eliminate needless "update" queries, and make part b possible.
b. Consider a single query that selects every id from the database that's in the array. This will be the list of items to use an UPDATE query for. Update each of these rows, removing them from the temporary array as you go. Then, you can create a single, multi-row insert statement (prepared, of course), that does all of the inserts at a single time.
Take a look at optimizing your MySQL server parameters to better handle the load.
I don't know if this would speed up a prepared INSERT statement at all, but it might be worth a try. You can wrap the INSERT statement within a transaction as detailed in an answer here: MySQL multiple insert performance
I hope that helps, and if anyone else has some suggestions, just post them in the comments and I'll try to include them.
Here's a look at the original code with just a few suggestions for changes:
<?php
/* You can make sure that the connection type is persistent and
* I personally prefer using the PDO driver.
*/
include(db.php);
/* Definitely think twice about each tool that is included.
* Only include what you need to evaluate the submitted data.
*/
include(tools.php);
$c = new GetDB(); // Connection to DB
/* Take a look at optimizing the code in the Tools class.
* Avoid any and all kinds of loops–this code is going to be used in
* a loop and could easily turn into O(n^2) performance drain.
* Minimize the amount of string manipulation requests.
* Optimize regular expressions.
*/
$t = new Tools(); // Classes to clean, prevent XSS and others
if(isset($_POST['var'])){ // !empty() catches more cases than isset()
$nv = json_decode($_POST['var'])
/* LOOP LOGIC
* Definitely test my hypothesis yourself, but this is similar
* to what I would try first.
*/
//Row in database query
$inTableSQL = "SELECT id FROM TABLE1 WHERE id IN("; //keep adding to it
foreach ($nv as $k) {
/* I would personally use specific methods per data type.
* Here, I might use a type cast, plus valid int range check.
*/
$id = $t->cleanId($k->id); //I would include a type cast: (int)
// Similarly for other values
//etc.
// Then save validated data to the array(s)
$data[$id] = array($values...);
/* Now would also be a good time to add the id to the SELECT
* statement
*/
$inTableSQL .= "$id,";
}
$inTableSQL .= ");";
// Execute query here
// Then step through the query ids returned, perform UPDATEs,
// remove the array element once UPDATE is done (use prepared statements)
foreach (.....
/* Then, insert the remaining rows all at once...
* You'll have to step through the remaining array elements to
* prepare the statement.
*/
foreach(.....
} //end initial POST data if
/* Everything below here becomes irrelevant */
foreach($nv as $k) {
$id = $t->clean($k->id);
// ... goes on for about 10 keys
// this might seems redundant or insufficient
$id = $c->real_escape_string($id);
// ... goes on for the rest of keys...
$q = $c->query("SELECT * FROM table WHERE id = '$id'");
$r = $q->fetch_row();
if ($r[1] > 0) {
// Item exist in DB then just UPDATE
$q1 = $c->query(UPDATE TABLE1);
$q4 = $c->query(UPDATE TABLE2);
if ($x == 1) {
$q2 = $c->query(SELECT);
$rq = $q2->fetch_row();
if ($rq[0] > 0) {
// Item already in table just update
$q3 = $c->query(UPDATE TABLE3);
} else {
// Item not in table then INSERT
$q3 = $c->query(INSERT TABLE3);
}
}
} else {
// Item not in DB then Insert
$q1 = $c->query(INSERT TABLE1);
$q4 = $c->query(INSERT TABLE2);
$q3 = $c->query(INSERT TABLE4);
if($x == 1) {
$q5 = $c->query(INSERT TABLE3);
}
}
}
}
The key is to minimize queries. Often, where you are looping over data doing one or more queries per iteration, you can replace it with a constant number of queries. In your case, you'll want to rewrite it into something like this:
include(db.php);
include(tools.php);
$c = new GetDB(); // Connection to DB
$t = new Tools(); // Classes to clean, prevent XSS and others
if(isset($_POST['var'])){
$nv = json_decode($_POST['var'])
$table1_data = array();
$table2_data = array();
$table3_data = array();
$table4_data = array();
foreach($nv as $k) {
$id = $t->clean($k->id);
// ... goes on for about 10 keys
// this might seems redundant or insufficient
$id = $c->real_escape_string($id);
// ... goes on for the rest of keys...
$table1_data[] = array( ... );
$table2_data[] = array( ... );
$table4_data[] = array( ... );
if ($x == 1) {
$table3_data[] = array( ... );
}
}
$values = array_to_sql($table1_data);
$c->query("INSERT INTO TABLE1 (...) VALUES $values ON DUPLICATE KEY UPDATE ...");
$values = array_to_sql($table2_data);
$c->query("INSERT INTO TABLE2 (...) VALUES $values ON DUPLICATE KEY UPDATE ...");
$values = array_to_sql($table3_data);
$c->query("INSERT INTO TABLE3 (...) VALUES $values ON DUPLICATE KEY UPDATE ...");
$values = array_to_sql($table4_data);
$c->query("INSERT IGNORE INTO TABLE4 (...) VALUES $values");
}
While your original code executed between 3 and 5 queries per row of your input data, the above code only executes 4 queries in total.
I leave the implementation of array_to_sql to the reader, but hopefully this should explain the idea. TABLE4 is an INSERT IGNORE here since you didn't have an UPDATE in the "found" clause of your original loop.
Im trying to think of a way to form a query where I can query for each parameter I need, and get the results back on a per param basis. Ive tried a combination of AND/OR but not getting the expected results.
the basis of the query I wanna do is to the extent of
select fb_wall, fb_checkin, phone
from member_prefs where
memeberid = 21
OR memeberid = 22
OR memeberid = 23
OR memeberid = 24
The catch is, I need to either ignore when one of the memberid's doesnt have data and keep the query going (or catch the id, so if one is found I can add the row for it in another query after the fact but still keep the query going).
Right now my dummy data is 3 out of the 4 memberid's have data and one doesn't if I do AND in the query, the query stops and yields no results. Where as if I do the OR i seem to only be returning one of there sets of data.
What I want to do is after the query build an array to pass around that would look like
array(
[0] array("memberid"=>21, "fb_wall"=>0, "fb_checkin"=>1, "phone"=>0),
[1] array("memberid"=>22, "fb_wall"=>1, "fb_checkin"=>0, "phone"=>1),
[2] array("memberid"=>24, "fb_wall"=>1, "fb_checkin"=>1, "phone"=>1)
)
just seem to be having a little issue forming the initial query, and like I said if I could in this case pass 23 else where so I could run an insert command on that id for the same table that would be awesome but not needed for the over all need here.
How about:
//parameter data
$members = array(
22 => null,
23 => null,
24 => null,
25 => null
);
//make a comma separated list of ids
$ids = implode(',', array_keys($members));
//generate query text
$sql = '
select memberid, fb_wall, fb_checkin, phone
from member_prefs
where memberid in ('.$ids.')
';
$result = mysql_query($sql);
while ($row = mysql_fetch_row($result)) {
$members[$row[0]] = $row;
}
Start with an array with the relevant keys, and replace the elements as you read the rows.
$ares = array(21 => null, 22 => null, 23 => null, 24 => null);