Subquery in FROM clause using information_schema.tables - mysql

I'am trying to get all tables from a database where table name contains 'logs' and get the sum last value in each table of a column named flag.
Query I tried:
Select SUM(flag) FROM (SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'db_test' AND table_name like '%logs') as c ORDER BY id DESC Limit 1;
But I'am having an issue with the subquery, I think the whole query is wrong.

I have broken this down into baby steps - nothing to stop you adjusting to taste.
drop table if exists onelog,twolog;
create table onelog (id int,flag int);
create table twolog (id int,flag int);
insert into onelog values (1,10),(2,1);
insert into twolog values (1,20),(2,1);
set #sql =
(
select group_concat(
concat('select id,flag from '
,tname, ' where id = (select max(id) from ', tname, ') union all'
)
)
from
(
select table_name tname from information_schema.tables where table_name like '%log' and table_schema = 'sandbox'
) s
)
;
set #sql = substring(#sql,1, length(#sql) - 10);
set #sql = replace(#sql,'union all,','union all ');
set #sql = concat('select sum(flag) from (', #sql , ' ) s');
#select #sql;
prepare sqlstmt from #sql;
execute sqlstmt;
deallocate prepare sqlstmt;
+-----------+
| sum(flag) |
+-----------+
| 2 |
+-----------+
1 row in set (0.001 sec)

Related

Mode of each column in MySQL (without explicitly writing column names)

