MySQL DELETE in a single table - sql

I have a database with only one table as below:
userurltag(id,userID(string),Url(String),tag(String))
I want to delete users that have less than 3 urls associated with them.
How can I do that?

Try this one:
DELETE
FROM userurltag USING userurltag
JOIN
(SELECT userID
FROM userurltag
GROUP BY userID HAVING COUNT(*) < 3) as tmp
ON userurltag.userID = tmp.userID;

DELETE
FROM userurltag
WHERE UserID IN
(SELECT UserID FROM userurltag GROUP BY userID Having COUNT(UserID) < 3)

Related

Joining on a table but only match if it has at least one row based on a condition

I'm trying to find all users who have at least 1 transaction that has the StoreLocationID=123.
The basic query to get the count of users is:
SELECT COUNT(*)
FROM Users u
The transaction table looks like:
Transactions
- ID
- UserID
- Amount
- Date
- StoreLocationID
How can I find ALL users who have at least 1 transaction where StoreLocationID=123.
I can join on the table, but I just need to know if there is at least 1 row with StoreLocationID=123.
You can use a correlated subquery with an exists condition:
select *
from users u
where exists (
select 1
from transactions t
where t.userID = u.userID
and t.StoreLocationID = 123
)
This will give you all users that have at least one transaction on in store 123.
If you just want to count of such users, then:
select count(*)
from users u
where exists (
select 1
from transactions t
where t.userID = u.userID
and t.StoreLocationID = 123
)
Or:
select count(distinct userID) from transactions where StoreLocationID = 123

Deleting Records Based on Criteria

I have a list of users (Field = UserName) that I got from a table (Users). Some are duplicates.
Where there is a duplicate record, I need to delete the most current record (CreatedOn) created.
Also, if it trips up on the ability to delete because there are 'relationships established to this user ID' in the database, I need it to skip to the next record and continue deleting.
How do I accomplish this??
Adding on from Diego's answer with a check that the UserName is not the oldest instance in the table.
DELETE FROM
Users
WHERE
UserName IN
(SELECT UserName FROM Users GROUP BY UserName HAVING COUNT(UserName) > 1)
AND
CreatedOn !=
(SELECT MIN(CreatedOn) FROM Users T1 WHERE UserName = T1.UserName);
You can try something like this:
Delete from Users
where nameuser in
(select nameuser from Users
Group by nameuser
Having count(nameuser) > 1)
Use EXISTS to remove a row if same UserName also has an older CreatedOn:
delete from users u1
where exists (select 1 from users u2
where u2.UserName = u1.UserName
and u2.CreatedOn < u1.CreatedOn)
Or, another approach:
delete from users
where (UserName, CreatedOn) not in (select UserName, MIN(CreatedOn)
from users
group by UserName)
DELETE from user u
WHERE u.UserId in ( SELECT Distinct userid from User u join User u2 where u.UserName = u2.UserName
and u.CreatedOn > u2.CreatedOn)
You can't skip on error in one sql, but you could exclude userid that exists in related tables.

How to implement/improve/ make faster this query with joins

Please bear with me I am not skilled in SQL:
I have three tables
1) Notifications - stores all my data
2) GroupTable - Has the names of groups and related id
3) GroupUser - this table maps Uname and Udob to a group from GroupTable.
Now before I fetch records from Notifications I want to check the GroupTable for GroupID take this GroupID and look in GroupUser for all the records in this GroupID (Names,DOB as these are unique) Once I get this data I want to fetch records from Notifications table for the Names and DOB's in ascending order of the date:
So far I have the following query, it works fine just that I am not satisfied and I think this can be improved:
SELECT
*
FROM
(SELECT
*
FROM Notifications
WHERE
DateToNotify < '2016-03-24' AND
NotificationDateFor IN
(SELECT gu.Name
FROM GroupUser AS gu
INNER JOIN GroupTable AS gt ON
gu.GroupID = gt._id AND
gt.GroupName = "Groupn"
) AND
DOB IN
(SELECT gu.DOB
FROM GroupUser AS gu
INNER JOIN GroupTable AS gt ON
gu.GroupID = gt._id AND
gt.GroupName = "Groupn"
)
) as T
ORDER BY
SUBSTR(DATE('NOW'), 0) > SUBSTR(DateToNotify, 0)
, SUBSTR(DateToNotify, 0)
I don't think that you would get this faster with joins instead of the IN clauses. It can be that re-writing would not even change the execution plan, because the dbms tries to access the data in the optimal way anyhow.
It seems a bit strange that you don't look for group users matching name and dob, but only ensure that there are group users matching the name and - possibly other - group users matching the dob. But as you say that the query works fine as is, okay.
EDIT: Okay, according to your comment you actually want groupuser matches on both name and dob. So what you are looking for would be
AND (NotificationDateFor, DOB) IN (SELECT gu.Name, gu.DOB FROM ...)
But SQLite doesn't support this beautiful syntax (Oracle is the only dbms I know of that does).
So you either join or use EXISTS.
With JOIN:
select distinct n.*
from notifications n
join
(
select name, dob
from groupuser
where groupid = (select _id from grouptable where groupname = 'groupn')
) as gu on n.notificationdatefor = gu.name and n.dob = gu.dob
where n.datetonotify < '2016-03-24'
order by date('now') > n.datetonotify, n.datetonotify;
With EXISTS:
select *
from notifications n
where datetonotify < '2016-03-24'
and exists
(
select *
from groupuser gu
where gu.groupid = (select _id from grouptable where groupname = 'groupn')
and gu.name = n.notificationdatefor
and gu.dob = n.dob
)
order by date('now') > n.datetonotify, n.datetonotify;

SQL - Select referential data from multiple tables in one SQL call

