I am trying to convert a mysql_query to pdo equivalent
My table structure is (removed unrelated columns - the one needed for my question is other_id) :
CREATE TABLE `temp_table` (
`id` int(12) NOT NULL DEFAULT '0',
`other_id` int(12) NOT NULL DEFAULT '0',
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
And the data it has:
-----------------
| id | other_id |
-----------------
| 1 | 123 |
-----------------
| 2 | 0 |
-----------------
| 3 | 456 |
-----------------
| 4 | 0 |
-----------------
The previous database query was :
$sql = "SELECT id FROM temp_table WHERE other_id = '{$other_id}'";
$result = mysql_query($sql, $db)
return mysql_fetch_assoc($result);
The query is called with $other_id as NULL ("null" in php and not string or anything).
Result mysql_query : This gives my values 2,4
PDO equivalent code :
$sql = "SELECT id FROM temp_table WHERE other_id = :other_id";
$sth = $dbConnection->prepare($sql);
$sth->bindValue(":other_id", $other_id);
$sth->execute();
return $sth->fetchAll(PDO::FETCH_COLUMN);
Result PDO : This gives no values at all.
This is weird issue which I have not encountered before (since I am more java developer and recently touched PHP after few years).
As a workaround I had to put below line to return 2,4 as result from pdo output, but want to understand more about the difference mentioned above.
$sth->bindValue(":other_id", empty($other_id) ? 0 : $other_id);
I also tried $sth->bindValue(":other_id", $other_id, PDO::PARAM_INT); which did not help
This is not an issue with the mysql_ vs PDO difference, but rather with type-juggling.
In the first non-PDO example, $other_id is being converted to a string in the query, so your query looks like this:
SELECT id FROM temp_table WHERE other_id = '';
MySQL treats an empty string the same as 0 for fields of numeric type during queries, so your query is actually correctly matching two records.
In the PDO example since you're passing $other_id (which is null), your query is bound as follows:
SELECT id FROM temp_table WHERE other_id = NULL;
MySQL does not treat 0 as NULL, so the queries that you're sending are actually not the same.
This is weird issue which I have not encountered before.
There is nothing weird here.
When $other_id is NULL the first query becomes:
SELECT id FROM temp_table WHERE other_id = ''
Because the type of the other_id column is numeric, the provided value (the empty string) is converted to the number 0 and there are two matching rows.
On the other hand, the query sent through PDO is equivalent to:
SELECT id FROM temp_table WHERE other_id = NULL
Not only that there are no NULLs in the table, but this query never returns any row, even if there are rows having NULL in the column other_id. (The correct way to select the rows having NULL in other_id is to use the IS NULL operator).
Related
I have a field COLORS (varchar(50)) in a my table SHIRTS that contains a comma delimited string such as 1,2,5,12,15,. Each number representing the available colors.
When running the query select * from shirts where colors like '%1%' to get all the red shirts (color=1), I also get the shirts whose color is grey (=12) and orange (=15).
How should I rewrite the query so that is selects ONLY the color 1 and not all colors containing the number 1?
The classic way would be to add commas to the left and right:
select * from shirts where CONCAT(',', colors, ',') like '%,1,%'
But find_in_set also works:
select * from shirts where find_in_set('1',colors) <> 0
FIND_IN_SET is your friend in this case
select * from shirts where FIND_IN_SET(1,colors)
Take a look at the FIND_IN_SET function for MySQL.
SELECT *
FROM shirts
WHERE FIND_IN_SET('1',colors) > 0
This will work for sure, and I actually tried it out:
lwdba#localhost (DB test) :: DROP TABLE IF EXISTS shirts;
Query OK, 0 rows affected (0.08 sec)
lwdba#localhost (DB test) :: CREATE TABLE shirts
-> (<BR>
-> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> ticketnumber INT,
-> colors VARCHAR(30)
-> );<BR>
Query OK, 0 rows affected (0.19 sec)
lwdba#localhost (DB test) :: INSERT INTO shirts (ticketnumber,colors) VALUES
-> (32423,'1,2,5,12,15'),
-> (32424,'1,5,12,15,30'),
-> (32425,'2,5,11,15,28'),
-> (32426,'1,2,7,12,15'),
-> (32427,'2,4,8,12,15');
Query OK, 5 rows affected (0.06 sec)
Records: 5 Duplicates: 0 Warnings: 0
lwdba#localhost (DB test) :: SELECT * FROM shirts WHERE LOCATE(CONCAT(',', 1 ,','),CONCAT(',',colors,',')) > 0;
+----+--------------+--------------+
| id | ticketnumber | colors |
+----+--------------+--------------+
| 1 | 32423 | 1,2,5,12,15 |
| 2 | 32424 | 1,5,12,15,30 |
| 4 | 32426 | 1,2,7,12,15 |
+----+--------------+--------------+
3 rows in set (0.00 sec)
Give it a Try !!!
If the set of colors is more or less fixed, the most efficient and also most readable way would be to use string constants in your app and then use MySQL's SET type with FIND_IN_SET('red',colors) in your queries. When using the SET type with FIND_IN_SET, MySQL uses one integer to store all values and uses binary "and" operation to check for presence of values which is way more efficient than scanning a comma-separated string.
In SET('red','blue','green'), 'red' would be stored internally as 1, 'blue' would be stored internally as 2 and 'green' would be stored internally as 4. The value 'red,blue' would be stored as 3 (1|2) and 'red,green' as 5 (1|4).
select * from shirts where find_in_set('1',colors) <> 0
Works for me
If you're using MySQL, there is a method REGEXP that you can use...
http://dev.mysql.com/doc/refman/5.1/en/regexp.html#operator_regexp
So then you would use:
SELECT * FROM `shirts` WHERE `colors` REGEXP '\b1\b'
You should actually fix your database schema so that you have three tables:
shirt: shirt_id, shirt_name
color: color_id, color_name
shirtcolor: shirt_id, color_id
Then if you want to find all of the shirts that are red, you'd do a query like:
SELECT *
FROM shirt, color
WHERE color.color_name = 'red'
AND shirt.shirt_id = shirtcolor.shirt_id
AND color.color_id = shirtcolor.color_id
You can achieve this by following function.
Run following query to create function.
DELIMITER ||
CREATE FUNCTION `TOTAL_OCCURANCE`(`commastring` TEXT, `findme` VARCHAR(255)) RETURNS int(11)
NO SQL
-- SANI: First param is for comma separated string and 2nd for string to find.
return ROUND (
(
LENGTH(commastring)
- LENGTH( REPLACE ( commastring, findme, "") )
) / LENGTH(findme)
);
And call this function like this
msyql> select TOTAL_OCCURANCE('A,B,C,A,D,X,B,AB', 'A');
1. For MySQL:
SELECT FIND_IN_SET(5, columnname) AS result
FROM table
2.For Postgres SQL :
SELECT *
FROM TABLENAME f
WHERE 'searchvalue' = ANY (string_to_array(COLUMNNAME, ','))
Example
select *
from customer f
where '11' = ANY (string_to_array(customerids, ','))
All the answers are not really correct, try this:
select * from shirts where 1 IN (colors);
I have a table like so:
User_Id Column1 Column2 Column3
1 Yes No Yes
2
I want to use mysql query to list all the column names (there are more than 3) which match the User_Id '1' and have a value of 'Yes'.
I get an error:
Trying to get property 'num_rows' of non-object
Here is what I have tried:
<?php $myStats = $mysqli->query("SELECT COLUMN_NAME FROM user_services.columns WHERE myColumn = 'Yes'");
if ($myStats->num_rows > 0) {
// output data of each row
while($row = $myStats->fetch_assoc()) {
$rows[] = $row; }
return $rows; ?>
Please can someone show me where I am going wrong?
Thanks in advance.
The CONCAT_WS function comes in handy here:
SELECT CONCAT_WS(',', IF(Column1='Yes', 'Column1', NULL),
IF(Column2='Yes', 'Column2', NULL),
IF(Column3='Yes', 'Column3', NULL)) AS columns
FROM user_services.columns
WHERE User_Id = 1;
If you have more than 3 columns, then you may add more terms to above CONCAT_WS call. Your problem mainly seems to be a SQL one, so I won't add any PHP code.
Note that your design might be better off if your column strings were spread across rows, rather than columns. For instance, consider the following alternative:
User_Id | number | val
1 | 1 | Yes
1 | 2 | No
1 | 3 | Yes
Then, if you wanted all column numbers which were yes for user 1, you could simply do:
SELECT
User_Id,
GROUP_CONCAT(number ORDER BY number) columns
FROM yourTable
WHERE
User_Id = 1
GROUP BY
User_Id;
I have a field COLORS (varchar(50)) in a my table SHIRTS that contains a comma delimited string such as 1,2,5,12,15,. Each number representing the available colors.
When running the query select * from shirts where colors like '%1%' to get all the red shirts (color=1), I also get the shirts whose color is grey (=12) and orange (=15).
How should I rewrite the query so that is selects ONLY the color 1 and not all colors containing the number 1?
The classic way would be to add commas to the left and right:
select * from shirts where CONCAT(',', colors, ',') like '%,1,%'
But find_in_set also works:
select * from shirts where find_in_set('1',colors) <> 0
FIND_IN_SET is your friend in this case
select * from shirts where FIND_IN_SET(1,colors)
Take a look at the FIND_IN_SET function for MySQL.
SELECT *
FROM shirts
WHERE FIND_IN_SET('1',colors) > 0
This will work for sure, and I actually tried it out:
lwdba#localhost (DB test) :: DROP TABLE IF EXISTS shirts;
Query OK, 0 rows affected (0.08 sec)
lwdba#localhost (DB test) :: CREATE TABLE shirts
-> (<BR>
-> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> ticketnumber INT,
-> colors VARCHAR(30)
-> );<BR>
Query OK, 0 rows affected (0.19 sec)
lwdba#localhost (DB test) :: INSERT INTO shirts (ticketnumber,colors) VALUES
-> (32423,'1,2,5,12,15'),
-> (32424,'1,5,12,15,30'),
-> (32425,'2,5,11,15,28'),
-> (32426,'1,2,7,12,15'),
-> (32427,'2,4,8,12,15');
Query OK, 5 rows affected (0.06 sec)
Records: 5 Duplicates: 0 Warnings: 0
lwdba#localhost (DB test) :: SELECT * FROM shirts WHERE LOCATE(CONCAT(',', 1 ,','),CONCAT(',',colors,',')) > 0;
+----+--------------+--------------+
| id | ticketnumber | colors |
+----+--------------+--------------+
| 1 | 32423 | 1,2,5,12,15 |
| 2 | 32424 | 1,5,12,15,30 |
| 4 | 32426 | 1,2,7,12,15 |
+----+--------------+--------------+
3 rows in set (0.00 sec)
Give it a Try !!!
If the set of colors is more or less fixed, the most efficient and also most readable way would be to use string constants in your app and then use MySQL's SET type with FIND_IN_SET('red',colors) in your queries. When using the SET type with FIND_IN_SET, MySQL uses one integer to store all values and uses binary "and" operation to check for presence of values which is way more efficient than scanning a comma-separated string.
In SET('red','blue','green'), 'red' would be stored internally as 1, 'blue' would be stored internally as 2 and 'green' would be stored internally as 4. The value 'red,blue' would be stored as 3 (1|2) and 'red,green' as 5 (1|4).
select * from shirts where find_in_set('1',colors) <> 0
Works for me
If you're using MySQL, there is a method REGEXP that you can use...
http://dev.mysql.com/doc/refman/5.1/en/regexp.html#operator_regexp
So then you would use:
SELECT * FROM `shirts` WHERE `colors` REGEXP '\b1\b'
You should actually fix your database schema so that you have three tables:
shirt: shirt_id, shirt_name
color: color_id, color_name
shirtcolor: shirt_id, color_id
Then if you want to find all of the shirts that are red, you'd do a query like:
SELECT *
FROM shirt, color
WHERE color.color_name = 'red'
AND shirt.shirt_id = shirtcolor.shirt_id
AND color.color_id = shirtcolor.color_id
You can achieve this by following function.
Run following query to create function.
DELIMITER ||
CREATE FUNCTION `TOTAL_OCCURANCE`(`commastring` TEXT, `findme` VARCHAR(255)) RETURNS int(11)
NO SQL
-- SANI: First param is for comma separated string and 2nd for string to find.
return ROUND (
(
LENGTH(commastring)
- LENGTH( REPLACE ( commastring, findme, "") )
) / LENGTH(findme)
);
And call this function like this
msyql> select TOTAL_OCCURANCE('A,B,C,A,D,X,B,AB', 'A');
1. For MySQL:
SELECT FIND_IN_SET(5, columnname) AS result
FROM table
2.For Postgres SQL :
SELECT *
FROM TABLENAME f
WHERE 'searchvalue' = ANY (string_to_array(COLUMNNAME, ','))
Example
select *
from customer f
where '11' = ANY (string_to_array(customerids, ','))
All the answers are not really correct, try this:
select * from shirts where 1 IN (colors);
My db is structured like:
id | posts | groups
----+--------+-----------
1 | 10 | 2
2 | 30 | 2
3 | 20 | 2
4 | 50 | 2,8
5 | 54 | 2,8
When a user gets to 50 or more posts I want the script to remove the group '2'. There is already a prior script that adds the '8'.
I have this:
$cusstring = mysql_query("SELECT `groups` FROM `users` WHERE `postnum` >= 50 ");
$row = mysql_fetch_array($cusstring);
$array = explode(',', $row[groups]);
$pos = array_search('2', $array);
unset($array[$pos]);
$row[groups] = implode(',', $array);
mysql_query("UPDATE `users` SET `groups` = $row[groups] WHERE `postnum` >= 50 ");
It just doesn't seem to update though. I don't know if this is because it picks up multiple fields in the array or if I'm doing something wrong with the greater than or equal to symbol.
Can anyone offer a solution?
Thanks.
EDIT:
I've worked out that if I change the symbol to equal to the query works on the first row it comes across with a post count of 50 but it leaves the rest. It would appear it's only able to process one row.
I think this would work for you. I think a SQL approach would be more efficient but you've said you want to keep it in PHP.
$cusstring = mysql_query("SELECT `groups`, `id ` FROM `users` WHERE `postnum` >= 50 ");
while($row = mysql_fetch_array($cusstring)) {
$groups = mysql_real_escape_string(preg_replace('~(^|\s+)2(,|$)~', '', $row['groups']));
//regex demo https://regex101.com/r/eX7qD1/1
$id = (int)$row['id'];
mysql_query("UPDATE `users` SET `groups` = '$groups' WHERE `id ` = $id ");
}
Your code is only getting one record because you aren't looping the fetch.
Also don't put data that comes from your DB back into a query directly this can lead to a SQL injection. Note I cast the ID here to an int and escaped the groups value. This should prevent the possibility of an injection.
You should switch drivers to PDO or MYSQLI. Once using one of those drivers you can use prepared statements.
This solution also will put an empty value in the groups field if 2 was the only value.
I have a stored procedure in a sql server 2008 db.
This stored procedure takes a parameter that is a list of max 5 comma separated integer values. This string is passed through a php sript and formatted like this:
01,02,03,14,15
These values should go in this table:
Id | Type id
-------------
1 | 1
1 | 2
1 | 3
1 | 14
1 | 15
...where id is the same and type id is one of the comma separated values.
Obviously, I could have a string like "01,02,03" with only 3 values. So I need a way to insert only 3 rows in the above table resulting in:
Id | Type id
-------------
1 | 1
1 | 2
1 | 3
I could modify either the php script or the stored procedure, so I'm open to all kinds of suggestion.
You could create a function to help you do this.
CREATE FUNCTION dbo.Split(#origString varchar(max), #Delimiter char(1))
returns #temptable TABLE (items varchar(max))
as
begin
declare #idx int
declare #split varchar(max)
select #idx = 1
if len(#origString )<1 or #origString is null return
while #idx!= 0
begin
set #idx = charindex(#Delimiter,#origString)
if #idx!=0
set #split= left(#origString,#idx - 1)
else
set #split= #origString
if(len(#split)>0)
insert into #temptable(Items) values(#split)
set #origString= right(#origString,len(#origString) - #idx)
if len(#origString) = 0 break
end
return
end
Then you can call this function to split out the parameter values:
Not 100% sure where the ID value comes from so I have declared a variable.
Declare #newId int
Set #newId = 1
Insert Into dbo.MyTable (ID, TypeId)
Select #newId, *
From dbo.Split(#ParameterValues, ',')
I would pass XML into SQL server, which can then easily be processed as nodeset (like a table) using OPENXML or using the nodes() function on the xml datatype. It works very well and it's quite performant, almost always faster than any handmade string processing code.
DECLARE #x xml;
SET #x = '<id>01</id><id>02</id><id>03</id><id>14</id><id>15</id>';
SELECT x.id.value('.', 'int') FROM #x.nodes('id') AS x(id);
If you pass in 5 arguments, you could do something like this:
-- simulation of the arguments
DECLARE #p1 INT, #p2 INT, #p3 INT, #p4 INT, #p5 int;
SELECT #p1=1, #p2=2, #p4=14;
-- Select only parameters which aren't null
SELECT #p1 AS id WHERE #p1 IS NOT NULL
UNION ALL
SELECT #p2 WHERE #p2 IS NOT NULL
UNION ALL
SELECT #p3 WHERE #p3 IS NOT NULL
UNION ALL
SELECT #p4 WHERE #p4 IS NOT NULL
UNION ALL
SELECT #p5 WHERE #p5 IS NOT NULL;