MySQL query optimization - php

$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.

Related

Preventing insert duplicate values in mysql table

I have simply made an online exam program in php and mysql. I have taken a mock test for this and found that the answer is submitting successfully except 5/6 students answer inserting double, that is duplicate entries found. That means out of 30 / 40 questions students answers are saving 34 / 45 etc. question answers. Please help me I am not a master in mysql. Here is my code given.
$exam->data = array(
':usr_id' => $stud_id,
':exam_id' => $exam_id,
':qust_id' => $question_id
);
$exam->query = "SELECT * FROM `user_exams_answer` WHERE `user_id` = :usr_id AND `exam_id` = :exam_id AND `question_id` = :qust_id";
$total_rowqq = $exam->total_row();
if($total_rowqq == 0){
$exam->data = array(
':user_answer_option' => $answer_option,
':marks' => $marks,
':usr_id' => $stud_id,
':exam_id' => $exam_id,
':qust_id' => $question_id
);
$exam->query = "INSERT INTO `user_exams_answer`(`user_id`, `exam_id`, `question_id`, `user_answer_option`, `marks`) VALUES(:usr_id, :exam_id, :qust_id, :user_answer_option, :marks)";
$exam->execute_query();
$output = "Option has been saved.";
}else{
$exam->data = array(
':user_answer_option' => $answer_option,
':marks' => $marks,
':usr_id' => $stud_id,
':exam_id' => $exam_id,
':qust_id' => $question_id
);
$exam->query = "UPDATE `user_exams_answer` SET `user_answer_option` = :user_answer_option, `marks` = :marks WHERE `user_id` = :usr_id AND `exam_id` = :exam_id AND `question_id` = :qust_id";
$exam->execute_query();
$output = "Option has been modified.";
}
echo $output;
Here sometime it is inserting double/ triple values in mysql table. Is it the problem of multiple sessions ?
Please help how to overcome from this duplicate values

PDO ODBC return null for a column value but odbc_conect doesn't

For some weird reason, my PDO keeps returning NULL when it should return a string/integer. I've tried to use the odbc_conect and it works fine.
Context:
CentOS 7
PHP 7.1.13
unixODBC 2.3.1 (x86_64 . 11.el7)
ODCB Driver: SAP HANA ODBC v2.2.36.1512099132 64 bits
When it's happening? Until now, I detected few:
Function returns: SELECT id,GEO.ST_Asgeojson() FROM DEV.GEOMETRIES
Using CAST: SELECT id, CAST(GEO.ST_AsText() AS VARCHAR) FROM DEV.GEOMETRIES or SELECT CAST(1 AS INTEGER) AS "int" FROM DUMMY
Any blob column : SELECT id,GEO FROM DEV.GEOMETRIES
Date Types: SELECT insertdate FROM DEV.GEOMETRIES
I also tried to use PDOStatement::bindColumn to force data types but was unsuccessful
Where the code :
<?php
try{
ini_set('memory_limit','256M'); //odbc_fetch_array is hungry
// $sql = 'SELECT NOW() AS "now" FROM DUMMY';
$sql = "SELECT id,GEO.ST_Asgeojson(),GEO.ST_AsText(),insertdate FROM DEV.geometries limit 1";
//$sql = "SELECT * FROM DEV.geometries limit 1";
// $sql = "SELECT CAST(GEO.ST_AsText() AS VARCHAR) from geometries";
// $sql = "SELECT CAST(10 AS VARCHAR) from DUMMY";
// $sql = "SELECT CAST(10 AS INTEGER) from DUMMY";
echo "SQL: ",$sql,PHP_EOL;
$DSN = "DRIVER=HANADB;UID=DEV;PWD=DEV#pwd;SERVERNODE=192.168.1.163:39013;DATABASENAME=SYSTEMDB";
$options = [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_CASE =>\PDO::CASE_LOWER, //Compatibility with mysql case
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
\PDO::ATTR_EMULATE_PREPARES, true //With or without
];
echo "############# Using PDO:ODBC:",PHP_EOL;
$DB = new \PDO("odbc:".$DSN,'DEV','DEV#pwd',$options);
echo "Driver: ",$DB->getAttribute(\PDO::ATTR_CLIENT_VERSION),PHP_EOL;
echo "Using Prepared Statement:",PHP_EOL;
$stmt = $DB->prepare($sql);
// bindColumn Also doesn't work
//$stmt->bindColumn('id', $id, \PDO::PARAM_INT);
//$stmt->bindColumn('geojson', $geometry, \PDO::PARAM_STR);
$stmt->execute();
/*while ($row = $stmt->fetch(\PDO::FETCH_BOUND)) {
var_dump($row);
var_dump($id, $geometry);
}*/
//var_dump($ret,$stmt);
var_export($stmt->fetchAll());
$stmt->closeCursor();
echo PHP_EOL,"Using Query:",PHP_EOL;
$qR = $DB->query($sql);
foreach($qR as $row){
var_export($row);
}
echo PHP_EOL,PHP_EOL,"############# Using odbc_*( ) Functions",PHP_EOL;
$conn = odbc_connect($DSN,'DEV','DEV#pwd');
$rs = odbc_exec($conn,$sql);
while($row = odbc_fetch_array($rs)){
var_export($row);
}
}catch(\Exception $e){
echo $e->getMessage(),PHP_EOL,$e->getTraceAsString();
}
Output:
############# Using PDO:ODBC:
Using Prepared Statement:
array (
0 =>
array (
'id' => '363491',
'geojson' => NULL,
'geotext' => NULL,
'__dateinsert' => NULL,
'info' => NULL,
),
)
Using Query:
array (
'id' => '363491',
'geojson' => NULL,
'geotext' => NULL,
'insertdate' => NULL,
'info' => NULL,
)
############# Using odbc_*( ) Functions
array (
'id' => '363491',
'geojson' => '{"type": "Point", "coordinates": [16.352878, 48.225628]}',
'geotext' => 'POINT (16.352878 48.225628)',
'insertdate ' => '2018-02-06 15:08:19.414000000',
'info' => NULL,
)
I'm not sure if it's a Drive problem because it works with odbc_* functions, maybe it's a PDO BUG.

