Mirror mysql table structure - php

How would someone maintain different tables that share a similar structure:
Example: I have 600 tables with 20 fields and I've been using this structure for months, what if I need to delete 1 field and add 2 new ones, how could it possibly be done just by changing a master table which contains the structure that must be used by all of the other cloned tables?

Well, you probably know that your structure is far from being optimal and the best solution is to reorganise it. It is not always easy with legacy systems though, so the task can be still performed with MySQL only.
You will need "cursors" there which can be only used inside stored procedures, so you'll need to create a stored procedure first (its sample code is below) and then to execute is as CALL alter_many_tables();
CREATE PROCEDURE alter_many_tables()
BEGIN
-- reading names of the table to update in a cursor
DECLARE tables_cursor CURSOR FOR
SELECT DISTINCT
`TABLE_NAME`
FROM
`information_schema`.`columns`
WHERE
`TABLE_NAME` LIKE '%\_modulep'
;
-- condition for the loop over found tables to stop
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- looping
read_loop: LOOP
-- reading table name into a variable
FETCH tables_cursor INTO table_name;
-- check if the loop is over
IF done THEN
LEAVE read_loop;
END IF;
-- forming a table update SQL (modify it as you need)
SET #sql = CONCAT('ALTER TABLE ', #table_name, ' ADD COLUMN `new_column` VARCHAR(45) NULL DEFAULT NULL');
-- executing the SQL we have composed above
PREPARE stmt FROM #sql;
EXECUTE stmt;
END LOOP;
-- closing the cursor
CLOSE table_cursor;
END;
There might be some minor syntax errors in the snippet above as I can't test at the moment, but you get the idea.

Related

Summing columns x through x+n in an SQL table

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.

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.

Selecting inserted record in Oracle from PHP OCI

I am executing a PLSQL Block using OCI from my PHP site which is running some procedures inside. The final procedure is returning the inserted records rowid of a specific table.
BEGIN
proc1(1);
proc2(2, rowid_);
END;
What I want to do is, I want to get the primary key values of the record for this rowid?
Is there a way to run it somehow like below and get the select results out to PHP with oci_fetch_row or something?
BEGIN
proc1();
proc2(rowid_); -- out variable
SELECT column1, column2
FROM my_table
WHERE rowid = rowid_;
END;
There's a better way. Try something like:
DECLARE
nPK_col NUMBER;
nCol1 NUMBER := 1;
nCol2 NUMBER := 2;
BEGIN
INSERT INTO SOME_TABLE(COL1, COL2)
VALUES (nCol1, nCol2)
RETURNING PK_COL INTO nPK_col;
END;
This example assumes that the primary key column named PK_COL is populated in some way during the execution of the INSERT statement, e.g. by a trigger. The RETURNING clause of the INSERT statement specifies that the value of PK_COL from the inserted row should be put into the variable specified, in this case nPK_col. You can specify multiple columns and variables in the RETURNING clause - documentation here. You may need to put this into whatever procedure performs the actual INSERT and then add an OUT parameter to allow the value to be passed back to the caller - or use a FUNCTION instead of a PROCEDURE and have the primary key value be the return value of the FUNCTION.
Share and enjoy.

can i combine update and insert on different tables (with index) on 1 call?

Is it possible to insert values to one table and update another table with a single mysql call? If yes, is this faster or is it faster to use two separate calls?
For Example:
table1 - cars
id - color - brand
1 - red - audi
2 - blue - pontiac
table2 - people
id - name - last
1 - dave - ann
2 - beth - elane
tables do not relate
and lets say i would like to add another row to people while same time updating table1 cars's color
is that possible ?
Use a stored procedure... Something like...
DROP PROCEDURE IF EXISTS insert_update $$
CREATE PROCEDURE insert_update
(
IN id INT,
IN color VARCHAR(10),
IN name VARCHAR(20)
)
BEGIN
-- do insert
THEN
-- do update
END IF;
Note: I haven't written your queries for you because you didn't supply any cogent information about your tables, per se.
In terms of semantics, I think you want perform a transaction not a single query.
If all your data is MyISAM, STOP RIGHT HERE. It is no possible.
If all you data is InnoDB, there are two paradigms to could set up:
PARADIGM #1
START TRANSACTION;
INSERT INTO people ...
INSERT INTO cars ...
COMMIT;
PARADIGM #2
Do the following within the DB Session:
SET autocommit = 0;
then do all the INSERTs you want then run
COMMIT;
If you close the DB Connection, everything will rollback. So, try to commit often.

MySQL: updating a row and deleting the original in case it becomes a duplicate

I have a simple table made up of two columns: col_A and col_B.
The primary key is defined over both.
I need to update some rows and assign to col_A values that may generate duplicates, for example:
UPDATE `table` SET `col_A` = 66 WHERE `col_A` = 70
This statement sometimes yields a duplicate key error.
I don't want to simply ignore the error with UPDATE IGNORE, because then the rows that generate the error would remain unchanged. Instead, I want them to be deleted when they would conflict with another row after they have been updated
I'd like to write something like:
UPDATE `table` SET `col_A` = 66 WHERE `col_A` = 70 ON DUPLICATE KEY REPLACE
which unfortunately isn't legal in SQL, so I need help finding another way around.
Also, I'm using PHP and could consider a hybrid solution (i.e. part query part php code), but keep in mind that I have to perform this updating operation many millions of times.
thanks for your attention,
Silvio
Reminder: UPDATE's syntax has problems with joins with the same table that is being updated
EDIT: sorry, the column name in the WHERE clause was wrong, now I fixed it
Answer to revised question:
DELETE FROM
table_A
USING
table AS table_A
JOIN table AS table_B ON
table_A.col_B = table_B.col_B AND
table_B.col_A = 70
WHERE
table_A.col_A = 66
This gets rid of the rows that would cause problems. Then you issue your UPDATE query. Ideally you will do it all inside a transaction to avoid a situation where troublesome rows are re-inserted in between the two queries.
Are there any foreign keys referencing this table? If not then the following should do:
CREATE PROCEDURE `MyProcedure` (IN invarA INT, IN invarB INT)
LANGUAGE SQL
NOT DETERMINISTIC
MODIFIES SQL DATA
SQL SECURITY DEFINER
BEGIN
DELETE FROM table WHERE col_B = invarB;
IF ROW_COUNT() > 0 THEN
INSERT INTO table (`col_A`, `col_B`) VALUES (invarA, invarB);
END IF;
END
Example call:
CALL `MyProcedure`(66, 70)

Categories