For example I have 4 tables, all with say userID (which can be referenced more than once. So for example I can have:
Cars
id, ..., userID // userID = owner of the car
Garages
id, ..., userID // userID = owner of the garage
Tools
id, ..., userID // userID = owner of the tool
Now I want to do a query to delete the user, but I only want to delete the user if all their related data is gone. In other words, I want to make sure there is no referenced data (let's say I'm not allowed to have userID = -1 or null. It has to be assigned to a user
The only way I can see to do the checks is:
SELECT count(*) FROM Cars WHERE USERID = userID
SELECT count(*) FROM Garages WHERE USERID = userID
SELECT count(*) FROM Tools WHERE USERID = userID
Where I have to check if any of the results is greater than 0. Is there a way to do this check across N tables in one SQL SELECT query?
If you want to prevent deleting of users that still have cars, garages or tools you should add a foreign key from those tables to the user table.
If you then try to delete a user which is still referenced you'll get an error which you can catch and deal with, e.g. by displaying an approriate error message to the user.
This has the added benefit that the deletion will always be prevented, regardless on how someone tries to delete a user (SQL commandline, a buggy piece of code in your application, ...)
You can just add up the counts, like this:
SELECT (SELECT count(*) FROM Cars WHERE USERID = userID)
+ (SELECT count(*) FROM Garages WHERE USERID = userID)
+ (SELECT count(*) FROM Tools WHERE USERID = userID)
FROM ...
If the result is non-0, you have a referenced user.
SELECT (SELECT count(*) FROM Cars WHERE USERID = userID) as CarCount
, (SELECT count(*) FROM Garages WHERE USERID = userID) as GarageCount
, (SELECT count(*) FROM Tools WHERE USERID = userID) as ToolCount
You could also try this, if Cars is the main table which you want to change:
SELECT count(*)
FROM Cars
INNER JOIN
Garages
ON Cars.USERID = Garages.USERID,
INNER JOIN
Tools
ON Cars.USERID= Tools.USERID
WHERE Cars.USERID = SOME_USER_ID;
Suggest to create a foreign key for the USERID.

What's wrong with this MySQL query? SELECT * AS `x`, how to use x again later?

The following MySQL query:
select `userID` as uID,
(select `siteID` from `users` where `userID` = uID) as `sID`,
from `actions`
where `sID` in (select `siteID` from `sites` where `foo` = "bar")
order by `timestamp` desc limit 100
…returns an error:
Unknown column 'sID' in 'IN/ALL/ANY subquery'
I don't understand what I'm doing wrong here. The sID thing is not supposed to be a column, but the 'alias' (what is this called?) I created by executing (select siteID from users where userID = uID) as sID. And it’s not even inside the IN subquery.
Any ideas?
Edit: #Roland: Thanks for your comment. I have three tables, actions, users and sites. The table actions contains a userID field, which corresponds to an entry in the users table. Every user in this table (users) has a siteID.
I'm trying to select the latest actions from the actions table, and link them to the users and sites table to find out who performed those actions, and on which site. Hope that makes sense :)
You either need to enclose it into a subquery:
SELECT *
FROM (
SELECT userID as uID, (select siteID from users where userID = actions.userID) as sID,
FROM actions
) q
WHERE sID IN (select siteID from sites where foo = "bar")
ORDER BY
timestamp DESC
LIMIT 100
, or, better, rewrite it as a JOIN
SELECT a.userId, u.siteID
FROM actions a
JOIN users u
ON u.userID = a.userID
WHERE siteID IN
(
SELECT siteID
FROM sites
WHERE foo = 'bar'
)
ORDER BY
timestamp DESC
LIMIT 100
Create the following indexes:
actions (timestamp)
users (userId)
sites (foo, siteID)
The column alias is not established until the query processor finishes the Select clause, and buiulds the first intermediate result set, so it can only be referenced in a group By, (since the group By clause operates on that intermediate result set) if you want ot use it this way, puit the alias inside the sub-query, then it will be in the resultset generated by the subquery, and therefore accessible to the outer query. To illustrate
(This is not the simplest way to do this query but it illustrates how to establish and use a column alias from a subquery)
select a.userID as uID, z.Sid
from actions a
Join (select userID, siteID as sid1 from users) Z,
On z.userID = a.userID
where Z.sID in (select siteID from sites where foo = "bar")
order by timestamp desc limit 100
Try the following:
SELECT
a.userID as uID
,u.siteID as sID
FROM
actions as a
INNER JOIN
users as u ON u.userID=a.userID
WHERE
u.siteID IN (SELECT siteID FROM sites WHERE foo = 'bar')
ORDER BY
a.timestamp DESC
LIMIT 100
I think the reason for the error is that the alias isn't available to the WHERE instruction, which is why we have HAVING.
select `userID` as uID,
(select `siteID` from `users` where `userID` = uID) as `sID`,
from `actions`
HAVING `sID` in (select `siteID` from `sites` where `foo` = "bar")
order by `timestamp` desc limit 100
Though i also agree with the other answers that your query could be better structured.
Try the following
SELECT
a.userID as uID
,u.siteID as sID
FROM
actions as a
INNER JOIN
users as u ON u.userID = a.userID
INNER JOIN
sites as s ON u.siteID = s.siteID
WHERE
s.foo = 'bar'
ORDER BY
a.timestamp DESC
LIMIT 100
If you wish to use a field from the select section later you can try a subselect
SELECT One,
Two,
One + Two as Three
FROM (
SELECT 1 AS One,
2 as Two
) sub
I don't know whether this was not in the SQL standard 11 years ago, but I found it the easiest way to use HAVING:
select `userID` as uID,
(select `siteID` from `users` where `userID` = uID) as `sID`,
from `actions`
order by `timestamp` desc limit 100
HAVING `sID` in (select `siteID` from `sites` where `foo` = "bar")