Use sql result to specify table to join - mysql

Is there any way how can I use result for specifying table to join?
I'd like to do something like
SELECT id, some_number, ... FROM sometable NATURAL JOIN someothertable_$some_number;
I know that there's nothing like this in relational algebra, so probably I'll not succeed, I just wanted to ask to be sure.
I don't want to use any SQL scripts.

Runnable Example Here: http://sqlfiddle.com/#!2/5e92c/36
Code to setup tables for this example:
create table if not exists someTable
(
someTableId bigint not null auto_increment
, tableId int not null
, someOtherTableId bigint not null
, primary key (someTableId)
, index (tableId, someOtherTableId)
);
create table if not exists someOtherTable_$1
(
someOtherTableId bigint not null auto_increment
, data varchar(128) character set utf8
, primary key (someOtherTableId)
);
create table if not exists someOtherTable_$2
(
someOtherTableId bigint not null auto_increment
, data varchar(128) character set utf8
, primary key (someOtherTableId)
);
insert sometable (tableId, someOtherTableId) values (1, 1);
insert sometable (tableId, someOtherTableId) values (1, 2);
insert sometable (tableId, someOtherTableId) values (2, 2);
insert sometable (tableId, someOtherTableId) values (2, 3);
insert someothertable_$1(data) values ('table 1 row 1');
insert someothertable_$1(data) values ('table 1 row 2');
insert someothertable_$1(data) values ('table 1 row 3');
insert someothertable_$2(data) values ('table 1 row 1');
insert someothertable_$2(data) values ('table 1 row 2');
insert someothertable_$2(data) values ('table 1 row 3');
STATIC SOLUTION
Here's a solution if your tables are fixed (e.g. in the example you only have someOtherTable 1 and 2 / you don't need the code to change automatically as new tables are added):
select st.someTableId
, coalesce(sot1.data, sot2.data)
from someTable st
left outer join someOtherTable_$1 sot1
on st.tableId = 1
and st.someOtherTableId = sot1.someOtherTableId
left outer join someOtherTable_$2 sot2
on st.tableId = 2
and st.someOtherTableId = sot2.someOtherTableId;
DYNAMIC SOLUTION
If the number of tables may change at runtime you'd need to write dynamic SQL. Beware: with every successive table you're going to take a performance hit. I wouldn't recommend this for a production system; but it's a fun challenge. If you can describe your tool set & what you're hoping to achieve we may be able to give you a few pointers on a more suitable way forward.
select group_concat(distinct ' sot' , cast(tableId as char) , '.data ')
into #coalesceCols
from someTable;
select group_concat(distinct ' left outer join someOtherTable_$', cast(tableId as char), ' sot', cast(tableId as char), ' on st.tableId = ', cast(tableId as char), ' and st.someOtherTableId = sot', cast(tableId as char), '.someOtherTableId ' separator '')
into #tableJoins
from someTable;
set #sql = concat('select someTableId, coalesce(', #coalesceCols ,') from someTable st', #tableJoins);
prepare stmt from #sql;
execute stmt;

Related

How can I change SELECT statement to IF statement in Mysql?

