I want to write a SELECT statement, where the tablename is based on the response to a different SELECT query. I can't use stacked queries, and I can only use MySQL.
As pseudo-code, this is what I'd like to do:
tablenamevariable = (SELECT 'tablename');
SELECT * FROM tablenamevariable;
Which should be equivalent to executing SELECT * FROM tablename (where the string tablename comes from the database).
What I have so far is the following, which executes successfully:
SELECT * FROM (SELECT 'tablename') AS x;
However, the result simply prints tablename (which isn't what I want).
The background is an SQL injection which upper-cases all input. So what I want to do is SELECT * FROM (SELECT CHAR([...] USING UTF8MB4)) to be able to select data from a table with lower-case characters in the name.
You can't use a string as an identifier in the same query.
A subquery or an expression can return a string, but not an identifier.
So your subquery like select ... from (select ...) as x doesn't work the way you think. It will not query from the table named by the string. It will query from a derived table which consists of the string value returned by the subquery.
mysql> select * from (select 'abc' as tablename) as x;
+-----------+
| tablename |
+-----------+
| abc |
+-----------+
The reason for this is that in SQL, all identifiers must be fixed at the time the query is parsed, before it evaluates any expressions. This is so the table names can be validated that the corresponding tables exist, and you have SQL privileges to read those tables.
Another reason is that if the subquery worked the way you expect, then there would be no way to simply query strings from a subquery without querying an hypothetical table named by those strings. Also what would you expect it to do if the subquery returned multiple columns or multiple rows?
You clarified in an edit that what you're trying to do is to query a table after your query is formatted with uppercase table names, regardless of how the table was defined.
Case-sensitivity of identifiers in MySQL is a bit complex, because MySQL has versions on different operating systems, some of which have case-sensitive filesystems and some have case-insensitive filesystems.
But the result is that in most cases, it doesn't matter that your table names are uppercase in your query. Table name comparisons are case-insensitive by default on an OS that has uses case-insensitive filesystems.
mysql> select * from mytable limit 1;
+----+-------+
| pk | name |
+----+-------+
| 3 | hello |
+----+-------+
mysql> select * from MYTABLE limit 1;
+----+-------+
| pk | name |
+----+-------+
| 3 | hello |
+----+-------+
mysql> select * from MyTable limit 1;
+----+-------+
| pk | name |
+----+-------+
| 3 | hello |
+----+-------+
(Test performed on MySQL 8.0.31 on MacOS)
On UNIX and Linux, the default is that table comparisons are case-sensitive. But there is an option to configure this if you want it to work in a case-insensitive manner on UNIX or Linux. You should read https://dev.mysql.com/doc/refman/8.0/en/identifier-case-sensitivity.html to understand how this works on different operating systems, and the option you can use to control it.
To do that you would need to use prepared statements:
set #t = 'tablename';
PREPARE stmt FROM concat('select * from ', #n);
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
It is highly unusual that you would need to do that though.
Related
I am evaluating a PHP/MySQL based software.
I want to look which tables affected when certain operations triggered.
After some googling, I was told that checksum table tbl_name can do the job. I just need to know how to use checksum for all the tables in the db.
To checksum all the tables one by one manually definitely not preferred as the database contains hundreds of tables.
Checksumming all tables seems like a lot of expensive calculation work just to detect which tables changed.
I'd suggest to get this information using the sys.schema_table_statistics table.
mysql> select table_schema, table_name, rows_fetched, rows_inserted, rows_updated, rows_deleted
from sys.schema_table_statistics where table_schema='test'
+--------------+---------------------+--------------+---------------+--------------+--------------+
| table_schema | table_name | rows_fetched | rows_inserted | rows_updated | rows_deleted |
+--------------+---------------------+--------------+---------------+--------------+--------------+
| test | sysbench_results | 870 | 144 | 0 | 0 |
+--------------+---------------------+--------------+---------------+--------------+--------------+
You probably want to reset the counters between your tests. Use sys.ps_truncate_all_tables()
mysql> call sys.ps_truncate_all_tables(FALSE);
+---------------------+
| summary |
+---------------------+
| Truncated 31 tables |
+---------------------+
mysql> select table_schema, table_name, rows_fetched, rows_inserted, rows_updated, rows_deleted
from sys.schema_table_statistics where table_schema='test';
+--------------+---------------------+--------------+---------------+--------------+--------------+
| table_schema | table_name | rows_fetched | rows_inserted | rows_updated | rows_deleted |
+--------------+---------------------+--------------+---------------+--------------+--------------+
| test | sysbench_results | 0 | 0 | 0 | 0 |
+--------------+---------------------+--------------+---------------+--------------+--------------+
The sys schema comes pre-installed in MySQL 5.7.
If you use MySQL 5.6, you may need to install it yourself. It's just an SQL script that creates some views into the performance_schema. Very easy to install.
You can get the sys schema here: https://github.com/mysql/mysql-sys
I want to look which tables affected when certain operations triggered.
What do you mean by this?
Do you know what operations have been triggered, and you're merely attempting to understand what effect they had on your database (e.g. to verify their correctness)? Or do you not know what operations have been triggered (e.g. during some interval) but you nevertheless want to understand how the database has changed, perhaps in an attempt to determine what those operations were?
There are very few situations where I would expect the best approach to be that which you are exploring (inspecting the database for changes). Instead, some form of logging—whether built-in to the RDBMS (such as MySQL's General Query Log or perhaps through triggers as suggested by Sumesh), or more likely at some higher level (e.g. within the accessing application)—would almost always be preferable. This leads me to lean toward thinking you have an XY Problem.
However, on the assumption that you really do want to identify the tables that have been modified since some last known good point in time, you can query the INFORMATION_SCHEMA.TABLES table, which contains not only the CHECKSUM for every table in the RDBMS but also other potentially useful information like UPDATE_TIME. So, for example, to identify all tables changed in the last five minutes one could do:
SELECT TABLE_SCHEMA, TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE UPDATE_TIME > NOW() - INTERVAL 5 MINUTE
You could generate the CHECKSUM statements for all tables:
SELECT CONCAT('CHECKSUM TABLE ', table_name, ';') AS statement
FROM information_schema.tables
WHERE table_schema = 'YourDBNameHere'
Then copy this output and paste it into Workbench or whatever tool you need to use. If you need to do this from within application (e.g. PHP) code, then you would probably have to use pure dynamic MySQL.
For those who came here for an answer how to get checksum for all the tables in one query (as it was in my case):
SET group_concat_max_len = CAST(
(
SELECT SUM(LENGTH(TABLE_NAME)) + COUNT(*) * LENGTH(', ')
FROM information_schema.tables WHERE `TABLE_SCHEMA` = 'your_database_name'
) AS UNSIGNED
);
SET #sql_command:= (
SELECT CONCAT(
'CHECKSUM TABLE ',
GROUP_CONCAT( TABLE_NAME ORDER BY `TABLE_NAME` SEPARATOR ', ' )
)
FROM information_schema.tables
WHERE `TABLE_SCHEMA` = 'your_database_name'
ORDER BY `TABLE_NAME`
);
PREPARE statement FROM #sql_command;
EXECUTE statement;
DEALLOCATE PREPARE statement;
The mere idea is to create CHECKSUM TABLE statement which include all tables names in it. So yes, it is some sort of little bit upgraded version of answer given by Tim Biegeleisen.
First we set maximum permitted result lenght for GROUP_CONCAT() function (which is 1024 bytes by default). It is calculated as number of symbols in all table names inculding the separator which will be putted between these names:
SET group_concat_max_len = CAST(
(
SELECT SUM(LENGTH(TABLE_NAME)) + COUNT(*) * LENGTH(', ')
FROM information_schema.tables WHERE `TABLE_SCHEMA` = 'your_database_name'
) AS UNSIGNED
);
Then we put all the tables names together in one CHECKSUM TABLE statement and store it in string variable:
SET #sql_command:= (
SELECT CONCAT(
'CHECKSUM TABLE ',
GROUP_CONCAT( TABLE_NAME ORDER BY `TABLE_NAME` SEPARATOR ', ' )
)
FROM information_schema.tables
WHERE `TABLE_SCHEMA` = 'your_database_name'
ORDER BY `TABLE_NAME`
);
And finally executing the statement to see the results:
PREPARE statement FROM #sql_command;
EXECUTE statement;
DEALLOCATE PREPARE statement;
Unfortunately you can't further manipulate with result set using MySQL statements only (i.e. insert to table or join with other result sets).
So if you require to do some comparisons you will eventually need to use additional code in your favorite programming language (or use capable software) to accomplish the task.
The question does not state using a shell script to accomplish things isn't allowed, so I'll post one such approach here (PHP is able to invoke shell scripts - see http://php.net/manual/en/function.shell-exec.php - if safe mode is not enabled):
If your script has shell access at its disposal and a checksum tool - like md5sum - one can also do something like this to collect checksums for each table:
#!/bin/bash
DATABASEPATH="/var/lib/mysql/yourdatabase"
cd "$DATABASEPATH" &&
for TABLEFILE in `ls -t *.ibd`; do
SUMANDTABLE=`md5sum "$TABLEFILE"`
echo "${SUMANDTABLE//.ibd}"
done
And optionally, if you don't want a checksum calculated for all tables, you could also check if the modification date of the "$TABLEFILE" is within range. If not, you just exit the script (the ls -t orders by modification date, descending).
To access modification date use something like e.g. stat -c %Y "$TABLEFILE". This would give you the modification date in seconds since Epoch.
To access current date, also in seconds since Epoch use: date +%s.
One can then subtract the modification date from the current date to establish how many seconds ago a "$TABLEFILE" has changed.
Another related method, which in some cases could apply, would be to save the ls -t *.ibd listing (without even calculating checksums, just store filenames in order), then start an operation and at the end of that operation check for difference in file listing with another execution of ls -t *.ibd.
I have created the following database:
CREATE TABLE QuizRepo (
User_ID BIGINT AUTO_INCREMENT PRIMARY KEY,
Name TEXT
)
I populate it via JDBC, and when I populate it, I get:
mysql> select * from QuizRepo;
| User_ID | Name |
| 1 | "XXQuiz"|
When I do the following command, it works as expected:
mysql> select * from QuizRepo where USER_ID=1;
User_ID | Name |
| 1 | "XXQuiz"|
However, when I do the following command, I get a weird result
mysql> select * from QuizRepo where Name="XXQuiz";
Empty set (0.01 sec)
Has anyone seen this happen before? How could this be possible? Perhaps I am adding it in the DB incorrectly (doesnt seem likely) but then you can clearly see there is an entry called "XXQuiz" so why is it not finding it?
It looks like you've stored the quotes as well. So you'll need to so a :
select * from QuizRepo where Name = "\"XXQuiz\"";
or similar.
Could be you name don't match exactly check for this and try also
select * from QuizRepo where Name like "%XXQuiz%";
"Alias" is probably the wrong word, since that's used in the context of referencing column/table names as something else in a Query.
What I'm interested in is if there's a way to give a column two names in the database. If I were to print such a table, it would look like this:
mysql> SELECT * FROM User;
+--------------------------+-----------------+
| id | username | uname | password | pswd |
+----+---------------------+-----------------+
| 0 | bob_jones#gmail.com | some_pw_hash |
| 1 | sue_smith#gmail.com | some_pw_hash |
+--------------------------------------------+
In this case, username and uname would be synonymous in query statements, as would password and pswd.
So the following statements would have the same output:
mysql> SELECT id, username FROM User;
...
mysql> SELECT id, uname FROM User;
...
I would like to avoid having to do something like
mysql> SELECT id, username AS uname FROM User;
So, does a feature like this exist?
Cheers,
Neil
No, this is not possible. To do so, you'd have to add a new, actual second column and use triggers to keep them in sync, which would be silly.
Just write your SQL properly to use the proper column names.
If you don't mind selecting from V_User instead of User, then views can get you what you need.
CREATE VIEW V_User AS
SELECT username as username,
username as uname
FROM User;
Then these 2 queries have the same result:
mysql> SELECT id, username FROM V_User;
...
mysql> SELECT id, uname FROM V_User;
...
As Ken points out, this is not precisely what was asked. But depending on the precise context, it might be just what's needed.
I wanna create temporary tables with random names in a MySQL stored procedure. I also need to store the name in order to access the table from another stored procedure. I was thinking of using MD5 hashes:
SELECT md5(RAND()+CURRENT_TIMESTAMP());
I wonder if this will generate totally collision free strings or not?
You could use uuid() and remove the dashes from the result... that is the closest thing I can think of that would give you anything reliably unique.
select concat("table_prefix_", replace(uuid(), '-', '')) as unique_name;
it would end up being like this:
mysql> select concat("table_prefix_",replace(uuid(), '-', '')) as unique_name;
+-----------------------------------------------+
| unique_name |
+-----------------------------------------------+
| table_prefix_39f14dd9418011e3bd86c0cb38cd4f18 |
+-----------------------------------------------+
1 row in set (0.00 sec)
Environment: MySQL 5.1, Linux
I have a stored procedure that computes several values from a single input value. The values are returned as OUT parameters. I would like to call this procedure for every row set and have the values appear as columns in the result set. The values have a complex relationship such that distinct functions for each value is not easily constructed.
The question: How can I get OUT parameters to show up as columns in a table?
Here's what I have so far:
DELIMITER $_$
DROP PROCEDURE IF EXISTS in_out;
CREATE PROCEDURE in_out (
IN s TEXT,
OUT op TEXT,
OUT opn INT,
OUT opd TEXT,
OUT len INT
)
BEGIN
SET op = 'del';
SET opn = 1;
SET opd = substr(s,4);
SET len = LENGTH(SUBSTR(s,4));
END
$_$
DELIMITER ;
Then:
mysql> call in_out('delACT',#op,#opn,#opd,#len);
Query OK, 0 rows affected (0.00 sec)
mysql> select #op,#opn,#opd,#len;
+------+------+------+------+
| #op | #opn | #opd | #len |
+------+------+------+------+
| snv | 1 | ACT | 3 |
+------+------+------+------+
1 row in set (0.00 sec)
So far so good, but I can't figure out how to call this procedure for every row and return the results in the result set. I want is something like this:
dream> select mycol,in_out(mycol) from mytable
+---------+------+------+------+------+
| mycol | #op | #opn | #opd | #len |
+---------+------+------+------+------+
| delACT | del | 1 | ACT | 3 |
+---------+------+------+------+------+
Thanks!
You confuse the stored procedures and stored functions:
stored function will be return a value, the results can be used in
expressions (like COS() and other mysql built-in functions).
stored procedure need use CALL , is an independent operation, can not
be used in expressions.
If you want to "select mycol,in_out(mycol) from mytable",you must:
CREATE FUNCTION in_out( ...
This appears to be a trick question: one can't create table relations out of function/procedure results in MySQL. I ended up refactoring into separate functions (as suggested by Michał). I had been hoping for a MySQL equivalent to PostgreSQL's table functions (http://goo.gl/77QVE).
I'd recommend to prepare data in stored procedure for each possible value in:
select distinct mycol
from mytable
where <... condition that you would use anyway in final result ...>
where mycol is your parameter for stored procedure save it to temporary table and than join to this values.
-- way the temp table may look in your sp
create temporary table tmptable (
mycol text
op text,
opn int,
opd text,
len int
)
after that use join:
select m.mycol, t.op, t.opn, t.opd, t.len
from mytable m
join tmptable t on m.mycol = t.mycol
where <... condition that you would use anyway in final result ...>
Bit different question, are you absolutely sure that there is no different way to process your final result than using a stored procedure?