I have two tables, user and ban. User table has two columns id and type. Ban table also has two columns userID and bannedUserID. I want to delete records in ban table where bannedUserID has type of 'Admin'.
I've come up with such query but I am not sure if it is correct or not.
DELETE FROM ban WHERE ban.bannedUserID IN (SELECT id FROM user WHERE type = 'Admin')
Is it correct? Do I have to add/remove anything?
If you're not sure whether the DELETE is correct, you can test it by converting to an equivalent SELECT:
SELECT b.bannedUserID
FROM ban b JOIN user u ON b.bannedUserID = u.id
WHERE u.type = 'Admin';
Are the id's returned by that query the ones you want to DELETE?
Then try it in a transaction (if you use a storage engine that supports transactions, i.e. not MyISAM), so you can roll back if necessary:
START TRANSACTION;
DELETE b
FROM ban b JOIN user u ON b.bannedUserID = u.id
WHERE u.type = 'Admin';
/* do some SELECT to see if the correct rows have been deleted and no others */
/* but if anything appears wrong, and ban is an InnoDB table, you can... */
ROLLBACK;
Note that I'm using MySQL's multi-table DELETE syntax. It should work identically to your subquery approach, but in some cases it can be more flexible.
Related
I used to know how to do this but a lack of practice made me lose it.
I am trying to update usernames from a table by comparing matching email in another.
basically the first table has username empty, while the other has username and emails filled.
here is my wrong query :
UPDATE users SET username = (SELECT Username FROM clients WHERE email in mail)
email is from my clients table, mail is from my users table
I would suggest update with using JOIN with UPDATE, something like this should work
UPDATE users
INNER JOIN
#your relationship / for example
clients ON (users.id = clients.user_id)
SET
users.username = clients.email
WHERE
users.username IS NULL
Just make sure ON clause is correct relation that you have between users and clients and it should update all records in users username column with email from clients
You have not provided enough information to get a clear answer.
If you don't set a WHERE clause in your UPDATE statement, you will update all records with the same value, which is probably not what you want
If you want to update the users table with some info on matching records in the client table, then you need to join both tables on your matching field. Something like this :
UPDATE users U, clients C
SET u.username = c.username
WHERE U.email=C.email
AND C.email IN (...)
In this example I assumed that the email address was the matching field between the 2 tables (you should adapt this), and that you provide a list of target email addresses (you can remove this)
I'm currently having a problem with a legacy app I just inherited on my new job. I have a SQL query that's way too long to respond and I need to find a way to fasten it.
This query acts on 3 tables:
SESSION contains all users visits
CONTACT contains all the messages people have been sending through a form and contains a "session_id" field that links back to the SESSION id field
ACCOUNT contains users accounts (people who registered on the website) and whose "id" field is linked back in SESSION (through a "SESSION.account_id" field). ACCOUNT and CONTACT are no linked in any way, besides the SESSION table (legacy app...).
I can't change this structure unfortunately.
My query tries to recover ALL the interesting sessions to serve to the administrator. I need to find all sessions that links back to an account OR a contact form.
Currently, the query is structured like that :
SELECT s.id
/* a few fields from ACCOUNT and CONTACT tables */
FROM session s
LEFT JOIN account act ON act.id = s.account_id
LEFT JOIN contact c on c.session_id = s.id
WHERE s.programme_id = :program_id
AND (
c.id IS NOT NULL
OR
act.id IS NOT NULL
)
Problem is, the SESSION table is growing pretty fast (as you can expect) and with 400k records it slows things down for some programs ( :programme_id in the query).
I tried to use an UNION query with two INNER JOIN query, one between SESSION and ACCOUNT and the other one between SESSION and CONTACT, but it doesn't give me the same number of records and I don't really understand why.
Can somebody help me to find a better way to make this query ?
Thanks a lot in advance.
I think you just need indexes. For this query:
SELECT s.id
/* a few fields from ACCOUNT and CONTACT tables */
FROM session s LEFT JOIN
account act
ON act.id = s.account_id LEFT JOIN
contact c
ON c.session_id = s.id
WHERE s.programme_id = :program_id AND
(c.id IS NOT NULL OR act.id IS NOT NULL);
You want indexes on session(programme_id, account_id, id), account(id) and contact(session_id).
It is important that programme_id be the first column in the index on session.
#Gordon already suggested you add an index, which is generally the easy and effective solution, so I'm going to answer a different part of your question.
I tried to use an UNION query with two INNER JOIN query, one between
SESSION and ACCOUNT and the other one between SESSION and CONTACT, but
it doesn't give me the same number of records and I don't really
understand why.
That part is rather simple: the JOIN returns a result set that contains the rows of both tables joined together. So in the first case you would end up with a result that looks like
session.id, session.column2, session.column3, ..., account.id, account.column2, account.column3, ....
and a second where
session.id, session.column2, session.column3, ..., contact.id, contact.column2, contact.column3, ....
Then an UNION will faill unless the contact and account tables have the same number of columns with correspoding types, which is unlikely. Otherwise, the database will be unable to perform a UNION. From the docs (emphasis mine):
The column names from the first SELECT statement are used as the column names for the results returned. Selected columns listed in corresponding positions of each SELECT statement should have the same data type. (For example, the first column selected by the first statement should have the same type as the first column selected by the other statements.)
Just perform both INNER JOINs seperately and compare the results if you're unsure.
If you want to stick to an UNION solution, make sure to perform a SELECT only on corresponding columns : doing SELECT s.id would be trivial but it should work, for instance.
I am trying to find out how to find the emails that do not exist in a table using the emails from the conditional.
I could create a table with these emails but that seems like overkill for what I need it for.
What I am looking for is a query that would show me the conditional value and NULL as the user ID.
Is this possible?
I have a query like this:
SELECT u.uid, u.mail
FROM `users` u
WHERE u.mail IN (
'alot#of',
'emails#that',
'ineed#tofind',
)
This works great at finding the emails and associating the user id. Now I need to identify which emails do not exist in the result. I am currently only using 56 emails and 6 do not appear in the list. I am trying to identify which emails are not found.
NOT IN won't work as I have over 40,000 users. I only want to identify the emails not found from my conditional. I have 56 emails and only 50 results. I need to identify the 6 not found (they may not even be in the table at all)
Let me attempt to clarify this a little more:
I am given a list of emails for supposed accounts in the system. I am trying to find the accounts from the given email. This part is fine. Now, the issue I am having, I was given 56 emails but only 50 were found. I need to identify which emails out of the 56 were not found. The emails are all thrown into the conditional. NOT IN won't work because it would return all user but the 50 that were found. (roughly 40,000) I just need to identify the emails from the conditional that were not found in the table.
Thanks for any insight or suggestions to do what I need.
There isn't a way to do what you want without creating some additional items to track the emails. Basically, you're trying to get MySQL to tell you which items in the WHERE portion aren't found, but MySQL can only tell you about rows in a table.
You need to make a secondary table that stores the email addresses from your list, call it list. I would make it a single column table with just the emails. Then LEFT JOIN it against the users table and find where the uid is null.
SELECT u.uid, l.mail
FROM `list` l
LEFT JOIN `users` u ON u.mail=l.mail
WHERE u.uid IS NULL
As posted in the comments, NOT IN may be helpful. But there are also other ways. One of them is to left join your table with the result of your query and show only non-coincident rows:
select u.uid, u.mail
from users as u
left join (
select u.uid, u.mail
from users
where mail in ('alot#of','emails#that','ineed#tofind')
) as a on u.uid = a.uid
where a.uid is null;
Add the fields you need to the join (if uid is not enough)
So your question now becomes more complicated... you want to find all the E-Mails in your condition that are not found in your table.
As far as I know, there's not a simple SQL sentence that will give you that... but you can work with temp tables and get it. The solution implies:
Create a temporary table to hold the values you want to search (and add the appropriate indexes to it)
Insert the values you want to search
Execute a select query to find non-matching rows
So... let's do it:
-- 1. Create a temp table to hold the values
drop table if exists temp_search_values;
create temporary table temp_search_values (
mail varchar(100),
unique index idx_mail(mail) -- Don't allow duplicate values here
);
-- 2. Insert the search values
insert into temp_search_values (mail) values
('alot#of'),('emails#that'),('ineed#tofind');
-- 3. Execute the query
select a.*
from users as u
left join temp_search_values as a on u.mail = a.mail
where u.mail is null;
Remember: Temporary tables are only visible to the connection that created them, and are deleted when the connection is closed or killed.
NULL is a strange result. It's not true and it's not false. If you want to check for it, you have to look specifically.
SELECT u.uid, u.mail
FROM `users` u
WHERE u.mail NOT IN (
'alot#of',
'emails#that',
'ineed#tofind',
) and u.uid IS NULL
* Oh, I see what you're getting at. This will work, although it's not pretty. *
select * from
(SELECT 'emails#that' as v
UNION SELECT 'alot#of' as v,
UNION SELECT 'ineed#tofind' as v
) as test
left join users on u.mail = test.v
where u.uid is null
I have a table of users
user id
3 ,Frank
4 ,Steve
5 ,Joe
and a table of roles where column 1 is id of users in above table:
1, billing
3, Admin
2, Admin
4, user
5, billing
as you can see users 1 and 2 has been orphaned, and there is no user with id of 2. how can i delete this entry from the roles table? there could be dozens of entries in roles that dont have a user.
will this work?
DELETE from roles,users where roles.userId!==users.userId
You don't need to delete from users if the user is not there. So you can just do:
delete r from roles r
where not exists (select 1 from users u where u.userId = r.userId);
As mentioned in a comment, if you declared the roles.userId value to be a foreign key reference, then the database would not let this happen. After the data is fixed, you can do:
alter table roles add constraint foreign key (userId) references user(userId);
DELETE FROM roles WHERE id NOT IN (SELECT id FROM users)
Q: Will this work?
A: Close, but no cigar. No, the statement in the question won't perform the specified operation.
First, write a query to identify the orphaned rows. An anti-join pattern is a common approach (though there are several other workable approaches):
SELECT r.*
FROM `roles` r
LEFT
JOIN `user` u
ON u.id = r.user_id
WHERE u.id IS NULL
This uses an "outer" join, to return all rows from roles, along with matching rows from user. If no matching row is found in user, the columns from the user table will be returned as NULL. Given the equality comparison in the join predicate (ON clause), we know that all matching rows will have a non-NULL value for u.id. The "trick" is the predicate in the WHERE clause that excludes all rows that found a match, so we are left with rows in roles that don't have a matching row in users
(If you want to examine the rows that will be deleted and/or create a "backup" of those rows, this query gives you that.)
Next, convert the SELECT statement into a DELETE statement. For this particular statement, it's very straightforward with MySQL. The only change required is to replace the SELECT keyword with the DELETE keyword:
DELETE r.*
FROM `roles` r
LEFT
JOIN `user` u
ON u.id = r.user_id
WHERE u.id IS NULL
This is an example of just one of several workable approaches.
I have a table in my database to store user data. I found a defect in the code that adds data to this table database where if a network timeout occurs, the code updated the next user's data with the previous user's data. I've addressed this defect but I need to clean the database. I've added a flag to indicate the rows that need to be ignored and my goal is to mark these flags accordingly for duplicates. In some cases, though, duplicate values may actually be legitimate so I am more interested in finding several user's with the same data (i.e, u> 2).
Here's an example (tablename = Data):
id---- user_id----data1----data2----data3----datetime-----------flag
1-----usr1--------3---------- 2---------2---------2012-02-16..-----0
2-----usr2--------3---------- 2---------2---------2012-02-16..-----0
3-----usr3--------3---------- 2---------2---------2012-02-16..-----0
In this case, I'd like to mark the 1 and 2 id flags as 1 (to indicate ignore). Since we know usr1 was the original datapoint (assuming the oldest dates are earlier in the list).
At this point there are so many entries in the table that I'm not sure the best way to identify the users that have duplicate entries.
I'm looking for a mysql command to identify the problem data first and then I'll be able to mark the entries. Could someone guide me in the right direction?
Well, first select duplicate data with their min user id:
CREATE TEMPORARY TABLE duplicates
SELECT MIN(user_id), data1,data2,data3
FROM data
GROUP BY data1,data2,data3
HAVING COUNT(*) > 1 -- at least two rows
AND COUNT(*) = COUNT(DISTINCT user_id) -- all user_ids must be different
AND TIMESTAMPDIFF( MINUTE, MIN(`datetime`), MAX(`datetime`)) <= 45;
(I'm not sure, if I used TIMESTAMPDIFF properly.)
Now we can update the flag in those rows where user_id is different:
UPDATE duplicate
INNER JOIN data ON data.data1 = duplicate.data1
AND data.data2 = duplicate.data2
AND data.data3 = duplicate.data3
AND data.user_id != duplicate.user_id
SET data.flag = 1;
UPDATE Data A
LEFT JOIN
(
SELECT user_id,data1,data2,data3,min(id) min_id
FROM Data GROUP BY user_id,data1,data2,data3
) B
ON A.id = B.min_id
SET A.flag = IF(ISNULL(B.min_id),1,0);
If there are duplicate times involved, maybe try this
UPDATE Data A
LEFT JOIN
(
SELECT user_id,data1,data2,data3,,`datetime`,min(id) min_id
FROM Data GROUP BY user_id,data1,data2,data3,`datetime`
) B
ON A.id = B.min_id
SET A.flag = IF(ISNULL(B.min_id),1,0);