Mysql: Create inline table within select statement? - mysql

Is there a way in MySql to create an inline table to use for join?
Something like:
SELECT LONG [1,2,3] as ID, VARCHAR(1) ['a','b','c'] as CONTENT
that would output
| ID | CONTENT |
| LONG | VARCHAR(1)|
+------+-----------+
| 1 | 'a' |
| 2 | 'b' |
| 3 | 'c' |
and that I could use in a join like this:
SELECT
MyTable.*,
MyInlineTable.CONTENT
FROM
MyTable
JOIN
(SELECT LONG [1,2,3] as ID, VARCHAR(1) ['a','b','c'] as CONTENT MyInlineTable)
ON MyTable.ID = MyInlineTable.ID
I realize that I can do
SELECT 1,'a' UNION SELECT 2,'b' UNION SELECT 3,'c'
But that seems pretty evil
I don't want to do a stored procedure because potentially a,b,c can change at every query and the size of the data as well. Also a stored procedure needs to be saved in the database, and I don't want to have to modify the database just for that.
View is the same thing.
What I am really looking for is something that does SELECT 1,'a' UNION SELECT 2,'b' UNION SELECT 3,'c' with a nicer syntax.

The only ways i can remember now is using UNION or creating a TEMPORARY TABLE and inserting those values into it. Does it suit you?
TEMPORARY_TABLE (tested and it works):
Creation:
CREATE TEMPORARY TABLE MyInlineTable (id LONG, content VARCHAR(1) );
INSERT INTO MyInlineTable VALUES
(1, 'a'),
(2, 'b'),
(3, 'c');
Usage:
SELECT
MyTable.*,
MyInlineTable.CONTENT
FROM
MyTable
JOIN
SELECT * FROM MyInlineTable;
ON MyTable.ID = MyInlineTable.ID
TEMPORARY_TABLES lifetime (reference):
Temporary tables are automatically dropped when they go out of scope, unless they have already been explicitly dropped using DROP TABLE:
.
All other local temporary tables are dropped automatically at the end of the current session.
.
Global temporary tables are automatically dropped when the session that created the table ends and all other tasks have stopped referencing them. The association between a task and a table is maintained only for the life of a single Transact-SQL statement. This means that a global temporary table is dropped at the completion of the last Transact-SQL statement that was actively referencing the table when the creating session ended.`

What I am really looking for is something that does SELECT 1,'a' UNION SELECT 2,'b' UNION SELECT 3,'c' with a nicer syntax.
Yes, it is possible with ROW CONSTRUCTOR introduced in MySQL 8.0.19:
VALUES ROW (1,'a'), ROW(2,'b'), ROW(3,'c')
and with JOIN:
SELECT *
FROM tab
JOIN (VALUES ROW (1,'a'), ROW(2,'b'), ROW(3,'c') ) sub(id, content)
ON tab.id = sub.id;
db<>fiddle demo

Yes. Do with stored procedures or views.

Related

insert/update mysql column based on existing data

I'm updating an existing table by adding data into an existing column.
I have already have an output of the data to be inserted, but due to the amount of records, i'm looking for the best way to insert this into my table without having to manually write to each line of sql.
Here's my sql (partial) i want to insert into
INSERT INTO `tbl_user_variables_dobRE` (`user_id`, `value`) VALUES
(150, '1959-11-02'),
(151, '1948-04-20'),
(152, '1961-06-18'),
And this is the table i want to insert it into
id | 7
username | guestinvite
password | BLANK
forname | forname
surname | surname
email | guestinvite#test.com
address_id | 286
type_id | 4
dob | 0000-00-00
plusGuest | 0
update | 2016-02-16 11:54:36
created | 2016-04-04 17:03:12
So i want to insert the second item into the 'dob' column where first item = id
Is there anyway to do this programmatically or do i have to write WHERE & OR statements for every line?
You tagged both MySql AND sql-server in your post. The following is assuming you're using SQL Server, but the idea would remain the same in MySQL (just different syntax)...
If I'm understanding correctly, it sounds like you want to do an UPDATE, not an INSERT, being that you're modifying existing rows.
You said that you have an output of the data to be inserted - Insert this into a TEMP table and JOIN it to the table you'd like to update where the id's match.
BEGIN TRANSACTION [Transaction1] -- Do large updates as transactions to avoid data loss
CREATE TABLE #temp ( -- Create temp table
[user_id] int,
[dob] nvarchar(20)
)
INSERT INTO #temp
-- YOUR SELECT GOES HERE
SELECT my_id as [user_id], my_dob as [dob]
UPDATE my_table
SET my_table.dob = t.dob
FROM tbl_user_variables_dobRE my_table
INNER JOIN #temp t ON t.user_id = my_table.id
DROP TABLE #temp
If your data looks good, commit the transaction: (Don't dwell too long, transactions lock table data!)
COMMIT TRANSACTION [Transaction1]
Otherwise:
ROLLBACK TRANSACTION [Transaction1]
The quickest way I can think of doing this is creating a temporary table with the new data that you want to add (you could possibly bulk import it all from say, a CSV file).
The temporary table will just need a couple of columns - one with user_id and the other one dob - you'll be getting rid of it after anyway.
You could then do something like this:
UPDATE tbl_user_variables_dobRE a
JOIN tmp_table b
ON ( a.user_id = b.user_id )
SET a.dob = b.dob
Once you've done that you can DROP your temporary table and be good to go - good luck!
Important
Be super-careful when updating data - it's so easy to mess up your data by forgetting to add a clause. If possible, do this with some test data before trying it with the real production data.

Quickly copying all values from one table into another – MySQL

Suppose I have two tables with BTREE indices that have the same number of rows but don't necessarily share data, and I want to copy the values in one column from one table into the other table. I've seen other questions' answers regarding SELECTing that involve generating sequential IDs for both tables, then performing some JOIN operation based on matching these IDs together. For example (from the first answer):
select result1.title1, title1.age1,result2.title2, title2.age2 from
(select #i:=#i+1 AS rowId, title1, age1 from tab1,(SELECT #i:=0) a) as result1 ,
(select #j:=#j+1 AS rowId,title2, age2 from tab2,(SELECT #j:=0) a ) as result2
where
result1.rowId = result2.rowId; #sic.
However, I have two concerns:
I'm not sure how to update this way, since I don't know if I can make a dummy column for the destination table on the fly (i.e. something equivalent to UPDATE (SELECT (#x:=#x+1), title1 FROM title1,(SELECT #x:=0) a ) INNER JOIN...).
I am suspicious that this is in O(n^2) time with respect to the number of rows. This would be the case if the dummy IDs were generated first, then each run of the JOIN required a linear search of one or both tables. Is this the case? If so, is there a faster way to do this?
For example, consider the following two tables:
CREATE TABLE t1 (
id INT NOT NULL PRIMARY KEY,
v INT
);
INSERT INTO t1 VALUES
(1, 3),
(3, 4),
(25, 7);
CREATE TABLE t2 LIKE t1;
INSERT INTO t2 VALUES
(6, 150),
(9, 143),
(14, 175);
Suppose I wanted to replace the v values in t2 with those in t1 in one query so that t1 becomes:
mysql> SELECT * FROM t1;
+----+------+
| id | v |
+----+------+
| 1 | 150 |
| 3 | 143 |
| 25 | 175 |
+----+------+
3 rows in set (0.00 sec)
How would I do so?
After lots of testing I think I've found a pretty viable solution. If you have sequential IDs on the source table, you can copy the values relatively quickly to the destination table (especially if the source table is ENGINE=MEMORY) using the following [admittedly somewhat unusual] query:
UPDATE t1 CROSS JOIN (SELECT #x:=0) a
SET t1.v=(#x:=#x+1)+(SELECT v FROM t2 WHERE id=#x)-#x;
The overhead from the SELECT v... statement is quite significant however, and it slows the query significantly.

Update multiple rows in table with values from a temporary table

I'm trying to write a database migration script to add a column to a table that contains existing data, and then populate that column with appropriate data.
I'm doing the migration in a few steps. I've created a temporary table that contains a single column with ids like this:
new_column
==========
1000
1001
1002
1003
...
I now want to update my existing table so that each row in the temporary table above is used to update each row in my existing table. The existing table looks like this:
old_column_1 | old_column_2 | new_column
========================================
1 | 100 | null
2 | 101 | null
3 | 102 | null
...
I've tried a few variations of this sort of update -
select min(t.new_column)
from temp t
where t.new_column not in (select new_column from existing_table);
But I can't seem to get the syntax right...
Your problem is more complicated than you think. There's nothing reliable to join on. So, either you write a stored procedure which uses a cursor to loop through both tables and updating the existing table row by row (which can quickly become a performance nightmare, therefore I wouldn't recommend it) or you use this a little complicated query:
CREATE TABLE temp
(id int auto_increment primary key, `new_column` int)
;
INSERT INTO temp
(`new_column`)
VALUES
(1000),
(1001),
(1002),
(1003)
;
CREATE TABLE existing
(`old_column_1` int, `old_column_2` int, `new_column` varchar(4))
;
INSERT INTO existing
(`old_column_1`, `old_column_2`, `new_column`)
VALUES
(1, 100, NULL),
(2, 101, NULL),
(3, 102, NULL)
;
update
existing e
inner join (
select * from (
select
t.*
from temp t
)t
inner join
(
select
e.old_column_1, e.old_column_2,
#rownum := #rownum + 1 as rn
from existing e
, (select #rownum:=0) vars
)e on t.id = e.rn
) sq on sq.old_column_1 = e.old_column_1 and sq.old_column_2 = e.old_column_2
set e.new_column = sq.new_column;
see it working live in an sqlfiddle
I added an auto_increment column in your temporary table. Either you do it this way, or you simulate a rownumber like I did here:
select
e.old_column_1, e.old_column_2,
#rownum := #rownum + 1 as rn
from existing e
, (select #rownum:=0) vars
If you want to influence which row gets which row number, you can use ORDER BY whatever_column ASC|DESC in there.
So, what the query basically does, is, to create a row number in your existing table and join it via this column and the auto_increment column in the temporary table. Then I join this subquery again to the existing table, so that we can easily copy the column from temporary table to existing table.

SQL Merging Tables Based On Multiple Matching Column Values

Trying to merge two game servers' tables that have this structure:
map | authid | name | time | date | ...
I would like to replace a row only if the time value of table2 is less than that of table1 AND ONLY if the map and authid values are BOTH the same. If the time values in table2 are greater, then the row from the current table (table1) should be kept untouched. Otherwise (on different map or authid values), the row from table2 should simply be appended to table1.
My way would be to
1st: create a view for replacing rows and another with the correct result set that should be appended like
--view for update
create view ChangeTable1
as
select table2.map, table2.authid,table2.name, table2.date, table1.map as t1map, table1.authid as t1authid...from table1 to inner join table2 on table1.map=table2.map and table1.athid=table2.autid
where table1.time>table2.time
-- view for appending
create view Add2Table1
select table2.map, table2.authid,table2.name, table2.date... from table2 where concat(table2.map, table2.authid) not in (select concat(table1.map, table1.authid) from table1)
-- update statement based on first view ChangeTable1
update ChangeTable1 set t1date=date, t1somevalue=somevalue......
-- insert Statement based an second view Add2Table1
insert into table 1 (map, authid, name, time, date, .... as select map, authid, name, time, date,... from Add2Table1
I hope this helps. I mostly do MS SQL, so there might be some syntax issues that need translation to MYSQL, Nils
If you need a permanent process doing this, you might consider putting this in a stored procedure

MySQL: Select from coma separated list / list as table? "SELECT a FROM (1, 2, 3)"

Is it possible to do something like this (obviously this syntax does not work):
SELECT a FROM (1, 2, 3)
to get this:
| a |
+---+
| 1 |
| 2 |
| 3 |
?
That is I want to make rows from coma separated list, without using any table, or at least without creating table in db (maybe this is possible using something like temporary table?).
Maybe it is possible to get column of given values without using select, that is using some other sql statment? If it is not possible in MySQL, but possible in some other SQL it still would be interesting to know.
SELECT 1 a UNION ALL SELECT 2 a UNION ALL SELECT 3 a;
Would a temporary table be an option? Then I would have a suggestion which, admittedly, has more than 1 query:
-- DROP TEMPORARY TABLE tmp_list IF EXISTS;
CREATE TEMPORARY TABLE tmp_list (a INT);
INSERT INTO tmp_list (a) VALUES (1), (2), (3);
SELECT a FROM tmp_list;
If it is not possible in MySQL, but possible in some other SQL it still would be interesting to know.
In standard SQL this would be something like this
select *
from ( values (1), (2), (3) ) t
This works at least in PostgreSQL and DB2.
In PostgreSQL you can give the column a name by extending the alias (not sure if that column aliasing is part of the SQL standard).
select *
from ( values (1), (2), (3) ) t (id)
The following is an alternative to the above using a common table expression.
with my_values (id) as (
values (1), (2), (3)
)
select *
from my_values;
Since MariaDB v10.3.3 and MySQL v8.0.19 you can now do exactly that!
See docs: MariaDB, MySQL
MariaDB:
WITH mylist (a) AS (VALUES (1),(2),(3))
SELECT a FROM mylist
I used a WITH here because MariaDB doesn't supply nice column names for VALUES .... You can use it in a union without column names:
SELECT 1 AS a UNION ALL VALUES (2),(3)
And although the docs don't appear to mention it, you can even use it as a top-level query:
VALUES (1),(2),(3) ORDER BY 1 DESC
The actual column names are in fact the just first row of values, so you can even do this (though it's inelegant, and you can run into duplicate column name errors):
SELECT `4` AS a FROM (VALUES (4),(5),(6)) mylist
MySQL:
I don't have an instance of MySQL v8.0.19 to test against right now, but according to the docs [EDIT: Tested successfully on MySQL v8.0.23 using dbfiddle.uk, see links in comment #2] either of these should work:
SELECT column_0 AS a FROM (VALUES ROW(1), ROW(2), ROW(3)) mylist
SELECT a FROM (VALUES ROW(1), ROW(2), ROW(3)) mylist(a)
Unlike MariaDB, MySQL supplies automatic column names column_0, column_1, column_2, etc., and also supports renaming all of a subquery's columns when referencing it.
I'm not sure, but this dev worklog page seems to suggest that MySQL has also implemented [EDIT: not implemented yet, as of v8.0.26] the shorter sytax (omitting "ROW", like MariaDB), or that they might at some poiint in the near future.