What is the better way of writing this query? - sql

I have a simple join query as follows.
select *
from user u left join
user_roles ur
on ur.user_id = u.user_id
and ur.created_by = 'Mike'
where u.division = 'some division'
OR
select *
from user u left join
user_roles ur
on ur.user_id = u.user_id
where u.division = 'some division'
and ur.created_by = 'Mike'
The point is here is I have moved the additional filter clause condition from left join to where clause.
Does it make any difference if I join two tables on more than column or put it in where clause?

Yes it makes a big difference.
You are basicalling nullifying the left join and making it an inner join, hiding user roles not created by Mike
Bell Mike
Toe Mike
Bob Jerry
first query returns
Bell Mike
Toe Mike
Bob NULL
second Query returns
Bell Mike
Toe Mike

Yes - it makes an important difference
Any filter on the joined table should be in the join for it to work correctly - ie: the former will work, the second won't.
Try the following (SQL Server syntax) to see how the results differ
declare #u table (user_id int, division varchar(20))
declare #ur table (user_id int, created_by varchar(10))
insert #u values (1,'sales'), (2,'marketing'), (3,'engineering')
insert #ur values (1, 'mike'), (3,'james'), (3,'mike')
select * from #u u
left join #ur ur on ur.user_id = u.user_id and ur.created_by = 'Mike'
select * from #u u
left join #ur ur on ur.user_id = u.user_id
where ur.created_by = 'Mike'

Related

How can I run my SQL, to get the result such as "John Doe, plumbing"

I have table category, with id and cat_name
Example: 152, Plumbing
I have table user, with category_id and name
Example: 152, John Doe
When I do
SELECT name, category_id FROM user
As a result I will have something like
John Doe, 152
Question:
How can I run my SQL, to get the result such as
John Doe, plumbing
It is quite easy to get this, Please use below query for the same
SELECT User.Name, Category.cat_name FROM User
INNER JOIN Category ON Category.Id = User.category_id
Happy coding :-)
Try basic JOINS
SELECT C.cat_name, U.Name
FROM category C JOIN User U ON C.id = U.category_id
this will work:
select a.name,b.cat_name from user a, category b
where a.category_id=b.id
As you can understand you have two tables, and there is some 1 to 1 relationship between records of these tables(user & category).
So here we will use a simple join to connect this two table.
SELECT c.cat_name, u.name
FROM category as c, user as u
WHERE c.id = u.category_id
When I do category as c, this is called aliasing. This has two benefits. One is to keep our query short and sweet(no need to repeat category, just use c) and second is SQL query engine has 100% clarity of what we want to select.
Let's you also want to select id, then you should use c.id or u.category_id.
Check these alternate methods-
IF OBJECT_ID('Category') IS NOT NULL
DROP TABLE Category
IF OBJECT_ID('User') IS NOT NULL
DROP TABLE [User]
CREATE TABLE Category
(ID INT,Cat_name VARCHAR(20))
CREATE TABLE [User]
(Category_id INT,[Name] VARCHAR(20))
INSERT INTO Category(ID ,Cat_name)
VALUES (152,'Plumbing')
INSERT INTO [User](Category_id ,[Name])
VALUES (152,'John Doe')
--Method 1 (using CROSS APPLY)
SELECT U.[Name], C.Cat_name FROM [User] U CROSS APPLY Category C WHERE U.Category_id=C.ID
--Method 2 (using INNER JOIN)
SELECT U.[Name], C.Cat_name FROM [User] U INNER JOIN Category C ON U.Category_id=C.ID
--Method 3 (using WHERE and WITHOUT JOIN)
SELECT U.[Name], C.Cat_name FROM [User] U,Category C WHERE U.Category_id=C.ID

SQL correct way of joining if the other parameter is null

