MySQL - Passing Dynamic Column Names in Stored Procedures Using Prepared Statements - php

I've been pulling my hair out over this for the last 12 hours. I am trying to create a stored procedure to display the results of a search from a PHP page. There are a couple column names that are dynamic and are passed in by the php page. I thought i could use a prepared statement to get around this, but it doesn't seem to be working. When i run the SP below in phpMyAdmin, i get zero rows returned.
CREATE DEFINER=`test`#`localhost` PROCEDURE `AllLoads7`(
IN p_search_field VARCHAR(25),
IN p_search_for VARCHAR(250),
IN p_load_status INT,
IN p_date_field VARCHAR(25),
IN p_startDate DATETIME,
IN p_endDate DATETIME,
IN p_employeeID INT,
IN p_OrderBy VARCHAR(25)
)
begin
SET #GetLoads = CONCAT("SELECT * FROM loads_contacts WHERE ",p_search_field," LIKE '%",p_search_for,"%' AND status LIKE '%",p_load_status,"%' AND ",p_date_field," BETWEEN '",p_startDate,"' AND '",p_endDate,"' AND company_id IN(SELECT company_id FROM employees_companies WHERE employee_id = ",p_employeeID,") ORDER BY ",p_OrderBy);
PREPARE stmt FROM #GetLoads;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
end
Here is how phpMyAdmin is calling it
SET #p0='pronumber';
SET #p1='1';
SET #p2='1';
SET #p3='dateCreated';
SET #p4='2010-01-01';
SET #p5='2014-01-01';
SET #p6='2';
SET #p7='status';
CALL `AllLoads7`(#p0, #p1, #p2, #p3, #p4, #p5, #p6, #p7);
if I add "SELECT #GetLoads;" to the SP, i get the following query. If i run that query against my database, i get the expected rows returned. This tells me that the SQL is being generated correctly, it just seems that i'm not doing something quite right to execute the prepared statement
#GetLoads
SELECT * FROM loads_contacts WHERE pronumber LIKE '%1%' AND status LIKE '%1%' AND dateCreated BETWEEN '2010-01-01 00:00:00' AND '2014-01-01 00:00:00' AND company_id IN(SELECT company_id FROM employees_companies WHERE employee_id = 2) ORDER BY status

Related

Getting inserted row on if/-then-else statement

When I use an if-then-else to check if a record exists then call the last insert using $pdocon->lastInsertId() it does not return the inserted row. For example:
if exists(
SELECT first_name FROM `my_table`
WHERE first_name = 'adam'
)
then
SELECT * FROM `my_table` WHERE first_name = 'adam'
else
INSERT INTO my_table
(first_name)
values
('adam')
END IF;
This (simplified) code checks to see if a record exists. If it does exist, it grabs the info. If it does not exist it inserts it. That part all works fine, the problem comes in when I try to get the inserted ID from a name the doesn't exist (returns a 0 as lastInsertId).
Is there a practical way around this?
If you want to ensure that names are not duplicated, then you should define a unique index or constraint on the column:
alter table t add unique constraint unq_my_table_first_name unique (first_name);
Then you can use the following trick to get the correct id using last_insert_id():
insert into mytable (first_name)
values ('Adam')
on duplicate key update id = last_insert_id(id);
You can then use:
select last_insert_id()
to get the last insert id regardless of whether the statement was an update or insert.
Here is a db<>fiddle.
First the question should not have PHP tag because PHP doesn't have if-then-else conditional statement that's a Visual Basic conditional statement. Nevertheless an if-else can handle this with you using the right SQL query, SQL already provides a query that solves the issue above i.e. to check for the existence of a record and that should help.
SELECT column_name(s)
FROM table_name
WHERE EXISTS
(SELECT column_name FROM table_name WHERE condition);
To anyone looking in the future, another user posted the answer but then deleted it. The correct way to accomplish this is:
if exists(
SELECT first_name FROM `my_table`
WHERE first_name = 'adam'
)
then
SELECT * FROM `my_table` WHERE first_name = 'adam'
else
INSERT INTO my_table
(first_name)
values
('adam');
SELECT LAST_INSERT_ID() as inserted;
END IF;

MySQL database name as alias for query between multiple databases

mysqli_query("SELECT login.validto,
(SELECT databases.databasename FROM DB1.databases WHERE databases.ID = login.ID ORDER BY databases.lastused DESC LIMIT 1) AS dbname,
(SELECT users.username FROM dbname.users WHERE users.ID = '2') AS username
FROM DB1.login
WHERE login.token = 'abc13def456';");
Is it possible to use the alias dbname as database for the subquery instead of using 2 separate queries from PHP?
Im using 1 database to handle all the logins and then we use X databases to hold all the customers data, one database for each customer and later on it should be possible for the customers to change between the databases.
Im curently getting error:
Error Code: 1142
SELECT command denied to user 'myMySQLuser'#'myMySQLhost' for table 'users'
Changing the dbname to the real database name works just fine and the dbname and real database is the same, but i want to make 1 query instead of 2 from my PHP script.
It is possible to use the existing solution to create a procedure on MySQL server that will be called from your PHP appliation.
CREATE PROCEDURE my_proc()
BEGIN
DECLARE #dbname, #username VARCHAR(25);
# Set #dbname value
SELECT databases.databasename INTO #dbname FROM DB1.databases WHERE databases.ID = login.ID ORDER BY databases.lastused DESC LIMIT 1;
# Create query to set #username value from required database
SET #user_query = CONCAT('SELECT users.username INTO #username FROM ', #dbname, '.users WHERE users.ID =2;');
PREPARE stmt FROM #user_query;
EXECUTE stmt; # set #username value
DEALLOCATE PREPARE stmt;
# Main query
SELECT validto, #dbname, #username
FROM FROM DB1.login WHERE login.token = 'abc13def456';
END
Also you can call the procedure with parameters that you need. Then you will be able to use simple PHP statement to get required data.

Prepared statement - cross table update

I am attempting to use a prepared statement in combination with a cross table update. I have prepared a sample script that is representative of our larger database. This first section does what I want without a prepared statement, but I am hoping to avoid copy/pasting this for every column of my data.
SET SESSION group_concat_max_len = 1000000000;
drop table if exists update_test;
create table update_test(
time_index decimal(12,4),
a varchar(20),
b varchar(20),
c varchar(20));
insert into update_test(time_index) values(20150101.0000),(20150101.0015),(20150101.0030);
drop table if exists energy_values;
create table energy_values(
time_stamp decimal(12,4),
site_id varchar(5),
energy int);
insert into energy_values
values(20150101.0000,'a',100),(20150101.0000,'b',200),(20150101.0000,'c',300),
(20150101.0015,'a',400),(20150101.0015,'b',500),(20150101.0015,'c',600),
(20150101.0030,'a',700),(20150101.0030,'b',800),(20150101.0030,'c',900);
drop table if exists update_test_sites;
create table update_Test_sites(
sites varchar(5));
insert into update_test_sites values
('a'),('b'),('c');
update update_test, energy_values, update_test_sites
set update_test.a=energy_values.energy
where update_test.time_index = energy_values.time_stamp
and energy_values.site_id ='a';
update update_test, energy_values, update_test_sites
set update_test.b=energy_values.energy
where update_test.time_index = energy_values.time_stamp
and energy_values.site_id ='b';
update update_test, energy_values, update_test_sites
set update_test.c=energy_values.energy
where update_test.time_index = energy_values.time_stamp
and energy_values.site_id ='c';
select * from update_test;
Which is why I have attempted something like this as a replacement for the update functions. However, I often get a syntax error report. Can anyone identify where I am going wrong? It would be much appreciated!
SELECT
concat(
'update update_test, energy_values, update_test_sites
set update_test.',sites,'=energy_values.energy
where update_test.time_index = energy_values.time_stamp
and energy_values.site_id = ',sites,';
select * from update_test;')
from update_test_sites
where sites = 'a'
INTO #sql;
PREPARE stmt FROM #sql;
EXECUTE stmt;
I've never seen "SELECT INTO" work that way. In my experience, it is used like so:
SELECT [field_list] INTO [variable_list]
FROM [some_table]
[etc...]
I don't think it can be used to store a resultset like it appears you are attempting.
With some tweaking and doing this in a stored procedure, you could use a cursor to iterate over the results to prepare and execute each generated statement individually.

MySQL Stored Procedure Creation with Input and Output Parameters

Answer: https://stackoverflow.com/a/24034862/1173155
I am testing a Stored procedure as follows:
DELIMITER $$
CREATE PROCEDURE `test1`(IN `tab_name` VARCHAR(40), IN `value_of` VARCHAR(40), OUT `the_id` INT(1))
BEGIN
SET #t1=CONCAT('
BEGIN
IF EXISTS (SELECT ',tab_name,'.id from ',tab_name,' where ',tab_name,'.',tab_name,' = ',value_of,')
THEN
select id into ',the_id,' from ',tab_name,' where ',tab_name,'.',tab_name,' = ',value_of,';
ELSE
insert into ',tab_name,' values (NULL,',value_of,');
END IF;');
PREPARE stmt3 FROM #t1;
EXECUTE stmt3;
DEALLOCATE PREPARE stmt3;
END $$
DELIMITER ;
Trying to select the id, or insert. I have a single row in the table, when I try and use this Procedure as is; it doesn't return anything.
The identifier for the value I want is the table name for simplicities sake. Have spent many hours and I'm at a loss.
Have tried everything, even now just thought adding the NULL, would work as the table has two values and I had forgotten to put it in only to realise I was trying it within the first if clause... IE, entering data to ensure that the first statement would be invoked
Thanks for any help
Update
Still having trouble with this. Trying to make it as simple as possible now.
I have a city table, with 1 record it in. Structure is like so city.city = 'Dublin'
Value and table name are the same. The follow select statement works when executed via phpMyAdmin
BEGIN
SET #t1= CONCAT("
SELECT id
FROM ",tab_name,"
WHERE ",tab_name," = '",value_of,"'
");
PREPARE stmt3 FROM #t1;
EXECUTE stmt3;
DEALLOCATE PREPARE stmt3;
END
Now I just need to ensure that if the select statement doesn't return anything, then insert the value and return the select statement.
This seems like such a simple problem, yet I can't find the solution anywhere
--
INSERT IGNORE is not good because it increments the ID regardless of inserting. Is there a work around for this because I could just use that then.
INSERT IGNORE... // Without increment?
SELECT ^ ....
Further Update
My procedure now looks like this (takes two VARCHAR parameters) and this is currently working. It doesn't increment the ID if the value already exists either which is simply fantastic.
BEGIN
SET #t1= CONCAT("INSERT INTO ",tab_name,"(",tab_name,")
SELECT '",city_name,"' FROM dual
WHERE NOT EXISTS (SELECT 1
FROM ",tab_name,"
WHERE ",tab_name," = '",city_name,"');
");
PREPARE stmt3 FROM #t1;
EXECUTE stmt3;
DEALLOCATE PREPARE stmt3;
END
All I want now is for a simple SELECT statement to return the id of the row with that city name. eg SELECT id FROM tab_name WHERE tab_name = 'city_name';
But adding this in causes an error :( Thanks if anyone has a solution to this ridiculous problem
The solution that is working via phpMyAdmin. Will have to test in PHP and PDO too and see if all is good.
CREATE PROCEDURE `select_insert`(IN `the_value` VARCHAR(150), IN `tab_name` VARCHAR(50))
BEGIN
SET #t1= CONCAT("INSERT INTO ",tab_name,"(",tab_name,")
SELECT '",the_value,"' FROM dual
WHERE NOT EXISTS (SELECT 1
FROM ",tab_name,"
WHERE ",tab_name," = '",the_value,"');
");
PREPARE stmt FROM #t1;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #t2 = CONCAT("SELECT id FROM ",tab_name," WHERE ",tab_name," = '",the_value,"'");
PREPARE stmt1 FROM #t2;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
END
This is a generic procedure for inserting or retrieving values that are unique in the table. I used a city example while trying to figure this out.
The first prepared statement inserts the value provided into the table name provided. It doesn't increase the auto increment value if no insertion is made.
The second prepared statement retrieves the value. There shouldn't really be a case where no value is returned due to the nature of the first query.
The statement you wrote should not return results. Both the select into and insert statements do not return results.
It's a stored procedure.
Do the SELECT first, then follow it with the insert(s). The select will be the first output result set of the procedure.

MySQL Queries overlapping each other

I'm having an issue with MySQL Queries, there are a couple queries that seem to be overlapping each other. Basically, I have some PHP code (being called via a REST API) using MySQL with PDO that inserts a value into a table if it doesn't already exist. If it exists, then it updates the record instead. Pretty simple stuff, and it seems to be working 99% of the time. But on certain occasions the same record gets inserted twice (instead of being updated the second time).
I looked into the MySQL log and found that on rare occasions the script is somehow being run twice at the same time (causing overlapping queries). Notice how the process IDs 95587 and 95588 seem to be overlapping.
95587 Connect #localhost on ****
95588 Connect #localhost on ****
95587 Query SET NAMES utf8
95588 Query SET NAMES utf8
95588 Query SELECT * FROM `my_table` WHERE `data` = '89158268' LIMIT 1
95587 Query SELECT * FROM `my_table` WHERE `data` = '89158268' LIMIT 1
95588 Query INSERT INTO `my_table` (`data`, `date_created`, `date_updated`) VALUES ('89158268', '2011-08-21 17:11:10 ', '2011-08-21 17:11:10 ')
95587 Query INSERT INTO `my_table` (`data`, `date_created`, `date_updated`) VALUES ('89158268', '2011-08-21 17:11:10 ', '2011-08-21 17:11:10 ')
95588 Quit
95587 Query SELECT * FROM `another_table`
95587 ... more queries
This is the order of the code:
SELECT * FROM my_table WHERE data = '89158268' LIMIT 1
if record found, then update my_table
else if record not found, insert new record into my_table and then continue other queries
Can anyone think of a reason why these 2 mysql processes seem to be overlapping each other? Any help is greatly appreciated. Thank you.
Try locking the table before the select:
LOCK TABLE my_table WRITE;
SELECT * FROM `my_table` WHERE `data` = '89158268' LIMIT 1;
INSERT INTO `my_table` (`data`, `date_created`, `date_updated`);
UNLOCK TABLES;
This will avoid any other MySQL session from using the my_table table, and avoid this race condition.

Categories