I'm looking for a straight way to run a query on all databases hosted on my mysql server.
I have a bunch of Magento installations and I want to truncate all Magento log table on all databases:
log_customer
log_visitor
log_visitor_info
log_url
log_url_info
log_quote
report_viewed_product_index
report_compared_product_index
report_event
catalog_compare_item
I think it something very easy to accomplish in mysql but I cannot find a straight answer/solution.
*UPDATE *
According to #Ollie Jones it is not possible to do it without a STORE PROCEDURE or a server side language ( PHP or whatever )
UPDATE 1
I choose to follow the PHP approach (#samitha) for 2 reasons:
STORE PROCEDURE looks more complicated
Query on 'information_schema' table is very slow ( at least if you have many DB/TABLES)
SELECT DISTINCT SCHEMA_NAME AS `database`
FROM information_schema.SCHEMATA
WHERE SCHEMA_NAME NOT IN ('information_schema', 'performance_schema', 'mysql')
ORDER BY SCHEMA_NAME
gets you a list of all the non-MYSQL databases on your system.
SELECT TABLE_SCHEMA AS `database`,
TABLE_NAME AS `table`
FROM information_schema.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
ORDER BY TABLE_SCHEMA, TABLE_NAME
gets you a list of all the actual tables (excluding SYSTEM VIEWs like the TABLES table, and user-defined views) in all the databases.
Then, you should implement logic in your program to ensure that, for each database, it really is a Magento database before you truncate certain tables. Otherwise, you might become a despised person among your co-workers. :-)
Edit
Here's a stored procedure.
You need to edit it to do exactly what you need it to do; in particular, it counts rows rather than truncating tables, and it doesn't contain the correct list of log tables. (It would be irresponsible for me to publish such a wildly destructive stored procedure; you should edit it yourself to do the destructive part.)
DELIMITER $$
DROP PROCEDURE IF EXISTS `zap_magento_logs`$$
CREATE PROCEDURE `zap_magento_logs`()
BEGIN
-- declare variables for database and table names
DECLARE dbname VARCHAR(128) DEFAULT '';
DECLARE tbname VARCHAR(128) DEFAULT '';
DECLARE done INTEGER DEFAULT 0;
-- declare cursor for list of log tables
DECLARE log_table_list CURSOR FOR
SELECT TABLE_SCHEMA AS `database`,
TABLE_NAME AS `table`
FROM `information_schema`.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND TABLE_NAME IN
(
'log_customer',
'log_visitor',
'log_visitor_info',
'log_url',
'log_url_info',
'log_quote'
)
ORDER BY TABLE_SCHEMA, TABLE_NAME;
-- declare NOT FOUND handler
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET done = 1;
OPEN log_table_list;
log_table: LOOP
FETCH log_table_list INTO dbname, tbname;
IF done = 1 THEN
LEAVE log_table;
END IF;
-- create an appropriate text string for a DDL or other SQL statement
SET #s = CONCAT('SELECT COUNT(*) AS num FROM ',dbname,'.',tbname);
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP log_table;
CLOSE log_table_list;
END$$
DELIMITER ;
You run this by issuing the SQL command
CALL zap_magento_logs();
A PHP approach would be:
$tables = array(
'log_customer',
'log_visitor',
'log_visitor_info',
'log_url',
'log_url_info',
'log_quote',
'report_viewed_product_index',
'report_compared_product_index',
'report_event',
'catalog_compare_item',
);
$dbh = new PDO('mysql:host=localhost;', 'USERNAME', 'PASSWORD', array(
PDO::ATTR_PERSISTENT => true
));
$sql = $dbh->query('SHOW DATABASES');
$getAllDbs = $sql->fetchALL(PDO::FETCH_ASSOC);
foreach ($getAllDbs as $DB) {
foreach ($tables as $table) {
$dbh->query('TRUNCATE TABLE ' . $DB['Database'] . '.' . $table);
};
};
I didn't feel like writing code to solve this so I found a different solution. I wrote SQL that generates the SQL that I need. So I saved the following to a file called createSomeSQL.sql:
SET sql_mode='PIPES_AS_CONCAT';
select
'truncate table ' || dbs.database || '.someLogTable;'
as ''
from (SELECT DISTINCT SCHEMA_NAME AS `database`
FROM information_schema.SCHEMATA
WHERE SCHEMA_NAME NOT IN ('information_schema', 'performance_schema', 'mysql', 'test')
ORDER BY SCHEMA_NAME) as dbs;
You could replace the SQL in line 4 with anything you want. Then I ran this command to generate the SQL that I need:
mysql -u root -p < createSomeSQL.sql > sqlToExecute.sql
Replace "root" with your username, of course. Now the file sqlToExecute.sql contains a script you can run to execute that SQL against all your databases.
Try the following (very basic, no error handling, may not work at all, I've not tested this):
$db = mysqli_connect(); // your database connection
$tables = ["log_customer", "log_visitor", "log_visitor_info"]; // array with all the tables
foreach ($tables as $table) {
mysqli_query($db, "TRUNCATE TABLE `".$table."`"); // executes query for each element in the array
}
Related
I'm trying to update a column (in this case, a date) that is present on most of the tables on my database. Sadly, my database has more than 100 tables already created and full of information. Is there any way to loop through them and just use:
UPDATE SET date = '2016-04-20' WHERE name = 'Example'
on the loop?
One painless option would be to create a query which generates the UPDATE statements you want to run on all the tables:
SELECT CONCAT('UPDATE ', a.table_name, ' SET date = "2016-04-20" WHERE name = "Example";')
FROM information_schema.tables a
WHERE a.table_schema = 'YourDBNameHere'
You can copy the output from this query, paste it in the query editor, and run it.
Update:
As #PaulSpiegel pointed out, the above solution might be inconvenient if one be using an editor such as HeidiSQL, because it would require manually copying each record in the result set. Employing a trick using GROUP_CONCAT() would give a single string containing every desired UPDATE query in it:
SELECT GROUP_CONCAT(t.query SEPARATOR '; ')
FROM
(
SELECT CONCAT('UPDATE ', a.table_name,
' SET date = "2016-04-20" WHERE name = "Example";') AS query,
'1' AS id
FROM information_schema.tables a
WHERE a.table_schema = 'YourDBNameHere'
) t
GROUP BY t.id
You can use SHOW TABLES command to list all tables in database. Next you can check if column presented in table with SHOW COLUMNS command. It can be used this way:
SHOW COLUMNS FROM `table_name` LIKE `column_name`
If this query returns result, then column exists and you can perform UPDATE query on it.
Update
You can check this procedure on sqlfiddle.
CREATE PROCEDURE UpdateTables (IN WhereColumn VARCHAR(10),
IN WhereValue VARCHAR(10),
IN UpdateColumn VARCHAR(10),
IN UpdateValue VARCHAR(10))
BEGIN
DECLARE Finished BOOL DEFAULT FALSE;
DECLARE TableName VARCHAR(10);
DECLARE TablesCursor CURSOR FOR
SELECT c1.TABLE_NAME
FROM INFORMATION_SCHEMA.COLUMNS c1
JOIN INFORMATION_SCHEMA.COLUMNS c2 ON (c1.TABLE_SCHEMA = c2.TABLE_SCHEMA AND c1.TABLE_NAME = c2.TABLE_NAME)
WHERE c1.TABLE_SCHEMA = DATABASE()
AND c1.COLUMN_NAME = WhereColumn
AND c2.COLUMN_NAME = UpdateColumn;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET Finished = TRUE;
OPEN TablesCursor;
MainLoop: LOOP
FETCH TablesCursor INTO TableName;
IF Finished THEN
LEAVE MainLoop;
END IF;
SET #queryText = CONCAT('UPDATE ', TableName, ' SET ', UpdateColumn, '=', QUOTE(UpdateValue), ' WHERE ', WhereColumn, '=', QUOTE(WhereValue));
PREPARE updateQuery FROM #queryText;
EXECUTE updateQuery;
DEALLOCATE PREPARE updateQuery;
END LOOP;
CLOSE TablesCursor;
END
This is just an example how to iterate through all tables in database and perform some action with them. Procedure can be changed according to your needs.
Assuming you are using MySQL, You can use Stored Procedure.
This post is a very helpful.
Mysql-loop-through-tables
I have a database, let's simply call it 'db', on my computer, with a few tables that have multiple columns and data inside those tables.
I have a software using this database to store configuration elements and some other stuff.
Now, I am releasing a new version of my software, with only slight modifications in the database, i.e. some columns may have been added to tables, or removed (but no column renamed).
I must keep all data, so I would like to transfer it to the new "version" of my database.
What I thought of :
Rename 'db' into 'db_old'.
Install the new database as 'db_new', with the default values in the new columns
For each table, get a list of all the columns from 'db_old' that are present in 'db_new'
Use a INSERT INTO ... SELECT to put that old stuff back into 'db_new'.
drop the old db and use my new db.
Do you think it can work ? Do you have any easy solution ?
Also, I'm absolutely not an SQL expert... And I tried this (without looking if the column has been removed or not yet) :
SELECT
GROUP_CONCAT(COLUMN_NAME
SEPARATOR ',')
INTO #colList FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_SCHEMA = 'db_old'
AND TABLE_NAME = 'configuration';
INSERT
INTO db_new.configuration (SELECT #colList)
SELECT #colList FROM
db_old.configuration;
But it fails on replacing the second #colList by the effective list... Can you also help me on this issue ?
Thank you everyone and have a nice day !
You should first take a dump of your DB Database and create a .sql file. Depending upon on your DB Data, this file can even go in GBs. This SQL File will contain all your tables and all the data inside those tables. I will suggest you open and see the file.
Then you should use this new created file and use it to import all the data into new DB. It will put all those tables, data into this new DB.
Here is how to do that. First create SQL file:
mysqldump -h [SeverIpAddress] -u [UserName] -p[password] YourDbname > db_backup.sql
Use -h [SeverIpAddress] in case of Remote severs. In case, it resdies in your own system, you don't need to use this.
Then You should create your new DB, lets say DB_new. once created, switch to it using use command.
use DB_new
Once done, now import your .SQl file that we have created before using source command.
source YourSQLFilePath
In your case, source db_backup.sql
OK. If anyone ever encounter the same problem, here is the solution.
First, admit you have a database called 'myDatabase', with a table called 'myTable' that you want to "upgrade", i.e. you want to modify the table structure by adding/removing columns but keep the data inside.
First step is to drop foreign keys (if any) and to rename "myTable" :
USE `myDatabase`;
ALTER TABLE `myTable` DROP FOREIGN KEY `my_fk_constraint`;
ALTER TABLE `myTable` RENAME TO `old_myTable`;
Second step is to import the new table structure, by using SOURCE for example.
SOURCE C:/new_table_structure.sql
Third step is optional, but you may need this if your table has a lot of columns :
USE `myDatabase`;
SET GLOBAL group_concat_max_len = 4294967295;
Fourth step is to store the following routine :
delimiter //
DROP PROCEDURE IF EXISTS updateConf//
CREATE PROCEDURE updateConf(IN dbName TEXT, IN old_table TEXT, IN new_table TEXT, IN primary_key_name TEXT)
BEGIN
-- get column count in old table
SELECT count(*)
INTO #colNb
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = dbName
AND TABLE_NAME = old_table;
-- get string with all column names from old_table
SELECT GROUP_CONCAT(COLUMN_NAME)
INTO #colNames1
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = dbName
AND TABLE_NAME = old_table;
SET #colNames1 = CONCAT(#colNames1, ',');
-- get string with all column names from new_table
SELECT GROUP_CONCAT(COLUMN_NAME)
INTO #colNames2
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = dbName
AND TABLE_NAME = new_table;
-- variables initialization
SET #cpt = 1; -- column number counter
SET #pos = 1; -- position of column name first char
SET #vir = 1; -- next comma position
-- start of loop
label: LOOP
IF #cpt <= #colNb THEN
SET #vir = LOCATE(',',#colNames1,#pos); -- localize next comma
SET #colName = SUBSTRING(#colNames1, #pos, #vir - #pos); -- get column name
SET #pos = #vir + 1; -- update next column position
-- if column is in both tables
IF FIND_IN_SET(#colName, #colNames2) AND #colName != primary_key_name THEN
SET #execut = CONCAT("INSERT INTO ", new_table, " (", primary_key_name, ",", #colName, ") SELECT ", primary_key_name, ",", #colName, " FROM ", old_table, " ON DUPLICATE KEY UPDATE ", new_table, ".", #colName, " = ", old_table, ".", #colName);
PREPARE stmt FROM #execut;
EXECUTE stmt;
END IF;
SET #cpt = #cpt + 1; -- counter increment
-- when all columns parsed
ELSE
LEAVE label; -- end of loop
END IF;
END LOOP label;
END //
delimiter ;
Final step is to call the procedure on tables, and to drop the temporary table:
CALL updateConf( 'myDatabase', 'old_myTable', 'myTable', 'primaryKeyName' );
DROP TABLE `old_myTable`;
And voila ! Just don't forget to put back the foreign keys you dropped :)
It surely can be done in better ways, but i got this to work correctly.
Thank you everyone !
I have a table with a lot of fields about a person and then several recommendations of other people.
They are named:
"recommendation_1_name" "recommendation_1_company" 'recommendation_1_contact"
"recommendation_2_name" "recommendation_2_company" "recommendation_2_contact"
and so on.
I am trying to come up with a statement that allows me to only get the recommendations.
I imported an excel file into the table so it's just one large table.
This is what I have and it is returning an Empty set.
select * from questionnaire where 'COLUMN_NAME' like '%recommendation%';
I've been playing around with it making a table with only the recommendation fields and it still doesn't return anything.
Mysql: select recommendation_1_name, recommendation_2_name etc... from (table) where (USER) = (USERID) or however you can uniquely identify that user.
This Query generates you dynamic a SELECT query with all fields like 'recommendation%'. You only must setup the Databasename, and the Tablename. You can directly query the result of my query or add the WHERE clause.
SELECT
CONCAT( 'SELECT ',
GROUP_CONCAT(COLUMN_NAME SEPARATOR ',\n')
)
FROM information_schema.columns
WHERE TABLE_SCHEMA = 'DBNAME'
AND TABLE_NAME = 'TABLENAME'
AND COLUMN_NAME LIKE 'recommendation%';
You really need to normalize your schema.
But just as an experiment and example for some other cases (maybe somebody really need it). Here is solution to get this case resolved using stored procedure:
CREATE PROCEDURE `get_recommendations`()
BEGIN
DECLARE Q VARCHAR(100);
DECLARE C_NAME VARCHAR(100);
DECLARE cur CURSOR FOR SELECT GROUP_CONCAT(column_name) as `columns`
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'test'
AND TABLE_NAME ='questionnaire'
AND COLUMN_NAME LIKE '%recommendation%'
;
SET Q = 'SELECT ';
OPEN cur;
FETCH cur INTO C_NAME;
SET Q = CONCAT(Q,C_NAME,' ');
CLOSE cur;
SET #Q = CONCAT(Q,'FROM questionnaire;');
PREPARE stmt FROM #Q;
EXECUTE stmt ;
END
Don't forget to replace TABLE_SCHEMA = 'test' with your real database name.
I have databases named company_abc, company_xyz, etc. Those company_* databases have all the same structure and they contain users table.
What I need to do is to aggregate all users data from just company_* databases and replicate this view to another server. The view would just be something like
COMPANY NAME | USERNAME
abc | user#email.com
abc | user1#email.com
xyz | user2#email.com
company3 | user3#email.com
Is something like that possible in MySQL?
The databases are created dynamically, as well as the users so I can't create a view with just a static set of databases.
As you say you want to create view with dynamic database names - so the result you want to achieve is not possible in current versions of mysql.
So you have example following options:
Option 1
If you want to get result of all databases users tables you could define a stored procedure that uses prepared statement. This procedure needs parameter db_prefix what in your case is company_%. Basicly this procedure selects all tables named as users from information_schema when database name is like db_prefix parameter value. After that it loops through results and creates query string as union all users tables and executes this query. When creating a query string i also add field called source, so i can identify from what database this result is coming. In my example my databases are all in default collation utf8_unicode_ci.
In this case you can define procedure example "getAllUsers"
-- Dumping structure for procedure company_abc1.getAllUsers
DELIMITER //
CREATE DEFINER=`root`#`localhost` PROCEDURE `getAllUsers`(IN `db_prefix` TEXT)
DETERMINISTIC
COMMENT 'test'
BEGIN
DECLARE qStr TEXT DEFAULT '';
DECLARE cursor_VAL VARCHAR(255) DEFAULT '';
DECLARE done INTEGER DEFAULT 0;
DECLARE cursor_i CURSOR FOR SELECT DISTINCT (table_schema) FROM information_schema.tables WHERE table_name = 'users' AND table_schema LIKE db_prefix COLLATE utf8_unicode_ci;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cursor_i;
read_loop: LOOP
FETCH cursor_i INTO cursor_VAL;
IF done = 1 THEN
LEAVE read_loop;
END IF;
IF qStr != '' THEN
SET qStr = CONCAT(qStr, ' UNION ALL ');
END IF;
SET qStr = CONCAT(qStr, ' SELECT *, \'', cursor_VAL ,'\' as source FROM ', cursor_VAL, '.users');
END LOOP;
CLOSE cursor_i;
SET #qStr = qStr;
PREPARE stmt FROM #qStr;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #qStr = NULL;
END//
DELIMITER ;
Now you can get your all users result as:
CALL getAllUsers('company_%');
In my example database it results as:
id name source
1 User 1 company_abc1
2 User 2 company_abc1
3 User 3 company_abc1
1 User 1 company_abc2
2 User 2 company_abc2
3 User 3 company_abc2
1 User 1 company_abc3
2 User 2 company_abc3
3 User 3 company_abc3
1 User 1 company_abc4
2 User 2 company_abc4
3 User 3 company_abc4
1 User 1 company_abc5
2 User 2 company_abc5
3 User 3 company_abc5
Option 2
If you really, really need view then you can modify first procedure and instead of executeing select you can create view. Example like this:
-- Dumping structure for procedure company_abc1.createAllUsersView
DELIMITER //
CREATE DEFINER=`root`#`localhost` PROCEDURE `createAllUsersView`(IN `db_prefix` TEXT)
DETERMINISTIC
COMMENT 'test'
BEGIN
DECLARE qStr TEXT DEFAULT '';
DECLARE cursor_VAL VARCHAR(255) DEFAULT '';
DECLARE done INTEGER DEFAULT 0;
DECLARE cursor_i CURSOR FOR SELECT DISTINCT (table_schema) FROM information_schema.tables WHERE table_name = 'users' AND table_schema LIKE db_prefix COLLATE utf8_unicode_ci;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cursor_i;
read_loop: LOOP
FETCH cursor_i INTO cursor_VAL;
IF done = 1 THEN
LEAVE read_loop;
END IF;
IF qStr != '' THEN
SET qStr = CONCAT(qStr, ' UNION ALL ');
END IF;
SET qStr = CONCAT(qStr, ' SELECT *, \'', cursor_VAL ,'\' as source FROM ', cursor_VAL, '.users');
END LOOP;
CLOSE cursor_i;
SET #qStr = CONCAT('CREATE OR REPLACE VIEW allUsersView AS ', qStr);
PREPARE stmt FROM #qStr;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #qStr = NULL;
END//
DELIMITER ;
In this stored procedure we create/replace view called allUsersView, so basicly every time you will execute this procedure it will updates view.
In my test case it creates view like this:
CREATE OR REPLACE VIEW `allusersview` AS
SELECT *, 'company_abc1' as source FROM company_abc1.users
UNION ALL SELECT *, 'company_abc2' as source FROM company_abc2.users
UNION ALL SELECT *, 'company_abc3' as source FROM company_abc3.users
UNION ALL SELECT *, 'company_abc4' as source FROM company_abc4.users
UNION ALL SELECT *, 'company_abc5' as source FROM company_abc5.users ;
And now you can use view.
SELECT * FROM allusersview
And result is same as in first option.
All tested on:
Mysql 5.6.16
To find the list of database names:
SELECT SCHEMA_NAME
FROM information_schema.`SCHEMATA`
WHERE SCHEMA_NAME LIKE 'company%';
If you can code in something like PHP, the rest is pretty easy -- build a UNION of SELECTs from each database. But, if you must do it just in SQL...
To build the UNION, write a Stored Procedure. It will do the above query in a CURSOR. Inside the loop that walks through the cursor, CONCAT() a constructed SELECT onto a UNION you are building.
When the loop is finished, PREPARE and EXECUTE the constructed UNION. That will deliver something like the output example you had.
But, if you now need to INSERT the results of that into another server, you should leave the confines of the Stored Procedure and use some other language.
OK, OK, if you must stay in SQL, then you need some setup: Create a "Federated" table that connects to the other server. Now, in your SP, concatenate INSERT INTO fed_tbl in front of the UNION. Then the execute should do the entire task.
If you have trouble with the FEDERATED Engine, you may need to switch to FederatedX in MariaDB.
"The details are left as an exercise to the reader."
I already marked this as duplicate of Mysql union from multiple database tables
(SELECT *, 'abc' as COMPANY_NAME from company_abc.users)
union
(SELECT *, 'xyz' as COMPANY_NAME from company_xyz.users)
union
(SELECT *, 'company3' as COMPANY_NAME from company_company3.users)
...
I think that the only method to make this is to write a stored procedure that read all database and table name from information_schema.table, build a string with union select * from company_abc.users union all select * from company_xyz and then execute the command with prepared statement: http://dev.mysql.com/doc/refman/5.0/en/sql-syntax-prepared-statements.html
I need to delete all views from my MySQL database. How can I do that using query?
Can anyone can help me please?
I've been using this one:
/* DROP ALL VIEWS */
SET #views = NULL;
SELECT GROUP_CONCAT(table_schema, '.', table_name) INTO #views
FROM information_schema.views
WHERE table_schema = #database_name; -- Your DB name here
SET #views = IFNULL(CONCAT('DROP VIEW ', #views), 'SELECT "No Views"');
PREPARE stmt FROM #views;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Quoting from MySQL Reference Manual:
DROP VIEW [IF EXISTS]
view_name [, view_name] ...
[RESTRICT | CASCADE]
DROP VIEW removes one or more views. You must have the DROP privilege for each view. If any of the views named in the argument list do not exist, MySQL returns an error indicating by name which non-existing views it was unable to drop, but it also drops all of the views in the list that do exist.
The IF EXISTS clause prevents an error from occurring for views that don't exist. When this clause is given, a NOTE is generated for each nonexistent view. See Section 12.7.5.41, “SHOW WARNINGS Syntax”.
RESTRICT and CASCADE, if given, are parsed and ignored.
try this untested code
DECLARE VIEW_NAME VARCHAR(31);
DECLARE VIEW_NAMES CURSOR
FOR
SELECT table_name
FROM information_schema.views;
WHERE table_schema = 'DB_Name'
OPEN VIEW_NAMES;
REPEAT
FETCH VIEW_NAMES INTO VIEW_NAME;
DROP VIEW VIEW_NAME
UNTIL done END REPEAT;
CLOSE VIEW_NAMES;
END;
Here's a Ruby method that will do what you want:
# #param [Array] databases, e.g. ['db1', 'db2']
def drop_all_views(databases)
views = ActiveRecord::Base.connection.execute("SELECT CONCAT(TABLE_SCHEMA,'.',TABLE_NAME) FROM information_schema.TABLES WHERE TABLE_TYPE = 'VIEW' AND TABLE_SCHEMA IN('#{databases.join("', '")}');")
views = views.to_a.flatten
# Then drop all of those views from their respective databases
views.each do |v|
ActiveRecord::Base.connection.execute("DROP VIEW IF EXISTS #{v};")
end
end