I have this code and its temporary tables so you can run it.
create table #student
(
id int identity(1,1),
firstname varchar(50),
lastname varchar(50)
)
create table #quiz
(
id int identity(1,1),
quiz_name varchar(50)
)
create table #quiz_details
(
id int identity(1,1),
quiz_id int,
student_id int
)
insert into #student(firstname, lastname)
values ('LeBron', 'James'), ('Stephen', 'Curry')
insert into #quiz(quiz_name)
values('NBA 50 Greatest Player Quiz'), ('NBA Top 10 3 point shooters')
insert into #quiz_details(quiz_id, student_id)
values (1, 2), (2, 1)
drop table #student
drop table #quiz
drop table #quiz_details
So as you can see lebron james takes the quiz nba top 10 3 point shooters quiz and stephen curry takes the nba 50 greatest player quiz.
All I want is to get the thing that they didn't take yet for example LeBron hasn't taken the 50 greatest player quiz so what I want is like this.
id quiz_name firstname lastname
----------------------------------------------------
1 NBA 50 Greatest Player Quiz NULL NULL
I want 2 parameters, the id of lebron and the id of the quiz so that I will know that lebron or stephen hasn't taken it yet, but how would I do that if the value of the student_id is still null?
My attempt:
select
QD.id,
Q.quiz_name,
S.firstname,
S.lastname
from
#quiz_details QD
inner join
#quiz Q on Q.id = QD.quiz_id
inner join
#student S on S.id = QD.student_id
This should get you started:
-- filter out the student and quiz you want
DECLARE #qid INT = 1
DECLARE #sid INT = 1
SELECT *
FROM #student AS s
INNER JOIN #quiz AS q -- you want the quiz
ON 1=1
LEFT OUTER JOIN #quiz_details AS qd -- left join here to get result where rows not found
ON qd.id = q.id
AND qd.student_id=s.id
WHERE s.id = #sid
AND q.id = #qid
AND qd.id IS NULL -- only return quizes not taken
Pretty sure you want something along these lines. This will give you the quiz values and return NULL for the student and quiz_details when there is no matching data.
select *
from #quiz q
left join #quiz_details qd on q.id = qd.quiz_id
left join #student s on s.id = qd.student_id
This
Select Q.id , Q.quiz_name ,S.firstname, S.lastname
from
#quiz Q -- cross join, returns N*K results, do not use without
CROSS JOIN #student S -- where condition that limits it - SAS solution is nicer!
where not exists (select 1 from #quiz_details where quiz_id = Q.id and student_id = S.id)
will give you
id quiz_name firstname lastname
1 NBA 50 Greatest Player Quiz LeBron James
2 NBA Top 10 3 point shooters Stephen Curry
Edit: changed code to explicit cross join rather then implicit, leaving both in here for comparison
SELECT #quiz Q, # student S -- old implicit syntax - comma is easily missed
vs.
SELECT #quiz Q CROSS JOIN #student S -- makes it clearer what is wanted
My take on it - similar to Patrick's answer with a cross join.
Full sample available at sqlfiddle
select
Q.Quiz_Name Quiz
,S.LastName Last
,S.FirstName First
,QD.Quiz_ID
,QD.Student_ID
from
/* Get a full list of ALL Test/Student combinations */
quiz Q
CROSS JOIN student S
/* Join the taken tests to the combinations */
LEFT JOIN quiz_details QD on Q.id = QD.quiz_id
and S.id = QD.student_id
/* Only select where no Quiz_ID exists */
WHERE QD.Quiz_ID IS NULL
ORDER BY Q.Quiz_Name, S.Lastname, S.FirstName;
select s.firstname, s.lastname, q.id as not_taken_quiz_id, q.quiz_name as not_taken_quiz_name
from #student s
left join #quiz_details qd on s.id = qd.student_id
left join #quiz q on q.id <> qd.quiz_id
This will give you each student along with the quiz that they have not taken.

How to replace LEFT outer join with INNER join in SQL?

I have a view on which I need to provide cluster Indexing the problem is in order to provide cluster indexing the it should not have any of the left or right outer joins , and I want to replace the LEFT outer join with INNER join , one of the ways which I can think of is to insert a dummy value with lets say -1 in the right table and by doing this even if all the Ids from the left table wont match Ids from the right table in INNER JOIN but since we have inserted -1 in the right table and we are using IsNULL(u.UserId,-1) it should return all the values from the left table but somehow this approach is not working.
create table Users(
UserId int,
UserName nvarchar(255)
)
insert into Users values(1,'sid429')
insert into Users values(2,'ru654')
insert into Users values(3,'dick231')
create table managers
(
caseId int,
CaseName nvarchar(255),
UserId int
)
insert into managers values (100,'Case1',1)
insert into managers values (101,'Case2',2)
insert into managers values (-1,NULL,-1)
select username from users u inner join managers m on m.UserId=IsNULL(u.UserId,-1)
Don't talk about indexes, but I think you could replace LEFT JOIN by INNER JOIN + UNION
select username from users u inner join managers m on m.UserId= u.UserId
UNION ALL
select username from users u WHERE NOT EXISTS (SELECT 1 FROM managers m WHERE m.UserId = u.UserId)
IsNull(u.UserId,-1) doesn't seem right - u.UserId is never null, since the absence of data is in the managers table - in this case, u.UserId will always have a value, but m.UserId might not, so IsNull(u.UserId, -1) won't work.
I'm intrigued to see a better answer, but I don't think you can do that - I think you eventually need to substitute the value conditionally to -1 if it doesn't exist in the other table, like this:
select username from users u
inner join managers m on m.UserId =
case when not exists(select * from managers where UserId = u.UserId)
then -1 else u.UserId end
This has the desired effect, but looking at the execution plan, won't help your performance issue.
You can replace a LEFT OUTER JOIN with an INNER JOIN if you add the missing values in the related table.
It has not worked for you because you have added a -1 value. But the not matching value on your INNER JOIN is a 3, not a null or a -1.
You can do so at runtime with an UNION, no need to permanently create those values as you have tried to do (inserting that -1 value) :
with expanded_managers as (
select CaseId, CaseName, UserId
from managers
union
select null, null, UserId
from users
where not exists (select * from managers where managers.UserId = users.UserId)
)
select UserName, CaseName
from users
inner join expanded_managers on expanded_managers.UserId = users.UserId
if you require only username it should be simple:
select distinct username from users u inner join managers m on m.UserId=u.UserId OR ( m.UserId=-1 AND u.userId = u.userId)
I have cleaned-up this part a bit. I had to guess the logical model, given that you did not specify any constraints.
create table Users (
UserId int not null
, UserName nvarchar(255) not null
, constraint pk_users primary key (UserId)
, constraint ak_users unique (UserName)
);
create table Cases (
CaseId int not null
, CaseName nvarchar(255) not null
, UserId int not null
, constraint pk_cases primary key (CaseId)
, constraint ak_cases unique (CaseName)
, constraint fk_cases foreign key (UserId)
references Users (UserId)
);
insert into Users values(1,'sid429') ;
insert into Users values(2,'ru654') ;
insert into Users values(3,'dick231');
insert into Cases values (100,'Case1',1);
insert into Cases values (101,'Case2',2);
This is mostly self-explanatory, but you have to understand that candidate keys (unique) for the result are: {UserID, CaseId}, {UserName, CaseName}, {UserID, CaseName}, {UserName, CaseId}. Not sure if you were expecting that.
with
R_00 as (
select UserId from Users
except
select UserId from Cases
)
select u.UserId
, u.UserName
, c.CaseId
, c.CaseName
from Users as u
join Cases as c on u.UserId = c.UserId
union
select u.UserId
, u.UserName
, (-1) as CaseId
, 'n/a'as CaseName
from Users as u
join R_00 as r on r.UserId = u.UserID
;
Another version of this, similar to other examples in the post.
select u.UserId
, u.UserName
, c.CaseId
, c.CaseName
from Users as u
join Cases as c on u.UserId = c.UserId
union
select u.UserId
, u.UserName
, (-1) as CaseId
, 'n/a' as CaseName
from Users as u
where not exists (select 1 from Cases as c where c.UserId = u.userId)
;

Join a table based on condition

I have a situation where I need to join a table only on a particular condition.
Lets take the underwritten scenario
create table [premiumuser] (user_id int, name nvarchar(50));
create table [liteuser] (user_id int, name nvarchar(50));
create table [feature] (id nvarchar(50), user_id int, userkey int);
insert into [premiumuser] select 1, 'stephen';
insert into [premiumuser] select 2, 'roger';
insert into [liteuser] select 1, 'apollo';
insert into [liteuser] select 2, 'venus';
insert into feature select 'Upload content', 1, 1;
insert into feature select 'Create account', 1, 0;
insert into feature select 'View content', 2, 0;
Now I want to join only 1 table at a time from premiumUser or Lite user.
i.e.
select F.*, U.Name from feature
Inner Join
if condition 1
then
LiteUser U on U.User_Id = F.User_ID
else
PremiumUser U on U.User_Id = F.User_ID
end
Is there anyway to achieve this???
I know something like this can be done
select
f.id,
case when userkey=0 then l.name else p.name end as username
from [feature] f
left join [liteuser] l on l.user_id = f.user_id
left join [premium user] p on p.user_id = f.user_id
but since I have huge tables I don't want to join both the tables.
I want to be selective in joining the tables
No, you can't selective join in standard TSQL (definitely true for MS SQL and I'm pretty sure it's true for Oracle and MySQL).
You can however put the condition in the join. This will allow SQL to very quickly ignore the join because it will evaluate the conditions in order:
select
f.id,
case when userkey=0 then l.name else p.name end as username
from [feature] f
left join [liteuser] l
on condition1 = 1
AND l.user_id = f.user_id
left join [premium user] p
on condition1 = 2
and p.user_id = f.user_id

Where clause in sql query remove also null values

I have these tables:
'Users'
id email name last_access
1 luca#gmail.com Luca Pluto 2012-10-05 17:21:22.0
2 pippo#gmail.com Irene Pippo 2012-10-05 17:22:25.0
'Nets_permissions'
user_id network_id perm
1 1234 3
1 1235 1
2 1235 3
I've written this query:
SELECT u.id, u.name, n.perm
FROM users as u LEFT OUTER JOIN nets_permissions as n
ON u.id = n.user_id
WHERE n.network_id=1234 AND n.perm <> 3
because I want users that already have a permission for network_id (1234) , but also users that don't have a permission for the network_id. In other words I would have this result query:
2 null null
because the user Luca Pluto with id=1, for the net 1234, have perm=3 so I want left out. Instead, the user Irene Pippo with id=2 doesn't have any permission on the 1234 net. So it's row must have net_id and perm set to null.
My query result is empty. I don't know why. Without the clause n.perm <> 3 seems to work well, but after also the null value are left out, not only the rws with perm=3.
I've also tried in this way:
SELECT u.id, u.name, n.perm FROM users as u LEFT OUTER JOIN
(select * from nets_permissions WHERE network_id=1234) as n on u.id = n.user_id
WHERE n.perm <> 3
but it doesn't work. without the WHERE clause all works. After no. The result query is empty.
How I can resolve this problem? I need that the perm column is a value or null, I can't remove this column.
The right solution is:
SELECT u.id, u.name, n.perm FROM users as u LEFT OUTER JOIN
(select * from nets_permissions WHERE network_id=1234) as n on u.id = n.user_id
WHERE (n.perm <> 3 or n.perm is NULL)
Here Working with NULL Values there is a expanation of the treatment of NULL values in MySQL
Thank you all for your help!
Many SQL dialect use a special trinary logic with NULL the third possible value additional to true and false.
Anything compared to NULL results to NULL which in turn is handled as false.
So x = null will result in false for all x, just as x != null
If you want to include NULL values in the result you have to add special handling for that (Oracle syntax):
SELECT u.id, u.name, n.perm
FROM users as u LEFT OUTER JOIN nets_permissions as n
ON u.id = n.user_id
WHERE n.network_id=1234
AND (n.perm <> 3 or n.perm is not null)
SELECT u.id, u.name, n.perm
FROM
users as u
LEFT OUTER JOIN
nets_permissions as n ON u.id = n.user_id
WHERE
n.network_id = 1234 and n.perm <> 3
or
n.network_id <> 1234
or
n.network_id is null
I like users that user the "JOIN" syntax. It has additional benefit, aside from making the code easier to read, that people don't realize.
SELECT u.id, u.name, n.perm
FROM users as u LEFT OUTER JOIN nets_permissions as n
ON u.id = n.user_id
AND n.network_id=1234
WHERE COALESCE(n.perm,-1) <> 3