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

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.

Related

SQL selecting multiple record by different variable

Ok, so I have a table Assignment:
[UserId]
[GroupId]
[UpdatedBy]
[UpdatedAt]
Also, I have a function for returning users from a specific group:
select UserId
from dbo.GetGroupUsers() ggu
where ggu.GroupId = ?
In Assignment, I want to check all groups that our user is listed and then I want to select ALL users from these groups, without duplicate.
How can I achieve this?
Edit:
Sample output form selecting groupid = 4
for example user "test1" belong to other group where id=2 at the same time and i want selected all users from for example group 2 and 4 (cause in this example test1 said so) without records duplicate
All groups from one UserId (say UserId 10):
select GroupId from Assignment where UserId = 10
Select all users from those groups (without duplicate):
select distinct UserId
from dbo.GetGroupUsers() ggu
where ggu.GroupId in (select GroupId from Assignment where UserId = 10)
I hope this is what you wanted.
An inner self join should get you the IDs of the users you're looking for. Join the result with your user table (which you didn't post) to possibly get other information about these users.
SELECT DISTINCT
a2.userid
FROM assignment a1
INNER JOIN assignment a2
ON a2.groupid = a1.groupid
WHERE a1.userid = ?;
(Replace the ? with the ID of the user, you want to start with.)
Assuming your input is a user id:test1 and assuming that you are just looking at one table (Assignment)
DECLARE #UserId INT = 2
;WITH GROUPS AS
(
SELECT DISTINCT GroupId FROM Assignment WHERE UserId = #UserId
)
SELECT distinct assgn.UserName, gps.GroupId FROM Assignment assgn
INNER JOIN
GROUPS gps ON
assgn.GroupId = gps.GroupId
Please let me know if this helps

Delete rows from multiple tables with foreign keys

I have three tables
userTable with following rows Id, FirstName, LastName, Email
Product A with following rows Id, UserId(FK), startDate
Product D with following rows Id, UserId(FK), startDate
I want to delete rows from three tables with particular UserId.
What I want to achieve -
Find the id given email (select id from userTable where Email = 'abc.com') - got back id - 3
Delete from Product A where id = 3
Delete from Product D where id = 3
Delete from userTable where id = 3
I want to create a script.
Any help is appreciated or learning reference would be great.
Declare a variable and save the id to that variable. Use that Id for the delete query.
DECLARE #Id INT;
SELECT #Id=id
FROM userTable
WHERE Email = 'abc.com'
DELETE FROM Product A WHERE id = #Id
DELETE FROM Product D WHERE id = #Id
DELETE FROM userTable WHERE id = #Id
But I guess in your case you have UserId as the FK so you should be trying this:
DELETE FROM Product A WHERE UserId= #Id
DELETE FROM Product D WHERE UserId= #Id
DELETE FROM userTable WHERE id = #Id
Note: If you want to do the same thing what's there in question, go for 1st one. If you want to delete the relevant user records from A and D table then go for the 2nd method where you are deleting the records using the FK.
From what I can gather from your question you want something like this:
DELETE FROM
userTable,
ProductA,
ProductB
WHERE
userTable.Id = (select Id from userTable where Email like 'abc.com') AND
userTable.Id = ProductA.Id AND
userTable.Id = ProductB.Id;
And the Email in the subquery in the WHERE clause would be your input parameter
DISCLAIMER: From a Sybase/SQL Anywhere background, this would be possible, I'm not sure if it could be done in SQL-server.

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.

Select count field of rows that are related to another table

