I am using ThingEngineer/PHP-MySQLi-Database-Class and I am trying to perform a multiple insert while using OnDuplicate. The goal is to insert a new product record if the 'sku' does not already exist. If the 'sku' does exist then the 'name' should be updated instead of creating a new entry.
MySQL Schema:
CREATE TABLE `products` (
`product_pk` bigint(9) NOT NULL,
`product_id` int(20) UNSIGNED NOT NULL,
`name` varchar(255) NOT NULL,
`sku` varchar(16) NOT NULL,
`category` int(10) DEFAULT NULL,
`last_update` timestamp NOT NULL ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
ALTER TABLE `products`
ADD PRIMARY KEY (`product_pk`),
ADD UNIQUE KEY `sku` (`sku`);
ALTER TABLE `products`
MODIFY `product_pk` bigint(9) NOT NULL AUTO_INCREMENT;
PHP:
$sDate = date("Y-m-d H:i:s");
$lastid = $db->rawQuery('SELECT MAX( product_id ) AS max FROM products');
(!$lastid || !isset($lastid[0]['max'])) ? $pid = 0 : $pid = $lastid[0]['max']++;
foreach ($data as $item){
if (isset($item['sku']) && !null == $item['sku']){
$prod[$pid]['product_id'] = $pid;
$prod[$pid]['sku'] = $item['sku'];
$prod[$pid]['name'] = substr($item['product-name'],0,255);
$prod[$pid]['last_update'] = $sDate;
$pid++;
}
}
$utfEncodedArray =encodeArray($prod, 'UTF-8');
$db->onDuplicate('name', 'product_pk');
$db->insertMulti('products', $utfEncodedArray);
function encodeArray($array, $type)
{
foreach($array as $key => $value)
{
if (is_array($value)){ $array[$key] = encodeArray($value, $type);}else{ $array[$key] = mb_convert_encoding($value, $type);}
}
return $array;
}
The error I receive is:
Uncaught mysqli_sql_exception: Duplicate entry 'ABC123' for key 'sku'
Here is a sample of the array $utfEncodedArray used on the insertMulti call:
Array(
[1] => Array
(
[product_id] => 1
[sku] => ABC123
[name] => product1
[last_update] => 2018-09-08 18:55:20
)
[2] => Array
(
[product_id] => 2
[sku] => ABC124
[name] => product2
[last_update] => 2018-09-08 18:55:20
)
)
Steps I have tried so far:
Dropped the 'products' table and created it again. Multiple times.
Tried using 'sku' instead of 'product_pk' in the onDuplicate call.
Tried multiple collation types
Tried using unique key on both 'sku' and 'product_id'
When I attempted this method all entries were inserted correctly but when running it again it generated duplicates instead of updating the existing row. Not sure how this happened seeing as both 'sku' and 'product_id' are unique.
The $prod array currently contains the same values. So every time I run this I would expect to see the 'last_updated' column to be updated every time after the initial inserts.
This is my first experience using onDuplicate and despite hours of searching and reading docs I am still lost. I was trying to let the db class handle the multiple insert from the array but I am not against trying raw queries while iterating over my array of products instead.
Of course as soon as I posted this I find the issue...
Found a fork of the database class which resolved issues with insertMulti while using onDuplicate:
should fix insertMulti() if onDuplicate() is set
I'm want to write a function that saves a user data array to a custom table in my wp database. I have this working with this function.
function insert_player($playerArray){
global $wpdb;
$table_name = $wpdb->prefix . "hots_logs_plugin";
$wpdb->insert(
$table_name,
array(
'name' => $playerArray['name'],
'player_id' => $playerArray['pid'],
'hl_mmr' => $playerArray['heroLeague'],
'qm_mmr' => $playerArray['quickMatch'],
'comb_hero_level' => $playerArray['combLevel'],
'total_games_played' => $playerArray['totalGames']
)
);
}
Now I want to extend this function so that if a duplicate player_id is sent the function updates the all the cells in the row apart from the name and player_id. The player_id is a UNIQUE KEY.
I have come up with this function, which I can't seem to get to work, and to be honest is a little over my head..
function input($playerArray){
global $wpdb;
$table_name = $wpdb->prefix . "hots_logs_plugin";
$sql = "INSERT INTO $table_name(player_id, name, hl_mmr, qm_mmr, comb_hero_level, total_games_played)
VALUES(%d,%s,%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE(hl_mmr = %s, qm_mmr = %s, comb_hero_level = %s, total_games_played = %s)";
$sql = $wpdb->prepare(
$playerArray['pid'],
$playerArray['name'],
$playerArray['heroLeague'],
$playerArray['quickMatch'],
$playerArray['combLevel'],
$playerArray['totalGames']
);
$wpdb->query($sql);
}
I just need a nudge in the right direction.
OK, so I was really over doing it in my first attempt. The answer is actually really easy.
I just modified the original method to use $wpdb->replace() rather than $wpdb->insert()
The following method works great.
function insert_player($playerArray){
global $wpdb;
$table_name = $wpdb->prefix . "hots_logs_plugin";
$wpdb->replace(
$table_name,
array(
'player_id' => $playerArray['pid'],
'name' => $playerArray['name'],
'hl_mmr' => $playerArray['heroLeague'],
'qm_mmr' => $playerArray['quickMatch'],
'comb_hero_level' => $playerArray['combLevel'],
'total_games_played' => $playerArray['totalGames']
),
array (
'%d',
'%s',
'%s',
'%s',
'%s',
'%s'
)
);
}
I'm creating a wordpress plugin for woocommerce and the table i've created is not being inserted into the database? I get the error The plugin generated 5090 characters of unexpected output during activation. If you notice “headers already sent” messages, problems with syndication feeds or other issues, try deactivating or removing this plugin.
Here is the code:
register_activation_hook( __FILE__, 'pato_install' );
register_activation_hook( __FILE__, 'pato_install_data' );
function pato_install() {
global $wpdb;
$table_name = $wpdb->prefix . 'pato_shipping';
$sql = "CREATE TABLE $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
pato_shipping_data text NOT NULL,
);";
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
dbDelta( $sql );
}
function pato_install_data() {
global $wpdb;
$table_name = $wpdb->prefix . 'pato_shipping';
//shipping options flat rate charges
$option_array = array(
'plants_fast' => '45.5',
'plants_standard' => '0');
//shipping condition values
$condition_array = array(
'plant_quantity_m1_1' => '3',
'plant_quantity_m1_2' => '6',
'plant_quantity_m2_1' => '6',
'plant_quantity_m3_1' => '3');
//shipping item charges
$item_charge_array = array(
'plants_ml_1_charge' => '49.5',
'plants_m2_1_charge' => '69.5',
'plants_m3_1_charge' => '89.5');
$data_array = array_merge($option_array,$condition_array,$item_charge_array);
$data_array = serialize($data_array);
$wpdb->insert(
$table_name,
array('pato_shipping_data' => $data_array)
);
}
Where did i go wrong?
You have some errors in your create table query. Your auto increment column needs to be a key and you have an extra trailing comma after your second column. Try:
CREATE TABLE $table (
id mediumint(9) NOT NULL PRIMARY KEY AUTO_INCREMENT,
pato_shipping_data text NOT NULL
)
I usually try to run my queries directly before incorporating them into a plugin. It makes debugging a lot easier.
How to retrieve last inserted user id? I have to use that user id for inserting that user id into next query that also should be done in this transaction only.
my query:
$db->beginTransaction ();
$sql = $db->query ( "INSERT INTO user( user_id, title)
VALUES ( :p_user_id, :p_title )",
array ( 'p_user_id' => '', 'p_title' => $title ) );
You can use this method to retrive the last insert id :
$db->lastInsertId()
With you code :
$db->beginTransaction ();
$sql = $db->query ( "INSERT INTO user( user_id, title) VALUES ( :p_user_id, :p_title )",
array ( 'p_user_id' => '', 'p_title' => $title ) );
$db->lastInsertId() ;
Use insert() method for inserting data. Also it returns ID.
$db = new Zend_Db_Table('user');
$lastInsertId = $db->insert(array('user_id' => '', 'title' => $title));
Try this this should work:
$groupID = Engine_Db_Table::getDefaultAdapter()->lastInsertId();
$res (array)-> (count 50 (!) )
Example:
(
[1] => Array
(
[artistname] => Lady GaGa
[songname] => Love Games
[duration] => 3:31
[url] => 7e91a5ca16ae
[server] => 3
)
[2] => Array
(
[artistname] => DJ Layla
[songname] => Single Lady
[duration] => 3:20
[url] => f0906a3087eb
[server] => 3
)
[3] => Array
(
[artistname] => Lady Gaga
[songname] => Bad Romance (Bimbo Jones Clean Radio Remix)
[duration] => 3:59
[url] => 36e77d5a80357
[server] => 3
)
}
PHP code:
$massquery = '';
foreach($res as $value)
{
if(!get_magic_quotes_gpc())
{
$value['artistname'] = mysql_escape_string($value['artistname']);
$value['songname'] = mysql_escape_string($value['songname']);
$value['duration'] = mysql_escape_string($value['duration']);
$value['url'] = mysql_escape_string($value['url']);
$value['server'] = mysql_escape_string($value['server']);
}
$value['artistname'] = trim($value['artistname']);
$value['songname'] = trim($value['songname']);
$value['duration'] = trim($value['duration']);
$value['url'] = trim($value['url']);
$value['server'] = trim($value['server']);
$sh = mysql_query("SELECT `artistname`,`songname`,`server` FROM `music` WHERE `artistname`='".$value['artistname']."' AMD `songname`='".$value['songname']."' AND `server`='".$value['server']."' LIMIT 1");
if(!mysql_num_rows($sh))
{
$massquery .= '("'.$value['artistname'].'", "'.$value['songname'].'", "'.$value['duration'].'", "'.$value['url'].'", "'.$value['server'].'"),';
}
}
if(!empty($massquery))
{
$massquery = substr($massquery, 0, -1);
$query = mysql_query('INSERT INTO `music` (`artistname`, `songname`, `duration`, `url`, `server`) VALUES '.$massquery);
}
mysql_close($mysql);
It turns out 50 requests "SELECT" to the database, which is very bad = (
How can I optimize this code?
From answers:
CREATE TABLE `music` (
`id` int(50) NOT NULL auto_increment,
`artistname` varchar(50) NOT NULL,
`songname` varchar(50) NOT NULL,
`duration` varchar(6) NOT NULL,
`url` varchar(255) NOT NULL,
`server` int(5) NOT NULL,
PRIMARY KEY (`id`),
KEY `artistname` (`artistname`,`songname`,`server`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `music` VALUES ('test', 'btest', 1);
...
SELECT `artistname` , `songname` , `server`
FROM `music`
WHERE FALSE
OR (
`artistname` = 'test'
AND `songname` = 'btest'
AND `server` = '1'
)
OR (
`artistname` = 'sas'
AND `songname` = 'asf'
AND `server` = '1'
)
LIMIT 0 , 30
How do I INSERT those songs that are not yet in the database?
Sorry for bad english
You want to insert new records only if no other record with the tuple (artistname,songname,server) (already) exists.
If you create a unique index for these three fields MySQL won't insert a doublet. Then you can either use something like
INSERT IGNORE INTO
tablename
(a,b,c,x,y,z)
VALUES
(1,2,3,4,5,6),
(7,8,9,10,11,12),
...
(95,96,97,98,99,100)
or a prepared statement, e.g.
$pdo = new PDO("mysql:host=localhost;dbname=test", 'localonly', 'localonly');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
/* test table */
$pdo->exec('
CREATE TEMPORARY TABLE foo (
id int auto_increment,
artistname varchar(64) not null,
songname varchar(64) not null,
duration varchar(16) not null,
url varchar(64) not null,
server int not null,
primary key(id),
unique key (artistname,songname,server)
)
');
$data = array(
array(':artistname' => 'Lady GaGa', ':songname' => 'Love Games', ':duration' => '3:31', ':url' => '7e91a5ca16ae', ':server' => 3),
array(':artistname' => 'DJ Layla', ':songname' => 'Single Lady', ':duration' => '3:20', ':url' => 'f0906a3087eb', ':server' => 3),
array(':artistname' => 'Lady Gaga', ':songname' => 'Bad Romance (Bimbo Jones Clean Radio Remix)', ':duration' => '3:59', ':url' => '36e77d5a80357', ':server' => 3)
);
/* the "actual" test script */
$stmt = $pdo->prepare('
INSERT IGNORE INTO
foo
(duration, artistname, songname, server, url)
VALUES
(:duration, :artistname, :songname, :server, :url)
');
// first run, all three records should be inserted
foreach( $data as $params ) {
$stmt->execute($params);
}
// second run
// same artist/songname, different server
$newData = $data[0]; $newData[':server'] = 4;
$data[] = $newData;
// and a completly new record
$data[] = array(':artistname' => 'xyz', ':songname' => 'The ABC song', ':duration' => '2:31', ':url' => 'whatever', ':server' => 2);
// again insert all records (including the three that have already been inserted)
foreach( $data as $params ) {
$stmt->execute($params);
}
/* fetch all records */
foreach( $pdo->query('SELECT * FROM foo', PDO::FETCH_NUM) as $row ) {
echo join(', ', $row), "\n";
}
prints
1, Lady GaGa, Love Games, 3:31, 7e91a5ca16ae, 3
2, DJ Layla, Single Lady, 3:20, f0906a3087eb, 3
3, Lady Gaga, Bad Romance (Bimbo Jones Clean Radio Remix), 3:59, 36e77d5a80357, 3
4, Lady GaGa, Love Games, 3:31, 7e91a5ca16ae, 4
5, xyz, The ABC song, 2:31, whatever, 2
The first three records have not been duplicated.
Create a single select for all the relevant cases like this, and verify the results by means of PHP:
$sh = "SELECT `artistname`,`songname`,`server` FROM `music` WHERE ";
$pq = ""
foreach($res as $value)
{
if(!get_magic_quotes_gpc())
{
$value['artistname'] = mysql_escape_string($value['artistname']);
$value['songname'] = mysql_escape_string($value['songname']);
$value['duration'] = mysql_escape_string($value['duration']);
$value['url'] = mysql_escape_string($value['url']);
$value['server'] = mysql_escape_string($value['server']);
}
$value['artistname'] = trim($value['artistname']);
$value['songname'] = trim($value['songname']);
$value['duration'] = trim($value['duration']);
$value['url'] = trim($value['url']);
$value['server'] = trim($value['server']);
$sh .= $pq . `(artistname`='".$value['artistname']."' AMD `songname`='".$value['songname']."' AND `server`='".$value['server']."')");
$pq = " OR ";
}
$res = mysql_query($sh);
The select query you wrote can't be inside the foreach, or of course it'll query each time. Turn your $res into a long WHERE clause:
$sql = "SELECT `artistname`,`songname`,`server` FROM `music` WHERE FALSE ";
foreach($res as $value)
{
// ...
$sql .= "OR (`artistname`='".$value['artistname']."' AND `songname`='".$value['songname']."' AND `server`='".$value['server'].")";
}
and then run that query against the database and build your INSERT query.