PDO - SQLSTATE[HY093]: Invalid parameter number with UNION query - php

I spent my whole evening researching and trying to figure out what's wrong with my search query. I do some wildcard search using union queries and pagination.
$current_page = 0;
$search1 = $search;
$search2 = $search."%";
$search3 = "%".$search."%";
$pdo = DB::connection()->getPdo();
$stmt = $pdo->prepare('
SELECT id, desc FROM table WHERE desc LIKE :search1 LIMIT :skip, 15
UNION
SELECT id, desc FROM table WHERE desc LIKE :search2 LIMIT :skip, 15
UNION
SELECT id, desc FROM table WHERE desc LIKE :search3 LIMIT :skip, 15
');
$stmt->bindParam(':search1', $search1);
$stmt->bindParam(':search2', $search2);
$stmt->bindParam(':search3', $search3);
$stmt->bindParam(':skip', $current_page, PDO::PARAM_INT);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
The first query (without the unions) works fine, or if I remove the :skip parameter, it works fine as well.
Any ideas what's wrong?

Using named parameters in PDO, you can't use the same name more than once, unless you do:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
But if you do that, the values for :skip will be interpolated into the query as quoted strings, which are not valid in the context of LIMIT. In other words the following:
LIMIT '0', 15
results in syntax error, because LIMIT wants only true integers as its arguments.
For more examples and explanation, see my answer to Parametrized PDO query and LIMIT clause - not working
So your choices are to add a separate parameter for each occurrence of :skip:
$stmt = $pdo->prepare('
SELECT id, desc FROM table WHERE desc LIKE :search1 LIMIT :skip1, 15
UNION
SELECT id, desc FROM table WHERE desc LIKE :search2 LIMIT :skip2, 15
UNION
SELECT id, desc FROM table WHERE desc LIKE :search3 LIMIT :skip3, 15
');
$stmt->bindParam(':search1', $search1);
$stmt->bindParam(':search2', $search2);
$stmt->bindParam(':search3', $search3);
$stmt->bindParam(':skip1', $current_page, PDO::PARAM_INT);
$stmt->bindParam(':skip2', $current_page, PDO::PARAM_INT);
$stmt->bindParam(':skip3', $current_page, PDO::PARAM_INT);
Or else interpolate the value into the query yourself, without quoting the integer.
But I agree with the comment from #Class, you don't have to do UNION at all. If nothing else, '%search%' works for both of the other two patterns: 'search%' and '%search'. You don't have to search all three.

Just as an update, if someone ended up here trying to implement a similar search that returns results by relevancy, the original query above will duplicate some of the results, and the limit for each select will skip some of the results.
After some testing the following query seems to do the job as expected:
$current_page = ($_GET['page'] - 1) * 15;
$search1 = $search;
$search2 = $search."%";
$search3 = "%".$search."%";
$pdo = DB::connection()->getPdo();
$stmt = $pdo->prepare('
SELECT id, desc FROM table WHERE desc LIKE :search1
UNION
SELECT id, desc FROM table WHERE desc LIKE :search2 AND desc NOT LIKE :search1a
UNION
SELECT id, desc FROM table WHERE desc LIKE :search3 AND desc NOT LIKE :search2a
LIMIT :skip, 15
');
$stmt->bindParam(':search1', $search1);
$stmt->bindParam(':search1a', $search1);
$stmt->bindParam(':search2', $search2);
$stmt->bindParam(':search2a', $search2);
$stmt->bindParam(':search3', $search3);
$stmt->bindParam(':skip', $current_page, PDO::PARAM_INT);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);

Put each SELECT in parentheses.
from documentation
To apply ORDER BY or LIMIT to an individual SELECT, place the clause inside the parentheses that enclose the SELECT:
$stmt = $pdo->prepare('
(SELECT id, desc FROM table WHERE desc LIKE :search1 LIMIT :skip, 15)
UNION
(SELECT id, desc FROM table WHERE desc LIKE :search2 LIMIT :skip, 15)
UNION
(SELECT id, desc FROM table WHERE desc LIKE :search3 LIMIT :skip, 15)
');

Related

Select attribute of table based on min value from another table

My table structure is
I want to get the recent video uploaded of the user
What I want to do is:
"select video_id of a record who is having minimum of timestamp where userid='something'"
What I currently have is:
$recent_video = "$db->query("
SELECT video_id
FROM video_primary
ORDER BY timestamp DESC LIMIT 1
WHERE userid = '$userid'"
) or die($db->error);"`
while($row=mysqli_fetch_assoc($recent_video))
{
$video_id=$row['video_id'];
}
echo $video_id;
My table data is
How do I get the most recent view uploaded by the user?
Write WHERE clause after FROM part
Try this:
SELECT vp.video_id
FROM video_primary vp
WHERE userid='$userid'
ORDER BY vp.timestamp DESC
LIMIT 1;
try this
$recent_video=$db->query("select video_id from video_primary where userid='$userid'
ORDER BY timestamp DESC LIMIT 1 ") or die($db->error);
instead of
$recent_video="$db->query("select video_id from video_primary ORDER BY timestamp DESC
LIMIT 1 where userid='$userid'") or die($db->error);"
you are using where clause after order by and limit 1 which is a syntax error. see tutorial
Tutorial
There is a specific sequence of every clause in Query statement. Below is the sequence:
Select
Where
Order by
Limit
So, as per the above sequence, you should correct your query like below. Also, you should place double quote(") correctly.
$recent_video = $db->query("
SELECT video_id
FROM video_primary
WHERE userid = '$userid'
ORDER BY timestamp DESC LIMIT 1"
) or die($db->error);

SQL - concatenate results from 2 tables

First I need to get exact match like
SELECT * FROM movies WHERE title='STRING' ORDER BY x DESC
and then append to these results query with LIKE match
SELECT * FROM movies WHERE title LIKE '%STRING&' AND title<>'STRING' ORDER BY x DESC
and limit these results with maximum of 10 results.
UNION wont`t do the jobs as it sorts all results together and returns wrong order (I need exact match first, then with LIKE)
SELECT * FROM movies WHERE title='STRING' UNION
SELECT * FROM movies WHERE title LIKE '%STRING%' ORDER BY x DESC LIMIT 10
The best solution I got is to use multi_query()
$query = "SELECT * FROM movies WHERE title='STRING' ORDER BY x DESC; ";
$query .= "SELECT * FROM movies WHERE title LIKE '%STRING%' AND title<>'red' ORDER BY x DESC";
$Dbi->multi_query($query);
do {
$sql = $Dbi->store_result();
while($x = $sql->fetch_array()) {
...
}
} while($Dbi->next_result());
but in this case it is not possible to use any mysql inside the inner loop and there also must be better looking solution!
You can do this with one query, by using the order by clause:
SELECT *
FROM movies
WHERE title like '%STRING%'
ORDER BY title = 'STRING' desc,
title like '%STRING%' desc
LIMIT 10;
The first clause in the ORDER BY puts the exact matches first. The second then orders by the partial matches. The WHERE clause ensures that there is a match of some kind.
You don't need the UNION, it's accessing the same table twice:
SELECT *
FROM movies
WHERE title LIKE '%STRING&'
ORDER BY CASE WHEN title='STRING' THEN 1 ELSE 2 END
LIMIT 10
(SELECT * FROM movies WHERE title='STRING')
UNION
(SELECT * FROM movies WHERE title LIKE '%STRING%' ORDER BY x DESC LIMIT 10)

How to order by this query?

I have table of likes/dislikes on game: id, game_id, type(like/dislike), time
Table example: Image Link
This code gives me the last week games the had likes, order by their likes count:
$limit = 10;
$time = _time() - 60*60*24*7;
$games_id = array();
$games_id_query = $this->db->execQuery("SELECT `game_id`, count(*) as `likes_count` FROM `likes` WHERE `type` = 'like' AND `time` > '{$time}' group by `game_id` order by `likes_count` DESC LIMIT {$limit}");
$games_id_num = $games_id_query->num_rows;
if($games_id_num > 0) {
while($row = $games_id_query->fetch_object()) {
unset($row->likes_count);
$games_id[] = (array) $row;
}
}
I need addition to this code. I want one more order by, and it will be the dislikes count of this game_id, ASC.
How should I do this? with SQL only... thanks very much!
You probably want something like this:
SELECT `game_id`,
SUM(IF(`type` = 'like', 1, 0)) AS likes_count,
SUM(IF(`type` = 'dislike', 1, 0)) AS dislikes_count
FROM `likes`
WHERE `time` > '{$time}'
GROUP BY `game_id`
ORDER BY `likes_count` DESC, `dislikes_count` ASC
LIMIT {$limit}
That being said, please look into using prepared statements and parameterized queries instead of directly embedding your PHP variables in your SQL.
See this question for more information: How can I prevent SQL injection in PHP?
Change from DESC to ASC, is that what you want?
SELECT game_id, count(*) as likes_count
FROM likes
WHERE type = 'like'
AND time > '{$time}'
group by game_id
order by likes_count ASC LIMIT {$limit}

select data from multiple table using php union

anyone can help me how to select from multiple MySQL table and sort by the gameplayed limit 15 using php?
<?php
//$query = "SELECT id, gamename, gameplayed FROM action, adventure, augur, beauty, chess, joke, mmorpg, multiplayer, platform, puzzle, racing, shooting, sport, stratergy WHERE id = :id";
$query = '
SELECT id, gamename, gameplayed FROM((
SELECT id, gamename, gameplayed
FROM action
ORDER BY gameplayed
DESC LIMIT 15
) UNION (
SELECT id, gamename, gameplayed
FROM adventure
ORDER BY gameplayed
DESC LIMIT 15
))as t ORDER BY gameplayed';
$query_params = array(':id' => '1');
//$query = "SELECT id, gamename FROM action, adventure, augur, beauty, chess, joke, mmorpg, multiplayer, platform, puzzle, racing, shooting, sport, stratergy ORDER BY gameplayed DESC LIMIT 15";
try
{
// These two statements run the query against your database table.
$stmt = $db->prepare($query);
$stmt->execute($query_params);
}
catch(PDOException $ex)
{
// Note: On a production website, you should not output $ex->getMessage().
// It may provide an attacker with helpful information about your code.
die("Failed to run query: " . $ex->getMessage());
}
$rows = $stmt->fetchAll();
foreach($rows as $row):
echo $rows['t'];
endforeach;
unset($row);
?>
i search google found the solution is using union , but i'm keep getting error "undefine index t"
Try changing your SQL query to this
SELECT id, gamename, gameplayed
FROM action
LIMIT 15
UNION
SELECT id, gamename, gameplayed
FROM adventure
ORDER BY gameplayed DESC
LIMIT 15
ORDER BY gameplayed
It don't see a field named "t" in your select query? Try to var_dump() $rows, then you may see whats going wrong.
What you are doing is trying to select a table alias. You cannot select them, only select fields.
Your result should now look at the beginning of the subarrays of your array $rows like the elements of the first table and the second, mixed, following the order in the gameplayed column.
You have tried to make all the results from the UNION select as an alias 't' ? It won't work, you have to use them one by one i.e.:
...UNION (
SELECT t.id as 'tid', t.gamename as 'tgamename', t.gameplayed as 'tgameplayed'
FROM adventure AS t
...
$row['tid']; $row['tgamename']; $row['tgameplayed']

Yii: Select 20 last entries order by id ASC

I would like to get the 20 last entries of my table but order by ascending id.
In Sql it's not very complicated:
SELECT *
FROM (SELECT * FROM comments
WHERE postID='$id'
ORDER BY id DESC
LIMIT 20) t
ORDER BY id ASC;
But I would like to to it with my yii model like:
Comment::model()->findAll($criteria)
But i really don't know what I should put in my CDbCriteria!
$models = Comment::model()->findAll(array(
"condition" => "WHERE postID = '".$id."'",
"order" => "id DESC",
"limit" => 20,
));
Will get the last 20. And now you want to order that record set by id ASC correct? Is there not another field you can order by for a similar result (maybe a date or created field?) eg:
"order" => "id DESC, created ASC"
Scrap that secondary ordering, but why not just use array reverse?
$models = array_reverse($models);
There is a way without using array_reverse, if you think of using this sql:
SELECT * FROM `comments` `t`
WHERE id
in (SELECT id
FROM (SELECT id FROM comments Where postID = xyz ORDER BY id DESC LIMIT 20)
as q)
ORDER BY id ASC
which in criteria will become:
$criteria=new CDbCriteria();
$criteria->condition='id in (SELECT id FROM (SELECT id FROM comments Where postID='.$id.' ORDER BY id DESC LIMIT 20) as q)';
$criteria->order='id ASC';
Update:
With your original query, you could have also used findBySql :
$sql='SELECT * FROM (SELECT * FROM comments WHERE postID= :postid ORDER BY id DESC LIMIT 20) q ORDER BY id ASC';
$params=array('postid'=>$id);
$comments=Comment::model()->findAllBySql($sql,$params);
The performance of this query was better than my previous query.
UPD:
Please note, that in general, some other solutions are better than mine.
Using offset can decrease performance of your queries.
See: http://www.slideshare.net/Eweaver/efficient-pagination-using-mysql and Why does MYSQL higher LIMIT offset slow the query down?
So, when the number of Comments will increase, you can get performance degradation.
What about using offset feature?
$model = Comment::model();
$condition = 'postID =' . $id;
$limit = 20;
$totalItems = $model->count($condition);
$criteria = new CDbCriteria(array(
'condition' => $condition,
'order' => 'id ASC',
'limit' => $limit,
'offset' => $totalItems - $limit // if offset less, thah 0 - it starts from the beginning
));
$result = $model->findAll($criteria);

Categories