In mysql, I tried to print '-1' if 3 conditions are satisfied.
SELECT '-1'
WHERE not exists(select * from acq_staff where acq_person_id = staffID)
OR not exists(select * from acq_training_course_session where training_course_id = course_id)
OR exists(select * from acq_training_enrolment where acq_staff_acq_person_id = staffID);
But how can I change this SELECT statement to IF statement so that if either those 3 conditions are satisfied, print -1 otherwise I am going to insert a data.
Sorry for not enough information
MySQL INNER JOIN, along with WHERE NOT EXISTS, can be used to determine if there's an existing course, and existing staff, and that staff is enrolled in that course, and if not, INSERT the staff and course id in the enrollment table.
-- create
CREATE TABLE acq_staff (
acq_person_id INTEGER PRIMARY KEY
);
CREATE TABLE acq_training_course_session (
training_course_id INTEGER PRIMARY KEY
);
CREATE TABLE acq_training_enrolment (
training_course_id INTEGER NOT NULL,
acq_staff_acq_person_id INTEGER NOT NULL
);
-- insert
INSERT INTO acq_staff VALUES (1), (2), (3);
INSERT INTO acq_training_course_session VALUES (1), (2), (3), (4);
INSERT INTO acq_training_enrolment VALUES (1,1), (1,2), (2,1), (3,1);
-- fetch
INSERT INTO acq_training_enrolment (training_course_id, acq_staff_acq_person_id)
SELECT 3, 1 WHERE NOT EXISTS
(SELECT *
FROM acq_training_course_session
INNER JOIN acq_training_enrolment
ON acq_training_course_session.training_course_id = acq_training_enrolment.training_course_id
INNER JOIN acq_staff ON acq_training_enrolment.acq_staff_acq_person_id = acq_staff.acq_person_id
WHERE acq_training_course_session.training_course_id = 3
AND acq_staff.acq_person_id = 1)
;
Try it here: https://onecompiler.com/mysql/3yk7xynkg
I guess you can do something like this: How can I simulate a print statement in MySQL?
`mysql>SELECT 'some text' as '';
+-----------+
| |
+-----------+
| some text |
+-----------+
1 row in set (0.00 sec)`
and just instead of some text set -1.
And one more thing i noticed in your question, that part "if those 3 conditions are satisfied" if you want all 3 conditions to be satisfied you need to change OR to AND. Because in your case, with OR, there needs to be satisfied only 1 condition, but with AND all 3 of them need to be satisfied.
maybe you can try that
select
if(
acq_staff.acq_person_id = staffID , '-1' ,
if(
acq_training_course_session.training_course_id = course_id , '-1' ,
if(acq_training_enrolment.acq_staff_acq_person_id = staffID , '-1' , 'not exist')
)
) as "check" from acq_staff , acq_training_course_session , acq_training_enrolment limit 1
The question is about how to conditionally execute an insert query. In pseudo-code, the question asks how to do the following
flag = (SELECT ... WHERE <all the conditions are met>)
IF flag == 1
INSERT INTO ....
ELSE IF flag == -1
DO NOTHING
Now think about it this way
result_set = (SELECT ... WHERE <all the conditions are met>)
# result_set here is the actual rows we want to insert
# length(result_set) > 0 if conditions are met
# length(result_set) == 0 if conditions are not met
INSERT INTO ... (result_set)
or simply
INSERT INTO ... (SELECT ... WHERE <all the conditions are met>)
When <all the conditions are met>, the insert will actually have something to insert. Otherwise, it will have an empty result set so no rows will be inserted.
So use INSERT INTO ... SELECT ... WHERE <all the conditions are met> Syntax to achieve desired results. Unfortunately, this solution does not have a way to return back -1.

How to `SELECT FROM` a table that is a part of a query itself using MySQL?

