The accepted answer to sql swap primary key values fails with the error Can't reopen table: 't' - presumably this has something to do with opening the same table for writing twice, causing a lock.
Is there any shortcut, or do I have to get both, set one of them to NULL, set the second one to the first one, then set the first one to the previously fetched value of the second?
Don't use temporary tables for this.
From the manual:
You cannot refer to a TEMPORARY table more than once in the same query.
For example, the following does not work:
mysql> SELECT * FROM temp_table, temp_table AS t2;
ERROR 1137: Can't reopen table: 'temp_table'
This error also occurs if you refer to a temporary table multiple
times in a stored function under different aliases, even if the
references occur in different statements within the function.
UPDATE:
Sorry if I don't get it right, but why does a simple three way exchange not work?
Like this:
create table yourTable(id int auto_increment, b int, primary key(id));
insert into yourTable(b) values(1), (2);
select * from yourTable;
DELIMITER $$
create procedure pkswap(IN a int, IN b int)
BEGIN
select #max_id:=max(id) + 1 from yourTable;
update yourTableset id=#max_id where id = a;
update yourTableset id=a where id = b;
update yourTableset id=b where id = #max_id;
END $$
DELIMITER ;
call pkswap(1, 2);
select * from yourTable;
To swap id values of 1 and 2, I would use a SQL statement like this:
EDIT : this does NOT work on an InnoDB table, only works on a MyISAM table, per my testing.
UPDATE mytable a
JOIN mytable b ON a.id = 1 AND b.id = 2
JOIN mytable c ON c.id = a.id
SET a.id = 0
, b.id = 1
, c.id = 2
For this statement to work, the id value of 0 must not exist in the table, any unused value would be suitable... but to get this to work in a single SQL statement, you need to (temporarily) use a third id value.
This solution works for regular MyISAM tables, not temporary tables. I missed that this was being performed on a temporary table, I was confused by the error message you reported Can't reopen table:.
To swap id values 1 and 2 in a temporary table, I'd run three separate statements, again, using a temporary placeholder value of 0:
UPDATE mytable a SET a.id = 0 WHERE a.id = 1;
UPDATE mytable b SET b.id = 1 WHERE b.id = 2;
UPDATE mytable c SET c.id = 2 WHERE c.id = 0;
Edit: Fixed errors
Related
This question already has answers here:
1052: Column 'id' in field list is ambiguous
(8 answers)
Closed 1 year ago.
I am a beginner in SQL queries/subqueries and I'm having a lot of problems with my code. I have two tables, TableA and TableB. My database is called data.
Contents of tables:
Table A:
IDA (primary key)
ColumnA1
Table B:
IDB (primary key)
IDA (foreign key)
ColumnB1
ColumnB2
I am attempting to select IDA from TableA and also select a new column I created called NewColumnSubtract which was created as a result of a join between TableA and TableB. The result I want displayed is IDA and NewColumnSubtraction.
Here is an example of my code:
use `data` ;
SELECT
IDA,
ColumnA1 - (ColumnB1 * ColumnB2) AS NewColumnSubtraction
FROM
`data` . TableA
JOIN
`data` . TableB ON TableB.IDA = TableA.IDA;
If I do not include the third line of my code, which is just selecting IDA first, it works and just selects the new column I created. The problem is that I also want to display IDA.
Another problem I am having is that if I do not include 'data' . Table_ (there is no space before and after the . in my actual code), I get an ambiguous error. I know it is bad practice to call the database name in the script, but I do not know how to get around this.
Any help here would be greatly appreciated. Thank you so much!!
You have to explain to the database which of the IDA columns you want.
You can write the table name before the column or use aliases, so that you must not write that much.
If you have more columns like that , you need also to add the table name or alias.
&To prevent such problems, rty to make unique column names
use `data` ;
SELECT
a.IDA,
ColumnA1 - (ColumnB1 * ColumnB2) AS NewColumnSubtraction
FROM
TableA a
JOIN
TableB b ON b.IDA = a.IDA;
example
CREATE TABLE TableA (IDA int, ColumnA1 int)
CREATE TABLE TableB (IDA int, ColumnB1 int, ColumnB2 int)
SELECT
a.IDA,
ColumnA1 - (ColumnB1 * ColumnB2) AS NewColumnSubtraction
FROM
TableA a
JOIN
TableB b ON b.IDA = a.IDA;
IDA
NewColumnSubtraction
db<>fiddle here
There should not be any syntax error. Please check the query again. Here goes an example.
Create table statements:
create table TableA(IDA int, ColumnA1 int);
create table TableB(IDB int, IDA int, ColumnB1 int,ColumnB2 int);
Query:
SELECT
a.IDA,
(a.ColumnA1 - (b.ColumnB1 * b.ColumnB2)) AS NewColumnSubtraction
FROM
TableA a
JOIN
TableB b ON b.IDA = a.IDA;
Output:
IDA
NewColumnSubtraction
db<>fiddle here
I found the solution by doing this:
use data ;
SELECT
TableA.IDA,
TableA.ColumnA1 - (TableB.ColumnB1 * TableB.ColumnB2) AS NewColumnSubtraction
FROM
data . TableA
JOIN
data . TableB ON TableB.IDA = TableA.IDA;
So, I had to not only call the database for each instance of a table but the table for each instance of a column. Anyone have any ideas as to why this is happening?
I have a table t with columns id(primary key),a,b,c,d. assume that the columns id,a,b and c are already populated. I want to set column d =md5(concat(b,c)). Now the issue is that this table contains millions of records and the unique combination of b and c is only a few thousands. I want to save the time required for computing md5 of same values. Is there a way in which I can update multiple rows of this table with the same value without computing the md5 again, something like this:
update t set d=md5(concat(b,c)) group by b,c;
As group by does not work with update statement.
One method is a join:
update t join
(select md5(concat(b, c)) as val
from table t
group by b, c
) tt
on t.b = tt.b and t.c = tt.c
set d = val;
However, it is quite possible that any working with the data would take longer than the md5() function, so doing the update directly could be feasible.
EDIT:
Actually, updating the entire table is likely to take time, just for the updates and logging. I would suggest that you create another table entirely for the b/c/d values and join in the values when you need them.
Create a temp table:
CREATE TEMPORARY TABLE IF NOT EXISTS tmpTable
AS (SELECT b, c, md5(concat(b, c)) as d FROM t group by b, c)
Update initial table:
UPDATE t orig
JOIN tmpTable tmp ON orig.b = tmp.b AND orig.c = tmp.c
SET orig.d = tmp.d
Drop the temp table:
DROP TABLE tmpTable
In a previous question I asked how I could sum up a total based on some conditions: Count total on couple of conditions
Suppose I have a table like this:
id col1 col2 col3
1 a 1 k1
2 a 2 k2
3 a -3 k3
4 b 3 k4
Now, when I get id=1, I want to delete all the rows where col1=a.
When I get id=4, I want to delete all the rows where col1=b.
How would I do this in SQL?
I tried based upon previous answer:
DELETE FROM table WHERE (col1) IN (SELECT col1 FROM table WHERE id = '1')
But that gave me an error: #1093 - You can't specify target table 'table' for update in FROM clause
This has been many times on stackowerflow, you cannot UPDATE/DELETE table with data from nested select on the same table. There're two ways to do this:
Load all data before (for example via php, sql procedure)
Create temporary table like the one you're using, clone data and use temporary table to select items
i have another suggested solution for this. What if you create a STORED PROCEDURE for this problem?
like this:
DELIMITER $$
CREATE PROCEDURE `DeleteRec`(IN xxx varchar(5))
BEGIN
DECLARE oID varchar(5);
SET oID := (SELECT col1 FROM table WHERE id = '1');
DELETE FROM table WHERE col1 = oID;
END$$
DELIMITER ;
do this helps you?
In MySQL, is there a way to specify that a.column cannot exist in b.column - a reverse foreign key?
In other words:
# Would not return any rows ever
SELECT * FROM a
INNER JOIN b ON a.column = b.column;
# Would fail
INSERT INTO a
SELECT * FROM b;
# Would not insert any rows
INSERT IGNORE INTO a
SELECT * FROM b;
No there is no such thing.
You would need to do that in a trigger:
DELIMITER $$
CREATE TRIGGER bi_a_each BEFORE INSERT ON a FOR EACH ROW
BEGIN
DECLARE forbidden_key INTEGER;
SELECT id INTO forbidden_key FROM b WHERE b.id = NEW.acolumn LIMIT 1;
IF forbidden_key IS NOT NULL THEN
SELECT * FROM error_insertion_of_this_value_is_not_allowed;
END IF;
END $$
DELIMITER ;
To a point, if you want "can be in A or B, but not both"
This is the "super key/sub type" pattern
Create a new table AB that has a 2 columns
SomeUniqueValue, PK
WhichChild char(1), limited to 'a' or 'b'
There is also a unique constraint on both columns
Then
Add a WhichChild column to tables A and B. In A, it is always 'a'. In B, always 'b'
Add foreign keys from A to AB and B to AB on both columns
Now, SomeUniqueValue can be in only A or B.
Note: in proper RDBMS you'd use check constraints or computed columns to restrict WhichChild to 'a' or 'b' as needed. But MySQL is limited so you need to use triggers in MySQL. However, this is simpler then testing table B for each insert in A etc
Try to create a trigger and throw an error from it.
I need to query a delete statement for the same table based on column conditions from the same table for a correlated subquery.
I can't directly run a delete statement and check a condition for the same table in mysql for a correlated subquery.
I want to know whether using temp table will affect mysql's memory/performance?
Any help will be highly appreciated.
Thanks.
You can make mysql do the temp table for you by wrapping your "where" query as an inline from table.
This original query will give you the dreaded "You can't specify target table for update in FROM clause":
DELETE FROM sametable
WHERE id IN (
SELECT id FROM sametable WHERE stuff=true
)
Rewriting it to use inline temp becomes...
DELETE FROM sametable
WHERE id IN (
SELECT implicitTemp.id from (SELECT id FROM sametable WHERE stuff=true) implicitTemp
)
Your question is really not clear, but I would guess you have a correlated subquery and you're having trouble doing a SELECT from the same table that is locked by the DELETE. For instance to delete all but the most recent revision of a document:
DELETE FROM document_revisions d1 WHERE edit_date <
(SELECT MAX(edit_date) FROM document_revisions d2
WHERE d2.document_id = d1.document_id);
This is a problem for MySQL.
Many examples of these types of problems can be solved using MySQL multi-table delete syntax:
DELETE d1 FROM document_revisions d1 JOIN document_revisions d2
ON d1.document_id = d2.document_id AND d1.edit_date < d2.edit_date;
But these solutions are best designed on a case-by-case basis, so if you edit your question and be more specific about the problem you're trying to solve, perhaps we can help you.
In other cases you may be right, using a temp table is the simplest solution.
can't directly run a delete statement and check a condition for the same table
Sure you can. If you want to delete from table1 while checking the condition that col1 = 'somevalue', you could do this:
DELETE
FROM table1
WHERE col1 = 'somevalue'
EDIT
To delete using a correlated subquery, please see the following example:
create table project (id int);
create table emp_project (id int, project_id int);
insert into project values (1);
insert into project values (2);
insert into emp_project values (100, 1);
insert into emp_project values (200, 1);
/* Delete any project record that doesn't have associated emp_project records */
DELETE
FROM project
WHERE NOT EXISTS
(SELECT *
FROM emp_project e
WHERE e.project_id = project.id);
/* project 2 doesn't have any emp_project records, so it was deleted, now
we have 1 project record remaining */
SELECT * FROM project;
Result:
id
1
Create a temp table with the values you want to delete, then join it to the table while deleting. In this example I have a table "Games" with an ID column. I will delete ids greater than 3. I will gather the targets in a temp table first so I can report on them later.
DECLARE #DeletedRows TABLE (ID int)
insert
#DeletedRows
(ID)
select
ID
from
Games
where
ID > 3
DELETE
Games
from
Games g
join
#DeletedRows x
on x.ID = g.ID
I have used group by aggregate with having clause and same table, where the query was like
DELETE
FROM TableName
WHERE id in
(select implicitTable.id
FROM (
SELECT id
FROM `TableName`
GROUP by id
HAVING count(id)>1
) as implicitTable
)
You mean something like:
DELETE FROM table WHERE someColumn = "someValue";
?
This is definitely possible, read about the DELETE syntax in the reference manual.
You can delete from same table. Delete statement is as follows
DELETE FROM table_name
WHERE some_column=some_value