I need to write a MySQL query that returns the # of occurrences of the mode of each column individually without explicitly specifying the column names.
Assume the data is:
| apples | bananas | oranges |
| 4 | 4 | 3 |
| 2 | 2 | 1 |
| 4 | 3 | 5 |
| 3 | 3 | 5 |
| 4 | 1 | 5 |
The result I'm looking for is:
| mode | count |
| 4 | 3 |
| 3 | 2 |
| 5 | 3 |
To get mode for an individual column (apples):
SELECT apples AS mode, COUNT(*) AS Count
FROM tablename
GROUP BY apples
HAVING COUNT(*) >= ALL (SELECT COUNT(*) FROM tablename GROUP BY apples);
To return column names, I can perform the following:
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tablename'
Inefficiently and with explicit column names, I can achieve the result with UNION:
SELECT apples AS mode, COUNT(*) AS Count
FROM tablename
GROUP BY apples
HAVING COUNT(*) >= ALL (SELECT COUNT(*) FROM tablename GROUP BY apples)
UNION
SELECT bananas AS mode, COUNT(*) AS Count
FROM tablename
GROUP BY bananas
HAVING COUNT(*) >= ALL (SELECT COUNT(*) FROM tablename GROUP BY bananas)
UNION
SELECT oranges AS mode, COUNT(*) AS Count
FROM tablename
GROUP BY oranges
HAVING COUNT(*) >= ALL (SELECT COUNT(*) FROM tablename GROUP BY oranges)
I've been trying a bunch of different queries incorporating the two with GROUP BY and subqueries, but they are a mess, and I'm not making much headway. I have yet to generate a query to efficiently display the mode of each column with explicitly specifying column names (let alone using column names returned by the second query).
For example (doesn't feel like I'm even on the right track):
SELECT COUNT(*) AS count
FROM tablename
GROUP BY (
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tablename')
HAVING COUNT(*) >= ALL (SELECT COUNT(*) FROM tablename
GROUP BY (
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tablename'))
Thank you!!
It looks not possible to get the expected result using pure SQL in a single query, but it might build a dynamic one like below:
DROP TABLE IF EXISTS archives.sample_table;
CREATE TABLE archives.sample_table (
apples INT,
bananas INT,
oranges INT
);
INSERT INTO sample_table VALUES
( 4 , 4 , 3 ),
( 2 , 2 , 1 ),
( 4 , 3 , 5 ),
( 3 , 3 , 5 ),
( 4 , 1 , 5 )
;
SET SESSION group_concat_max_len = 9999999;
SET #table_name = 'sample_table';
SET #table_schema = 'archives';
WITH RECURSIVE column_info AS (
SELECT ORDINAL_POSITION column_index, COLUMN_NAME column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #table_name and TABLE_SCHEMA = #table_schema
),
query_text AS (
SELECT i.column_index, CONCAT('SELECT column_name, column_value, counts FROM ( SELECT ''', i.column_name, ''' column_name, `', i.column_name, '` column_value, COUNT(*) counts, RANK() OVER(ORDER BY COUNT(*) DESC) rk FROM ', #table_name, ' GROUP BY `', i.column_name, '` ) r WHERE rk = 1') single_query
FROM column_info i
WHERE i.column_index = 1
UNION ALL
SELECT i.column_index, CONCAT('SELECT column_name, column_value, counts FROM ( SELECT ''', i.column_name, ''' column_name, `', i.column_name, '` column_value, COUNT(*) counts, RANK() OVER(ORDER BY COUNT(*) DESC) rk FROM ', #table_name, ' GROUP BY `', i.column_name, '` ) r WHERE rk = 1') single_query
FROM query_text prev
JOIN column_info i ON prev.column_index + 1 = i.column_index
)
SELECT GROUP_CONCAT(single_query SEPARATOR ' UNION ALL ') INTO #stat_query
FROM query_text
;
PREPARE stmt FROM #stat_query
;
EXECUTE stmt
;
DEALLOCATE PREPARE stmt
;
Sample output
mysql> DROP TABLE IF EXISTS archives.sample_table;
Query OK, 0 rows affected (0.03 sec)
mysql> CREATE TABLE archives.sample_table ( apples INT, bananas INT, oranges INT);
Query OK, 0 rows affected (0.05 sec)
mysql> INSERT INTO sample_table VALUES ( 4 , 4 , 3 ),( 2 , 2 , 1 ),( 4 , 3 , 5 ),( 3 , 3 , 5 ),( 4 , 1 , 5 );
Query OK, 5 rows affected (0.01 sec)
Records: 5 Duplicates: 0 Warnings: 0
mysql> SET SESSION group_concat_max_len = 9999999;
Query OK, 0 rows affected (0.00 sec)
mysql> SET #table_name = 'sample_table';
Query OK, 0 rows affected (0.00 sec)
mysql> SET #table_schema = 'archives';
Query OK, 0 rows affected (0.00 sec)
mysql> WITH RECURSIVE column_info AS ( SELECT ORDINAL_POSITION column_index, COLUMN_NAME column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #table_name and TABLE_SCHEMA = #table_schema),query_text AS ( SELECT i.column_index, CONCAT('SELECT column_name, column_value, counts FROM ( SELECT ''', i.column_name, ''' column_name, `', i.column_name, '` column_value, COUNT(*) counts, RANK() OVER(ORDER BY COUNT(*) DESC) rk FROM ', #table_name, ' GROUP BY `', i.column_name, '` ) r WHERE rk = 1') single_query FROM column_info i WHERE i.column_index = 1 UNION ALL SELECT i.column_index, CONCAT('SELECT column_name, column_value, counts FROM ( SELECT ''', i.column_name, ''' column_name, `', i.column_name, '` column_value, COUNT(*) counts, RANK() OVER(ORDER BY COUNT(*) DESC) rk FROM ', #table_name, ' GROUP BY `', i.column_name, '` ) r WHERE rk = 1') single_query FROM query_text prev JOIN column_info i ON prev.column_index + 1 = i.column_index) SELECT GROUP_CONCAT(single_query SEPARATOR ' UNION ALL ') INTO #stat_query FROM query_text;
Query OK, 1 row affected (0.00 sec)
mysql> PREPARE stmt FROM #stat_query;
Query OK, 0 rows affected (0.00 sec)
Statement prepared
mysql> EXECUTE stmt;
+-------------+--------------+--------+
| column_name | column_value | counts |
+-------------+--------------+--------+
| apples | 4 | 3 |
| bananas | 3 | 2 |
| oranges | 5 | 3 |
+-------------+--------------+--------+
3 rows in set (0.00 sec)
mysql> DEALLOCATE PREPARE stmt;
Query OK, 0 rows affected (0.00 sec)
Per #bill-karwin and #shadow, any attempt to dynamically set the columns in a query are impossible, so I:
Created a procedure that generates the query text to produce the modes with UNIONs- it grabs the columns from INFORMATION_SCHEMA and then loops through the columns to create the query:
CREATE DEFINER=`root`#`localhost` PROCEDURE `column_modes`(OUT strsql TEXT)
BEGIN
SET #cols = (SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tablename');
SET #ct = (SELECT COUNT(*) FROM tablename);
SET #n = 0;
SET #sql = '';
WHILE #n < #cols DO
PREPARE stmt FROM "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'tablename' LIMIT ?, 1 INTO #col";
EXECUTE stmt USING #n;
DEALLOCATE PREPARE stmt;
SET #sbq = CONCAT('SELECT ',
#col,
' AS mode, COUNT(*)/',
#ct,
' AS count FROM tablename GROUP BY ',
#col,
' HAVING COUNT(*) >= ALL (SELECT COUNT(*) FROM tablename GROUP BY ',
#col,
')');
SET #sql = CONCAT(#sql,
"SELECT '",
#col,
"' as 'column', GROUP_CONCAT(mode SEPARATOR ',') as mode, GROUP_CONCAT(count SEPARATOR ',') as count FROM (",
#sbq,
') temp');
IF #n != (#cols - 1) THEN
SET #sql = CONCAT(#sql, ' UNION ');
END IF;
SET #n = #n + 1;
END WHILE;
-- SELECT #sql;
SET strsql = #sql;
END
Executed the query with PREPARE:
CALL column_modes(#thisvar);
SELECT #thisvar;
PREPARE stmt FROM #thisvar;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Thanks very much to #ProGu for a second solution as well.

mySQL query across multiple schemas

I have a multi-schema structure (same table across all schemas).
Here is how I can fetch all the schemas:
select table_schema from information_schema.tables where table_name = 'messages' and TABLE_TYPE = 'BASE TABLE'
This query is for a join between 'messages' table across two tables-
select *, '1' as customer from customer_1.messages union select *, '2' as customer from customer_2.messages
How do I use the perform the above query for all the schemas from the first query? Need not be a join. Could be anything that helps me return the results across all schemas.
Note: There could be 5000-10000 schemas
DELIMITER ##;
CREATE PROCEDURE join_all_data()
BEGIN
SET #counter := 0;
SELECT GROUP_CONCAT('select *, ',
#counter := #counter + 1,
' as customer from ',
table_schema,
'.messages'
SEPARATOR ' UNION ALL ')
FROM information_schema.tables
WHERE table_name = 'messages'
AND table_type = 'BASE TABLE'
INTO #sql;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
END
##;
DELIMITER ;
CALL join_all_data;

SESSION group_concat_max_len size is not enough, it gives a different error when changing the size

So I am making a stored procedure in MySQL that accepts an integer _prof_id. This code is for changing my rows to columns dynamically.
Here is my code
DELIMITER $$
CREATE PROCEDURE Get_Attendance(IN _profID INT)
BEGIN
SET SESSION group_concat_max_len = 18446744073709547520;
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(date = ''',
date,
''', status_description, NULL)) AS ',
date
)
) INTO #sql
FROM current_att_view;
SET #sql = CONCAT(
'SELECT a.professor_fname, a.professor_lname, ', #sql,
' FROM professor_tbl m JOIN current_att_view a
ON m.professor_id = a.professor_id
WHERE m.professor_id = ', _profID,' Group BY entity_ID');
PREPARE stmt FROM #sql;
EXECUTE stmt;
END$$
DELIMITER ;
so I have researched and found out that 18446744073709547520 is the maximum value for the group_concat_max_len and it cannot be unlimited. but when I call the procedure, it gives me an error
Mysql Error Image, which came from the 24th and 25th row of my table 24th and 25th row of table image. There are still plenty of rows to concatenate. What should I do? Or are there any other way in order for me to change the rows to columns dynamically?
so my current_att_view (Which is a created view) structure is current_att_view and the professor table structure is like this Professor Table and I want to generate a query that would make a table structure like this
Table Structure
When I tried my code for group concat, the output is shown as output
Here's a guess how a simplified version of your data looks
drop table if exists current_att_view,professor_tbl;
create table current_att_view
(attendanceid int auto_increment primary key,
entity_id int,
Professor_id int,
dte date,
status_description varchar(8));
create table professor_tbl
(professor_id int, professor_fname varchar(20), professor_lname varchar(20));
insert into current_att_view (entity_id ,
Professor_id ,
dte ,
status_description) values
(1,1,'2018-01-01','Present'),(1,1,'2018-01-02','Absent'),(1,1,'2018-01-03','Present'),
(2,1,'2018-01-01','Absent'),(2,1,'2018-01-02','Absent'),(2,1,'2018-01-03','Present');
insert into professor_tbl values
(1,'bob','smith'),(2,'fred','jones');
select p.professor_fname,p.professor_lname, dte,status_description
from professor_tbl p
join current_att_view a on a.professor_id = p.professor_id
This procedure
drop procedure if exists p;
DELIMITER $$
CREATE PROCEDURE p(IN _profID INT)
BEGIN
SET SESSION group_concat_max_len = 18446744073709547520;
#SET SESSION group_concat_max_len = 1024;
SET #sql = NULL;
set #sql = (
SELECT
GROUP_CONCAT(DISTINCT
CONCAT('MAX(IF(dte = ', char(39),dte,char(39), ', status_description , null)) AS ', char(39),dte,char(39))
)
from current_att_view
);
select length(#sql);
SET #sql = CONCAT(
'SELECT m.professor_lname,m.professor_fname,entity_id ,', #sql,
' FROM professor_tbl m JOIN current_att_view a
ON m.professor_id = a.professor_id
WHERE m.professor_id = ', _profID,' Group BY m.professor_lname,m.professor_fname,entity_id ');
#set #sql = concat('select ',#sql,' from attendance;');
PREPARE stmt FROM #sql;
EXECUTE stmt;
deallocate prepare stmt;
END$$
DELIMITER ;
Generates this query
SELECT m.name,entity_id ,MAX(IF(dte = '2018-01-01', status_description , null)) AS '2018-01-01',
MAX(IF(dte = '2018-01-02', status_description , null)) AS '2018-01-02',
MAX(IF(dte = '2018-01-03', status_description , null)) AS '2018-01-03'
FROM professor_tbl m
JOIN current_att_view a ON m.id = a.professor_id
WHERE m.id = 1 Group BY m.name, a.entity_ID;
Which produces this result (as expected)
-----------------+-----------------+-----------+------------+------------+------------+
| professor_lname | professor_fname | entity_id | 2018-01-01 | 2018-01-02 | 2018-01-03 |
+-----------------+-----------------+-----------+------------+------------+------------+
| smith | bob | 1 | Present | Absent | Present |
| smith | bob | 2 | Absent | Absent | Present |
+-----------------+-----------------+-----------+------------+------------+------------+
2 rows in set (0.01 sec)
I don't think I can help you any further.
Not an answer more an extended comment.This code works fine for every day in 2016-2017 (albeit very slowly) ie 730 days which seems to be more than you would need. I think this proves there is no group_concat truncation on my 64bit box where max_allowed_packed is default 4194304 at least.
drop procedure if exists p;
DELIMITER $$
CREATE PROCEDURE p(IN _profID INT)
BEGIN
SET SESSION group_concat_max_len = 18446744073709547520;
#SET SESSION group_concat_max_len = 1024;
SET #sql = NULL;
set #sql = (
SELECT
GROUP_CONCAT(DISTINCT
CONCAT('MAX(IF(dte = ', char(39),dte,char(39), ', 1, 0)) AS ', char(39),dte,char(39))
)
from dates
where year(dte) in(2016,2017)
);
/*
SET #sql = CONCAT(
'SELECT a.professor_fname, a.professor_lname, ', #sql,
' FROM professor_tbl m JOIN current_att_view a
ON m.professor_id = a.professor_id
WHERE m.professor_id = ', _profID,' Group BY entity_ID');
*/
set #sql = concat('select ',#sql,' from dates;');
PREPARE stmt FROM #sql;
EXECUTE stmt;
deallocate prepare stmt;
END$$
DELIMITER ;
call p(1);

MYSQL How to UNION Multiple "Unknown" Tables

I have some History Tables with Structure of "TableNameYYMM"
How can I UNION all History Tables which Starts with "TableName"?
I need a search over all History Tables.
I tryed it with PREPARE Execute and DEALLOCATE.
But i get everytime a SQL Error.
SET group_concat_max_len = 2048;
SET #startQuery = (SELECT CONCAT('SELECT col1, col2, col3 FROM ',TABLE_SCHEMA,'.', TABLE_NAME)
FROM information_schema.tables
WHERE
ENGINE = 'MyISAM'
AND TABLE_NAME like 'hist%'
ORDER BY TABLE_NAME
LIMIT 0,1);
SET #subquery = (SELECT GROUP_CONCAT(#startquery, 'UNION ALL SELECT col1, col2, col3 FROM ',TABLE_SCHEMA,'.', TABLE_NAME)
FROM information_schema.tables
WHERE
ENGINE = 'MyISAM'
AND TABLE_NAME like 'hist%'
ORDER BY TABLE_NAME
LIMIT 1,1000);
PREPARE stmt1 FROM '? AS combinedTable';
EXECUTE stmt1 USING #subquery;
DEALLOCATE PREPARE stmt1;
On Part 1 (#startquery) i try to get the first part of the Query
"select xxx from table1"
On Part 2 (#subquery) I tried to get all unions (from table2-max 1000)
select xxx from table1
UNION ALL select xxx from table2
UNION ALL select xxx from table3
...
I hope someone have an idea about this problem.
/*
create table history1701 (id int,col1 int,col2 int);
create table history1702 (id int,col1 int,col2 int);
create table history1703 (id int,col1 int,col2 int);
create table history1704 (id int,col1 int,col2 int);
insert into history1701 values (1,1,1);
insert into history1702 values (2,2,2);
insert into history1703 values (3,3,3);
insert into history1704 values (4,4,4);
*/
SET #startQuery = (SELECT CONCAT('SELECT col1, col2 FROM ',TABLE_SCHEMA,'.', TABLE_NAME)
FROM information_schema.tables
WHERE
# ENGINE = 'MyISAM' AND
TABLE_NAME like 'hist%17%'
ORDER BY TABLE_NAME
LIMIT 0,1);
SET #subquery = (
SELECT group_CONCAT(' UNION ALL SELECT col1, col2 FROM ',TABLE_SCHEMA,'.', TABLE_NAME order by table_name separator ' ' )
FROM information_schema.tables
WHERE
TABLE_NAME like 'hist%17%'
and table_name <> (
select table_name from information_schema.tables WHERE
# ENGINE = 'MyISAM' AND
TABLE_NAME like 'hist%17%'
ORDER BY TABLE_NAME
LIMIT 0,1
)
);
select #startquery;
set #subquery = concat(#startquery,#subquery);
PREPARE stmt1 FROM #subquery;
EXECUTE stmt1 ;
DEALLOCATE PREPARE stmt1;
In your code The limit 1,1000 in the first set #subquery returns a null value (not good) and generates a comma after each union all (also not good). I have amended this to exlude the first history table and changed the group concat separator to a space and moved the order by to within the group_concat.

Select table from database where value is X

So far I've found out that this gives me a list of all the tables which has the column name "store_id" - but I only want it to select the columns if "store_id" = 4, how could I do this?
Right now I use this to find the tables which has the "store_id" column.
SELECT DISTINCT TABLE_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME IN ('store_id')
AND TABLE_SCHEMA='db1';
You can it with dynamic SQL like this
SET #sql = NULL;
SELECT GROUP_CONCAT(DISTINCT
CONCAT('SELECT ''', TABLE_NAME,
''' table_name FROM ', TABLE_NAME,
' WHERE store_id = 4')
SEPARATOR ' UNION ALL ')
INTO #sql
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME = 'store_id'
AND TABLE_SCHEMA = SCHEMA();
SET #sql = CONCAT(#sql, ' ORDER BY table_name');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Sample output:
| TABLE_NAME |
-------------|
| table1 |
Here is SQLFiddle demo
Now you can simplify things on calling end by wrapping it up into a stored procedure
DELIMITER $$
CREATE PROCEDURE list_tables(IN _column_name VARCHAR(64), IN _column_value VARCHAR(256))
BEGIN
SET #sql = NULL;
SELECT GROUP_CONCAT(DISTINCT
CONCAT('SELECT ''', TABLE_NAME, ''' table_name
FROM ', TABLE_NAME,
' WHERE ', _column_name, ' = ''', _column_value, '''')
SEPARATOR ' UNION ALL ')
INTO #sql
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME = _column_name
AND TABLE_SCHEMA = SCHEMA();
SET #sql = CONCAT(#sql, ' ORDER BY table_name');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END$$
DELIMITER ;
And use it like this
CALL list_tables('store_id', '4');
Here is SQLFiddle demo
Use IF clause in MySQL.
SELECT DISTINCT TABLE_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE
IF(store_id = 4)
COLUMN_NAME = store_id
END IF;
OR
You can use case statement.
SELECT DISTINCT TABLE_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE CASE WHEN store_id = 4
THEN COLUMN_NAME = store_id