How to write subquery IN conditional zend 2

I have MySQL Query i want to write this query zend 2.
select billsec, call_status, Count(*)
from (
select
billsec,
if(ANSWERED_NUM is Null, 'Missed', 'Answered') call_status
from cust_info
where billsec in (
select id
from `users`
where `account_id` = 452 and `added_by` = 20694 and `status` = 'active'
)
)a
group by a.billsec,a.call_status
in zend 2 am trying to write like this but
$adapter = $this->getAdapter();
$resultset = $this->select( function( Select $select ) use ( $request, $adapter ) {
$sub1 = new Select( 'users' );
$sub1->columns( array( 'id' ) );
$sub1->where( array( 'account_id' => '452','added_by' => '20694','status' => 'active' ) );
$sub2 = new Select( 'cust_info' );
$sub2->columns(array("id","billsec", "if(ANSWERED_NUM is Null, 'Missed','Answered')"=>"call_status"));
$sub2->where('billsec IN(?)', new \Zend\Db\Sql\Expression( '?', array( $sub1 ) ));
var_dump( $sub2->getSqlString( $adapter->getPlatform() ) );die();
});
When am print this query output like this:
"SELECT `cust_info`.`id` AS `id`, `cust_info`.`billsec` AS `billsec`, `cust_info`.`call_status` AS `if(ANSWERED_NUM is Null, 'Missed','Answered')` FROM `cust_info` WHERE billsec IN('')"
Here am not able to write IN condition Query, thank in advance.

Weird error when using MySQL when using PDO