Say, if I have multiple tables that have the same schema:
CREATE TABLE `tbl01`
(
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`name` TINYTEXT,
`data` INT
);
CREATE TABLE `tbl02`
(
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`name` TINYTEXT,
`data` INT
);
CREATE TABLE `tbl03`
(
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`name` TINYTEXT,
`data` INT
);
-- etc. ------------------
INSERT INTO `tbl01` (`name`, `data`) VALUES
('row 1', 1),
('row 2', 1),
('row 3', 3);
INSERT INTO `tbl02` (`name`, `data`) VALUES
('cube', 1),
('circle', 0);
INSERT INTO `tbl03` (`name`, `data`) VALUES
('one', 1);
and then one table that contains names of all other tables in one of its columns:
CREATE TABLE `AllTbls`
(
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`tblnm` VARCHAR(64) NOT NULL UNIQUE,
`desc` TINYTEXT,
`flgs` BIGINT UNSIGNED
);
INSERT INTO `AllTbls` (`tblnm`, `desc`, `flgs`) VALUES
('tbl01', 'Table 1', 0),
('tbl02', 'Table two', 1),
('tbl03', '3rd table', 0);
So if I want to write a query to retrieve contents of AllTbls and also in one column to include count of rows in each of corresponding tables, I thought the following would be the way to do it:
SELECT *, `tblnm` as TblName, (SELECT COUNT(*) FROM TblName) as cntRws
FROM `AllTbls` ORDER BY `id` ASC LIMIT 0,30;
But this returns an error:
#1146 - Table 'database.TblName' doesn't exist
I know that I can do this in multiple queries (using a loop in a programming language), but is it possible to do it in one query?
PS. I'm using MySQL v.5.7.28
The simple answer is: "you can't"
Table names are not supposed to be used like variables, to hold data, in this way. What you're supposed to have is one table:
tblContractCounts
Client, ContractCount
-------------------
IBM, 1
Microsoft, 3
Google, 2
Not three tables:
tblIBMContractCounts
ContractCount
1
tblMicrosoftContractCounts
ContractCount
3
tblGoogleContractCounts
ContractCount
2
If your number of tables is known and fixed you can perhaps remedy things by creating a view that unions them all back together, or embarking on an operation to put them all into one table, with separate views named the old names so things carry in working til you can change them. If new tables are added all the time it's a flaw in the data modelling and need to be corrected. In that case you'd have to use a programming language (front end or stored procedure) to build a single query:
//pseudo code
strSql = ""
for each row in dbquery("Select name from alltbls")
strSql += "select '" + row.name + "' as tbl, count(*) as ct from " + row.name + " union all "
next row
strSql += "select 'dummy', 0"
result = dbquery(strSql)
It doesn't have to be your front end that does this - you could also do this in mysql and leverage the dynamic sql / EXECUTE. See THIS ANSWER how we can concatenate a string using logic like above so that the string contains an sql query and then execute the query. The information schema will give you the info you need to get a list of all current table names
But all you're doing is working around the fact that your data modelling is broken; I recommend to fix that instead
ps: the INFORMATION_SCHEMA has rough counts for tables with their names, which may suffice for your needs in this particular case
select table_name, table_rows from infornation_schema.tables where table_name like ...
I managed to solve the problem using the following stored procedure.
-- DROP PROCEDURE sp_Count_Rows;
Delimiter $$
CREATE PROCEDURE sp_Count_Rows()
BEGIN
DECLARE table_name TEXT DEFAULT "";
DECLARE finished INTEGER DEFAULT 0;
DECLARE table_cursor
CURSOR FOR
SELECT tblnm FROM alltbls;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
OPEN table_cursor;
DROP TABLE IF EXISTS RowsCount;
CREATE TABLE IF NOT EXISTS RowsCount(Tlbnm text, ctnRws int);
table_loop: LOOP
FETCH table_cursor INTO table_name;
IF finished = 1 THEN
LEAVE table_loop;
END IF;
SET #s = CONCAT("insert into RowsCount select '", table_name ,"', count(*) as cntRws from ", table_name);
PREPARE stmt1 FROM #s;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
END LOOP table_loop;
CLOSE table_cursor;
SELECT * FROM RowsCount;
DROP TABLE RowsCount;
END
$$
And then when you call the procedure
CALL sp_Count_Rows();
You get this result

Alias a mysql table for a session?

