MYSQL variable tablename from value - mysql

I'm trying to get my head around the following sql problem:
I have an ACTIONS table that contains the following:
------------------------------------
| ACTIONS |
|----------------------------------|
| ID |
| GROUP_ID |
| TABLENAME |
| FEATURE_ID |
------------------------------------
And a bunch of tables that look like this:
------------------------------------
| GRASS or SAND or ... |
|----------------------------------|
| FEATURE_ID |
| POSITION |
|+(more columns depending on table)|
------------------------------------
Now the ACTIONS.TABLENAME points to a certain table (for example: GRASS or SAND or ...)
All these tables have a column called position
I would now like to query all actions from the ACTIONS table with their respective POSITION values.
How can i tell the query to go and look for the position values in their correct tables?
Thank you for pointing me in the right direction!
Max

You cannot do this directly as Mysql does not support joining to a variable table name.
The 2 solutions are to either generate the SQL dynamically (either in your scripting language or in an sql procedure) if you know that you will be joining to a particular link table, or building up a unioned query with one sub query per table.
SELECT actions.id, actions.group_id, actions.tablename, actions.feature_id, grass.position
FROM actions
INNER JOIN grass
ON actions.feature_id = grass.feature_id
WHERE actions.tablename = 'grass'
UNION
SELECT actions.id, actions.group_id, actions.tablename, actions.feature_id, sand.position
FROM actions
INNER JOIN sand
ON actions.feature_id = sand.feature_id
WHERE actions.tablename = 'sand'
UNION
........

You can use CONCAT & PREPARE for achieving what you asked for
Check this: MySQL Prepare
(search for 'how to choose the table on which to perform a query at runtime, by storing the name of the table as a user variable')
mysql> USE test;
mysql> CREATE TABLE t1 (a INT NOT NULL);
mysql> INSERT INTO t1 VALUES (4), (8), (11), (32), (80);
mysql> SET #table = 't1';
mysql> SET #s = CONCAT('SELECT * FROM ', #table);
mysql> PREPARE stmt3 FROM #s;
mysql> EXECUTE stmt3;
+----+
| a |
+----+
| 4 |
| 8 |
| 11 |
| 32 |
| 80 |
+----+
mysql> DEALLOCATE PREPARE stmt3;

Related

MariaDB JOIN tables from different databases based on column value