I'm struggling with this issue from a long time and don't know how to solve it. It's hard for me to describe, so please be patient. There are two tables:
Table "Users"
UserId PK
Gender
Table "Forms"
FormId PK
UserId1 FK
UserId2 FK
Type
Forms are always related to two users, but not all users have related forms. Now I want to count specified gender only of those users, who have related forms.
So as a result, I want to have sth. like this:
# | Gender | GenderCount
1 | male | 43
2 | female | 12
3 | trans | 2
I tried the following SQL-Script but the result isn't distinct (the sum of all GenderCount is greater then the actual number of users)
SELECT u.Gender AS 'Gender', COUNT(u.Gender) AS 'GenderCount'
FROM Users u, Forms f
WHERE ((f.UserId1 = u.UserId)
OR (f.UserId2 = u.UserId))
AND (Type = 'Foo')
GROUP BY Gender
ORDER BY GenderCount
DESC
Any tips to solve this?
Let's take a look at what you want:
How many of each gender answered any form?
Note: each user should only be counted once, no matter how many forms they've filled out.
Phrased like this, the answer becomes fairly obvious, at least in pseudo-code:
SELECT
u.Gender,
COUNT(u.Gender)
FROM
Users u
WHERE
[User has answered a form]
GROUP BY
u.Gender
The easiest way to determine if a user has answered a form depends on the specific flavour of SQL being used. You'll need to use a subquery. There are a couple of options for how to access it.
IN is the most common method:
SELECT
u.Gender Gender,
COUNT(u.Gender) GenderCount
FROM
Users u
WHERE
u.id IN (
SELECT f.UserId1 user_id FROM Forms f WHERE Type = 'Foo'
UNION
SELECT f.UserId2 user_id FROM Forms f WHERE Type = 'Foo'
)
GROUP BY
Gender
ORDER BY
GenderCount DESC
Where available, EXISTS is more natural to read, and is sometimes faster:
SELECT
u.Gender Gender,
COUNT(u.Gender) GenderCount
FROM
Users u
WHERE
EXISTS(
SELECT '1'
FROM Forms f
WHERE
(f.UserId1 = u.id OR f.UserId2 = u.id)
AND Type = 'Foo'
)
GROUP BY
Gender
ORDER BY
GenderCount DESC
Regarding speed: The query optimiser will often convert IN to EXISTS where possible, to avoid selecting extra rows unnecessarily. However, the use of multiple columns necessitates either an OR or a UNION, so it may be pretty even in this case. ie: neither OR nor UNION play nicely with indexes.
SELECT u1.Gender AS 'Gender', COUNT(*) AS 'GenderCount'
FROM
Users u1
INNER JOIN
(SELECT DISTINCT u.UserId
FROM
Users u
INNER JOIN Forms f ON ((f.UserId1 = u.UserId)
OR (f.UserId2 = u.UserId))
AND (f.Type = 'Foo')) T ON T.UserId = u1.UserId
GROUP BY Gender
ORDER BY GenderCount DESC
Skip the join which is generating multiple rows per user:
SELECT Gender, COUNT(Gender) AS 'GenderCount'
FROM Users
WHERE UserId IN (SELECT UserId1 FROM Forms WHERE Type = 'Foo'
UNION
SELECT UserId2 FROM Forms WHERE Type = 'Foo')
GROUP BY Gender
ORDER BY GenderCount DESC
Or if you prefer to avoid a UNION (which is perfectly valid in this scenario BTW) you can use OR like this:
SELECT Gender, COUNT(Gender) AS 'GenderCount'
FROM Users
WHERE UserId IN (SELECT UserId1 FROM Forms WHERE Type = 'Foo')
OR UserId IN (SELECT UserId2 FROM Forms WHERE Type = 'Foo')
GROUP BY Gender
ORDER BY GenderCount DESC
As others have pointed out, there are ways to do this using a JOIN as well. However, a JOIN adds needless complexity for the DBMS engine as it will first need to match up the rows, and then reduce to DISTINCT values.
You should use
count(distinct u.UserId)
that way users only get counted once: count(distinct field_name) counts the number of unique values contained in field_name, so counting distinct on the primary key gives you the number of unique users, which is what you're looking for.
Also, instead of joining, you probably would be better off using an in clause like this
select Gender, count(distinct UserId) as GenderCount
from Users
where u.UserId in (select UserId1 from Forms) or u.UserId in (select UserId2 from Forms)
It's probably also going to be slightly faster.

MySQL DELETE in a single table

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)