1

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.

Elwhis
  • 1,241
  • 2
  • 23
  • 45
  • possible duplicate of [Mysql union from multiple database tables](http://stackoverflow.com/questions/10089666/mysql-union-from-multiple-database-tables) – Alex Feb 23 '15 at 15:14

4 Answers4

3

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
Aivar
  • 2,029
  • 20
  • 18
1

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."

Rick James
  • 135,179
  • 13
  • 127
  • 222
0

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)
...
Community
  • 1
  • 1
Alex
  • 16,739
  • 1
  • 28
  • 51
  • This is exactly what doesn't work for me. As I have stated, the company databases are created dynamically so I can't define something like that because i practically don't know which databases are there. In addition to that, I need to replicate that to different server. – Elwhis Feb 23 '15 at 15:35
  • but do you have a place where you store companies name? and if you need to synchronize databases - that is absolutely another topic, and you don't need this view at all. so what is your real goal? – Alex Feb 23 '15 at 15:38
  • My real goal is to have a table with the structure specified in the question, but on a completely different server. I do really have a table where I store companies' names. Do you know any better sollution how to achieve this? – Elwhis Feb 23 '15 at 15:45
0

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

bdn02
  • 1,500
  • 9
  • 15