I encountered a very strange bug today. I am getting sideblinded by this so bad as it's breaking my entire application.
So, I have this little framework that I've built where I have a standard modell, so snippeting this will be a little long and descriptive.
<?php include('inc/inc.php'); ?>
<?php
if(!empty($_POST['answer']) && !empty($_POST['levelstart'])){
if($stmt = $site->answerQuestion($_POST['levelstart'], $_POST['answer'])){
if($stmt[0]){
echo json_encode(array('success' => true, 'correct' => $stmt[1], 'correctanswer' => $stmt[2], 'round_end' => $stmt[3]));
}else{
echo json_encode(array('success' => false, 'error' => 'error occurred'.$stmt[1]));
}
}else{
echo json_encode(array('sucess' => false, 'error' => 'Unknown error'));
}
}else{
echo json_encode(array('success' => false, 'error' => 'Provide all necessary parameters.'));
}
?>
this piece of code outputs the following.
INSERT INTO quiz_level_starts (`user_id`, `question_id`, `time`, `round_id`, `type`, `success`, `ref_id`) VALUES ('4', '10', '1471887809', '', '1', '1', '905'){"success":false,"error":"error occurred23000"}
The generated query above is only a dummy one that i simple put together so I don't need the parameterization for simply testing. The "error" key in the json array contains error data, and the errorcode is dumped there.
23000 is the mysql error code for there being a duplicate unique column, but there is no unique column that I'm using in the query(see table struct below.)
Since the function answerQuestion is a very long one, I'll only paste the related lines next. In $site->answerQuestion it calls a function called "insertLevelStarts" which is supposed to insert an entry to the db.
This is how i call it:
if($stmtss = $this->db->insertLevelStarts($_SESSION['user']['id'], $stmts['return'][0]['id'], time(), $roundid, 1, 1, $levelstart)){
And this is how it's declared, also the rest of the related and unknown code:
public function insertLevelStarts($user_id, $question_id, $time, $round_id, $type = 0, $success = 0, $refid = 0){
/*
Type=0 start 1 stop
success=0 for start 1 if successfull on stop
*/
$query = 'INSERT INTO quiz_level_starts (`user_id`, `question_id`, `time`, `round_id`, `type`, `success`, `ref_id`) VALUES (:user_id, :question_id, :time, :round_id, :type, :success, :refid)';
echo $this->genFakeQuery($query, array(
':user_id' => $user_id,
':question_id' => $question_id,
':time' => $time,
':type' => $type,
':success' => $success,
':refid' => $refid,
':round_id' => $round_id
));
return $this->execInsert($query, array(
':user_id' => $user_id,
':question_id' => $question_id,
':time' => $time,
':type' => $type,
':success' => $success,
':refid' => $refid,
':round_id' => $round_id
)
);
}
public function genFakeQuery($query, $array){
foreach($array as $key => $val){
$query = str_replace($key, "'$val'", $query);
}
return $query;
}
public function execUpdate($query, $preparray, $updatearr){
try {
$stmt = $this->db->prepare($query);
$stmt->execute(array_merge($preparray, $updatearr));
$rows = $stmt->rowCount();
if($rows > 0){
return array('type' => 'rowsaffected', 'return' => $rows);
}else{
return array('type' => 'noreturn', 'return' => 'none');
}
} catch(PDOException $ex) {
return array('type' => 'error', 'return' => $ex);
}
}
public function updateClause($query, $update, $updatearr){
if(count($update) > 0){
$count = 0;
foreach($update as $k => $v){
if($count > 0){
$query .= ',';
}
$query .= " `$k` = :$k";
$updatearr[":$k"] = $v;
$count++;
}
}
return array('query' => $query, 'updatearr' => $updatearr);
}
The aforementioned query
INSERT INTO quiz_level_starts (`user_id`, `question_id`, `time`, `round_id`, `type`, `success`, `ref_id`) VALUES ('4', '10', '1471887809', '', '1', '1', '905')
inserts into a table looking like this:
CREATE TABLE IF NOT EXISTS `quiz_level_starts` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`question_id` int(11) NOT NULL,
`time` int(11) NOT NULL,
`type` int(11) NOT NULL,
`success` int(11) NOT NULL,
`ref_id` int(11) NOT NULL,
`round_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `quiz_level_starts`
ADD PRIMARY KEY (`id`);
ALTER TABLE `quiz_level_starts`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
Will greatly appriciate any help recieved.
I assume that the error occurs because round_id is an integer field that can not be NULL and has no default value and you pass it an empty value.
Try if this query works:
INSERT INTO quiz_level_starts (`user_id`, `question_id`, `time`, `round_id`, `type`, `success`, `ref_id`) VALUES ('4', '10', '1471887809', '0', '1', '1', '905')

MySQL find multiple records by two columns

Let's say I have an array as follows (ID => Type):
$contentIndexes = [
32 => 'news',
40 => 'blog',
68 => 'blog',
109 => 'document',
124 => 'news'
]
And the following database table:
CREATE TABLE `ContentIndex` (
`ID` INT(11) NOT NULL AUTO_INCREMENT,
`ItemID` INT(11) NOT NULL,
`ItemType` VARCHAR(50) NOT NULL COLLATE 'utf8_unicode_ci',
//...
);
How would I retrieve each ContentIndex based on the combination of the 'ItemID' and 'ItemType' columns (preferably with just one query).
Using WHERE IN is not an option since it wouldn't take the combination in consideration:
ContentIndexQuery::create()
->filterByItemID(array_keys($contentIndexes))
->filterByItemType($contentIndexes)
->find();
Any ideas?
I don't know the Propel syntax, but the basic SQL syntax would be with OR.
WHERE ((ItemID = 32 AND ItemType = 'news')
OR
(ItemID = 40 AND ItemType = 'blog')
OR
(ItemID = 68 AND ItemType = 'blog')
OR
(ItemID = 109 AND ItemType = 'document')
OR
(ItemID = 124 AND ItemType = 'news')
)
For any Propel users encountering this problem in the future, this is what I came up with to create a query as Barmar stated:
$items = ContentIndexQuery::create();
$i = 0;
foreach ($contentIndexes as $id = > $type) {
if ($i > 0) $items->_or();
$items->condition('cond1'.$i, ContentIndexTableMap::COL_ITEM_ID . ' = ?', $id)
->condition('cond2'.$i, ContentIndexTableMap::COL_ITEM_TYPE . ' = ?', $type)
->where(['cond1'.$i, 'cond2'.$i], 'AND');
$i += 1;
}
$items = $items->find()->getData();

Categories