I know you can give an alias to a table for a query, like SELECT 1 FROM table AS t but is there a way to give an alias to a table for a session?
The use case is I have a "query pattern" that should apply to 3 different tables. So I would like to make only one query, using a table alias, and then tell mysql "Execute this query considering that is alias is table1, then execute the same query considering the alias is table2,..."
The use case:
INSERT INTO aliasedTable (id, value) VALUES (1,1)
The tables
CREATE TABLE table1 (id INT UNSIGNED, value TINYINT UNSIGNED)
CREATE TABLE table2 (id INT UNSIGNED, value TINYINT UNSIGNED)
CREATE TABLE table3 (id INT UNSIGNED, value TINYINT UNSIGNED)
The "supposed" syntax
ALIAS table1 AS aliasedTable;
INSERT INTO aliasedTable (id, value) VALUES (1,1)
ALIAS table2 AS aliasedTable;
INSERT INTO aliasedTable (id, value) VALUES (1,1)
ALIAS table3 AS aliasedTable;
INSERT INTO aliasedTable (id, value) VALUES (1,1)
What I thought of, is making a updatable VIEW of the table, but there is no such thing like CREATE TEMPORARY VIEW .... And using a CREATE TEMPORARY TABLE aliasedTable AS (SELECT * FROM table1) would create a table copy, instead of inserting in the original table.
Any suggestion?
Note that I have such case in a PHP code, but also in a procedure:
<?php
$query = 'INSERT INTO aliasedTable (id, value) VALUES (1,1)';
foreach (array('table1', 'table2', 'table3') AS $t) {
// Ideally, I would like to avoid string concat here and tell MySQL
// something like 'ALIAS :placeholder AS aliasedTable', ['placeholder' => $t]
$pdo->query('ALIAS ' . $table1 . ' AS aliasedTable');
$pdo->query($query);
}
or
SET #tables := '["table1","table2","table3"]';
SET #i := JSON_LENGTH(#tables) - 1;
WHILE (#i >= 0) DO
SET #table := JSON_UNQUOTE(JSON_EXTRACT(#tables, CONCAT('$[', #i, ']')));
ALIAS #table AS aliasedTable;
CALL inserting();
SET #i := #i - 1;
END WHILE;
where
CREATE PROCEDURE inserting()
BEGIN
INSERT INTO aliasedTable (id, value) VALUES (1, 1);
END$$
One thing that you could do is switch to a dynamic SQL statement and pass the table name as an input argument to the inserting function:
CREATE PROCEDURE inserting( IN tbl_name VARCHAR(25) )
BEGIN
SET #stmt = CONCAT('INSERT INTO ', tbl_name, ' (id, value) VALUES (1, 1)');
PREPARE stmt FROM #stmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
Depending on your call this will insert data to appropriate table. As an example:
CALL inserting('table1');
will execute following statement:
INSERT INTO table1 (id, value) VALUES (1,1);
Then from PHP you could call this procedure for each element in your array passing the current element as an argument to the function.

How to copy data from one table to another "EXCEPT" one field

How to INSERT into another table except specific field
e.g
TABLE A
ID(auto_inc) CODE NAME
1 001 TEST1
2 002 TEST2
I want to insert CODE and NAME to another table, in this case TABLE B but except ID because it is auto increment
Note: I don't want to use "INSERT INTO TABLE B SELECT CODE, NAME FROM TABLE A", because I have an existing table with around 50 fields and I don't want to write it one by one
Thanks for any suggests and replies
This can't be done without specifying the columns (excludes the primary key).
This question might help you. Copy data into another table
You can get all the columns using information_schema.columns:
select group_concat(column_name separator ', ')
from information_schema.columns c
where table_name = 'tableA' and
column_name <> 'id';
This gives you the list. Then past the list into your code. You can also use a prepared statement for this, but a prepared statement might be overkill.
If this is a one time thing?
If yes, do the insert into tableA (select * from table B)
then Alter the table to drop the column that your dont need.
I tried to copy from a table to another one with one extra field.
source table is TERRITORY_t
* the principle is to create a temp table identical to the source table, adjust column fields of the temp table and copy the content of the temp table to the destination table.
This is what I did:
create a temp table called TERRITORY_temp
generate SQL by running export
CREATE TABLE IF NOT EXISTS TERRITORY_temp (
Territory_Id int(11) NOT NULL,
Territory_Name varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (Territory_Id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
copy over with
INSERT INTO TERRITORY_temp (Territory_Id, Territory_Name) VALUES
(1, 'SouthEast'),
(2, 'SouthWest'),
(3, 'NorthEast'),
(4, 'NorthWest'),
(5, 'Central');
or
INSERT INTO TERRITORY_temp
SELECT * from TERRITORY_t
add the extra field(s) to match with the new table
copy from the temp table to the destination table
INSERT INTO TERRITORY_new
SELECT * from TERRITORY_temp
Please provide feedback.
Step 1. Create stored procedure
CREATE PROCEDURE CopyDataTable
#SourceTable varchar(255),
#TargetTable varchar(255),
#SourceFilter nvarchar(max) = ''
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SourceColumns VARCHAR(MAX)=''
DECLARE #TargetColumns VARCHAR(MAX)=''
DECLARE #Query VARCHAR(MAX)=''
SELECT
#SourceColumns = ISNULL(#SourceColumns +',', '') + T.COLUMN_NAME
FROM
(
select name as COLUMN_NAME from sys.all_columns
where object_id = (select object_id from sys.tables where name = #SourceTable)
and is_identity = 0
)T
SELECT
#TargetColumns = ISNULL(#TargetColumns +',', '') + T.COLUMN_NAME
FROM
(
select name as COLUMN_NAME from sys.all_columns
where object_id = (select object_id from sys.tables where name = #TargetTable)
and is_identity = 0
)T
set #Query = 'INSERT INTO ' + #TargetTable + ' (' + SUBSTRING(#TargetColumns,2 , 9999) + ') SELECT ' + SUBSTRING(#SourceColumns,2 , 9999) + ' FROM ' + #SourceTable + ' ' + #SourceFilter;
PRINT #Query
--EXEC(#Query)
END
GO
Step 2. Run stored procedure
use YourDatabaseName
exec dbo.CopyDataTable 'SourceTable','TargetTable'
Explanations
a) dbo.CopyDataTable will transfer all data from SourceTable to TargetTable, except field with Identity
b) You can apply filter when call stored procedure, in order to transfer only row based on criteria
exec dbo.CopyDataTable 'SourceTable','TargetTable', 'WHERE FieldName=3'
exec dbo.CopyDataTable 'SourceTable','TargetTable', 'WHERE FieldName=''TextValue'''
c) Remove -- from --EXEC(#Query) WHEN finish

How do I convert a many-to-many relationship from 2-column table?

Suppose I have this database table (some sample code below) that stores the relationship between two lists (requirements and testcases in my case) and I want to create a table with rows showing testcases and columns showing requirements with an indicator showing that a relationship exists.
A few limitations
I don't have the luxury of changing the db structure as this belongs to an open source test case management system (TestLink).
It's possible to write some code for this, but I'm hoping it can be done in MySQL.
Ah, and yes, it uses MySQL, so this would have to work in that environment.
This functionality used to exist, but has been taken out because typically, this type of work brings the db to its knees when there are tens-of-thousands of testcases and requirements.
create table pivot (
req_id int(11),
testcase_id int(11)
) ;
/*Data for the table pivot */
insert into pivot(req_id,testcase_id) values (1,1);
insert into pivot(req_id,testcase_id) values (2,2);
insert into pivot(req_id,testcase_id) values (3,3);
insert into pivot(req_id,testcase_id) values (4,1);
insert into pivot(req_id,testcase_id) values (5,2);
insert into pivot(req_id,testcase_id) values (6,3);
insert into pivot(req_id,testcase_id) values (2,1);
insert into pivot(req_id,testcase_id) values (3,2);
What I want to get out of the query is a table that looks somethign like this:
1 2 3 4 5 6
1 x x x
2 x x x
3 x x
Note:the row are the testcase_ids and the columns are the 'req_ids'
Anyone have a tip on how to get this with just SQL?
below is a lot more efficient:
create one table for test_cases, like
create table testCases(
id int(11) auto_increment,
testcase varchar(200),
primary key(id))
one table for requirements
requirements(
id int(11) auto_increment,
requirements varchar(200),
primary key(id))
then in a third table map the relationship
create table matchRequirementsToTests(
requirements varchar(200),
testcase varchar(200),
primary key(requirements, testcase),
foreign key (requirements) references Requirements(id),
foreign key(test case) references Test_cases(id))
select testcase_id,
if(sum(req_id = 1), 'X', '') as '1',
if(sum(req_id = 2), 'X', '') as '2',
if(sum(req_id = 3), 'X', '') as '3',
if(sum(req_id = 4), 'X', '') as '4',
if(sum(req_id = 5), 'X', '') as '5',
if(sum(req_id = 6), 'X', '') as '6'
from pivot
group by testcase_id;
It's ugly, but it works:
+-------------+---+---+---+---+---+---+
| testcase_id | 1 | 2 | 3 | 4 | 5 | 6 |
+-------------+---+---+---+---+---+---+
| 1 | X | X | | X | | |
| 2 | | X | X | | X | |
| 3 | | | X | | | X |
+-------------+---+---+---+---+---+---+
3 rows in set (0.00 sec)
I now have a name for what I'm trying to accomplish. It's a 'dynamic crosstab'. Here is how I got to the solution. Thanks to http://rpbouman.blogspot.com/2005/10/creating-crosstabs-in-mysql.html for the clear instructions for getting here.
Lines 1-20 - Set up a table to use for testing.
Lines 22-29 - a 'static' crosstab query, assuming I know how many requirements I've got.
Thanks D Mac for the solution you gave :)
Lines 30-44 - A query that dynamically generates the static query above.
Lines 45-72 - This is where I’m having the problem. The intent is to create a stored procedure that returns the result of the dynamic query. MySQL is saying there is a syntax issue, but I don't see how to fix it. Any thoughts?
drop table if exists pivot;
create table `pivot` (
`req_id` int(11),
`testcase_id` int(11)
);
/*Data for the table `pivot` */
insert into `pivot`(`req_id`,`testcase_id`) values (1,4);
insert into `pivot`(`req_id`,`testcase_id`) values (2,4);
insert into `pivot`(`req_id`,`testcase_id`) values (3,4);
insert into `pivot`(`req_id`,`testcase_id`) values (4,7);
insert into `pivot`(`req_id`,`testcase_id`) values (1,7);
insert into `pivot`(`req_id`,`testcase_id`) values (2,12);
insert into `pivot`(`req_id`,`testcase_id`) values (3,12);
insert into `pivot`(`req_id`,`testcase_id`) values (4,4);
select * from pivot;
select testcase_id
, if(sum(req_id = 1), 1, 0)
, if(sum(req_id = 2), 1, 0)
, if(sum(req_id = 3), 1, 0)
, if(sum(req_id = 4), 1, 0)
from pivot
group by testcase_id;
select concat(
'select testcase_id','\n'
, group_concat(
concat(
', if(sum(req_id = ',p2.req_id,'), 1, 0)','\n'
)
order by p2.req_id
separator ''
)
, 'from pivot','\n'
, 'group by testcase_id;','\n'
) statement
from pivot p2
order by p2.req_id;
CREATE PROCEDURE p_coverage()
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
begin
select concat(
'select testcase_id','\n'
, group_concat(
concat(
', if(sum(req_id = ',p2.req_id,'), 1, 0)','\n'
)
order by p2.req_id
separator ''
)
, 'from pivot','\n'
, 'group by testcase_id;','\n'
) statement
into #coverage_query
from pivot p2
order by p2.req_id;
prepare coverage from #coverage_query;
execute coverage;
deallocate prepare coverage;
end;
select * from pivot;