How do I use a column value as the database name to JOIN two tables from those two different databases?
I have already successfully joined two tables between two databases with a statically defined (second) database name:
SELECT *
FROM db1.table_a AS ta
INNER JOIN db2.table_b AS tb on (ta.db_table_name = b.user_id)
However where db2.table_b is in that query I need to somehow have the db2 instead be a value from the first table in the first database; the table name will be statically defined. All of the kind-of-related threads were totally useless and wildly convoluted.
Details: there is one common database and all of the other databases represent the same application but for different accounts. In order for all of the users on all of the different accounts to be able to interact with each other (e.g. database_2.accounts.user.43 (DB->Table->Column->ID (43)) the common database (db1 above) must not only store the id of the user but also the name of the database that must be joined.
To help visualize things:
Database: common
Database: db2
SELECT id, database_name
FROM common.table_a AS ct
INNER JOIN [database_name].table_b AS dn ON (ct.user_id = [database_name].users.id)
Visually the data returned should look something like this:
+----------+------------+----------+
| database | account_id | username |
+----------+------------+----------+
| db1 | 1 | John |
+----------+------------+----------+
| db2 | 1 | Sally |
+----------+------------+----------+
| db3 | 43 | John |
+----------+------------+----------+
| db4 | 1 | Sally |
+----------+------------+----------+
Then the HTML output should look something like this:
Comment from John from db1.
Comment from Sally from db2.
Comment from John from db3.
Comment from Sally from db4.
I can worry about ensuring visually that John from db1 and John from db3 (and Sally from db2 and Sally from db4) all four of which are different people in real life are represented as so. It's the dynamic aspect of selecting them based on the value of the column's value that contains the database name to be used to JOIN is all that matters.
Do you have hundreds of databases? That would be a 'bad' design. To discuss further, please explain why you have so many.
If you don't have many databases, but you need to dynamically pick which db, again, poor design; let's discuss further.
If you must do one of those, the hide it in Stored Routines (as P.Salmon almost suggested; his code needs some polishing) or in an application library (PHP, Java, whatever).
Otherwise, wherever you can say table_a, you can replace that with db1.table_a. In fact, you can see MySQl doing that: EXPLAIN EXTENDED SELECT ...; SHOW WARNINGS; Example:
mysql> EXPLAIN EXTENDED SELECT province FROM canada; SHOW WARNINGS;
+----+-------------+--------+-------+---------------+----------+---------+------+------+----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+-------+---------------+----------+---------+------+------+----------+-------------+
| 1 | SIMPLE | canada | index | NULL | province | 105 | NULL | 5484 | 100.00 | Using index |
+----+-------------+--------+-------+---------------+----------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
+-------+------+---------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+---------------------------------------------------------------------------------------+
| Note | 1003 | /* select#1 */ select `world`.`canada`.`province` AS `province` from `world`.`canada` |
+-------+------+---------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
In this example, the table canada was replaced by world.canada because world was the database.
All things being equal (and I bet they aren't) and assuming all schemas/dbs are on the same server you should be able to construct a simple dynamic sql statement.
so given
+----+------+
| id | name |
+----+------+
| 1 | aaa |
| 2 | bbb |
+----+------+
2 rows in set (0.00 sec)
Where name serves as proxy for db name we can first select all distinct names and create a sql statement unioning all the tables from all the dbs. Something like this.
SET #SQL =
(
SELECT GROUP_CONCAT(GCSTRING)
FROM
(
SELECT 'A' AS GC,CONCAT('SELECT ID,NAME FROM USERS U1 ',JSTRING,' ',DBNAME,' AS ',NAMEALIAS,' ON ',NAMEPREFIX,'.',USTRING) GCSTRING
FROM
(
SELECT DISTINCT 'JOIN ' AS JSTRING,NAME DBNAME ,
NAME AS NAMEALIAS, NAME AS NAMEPREFIX, 'TABLEB.USER_ID = UI.NAME UNION' USTRING
FROM USERS
) S
) T
GROUP BY GC
)
;
SET #SQL = REPLACE(#SQL,',',' ');
SET #SQL = SUBSTRING(#SQL,1,LENGTH(#SQL) - 5);
SET #SQL = CONCAT(#SQL,';');
SELECT #SQL
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| #SQL |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| SELECT ID NAME FROM USERS U1 JOIN aaa AS aaa ON aaa.TABLEB.USER_ID = UI.NAME UNION SELECT ID NAME FROM USERS U1 JOIN bbb AS bbb ON bbb.TABLEB.USER_ID = UI.NAME ; |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

MySql - using dynamic table names in one query

I have the following tables:
cars
id
name
color
bicycles
id
name
number_of_gearshift
I need a central index table of this tables in my mysql database and a unique id for them. Something like this:
items
id
table_name
Lets say, the id in the items-table is the same as in the corresponding table:
items
id | table_name
1 | cars
2 | cars
3 | bicycles
4 | cars
cars
id | name | color
1 | Peugeot | red
2 | BMW | green
4 | Nissan | blue
bicycles
id | name | number_of_gearshift
3 | Stevens | 24
My question - the following situation:
I have the ID (for example XXX) of an item. Now I want to get the data of this item, by only one query. Something like this (I know, that will not work):
SELECT table2.*
FROM (SELECT table_name FROM items WHERE id = XXX) AS table2
Is it possible?
Use can use a dynamic sql query to achieve this.
set #query = null;
set #id = 3;/*change according to requirement*/
SET #tn := (select `table_name` from items where id = #id);
set #query = concat('select * from ',#tn,' where id = ',#id);
prepare stmt from #query;
execute stmt;
deallocate prepare stmt;
Change the value of #id according to your requirement.
SQL Fiddle

MySQL how to form query

I have 2 mysql tables I'm working with.
Table 1 has employee information columns :
employee_number, date_of_birth, name, address, etc.
Table 2 has pay information columns : employee_number (foreign key), date, bonus
My boss has requested a screen to show the data like, with dates listed horizontally across the top and employee listed vertically :
1/1/2000 | 1/1/2001 | 1/1/2002 | 1/1/2003
Bill $500 | $600 | $700 | $900
Ferdie $300 | $500 | $800 | $434
Tony $450 | $234 | $432 | $343
What is the easiest way to format my query so that the dataset is in this format?
What you need is a pivot table.
MySQL does not have a built in way to create a pivot table, but you can create it "by hand":
SQL Fiddle
MySQL 5.6 Schema Setup:
create table employees(
employee_number int primary key,
name varchar(50)
);
create table payments(
employee_number int,
bonus_date date,
bonus decimal(8,2)
);
insert into employees values
(1, 'Bill'), (2, 'Freddie'), (3, 'Tony');
insert into payments values
(1, '2000-1-1',500),(1, '2001-1-1',600),(1, '2002-1-1',700),(1, '2003-1-1',900),
(2, '2000-1-1',300),(2, '2001-1-1',500),(2, '2002-1-1',800),(2, '2003-1-1',434),
(3, '2000-1-1',450),(3, '2001-1-1',234),(3, '2002-1-1',432),(3, '2003-1-1',343);
Query 1:
-- 1. Prepare the column definition
select group_concat(distinct
concat(
"sum(case when bonus_date='", bonus_date, "' then bonus else 0 end) as `", bonus_date, "`"
)
)
into #sql
from payments
Results: No results
Query 2:
-- 2. Write the full query
set #sql = concat("select e.employee_number, e.name, ", #sql, " from employees as e inner join payments as p on e.employee_number = p.employee_number group by e.employee_number")
Results: No results
Query 3:
-- 3. Check the generated query (optional)
-- select #sql
Results: (uncomment the above query and check the result)
Query 4:
-- 4. Create a prepared statement using the query you've just created
prepare stmt from #sql
Results: No results
Query 5:
-- 5. Execute the prepared statement
execute stmt
Results:
| employee_number | name | 2000-01-01 | 2001-01-01 | 2002-01-01 | 2003-01-01 |
|-----------------|---------|------------|------------|------------|------------|
| 1 | Bill | 500 | 600 | 700 | 900 |
| 2 | Freddie | 300 | 500 | 800 | 434 |
| 3 | Tony | 450 | 234 | 432 | 343 |
Query 6:
-- 6. When you're done, deallocate the prepared statement
deallocate prepare stmt
Results: No results
You may want to check my answer on a similar question.
Hope this helps.

Concat is not working in mysql inclause

Please help me, i try to fetch category list as like below, where category ids is passed in inclause, it returns two rows.
mysql> select c_id,c_name from category where c_id in (870,854);
+------+---------------+
| c_id | c_name |
+------+---------------+
| 854 | Telugu |
| 870 | Telugu Events |
+------+---------------+
Whereas same category id is concatenated and passed to inclause as parameter, but its returning only one row insted of two rows.
mysql> select c_id,c_name from category where c_id in (select concat(870,',',854) as c_id);
+------+---------------+
| c_id | c_name |
+------+---------------+
| 870 | Telugu Events |
+------+---------------+
Please clarify me.
Thanks.
Not sure why you want to use concat in the in-clause, this is needed when you have some dynamic data, and for this you need to use dynamic query using prepare
Here how it is done
set #c_id := concat(870,',',854);
set #qry = concat("select * from category where c_id in (",#c_id,")");
prepare stmt from #qry;
execute stmt;
DEMO
I found the solution,
SELECT c_id, c_name
FROM category
WHERE FIND_IN_SET(c_id, (SELECT CONCAT(870,',',854) AS c_id))
Thanks.

MySQL accessing two tables in single query

Here's a simplified version of my troubles. 3 tables, the first (transit) will be used in upcoming procedures and functions, the second (products) will hold stationary data about products, the third (userWatchList) will hold user-specific data related to products.
TABLE: transit
+---------+------+
| ranking | data |
+---------+------+
| | |
+---------+------+
TABLE: products
+----+------+-----------------+
| ID | data | importantnumber |
+----+------+-----------------+
| 1 | c | 10 |
| 2 | u | 20 |
| 3 | t | 20 |
| 4 | u | 40 |
+----+------+-----------------+
TABLE: userWatchList
+---------+----+
| ranking | ID |
+---------+----+
| 1 | 2 |
| 2 | 1 |
| 3 | 4 |
| 4 | 3 |
+---------+----+
I need to insert into "transit" the data and ranking of rows that are within the needed ranking range and the data of which meets certain requirements.
I now want the ranking and data of a product, that has an importantnumber value of 20.
Say the allowed ranking range was between 1 and 2, SELECT * FROM transit at the end of the desired process would output:
+---------+------+
| ranking | data |
+---------+------+
| 1 | 'u' |
+---------+------+
Say the allowed ranking range was between 1 and 3, SELECT * FROM transit at the end of the desired process would output:
+---------+------+
| ranking | data |
+---------+------+
| 1 | 'u' |
| 4 | 't' |
+---------+------+
My vision of a possible solution...
To make sure the ranking falls within the needed range, I thought I might use dynamic SQL:
SET #IDsRetrieveStmt = CONCAT("SELECT group_concat(ID SEPARATOR ',') INTO #IDsStr FROM userWatchList WHERE ranking BETWEEN ', #rankingmin,' AND ', #rankingmax,';');
PREPARE stmt FROM #IDsRetrieveStmt;
EXECUTE stmt;
Now. To add ranking value to those fields... what should i do? I imagine one option is somewhere along the lines of:
SET #fetch_data_stmt = CONCAT('INSERT INTO transit (data, ranking) SELECT data, ( **** ) FROM products WHERE ID IN ( ', #IDsStr, ') AND importantnumber=20;');
PREPARE stmt FROM #fetch_data_stmt;
EXECUTE stmt;
** some unknown magic here that fetches ranking from a row with the same ID from 'products' table. This could be SELECT ranking FROM userWatchList WHERE ID=ID, but as you see, the ID part will probably create a conflict. Also, it seems a bit ineffective to run a new SELECT query with every inserted row.
I am sure there is a more effective way of doing this that I haven't heard of yet.
What's the best way of achieving this? Thanks in advance!
The first, and most important, part of the answer is the query that generates the data you want. You need to join the two tables together and use your criteria as conditions in the query:
select ranking, data
from userWatchList u
join product p on p.ID = u.ID
where ranking between ? and ?
and importantnumber = ?
Of course substituting ? with your criteria.
The next part of the answer is more advice. Unless there's an extremely compelling reason to do so, don't create a table to hold the data output from this query, because it's derived data that is out of date the instant it's created, unless you put in complicated database infrastructure (triggers) to keep it fresh.
Instead, create a view, that's like a table to a client (an application), but is actually a query under the hood:
create view transit as
select ranking, data, importantnumber
from userWatchList u
join product p on p.ID = u.ID
Then to use:
select ranking, data
from transit
where ranking between ? and ?
and importantnumber = ?