MySQL create view across all prefixed databases' table - mysql

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

Related

Why is the cursor exiting after one iteration?

I have a table that lists tables that I want to get counts off of:
|dq_tbl_id|dq_tbl_tbl_name |dq_tbl_use_batch_stamp|
|---------|--------------------------|----------------------|
|1 |dev_credly_badges |1 |
|... |... |... |
|18 |tbl18 |[NULL] |
I can't figure out why the procedure exits after one iteration:
CREATE DEFINER=`channel`#`%` PROCEDURE `dq_job_dev_count`()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE proc_dq_tbl_id SMALLINT;
DECLARE tbl_count INT DEFAULT 0;
DECLARE tbl_name VARCHAR(35);
DECLARE dq_tbl_use_batch_stamp BOOL DEFAULT FALSE;
DECLARE dq_tbl_ids
CURSOR FOR
SELECT dq_tbl_id FROM ref_dq_tbl_count;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN dq_tbl_ids;
WHILE done = 0 DO
FETCH NEXT FROM dq_tbl_ids INTO proc_dq_tbl_id;
IF done = 0 THEN
SELECT dq_tbl_tbl_name, dq_tbl_use_batch_stamp INTO #tbl_name, #dq_tbl_use_batch_stamp FROM ref_dq_tbl_count WHERE dq_tbl_id = #proc_dq_tbl_id;
IF #dq_tbl_use_batch_stamp = 1 THEN
SET #sql_stmt = CONCAT('SELECT COUNT(*) INTO #tbl_count FROM ', #tbl_name, ' WHERE DATE(batch_stamp) = CURDATE()');
ELSE
SET #sql_stmt = CONCAT('SELECT COUNT(*) INTO #tbl_count FROM ', #tbl_name);
END IF;
PREPARE stmt FROM #sql_stmt;
EXECUTE stmt;
INSERT INTO prod_dq_tbl_load_counts (tbl_name, row_count) VALUES (#tbl_name, #tbl_count);
END IF;
END WHILE;
CLOSE dq_tbl_ids;
END
You are fetching the cursor result into a local variable:
FETCH NEXT FROM dq_tbl_ids INTO proc_dq_tbl_id;
Subsequently you use a user-defined variable #proc_dq_tbl_id under the mistaken assumption that it contains the value you fetched.
In MySQL stored procedures, a variable with the # sigil is a user-defined variable. It is literally a different variable than the local variable you created with DECLARE.
You can fetch the cursor into a user-defined variable (with the # sigil) or you can fetch the cursor into a local variable. But then you must use the same variable in subsequent expressions.
Since #proc_dq_tbl_id is likely to be NULL, the SELECT statement you use it in will never match any rows. Your cursor is probably looping plenty, but the SELECT statement in each loop iteration is finding no matches.
This treatment of variables in MySQL is different than other brands of SQL database (e.g. Microsoft SQL Server). It is sometimes confusing to developers who are accustomed to using other brands.

How to count values from the database table column?

I want to convert game tag data found here into a dimension table of a star schema.
But the problem is that steamspy_tag_data table is organised as such every column name is tag name of a game and one game can have multiple tags. For example, lets say I have game Warcraft3 with appid 30 it would be in a table like this.
appid|strategy|action|shooter|fantasy|
-----+--------+------+-------+-------
30 6345 1452 0 6340
Column value greater than 0 signifies amount of user votes that voted certain game to be of that game tag. For Warcraft3 game with appid 30 - 6345 users voted it classifies as strategy 1452 users it classifies as action, .. etc.
Some columns for example "abstract" (column) tag has almost all 0 throughout the whole column meaning almost no game uses that tag, so to simplify 372 columns with over 29k row value into something more compact I want to run a query that would count non-zero value per every tag column and put them in the new table "tagovi" for better visibility which columns(tags) have relatively low game usage count.
so far I came up with this:
CREATE DEFINER=`root`#`localhost` PROCEDURE `zbroji_tagove`()
BEGIN
DECLARE i INTEGER;
DECLARE total_row_count INTEGER;
DECLARE dummy VARCHAR(255);
DECLARE zbrojeno INTEGER;
DECLARE trenutna VARCHAR(255);
DECLARE kursor CURSOR FOR
SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_schema = 'sppi' AND table_name = 'steamspy_tag_data'
ORDER BY ordinal_position;
SELECT FOUND_ROWS() into total_row_count;
open kursor;
FETCH kursor into dummy;
SET i = 1;
ponavljanje: LOOP
IF i > total_row_count THEN
CLOSE kursor;
LEAVE ponavljanje;
END IF;
FETCH kursor INTO trenutna;
SET zbrojeno = 0;
SET zbrojeno = (SELECT COUNT(*) FROM steamspy_tag_data where trenutna <> 0);
INSERT INTO tagovi(kategorija,broj_igra)
VALUES (
(trenutna),(zbrojeno)
);
SET i = i + 1;
end LOOP;
END
New table tagovi has 3 columns (ID auto_increment, kategorija Varchar(255), broj_igra INTEGER).
When I execute my stored procedure "zbroji_tagove"() I get SQL ERROR CODE 1292; Truncated incorrect DOUBLE value 'some_tag_name'. So somehow sql treats variable value as value instead of column at line SET zbrojeno = (SELECT COUNT() FROM steamspy_tag_data where trenutna <> 0*);
Is there a way for me to accomplish what I want inside MySQL environment?
You can't use variables like that, you need a prepared statement
See
#sql = CONCAT("SELECT COUNT(*) INTO zbrojeno FROM steamspy_tag_data where ",trenutna," <> 0);";
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;

How to loop through all the tables on a database to update columns

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

MySQL Select columns where the column name contains a substring

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.

Check MySQL database for unique value over many tables

I'm looking for a way to easily check each table of a MySQL database and make sure that a certain field contains one value only. I have tables named Authors, Titles, Places, etc.
Each table contains a field called xuser and it needs to ask "does the field xuser contain the value xy in all records of all tables".
Can someone push me in the right direction how to do this with a SQL query if this is possible?
Thanks for reading, regards
Nico
I've created stored procedure which checks all table for provided db:
DELIMITER $$
DROP PROCEDURE IF EXISTS `UTL_CHECK_BACKUP_FOR_USER` $$
CREATE PROCEDURE `UTL_CHECK_BACKUP_FOR_USER`(
IN i_database_name VARCHAR(255),
IN i_user_column_name VARCHAR(255),
IN i_user_column_value VARCHAR(255),
OUT o_result TINYINT(1)
)
BEGIN
DECLARE v_table_name VARCHAR(255);
DECLARE v_last_row_fetched TINYINT(3) DEFAULT 0;
DECLARE tables_cursor CURSOR FOR
SELECT table_name
FROM information_schema.tables
WHERE table_schema = i_database_name
;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_last_row_fetched = 1;
SET v_last_row_fetched = 0;
OPEN tables_cursor;
SET #query =
CONCAT(
'SELECT SUM(IF(user_column=''',
i_user_column_value,
''', 1, -1)) = 1 INTO #o_result FROM ( SELECT ''test'' AS user_column FROM information_schema.tables WHERE 1<>1 '
)
;
table_loop: LOOP
FETCH tables_cursor INTO v_table_name;
IF (v_last_row_fetched = 1) THEN
LEAVE table_loop;
END IF;
SET #query =
CONCAT(
#query,
' UNION SELECT DISTINCT(',
i_user_column_name,
') AS user_column FROM ',
v_table_name
)
;
END LOOP table_loop;
CLOSE tables_cursor;
SET v_last_row_fetched=0;
SET #query =
CONCAT(
#query,
' ) all_xusers;'
)
;
PREPARE stmt FROM #query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET o_result = COALESCE(#o_result, 0);
END $$
DELIMITER ;
Just deploy this stored procedure to database.
And then it could be executed in the following way:
-- db_name, user_column_name, user_column_value, result
call UTL_CHECK_BACKUP_FOR_USER('test', 'xuser', 'xxx', #result);
select #result;
To get the rows from all three tables where xuser has the same value in all three tables you could use:
SELECT *
FROM authors a
JOIN titles t
ON t.xuser = a.xuser
JOIN places p
ON p.xuser = t.xuser
If you want to look at a specific xuser value you could add the following WHERE clause:
WHERE a.xuser = 'xy'
The first thing comes to my mind:
select sum(if(xuser='xxx', 1, -1)) = 1
from (
select distinct(xuser) from authors
union
select distinct(xuser) from titles
union
select distinct(xuser) from places
) all_xusers;
This will return 1 (true) if all tables contains records belonging ONLY to 'xxx' user. Otherwise (if there is no 'xxx' records or there is some other user records) it will return 0 (false).