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.
Related
I'm trying to sum columns x through x+n in an SQL table. Essentially, I have multiple tables that contain grades in them and a user_id. I want to sum all the grades to come up with a total grade column without specifying the column names as the names and number of columns changes with each table. For instance, one table might have columns (user_id, calculations, prelab, deductions) while another might have (user_id, accuracy, precision, graphs, prelab, deductions).
I could rename my columns col1, col2, col3, col4, col5, etc., but I can't figure out how to get around the varying number of columns.
As far as I know, there is no way to sum groups of columns without actually specifying the column names directly in SQL. It seems to me like this is a badly designed schema, but that's a separate topic.
In any your case, you're going to need to create a new column in each table that contains the sum of all the grades in that particular table, say called total, and then, do something like this:
select user_id, sum(table1.total, table2.total, table3.total)
from table1, table2, table3
where table1.user_id = table2.user_id
and table2.user_id = table3.user_id
group by user_id
1) You could write some pl/sql to go and hit the data dictionary and get the columns and then construct dynamic sql to do the work of adding them up correctly.
2) Or you could create views on top of the tables that contain the user_id and the sum of the interesting columns (the views themselves could be constructed programmatically - but that only needs to happen once rather than every time you want the totals).
But either of the above is probably over-kill compared to simply fixing your schema.
The following procedure would likely do the trick.
It will look for all column names for the given tableName in the INFORMATION_SCHEMA.COLUMNS table (excluding 'userid' - This may be subject to change if the name you use is different).
The procedure also creates a temporary table (this is also subject to improvement - it would probably be better to do a 'drop if exists before the create) to store the sum up to a point.
The items inside the loop is just building an SQL UPDATE statement with the given tableName argument and the columnName from the cursor and doing the math.
To test this (after creation):
call myProcedure('tableName');
DELIMITER //
DROP PROCEDURE IF EXISTS myProcedure //
CREATE PROCEDURE
myProcedure( tableName varchar(32) )
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE columnName varchar(64);
DECLARE cur1 CURSOR FOR SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = tableName and COLUMN_NAME <> 'userid';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
CREATE TEMPORARY TABLE intermediateresults(userid integer, sumOfScores integer);
SET #st1 = CONCAT('INSERT INTO intermediateresults (SELECT DISTINCT userid, 0 FROM ',tableName,' )' );
PREPARE stmt3 FROM #st1;
EXECUTE stmt3;
looping: LOOP
FETCH cur1 into columnName;
IF done THEN
LEAVE looping;
END IF;
SET #st1 = CONCAT('UPDATE intermediateresults set sumOfScores = sumOfScores + COALESCE( (SELECT ', columnName, ' FROM ',tableName, ' t WHERE t.userid=intermediateresults.userid) , 0)' );
PREPARE stmt3 FROM #st1;
EXECUTE stmt3;
DEALLOCATE PREPARE stmt3;
END LOOP;
CLOSE cur1;
SELECT * FROM intermediateresults;
DROP table intermediateresults;
END
//
DELIMITER ;
What might be of interest when doing this kind of thing:
INFORMATION_SCHEMA also has data on:
DATA_TYPE: which can be used to test if a specific column has the actual type you are expecting - a condition such as DATA_TYPE='int' can be added to the cursor definition to make sure that it is in fact an int (assuming that the columns to be summed are in fact INTs)
ORDINAL_POSITION: which can be used if you know in which order the columns are supposed to arrive (for cases where the last four are housekeeping, for instance)
TABLE_SCHEMA: the procedure above rather assumes that the table is only present in the current default schema. Using this would require an additional parameter in the procedure and a slight change in the constructed SQL statements.
I'm designing a search engine which will sift through data in a database. I have 2 tables in my database:
Members:= `MemberID|Firstname|Secondname|Member_Type|Skills|Marital Status`
Schedule:= `ScheduleID|MemberID (foreign key)|six_am|seven_am|...|nine_pm`
(where the values for six_am|seven_am|...|nine_pm are boolean values and represent availablity)
My question is if there's a way to create a single SQL query similar to:
SELECT * FROM members1 (WHERE $x LIKE $firstname OR $x LIKE SecondName...) INNER JOIN schedules ON (members1.members_id = schedules.member_id)
WHERE eleven_am=1 OR twelve_pm=1
This way, I can enter a single query instead of multiple ones.
You can use prepared statements:
SET #query = 'build your own query in any way';
PREPARE stmt FROM #query;
SET #id = 4; -- here you can add variable for safe inserting to the query. if you want to
EXECUTE prepared_query USING #id;
DEALLOCATE PREPARE stmt;
The #query can be built by some server side language (PHP for example) or by string manipulations in pure SQL.
Afterwards you send the "template" to execution.
If you'll provide more details I can build more exact example.
But this is an idea.
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
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.
What is the best way to get the auto-id value in the same SQL with a SELECT?
A forum said adding this "; has Return Scope_Identity()"
in the end of the SQL works in ASP.
Is there a corresponding way in PHP?
It depends on your database server. Using MySQL, call mysql_insert_id() immediately after your insert query. Using PostgreSQL, first query "select nextval(seq)" on the sequence and include the key in your insert query.
Querying for "select max(id) + 1 from tbl" could fail if another request inserts a record simultaneously.
In postgres the best way is to do something like:
insert into foos(name) values ('my_foo') returning id;
It depends on the database engine you are using. Some DBMS, like Firebird for example, have RETURNING clause you can add to your query. For example, if you have a table named TABLE1 with autoincrement column named ID, you can use this:
insert into TABLE1(columns...) values (values...) returning ID;
And it would return the inserted ID just like a regular select statement.
In Microsoft Transact SQL you can use ##IDENTITY.
e.g.
DECLARE #Table TABLE ( col0 INT IDENTITY, col1 VARCHAR(255), col2 VARCHAR(255))
INSERT INTO #Table (col1, col2) VALUES ('Hello','World!')
SELECT ##Identity
SELECT * FROM #Table
In php: mysql_insert_id()
http://us3.php.net/mysql_insert_id
or
If you wanted to genterate the number from your mySql select query, you could use this
EDIT:
SELECT LAST_INSERT_ID(`1`) + 1 FROM table
Be very careful: Apparently select nextval(seq) does not work in high concurrency - some other connection can insert between the time when you inserted and the time when you called select nextval(seq). Always test such code in high concurrency test harnesses.
In SQL Server a insert using the select statement can have an output clause which will return the identity value and whatever other columns you might need to identify which identity goes to which record. If you are using a values clause, then use select scope_identity () immediately after the insert.