FIND_IN_SET universal symbol? - php

I would like to create query like this:
$get_products = "SELECT P.*, C.`category_name`, GROUP_CONCAT(`category_name` SEPARATOR ', ') AS `cat`
FROM `products` P
NATURAL JOIN
`categories` C
NATURAL JOIN
`product_to_categories`";
WHERE
FIND_IN_SET(`category_name`, :selected_categories)
AND `product_price` BETWEEN :price_min AND :price_max
GROUP BY `product_name`
ORDER BY 1 DESC ";
$db = getConnection();
$stmt = $db->prepare($get_products);
$stmt -> bindValue(selected_categories, $selected_categories);
$stmt -> bindValue(price_min, $price_min);
$stmt -> bindValue(price_max, $price_max);
the problem is that string $selected_categories in some cases is empty, and when it is empty obviously nothing is found.
Is there any parameter which I can use (for example $selected_categories = '*';) and it will cause that FIND_IN_SET will find everything?
or should I create completely new query for case when $selected_categories is empty?

You can use a variable to reference the same replacement parameter twice:
WHERE IF((#selected_categories:=:selected_categories)='*',1,FIND_IN_SET(category_name,#selected_categories))
and test some value you want to mean all ('*' in this example, you could use ='' or IS NULL or something else).
Some prefer using the SQL standard CASE instead of IF; that would look like:
WHERE
CASE
WHEN (#selected_categories := :selected_categories) = '*' THEN 1
ELSE FIND_IN_SET(category_name, #selected_categories)
END

You can solve this with simple logic
WHERE
(
:selected_categories = '*' OR
FIND_IN_SET(`category_name`, :selected_categories)
)
AND `product_price` BETWEEN :price_min AND :price_max

Related

PDO error "Truncated incorrect DOUBLE value" using placeholder

I have the following PHP code that works perfectly ($qry_str is actually generated in the PHP):
$qry_str = <<<'QRY'
FIND_IN_SET('6-47', attributes)
AND FIND_IN_SET('4-176', attributes)
AND FIND_IN_SET('9-218', attributes)
QRY;
$pdo->query('DROP TEMPORARY TABLE IF EXISTS `temp_attr`');
$temp_sql = <<<"TEMP"
CREATE TEMPORARY TABLE IF NOT EXISTS `temp_attr` (
SELECT product_id, GROUP_CONCAT(CONCAT(group_id, '-', IF (custom_value != '', custom_value, value_id)) SEPARATOR ',') AS attributes
FROM `products_attributes`
GROUP BY `product_id`
HAVING $qry_str
);
TEMP;
$pdo->query($temp_sql);
$sql = "SELECT
m.recommended_price AS msrp,
m.purchase_price AS cost,
pp.USD AS regular_price,
pc.USD AS special_price,
pc.start_date AS start_date,
pc.end_date AS end_date,
pl.permalink AS permalink,
pi.name AS description,
m.sku AS sku,
m.default_category_id AS cat,
m.id AS prod_id
FROM `products` AS m
LEFT JOIN `products_prices` AS pp ON m.id = pp.product_id
LEFT JOIN `products_campaigns` AS pc ON m.id = pc.product_id
LEFT JOIN `permalinks` AS pl ON (m.id = pl.resource_id AND pl.resource = 'product')
LEFT JOIN `products_info` AS pi ON (m.id = pi.product_id)
LEFT JOIN `products_to_categories` AS ptc ON (m.id = ptc.product_id)
INNER JOIN `temp_attr` AS pa
WHERE ptc.category_id = :cat
AND m.status = 1
AND m.id = pa.product_id
LIMIT 55;
";
$data = $pdo->prepare($sql)
->bindValue('cat', 100)
->execute()
->fetchAll();
However, when I use a placeholder in the temp table code, i.e.
$temp_sql = <<<"TEMP"
CREATE TEMPORARY TABLE IF NOT EXISTS `temp_attr` (
SELECT product_id, GROUP_CONCAT(CONCAT(group_id, '-', IF (custom_value != '', custom_value, value_id)) SEPARATOR ',') AS attributes
FROM `products_attributes`
GROUP BY `product_id`
HAVING :qry_str
);
TEMP;
$sth = $pdo->prepare($temp_sql);
$sth->bindValue('qry_str', $qry_str, PDO::PARAM_STR);
$sth->execute();
I get the following error:
PHP Fatal error: Uncaught PDOException: SQLSTATE[22007]: Invalid datetime format: 1292 Truncated incorrect DOUBLE value: 'FIND_IN_SET('6-47', attributes)
AND FIND_IN_SET('4-176', attributes)
AND FIND_IN_SET('9-218', attributes)
AND FIND_IN_SET(...'
There are no datetime columns in this table.
group_id and value_id are integer columns
Since the code works fine without the placeholder, I'm at a loss as to why the use of a placeholder breaks the code. The placeholder in the main SQL works properly.
PHP 8.0
https://dev.mysql.com/doc/refman/8.0/en/prepare.html explains:
Parameter markers can be used only where data values should appear, not for SQL keywords, identifiers, and so forth.
In your case, you're apparently trying to bind an expression with the FIND_IN_SET() function. You can't do that. All expressions and other SQL syntax must be fixed in the query at the time you prepare it. You can use a parameter only in a place where you would otherwise use a scalar literal value. That is, a quoted string or a numeric literal.

Multiple LIKE's in query with 2 LEFT JOINS

EDIT: Strange I vardumpped every variable they are all a bool(false)
I'm trying to make this query. But when I click the search button nothing shows up.
Someone who can direct me in the right direction? Thanks in advance.
$STH = $DBH->query("SELECT * FROM artikel
LEFT JOIN barcode
ON artikel.barcode_id = barcode.barcode_id
LEFT JOIN debiteur
ON artikel.debiteur_id = debiteur.debiteur_id
WHERE debiteur.debiteur_naam LIKE '%$formulier%'
AND artikel.artikel_leverancier LIKE '%$leverancier%'
AND artikel.artikel_leverancier_code LIKE '%$artikelleverancier%'
AND artikel.artikel_code LIKE '%$artikelexact%'
AND artikel.artikel_omschrijving LIKE '%$artikelomschrijving%'
ORDER BY '$orderby' ASC");
Assuming you are interested in items which satisfy atleast one LIKE condition, you need to replace ANDs with ORs
$STH = $DBH->query("SELECT * FROM artikel
LEFT JOIN barcode
ON artikel.barcode_id = barcode.barcode_id
LEFT JOIN debiteur
ON artikel.debiteur_id = debiteur.debiteur_id
WHERE debiteur.debiteur_naam LIKE '%$formulier%'
OR artikel.artikel_leverancier LIKE '%$leverancier%'
OR artikel.artikel_leverancier_code LIKE '%$artikelleverancier%'
OR artikel.artikel_code LIKE '%$artikelexact%'
OR artikel.artikel_omschrijving LIKE '%$artikelomschrijving%'
ORDER BY '$orderby' ASC");
It sounds like you have to test for empty parameters. Here is one way in SQL:
WHERE ('$formulier' <> '' and debiteur.debiteur_naam LIKE '%$formulier%') or
('$leverancier' <> '' and artikel.artikel_leverancier LIKE '%$leverancier%') or
('$artikelleverancier' <> '' and artikel.artikel_leverancier_code LIKE '%$artikelleverancier%') or
('$artikelexact' <> '' and artikel.artikel_code LIKE '%$artikelexact%') or
('artikelomschrijving' <> '' and artikel.artikel_omschrijving LIKE '%$artikelomschrijving%')
Alternatively, you can build up the query clause by clause when the variables are not empty.

MySQL query to select specific data

I would like to display the data that belongs to any of my users when they login to the site, as well as the name of each table (they completed offers on them).
This is the code I used, but when I add it it's not working.
$result = mysql_query('SELECT *,\'tbl1\' AS tablename FROM (SELECT * FROM table1 WHERE user_id='$user_id') as tbl1 UNION SELECT *,\'tbl2\' AS tablename FROM (SELECT * FROM table1 WHERE user_id='$user_id') as tbl2'. ' ORDER BY `date` DESC');
while($sdata = mysql_fetch_array($result)){
echo $sdata['date'];
echo $sdata['tablename'];
echo $sdata['user_reward'];
}
Where did I make a mistake?
You are missing the concatenation operators here, around $user_id:
$result = mysql_query(
'SELECT *,\'tbl1\' AS tablename FROM (
SELECT * FROM table1 WHERE user_id=' . $user_id . '
) as tbl1
UNION
SELECT *,\'tbl2\' AS tablename FROM (
SELECT * FROM table1 WHERE user_id=' . $user_id . '
) as tbl2' . ' ORDER BY `date` DESC'
);
I've wrapped the call for more clarity - I suggest you do the same in your own code. I'd be inclined to use " marks here instead, so you don't need to escape apostrophes.
The ORDER BY clause seems to be redundantly concatenated as well - remove the dot and add this part of the query to the as tbl2 part.
Here's how I would do it:
$sql = "
SELECT *, 'tbl1' AS tablename FROM (
SELECT * FROM table1 WHERE user_id={$user_id}
) as tbl1
UNION
SELECT *, 'tbl2' AS tablename FROM (
SELECT * FROM table1 WHERE user_id={$user_id}
) as tbl2
ORDER BY `date` DESC
";
$result = mysql_query($sql);
Make sure that $user_id is properly escaped or cast, to avoid security problems. Also, this database library is no longer recommended, and will be removed in a future version of PHP. It would be better to move to PDO or mysqli, and use parameterisation.
Finally, it does rather look like the query itself is rather cumbersome - it looks like it could be simplified. Perhaps ask a separate question on that?

How do I perform this query without relying on looping in PHP?

I'm brand new with MySQL. It appears like I need to do some sort of loop in a MySQL request, but I think it could be way more efficient with some "INNER JOIN" stuff.
Here is my PHP code:
$query = 'SELECT id FROM membres WHERE pseudo = :pseudo LIMIT 1';
$req = $dtb -> prepare($query);
$req -> execute(array(
'pseudo' => $_COOKIE['pseudo']
));
while($donnes = $req -> fetch()){
$id_pseudo = $donnes['id'];
}
$req -> closeCursor();
$query = 'SELECT id_chanson FROM samples WHERE id_membre = :id_pseudo';
$req = $dtb -> prepare($query);
$req -> execute(array(
'id_pseudo' => $id_pseudo
));
$id_chansons = array();
while($donnes = $req -> fetch()){
$id_chansons[] = $donnes['id_chanson'];
}
$req->closeCursor();
$nSongs= count($id_chansons);
$query = 'SELECT nom, pathName, date FROM chansons WHERE id = :id_chanson';
$req = $dtb -> prepare($query);
for($i=0;$i<$nSongs;$i++){
$req -> execute(array(
'id_chanson' => $id_chansons[$i]
));
while($donnes = $req -> fetch()){
$nomChanson[$i] = $donnes['nom'];
$pathName[$i] = $donnes['pathName'];
$date[$i] = $donnes['date'];
}
}
EDIT: my table names are "chansons" (songs in french) "membres" (users) and "samples"
:)
You're right, it's often much more efficient to run a single query rather than multiple queries. It's faster on the database (fewer round trips), and it makes for much friendlier code.
Here's an example SQL query that will retrieve the rows from chansons:
SELECT c.nom
, c.pathName
, c.date
FROM (SELECT id FROM membres WHERE pseudo = :pseudo ORDER BY id LIMIT 1) m
JOIN samples s
ON s.id_membre = m.id
JOIN chansons c
ON c.id = s.id_chanson
ORDER BY 1
NOTES:
If you need the id values from the other tables returned in the result set, expressions for those can be included in the SELECT list as well. (From your code, I don't see that those id values being used for anything other than a subsequent query... but of course that doesn't mean they aren't referenced elsewhere.
I added an ORDER BY to the query in the inline view, to make it deterministic. If there is more than one row that satisfies the query, absent an ORDER BY clause, it is arbitrary which row will be returned by MySQL, that is, the result returned from a subsequent execution is not guaranteed to be the same as the previous run.)
The query above makes use of an inline view (MySQL calls it a derived table) aliased as m, to get the LIMIT 1 on the number of rows returned from membres. If you don't need that LIMIT, then the query can be simplified by removing that inline view.
The INNER keyword is optional, it has no influence on the optimizer. That is, INNER JOIN is synonymous with JOIN.
select m.id, s.id_chanson, ch.nom, ch.pathName, ch.date
from membres m
inner join samples s on m.id = s.id_membre
inner join chansons ch on s.id_chanson = ch.id
where pseudo = :pseudo

PHP: output count()

How do i work with the COUNT()
$sql = $connect->prepare("SELECT COUNT() FROM discos_events e INNER JOIN discos_events_guests eg ON (e.ID = eg.eID) INNER JOIN users u ON (eg.uID = u.id) WHERE e.dID =:id");
$sql->bindValue(":id", $cID);
$sql->execute();
...then what? echo $sql["count"]; ? to output the count?
You need an alias name for your COUNT() column:
$sql = $connect->prepare("SELECT COUNT() AS num_events FROM discos_events e INNER JOIN discos_events_guests eg ON (e.ID = eg.eID) INNER JOIN users u ON (eg.uID = u.id) WHERE e.dID =:id");
$sql->bindValue(":id", $cID);
// Fetch the results and then access the alias
$sql->execute();
$result = $sql->fetch();
echo $result['num_events'];
you need to execute() the query, so:
$result = $sql->execute(array('id' => $cId)); // just to illustrate that you can use this instead of bindParam
if ($result) {
$row = $sql->fetch();
}
after execute, you have to store_result() and fetch()
As #Michael suggests, you may alias the count(), to get it in more reatable form.
Your query needs to assign the count value to a name, like so:
SELECT COUNT() n FROM discos_events ...
Then you can reference the name n in your PHP array:
echo $sql["n"];
You can, of course, call it 'count' or any other name you prefer, but be careful of using names which are reserved words in SQL (such as 'count'). If you want to give it a reserved name, you need to enclose it in backtick characters so that SQL recognises that it's a name you want to use rather than its own reserved word.

Categories