Here are my data:
CREATE TABLE cats
(
cat_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20),
breed VARCHAR(20),
age INT
);
INSERT INTO cats(name, breed, age)
VALUES
('otto', 'tabby', 10),
('jerry', 'mynx', 7),
('nina', 'stray', 7),
('gandolf', 'american shorthair', 13),
('dumbledore', 'wizard', 10);
mysql> select * from cats;
+--------+------------+--------------------+------+
| cat_id | name | breed | age |
+--------+------------+--------------------+------+
| 1 | otto | tabby | 10 |
| 2 | jerry | mynx | 7 |
| 3 | nina | stray | 7 |
| 4 | gandolf | american shorthair | 13 |
| 5 | dumbledore | wizard | 10 |
+--------+------------+--------------------+------+
What I want to do is delete all the rows where the ages are the same in more than one row. In the example, after deletion, the only row I would have left is the row with cat_id = 4.
| cat_id | name | breed | age |
+--------+---------+--------------------+------+
| 4 | gandolf | american shorthair | 13 |
+--------+---------+--------------------+------+
I have tried:
DELETE FROM cats WHERE age IN (SELECT age FROM cats GROUP BY age HAVING COUNT(*) > 1)
but that gives me an error: ERROR 1093 (HY000): You can't specify target table 'cats' for update in FROM clause.
I've tried using a JOIN, too, but that ends up deleting all the rows.
Any help is greatly appreciated!
I can do this in MySQL v 8.0 but I can't figure out how to do it in 5.7.
MySql is a little fussy, try nesting your criteria in its own subquery:
DELETE FROM cats
WHERE age IN (
SELECT age FROM (
SELECT age FROM cats
GROUP BY age
HAVING COUNT(*) > 1
)a
);
Demo Fiddle
Well, about 3 minutes after I posted this question, I figured out the answer. I was not using the right JOIN criteria.
Here is the solution I came up with - any critique is greatly appreciated!
DELETE c1
FROM cats c1
JOIN cats c2
ON c1.age = c2.age
AND c1.name <> c2.name;
From the command line client:
mysql> DELETE c1
-> FROM cats c1
-> JOIN cats c2
-> ON c1.age = c2.age
-> AND c1.name <> c2.name;
Query OK, 4 rows affected (0.00 sec)
mysql> select * from cats;
+--------+---------+--------------------+------+
| cat_id | name | breed | age |
+--------+---------+--------------------+------+
| 4 | gandolf | american shorthair | 13 |
+--------+---------+--------------------+------+
1 row in set (0.00 sec)
Looking up this error i found Yehor answer, he provided a really good solution for the problem.
If you do this:
DELETE FROM cats
WHERE age
IN (SELECT age FROM cats GROUP BY age HAVING COUNT(*) > 1)
you are going to get an error.
But if you wrap the condition in one more select:
DELETE FROM cats
WHERE age IN (
SELECT age FROM (
SELECT age FROM cats GROUP BY age HAVING COUNT(*) > 1
) AS a
)
You probably will receive this error:
Error Code: 1175. You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column. To disable safe mode, toggle the option in Preferences -> SQL Editor and reconnect.
Just disable safe mode and it would do the right thing!!
Explanation: The query optimizer does a derived merge optimization for the first query (which causes it to fail with the error), but the second query doesn't qualify for the derived merge optimization. Hence the optimizer is forced to execute the subquery first.
So I have a weird situation in which I have a table like this :
mysql> select * from test;
+-------+------+
| name | sal |
+-------+------+
| agent | 1000 |
| agent | 2000 |
| NULL | 3000 |
| NULL | 4000 |
| smith | 5000 |
| smith | 6000 |
| neo | 7000 |
+-------+------+
I want to return a data set which looks like this :
+-------+------+
| name | sal |
+-------+------+
| agent | 1000 |
| NULL | 3000 |
| NULL | 4000 |
| smith | 5000 |
| neo | 7000 |
+-------+------+
Meaning I want to fetch unique rows wherever name is duplicated, but fetch all rows as it is when name is null or is not duplicated.
I have written this query below in order to achieve that and it works fine. But I want to optimize it.
select *
from test
where sal in (
select sal from (
select min(sal) as sal
from test
group by name
union
select sal
from test where name is null
) t
order by sal);
Queries for creating this sample data -
create table test (name text, sal int);
insert into test values ('agent',1000);
insert into test values ('agent',2000);
insert into test values (null,3000);
insert into test values (null,4000);
insert into test values ('smith',5000);
insert into test values ('smith',6000);
insert into test values ('neo',7000);
Can anyone help me with that? I know that we shouldn't use IN to fetch data because that will increase the query time a lot in production.
Any help is appreciated!
You can try to use two queries with UNION ALL one is for name which value is null, another one writes MIN aggregate function by name with name isn't NULL.
Query #1
SELECT *
FROM (
SELECT name,sal
FROM test
WHERE name IS NULL
UNION ALL
SELECT name,min(sal)
FROM test
WHERE name IS NOT NULL
group by name
)t1
ORDER BY sal;
name
sal
agent
1000
3000
4000
smith
5000
neo
7000
View on DB Fiddle
Note
You can try to create an index on name column which might help you improve the query performance
UPDATE
Sometime, when a family is being inactivated from a system, it may contain more than 1 individual. In my case show at the sql fiddle, the family with household_id=12 has 3 individuals.
I need to insert the data of these 3 individuals as the same from indiviudal table to individual_history table and just changing the ind_action field into the following message HH has been inactivated.
Here is a sample data:
| individual_id | household_id | family_relation_id | marital_status_id | ind_lmms_id | ind_un_id | head_of_hh | ind_first_name_ar | ind_last_name_ar | ind_first_name_en | ind_last_name_en | ind_gender | dob | ind_status | ind_date_added | user_id | system_date |
|---------------|--------------|--------------------|-------------------|-------------|-----------|------------|-------------------|------------------|-------------------|------------------|------------|------------|------------|----------------------|---------|----------------------|
| 1 | 12 | 3 | 1 | 321 | (null) | no | u | x | (null) | (null) | Male | 2012-01-01 | Active | 2018-07-19T00:00:00Z | 1 | 2018-07-19T00:00:00Z |
| 2 | 12 | 1 | 2 | 123 | (null) | no | x | y | (null) | (null) | Female | 1998-03-05 | Active | 2015-03-05T00:00:00Z | 1 | 2015-03-05T00:00:00Z |
| 3 | 12 | 3 | 1 | 1234 | (null) | no | x | z | (null) | (null) | Female | 2004-04-05 | Active | 2018-04-11T00:00:00Z | 1 | 2018-04-11T00:00:00Z |
All 3 fields should be inserted to the table individual_history and ind_action is set to the note I added above.
I need to insert into a table called individual_history values of a SELECT query from table individual.
Here is the query:
INSERT INTO individual_history
(individual_id,
household_id,
family_relation_id_history,
marital_status_id_history,
ind_lmms_id_history,
ind_un_id_history,
head_of_hh_history,
ind_first_name_ar_history,
ind_last_name_ar_history,
ind_first_name_en_history,
ind_last_name_en_history,
ind_gender_history,
dob_history,
ind_status_history,
ind_action,
ind_date_changed,
user_id,
system_date)
VALUES ((SELECT i.individual_id,
i.household_id,
i.family_relation_id,
i.marital_status_id,
i.ind_lmms_id,
i.ind_un_id,
i.head_of_hh,
i.ind_first_name_ar,
i.ind_last_name_ar,
i.ind_first_name_en,
i.ind_last_name_en,
i.ind_gender,
i.dob,
i.ind_status
FROM individual i
WHERE i.household_id = :hid),
'HH Status Changed to inactive',
(SELECT i.ind_date_added,
i.user_id
FROM individual i
WHERE i.household_id = :hid),
:systemDate)
As you can see from the query, I am splitting the SELECT statement into 2 parts, as I want to insert a specific ind_action message, then I will continue by getting the other 2 fields date added and user_id.
The systemDate is the just the now() function result.
I tried to run this query using 12 as hid and I received the following error:
1136 - Column count doesn't match value count at row 1
After doing few searches, I found that I should add parenthesis for each of the values. So I changed the query to:
INSERT INTO individual_history
(individual_id,
household_id,
family_relation_id_history,
marital_status_id_history,
ind_lmms_id_history,
ind_un_id_history,
head_of_hh_history,
ind_first_name_ar_history,
ind_last_name_ar_history,
ind_first_name_en_history,
ind_last_name_en_history,
ind_gender_history,
dob_history,
ind_status_history,
ind_action,
ind_date_changed,
user_id,
system_date)
VALUES ((SELECT i.individual_id,
i.household_id,
i.family_relation_id,
i.marital_status_id,
i.ind_lmms_id,
i.ind_un_id,
i.head_of_hh,
i.ind_first_name_ar,
i.ind_last_name_ar,
i.ind_first_name_en,
i.ind_last_name_en,
i.ind_gender,
i.dob,
i.ind_status
FROM individual i
WHERE i.household_id = 12),
( 'HH Status Changed to inactive' ),
(SELECT i.ind_date_added,
i.user_id
FROM individual i
WHERE i.household_id = 12),
( NOW() ))
But still got the same error.
I tried to count the number of fields I am inserting compared to the ones I am selecting, and they are the same (18 fields).
UPDATE
I changed the query by removing the VALUES clause:
INSERT INTO individual_history
(
individual_id,
household_id,
family_relation_id_history,
marital_status_id_history,
ind_lmms_id_history,
ind_un_id_history,
head_of_hh_history,
ind_first_name_ar_history,
ind_last_name_ar_history,
ind_first_name_en_history,
ind_last_name_en_history,
ind_gender_history,
dob_history,
ind_status_history,
ind_action,
ind_date_changed,
user_id,
system_date
)
SELECT i.individual_id,
i.household_id,
i.family_relation_id,
i.marital_status_id,
i.ind_lmms_id,
i.ind_un_id,
i.head_of_hh,
i.ind_first_name_ar,
i.ind_last_name_ar,
i.ind_first_name_en,
i.ind_last_name_en,
i.ind_gender,
i.dob,
i.ind_status
FROM individual i
WHERE i.household_id=12,
'HH Status Changed to inactive',
(
SELECT i.ind_date_added,
i.user_id
FROM individual i
WHERE i.household_id=12),
now()
And I got the following error:
1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use
near '
'HH Status Changed to inactive',
' at line 10
Please note that the datatype of fields are exactly the same in both tables, and individual_history table contain an auto-increment primary key.
HERE IS AN SQL FIDDLE to check with sample data.
You don't need two SELECTs for what you're trying to do. If you want to use some specific value for ind_action, simply replace it in your select, same as you did with the now() function:
INSERT INTO targetTable (col1, col2, col3, col4, colTime)
SELECT colA, colB, 'my specific string', colD, now()
FROM sourceTable WHERE colA = 12;
Here, col3 gets the string, colTime the now().
#Marting Hennings, I am a bit too late ... but this query should work:
INSERT INTO individual_history
(individual_id,
household_id,
family_relation_id_history,
marital_status_id_history,
ind_lmms_id_history,
ind_un_id_history,
head_of_hh_history,
ind_first_name_ar_history,
ind_last_name_ar_history,
ind_first_name_en_history,
ind_last_name_en_history,
ind_gender_history,
dob_history,
ind_status_history,
ind_action,
ind_date_changed,
user_id,
system_date)
SELECT individual_id,
household_id,
family_relation_id,
marital_status_id,
ind_lmms_id,
ind_un_id,
head_of_hh,
ind_first_name_ar,
ind_last_name_ar,
ind_first_name_en,
ind_last_name_en,
ind_gender,
dob,
ind_status,
'HH Status Changed to inactive',
ind_date_added,
user_id,
now()
FROM individual
WHERE individual.household_id = 12
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;
I have been facing a small confusion from which I am not able to get rid.
I have a table "user_questions" with the attributes id, question_sequence_number, title. From that table I want to take the records according to the priority given like first I have given 'question_sequence_number' and then 'id'. So what I want is that the records should be displayed first with the sequence numbers I have given and the records should be displayed next with Ids I have given.
For that I have written a mySql query something like follows:
SELECT "user_questions".* FROM "user_questions" WHERE (question_sequence_number IN (11,13,16,19) OR id IN (198,199,200,201,202))
But the records are coming first with ids I have given and the remaining records with question sequence number.
Can anybody please help me out in this to achieve ?
Thanks.
To get the order of records you need you have to use ORDER BY clause. Otherwise the order is not guaranteed.
That being said you can try something like this
SELECT *
FROM user_questions
WHERE question_sequence_number IN (11,13,16,19)
OR id IN (198,199,200,201,202)
ORDER BY (question_sequence_number IN (11,13,16,19)) DESC,
CASE WHEN question_sequence_number IN (11,13,16,19)
THEN question_sequence_number
ELSE id
END
Output:
| ID | QUESTION_SEQUENCE_NUMBER |
|-----|--------------------------|
| 2 | 11 |
| 4 | 13 |
| 3 | 16 |
| 1 | 19 |
| 198 | 5 |
| 199 | 4 |
| 200 | 3 |
| 201 | 2 |
| 202 | 1 |
Here is SQLFiddle demo
DECLARE _Str VARCHAR(500);
SET _Str=CONCAT("SELECT user_questions.* FROM user_questions WHERE (question_sequence_number IN ("11,13,16,19") OR id IN ("198,199,200,201,202")")
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;