How can I change my INNER JOIN to an OUTER JOIN? - sql

I have 2 tables : Users and Documents.
1 User can have 0 or several documents.
I would like to display each user and their documents, but the problem : I would like to display users that have no documents, in a result like this (assume IdUsers 3 and 5 have no documents):
IdUser IdDocument DocumentName
====== ========== ============
1 1 test11.pdf
1 2 test12.pdf
1 3 test13.pdf
2 4 test21.pdf
2 5 test21.pdf
3 NULL NULL
4 6 test41.pdf
5 NULL NULL

You will use a LEFT JOIN to perform this operation.
create table users
(
userid int
)
create table documents
(
documentid int,
userid int,
documentname varchar(10)
)
insert into users values (1)
insert into users values (2)
insert into users values (3)
insert into users values (4)
insert into documents values (1, 1, 'test')
insert into documents values (2, 1, 'test 1')
insert into documents values (3, 2, 'test 2')
insert into documents values (3, 3, 'test 3')
select *
from users u
left join documents d
on u.userid = d.userid
see a sqlfiddle for a test
You should do some research on JOINs, here is a good description of the JOINs:
A Visual Explanation of SQL Joins

Related

Alternative to NOT IN in SSMS

I have my table in this structure. I am trying to find all the unique ID's whose word's do not appear in the list. How can I achieve this in MS SQL Server.
id word
1 hello
2 friends
2 world
3 cat
3 dog
2 country
1 phone
4 eyes
I have a list of words
**List**
phone
eyes
hair
body
Expected Output
Except the words from the list, I need all the unique ID's. In this case it is,
2
3
I & 4 is not in the output as their words appears in the List
I tried the below code
Select count(distinct ID)
from Table1
where word not in ('phone','eyes','hair','body')
I tried Not Exists also which did not work
You can also use GROUP BY
SELECT id
FROM Table1
GROUP BY id
HAVING MAX(CASE WHEN word IN('phone', 'eyes', 'hair', 'body') THEN 1 ELSE 0 END) = 0
One way to do it is to use not exists, where the inner query is linked to the outer query by id and is filtered by the search words.
First, create and populate sample table (Please save us this step in your future questions):
DECLARE #T AS TABLE (
id int,
word varchar(20)
)
INSERT INTO #T VALUES
(1, 'hello'),
(2, 'friends'),
(2, 'world'),
(3, 'cat'),
(3, 'dog'),
(2, 'country'),
(1, 'phone'),
(4, 'eyes')
The query:
SELECT DISTINCT id
FROM #T t0
WHERE NOT EXISTS
(
SELECT 1
FROM #T t1
WHERE word IN('phone', 'eyes', 'hair', 'body')
AND t0.Id = t1.Id
)
Result:
id
2
3
SELECT t.id FROM dbo.table AS t
WHERE NOT EXISTS (SELECT 1 FROM dbo.table AS t2
INNER JOIN
(VALUES('phone'),('eyes'),('hair'),('body')) AS lw(word)
ON t2.word = lw.word
AND t2.id = t.id)
GROUP BY t.id;
You can try this as well: this is a dynamic table structure:
DECLARE #T AS TABLE (id int, word varchar(20))
INSERT INTO #T VALUES
(1, 'hello'),
(2, 'friends'),
(2, 'world'),
(3, 'cat'),
(3, 'dog'),
(2, 'country'),
(1, 'phone'),
(4, 'eyes')
DECLARE #tblNotUsed AS TABLE ( id int, word varchar(20))
DECLARE #tblNotUsedIds AS TABLE (id int)
INSERT INTO #tblNotUsed VALUES
(1, 'phone'),
(2, 'eyes'),
(3, 'hair'),
(4, 'body')
INSERT INTO #tblNotUsedIds (id)
SELECT [#T].id FROM #T INNER JOIN #tblNotUsed ON [#tblNotUsed].word = [#T].word
SELECT DISTINCT id FROM #T
WHERE id NOT IN (SELECT id FROM #tblNotUsedIds)
The nice thing about SQL is there are sometimes many ways to do things. Here is one way is to place your list of known values into a #temp table and then run something like this.
Select * from dbo.maintable
EXCEPT
Select * from #tempExcludeValues
The results will give you all records that aren't in your predefined list. A second way is to do the join like Larnu has mentioned in the comment above. NOT IN is typically not the fastest way to do things on larger datasets. JOINs are by far the most efficient method of filtering data. Many times better than using a IN or NOT IN clause.

Find missing setting

I have two tables in my DB. Table A is informational data table and Table B is a setting table. How do I find Table A is missing one of the setting in Table B.
E.G.
Table A
username setting
Mark 1
Mark 2
Martin 2
Jane 1
Table B
Possible_Setting
1
2
3
Result Table
username missing_setting
Mark 3
Martin 1
Martin 3
Jane 2
Jane 3
Thanks for help!
This may be inefficient if table sizes are significant, owing to the cross join but its the only answer I could come up with.
SELECT a.username, b.Possible_Setting AS missing_setting
FROM
(SELECT DISTINCT username FROM TableA a) a
CROSS JOIN TableB b
WHERE
NOT EXISTS (
SELECT *
FROM TableA real_a
WHERE real_a.username = a.username
AND real_a.setting = b.Possible_Setting)
ORDER BY 1, 2
Setup code:
CREATE TABLE TableA (username varchar(20), setting tinyint)
CREATE TABLE TableB (Possible_Setting tinyint PRIMARY KEY)
INSERT TableA VALUES
('Mark', 1),
('Mark', 2),
('Martin', 2),
('Jane', 1)
INSERT TableB VALUES
(1),
(2),
(3)

Where condition based on link table

I have a table of Users. Each User can be in multiple Disciplines, and they are linked by a link table, User_Discipline. The tables are pretty straight forward:
User
ID Name more...
3 | John Doe | ...
7 | Jane Smith | ...
12 | Joe Jones | ...
Discipline
ID name
1 | Civil
2 | Mechanical
3 | Piping
User_Discipline
UserID DisciplineID
3 | 2
3 | 1
7 | 2
12 | 3
Say John Doe is the logged in user. He needs to be able to select a list of all of the users in any of his disciplines. In the given example, I need a query that would return a list with John and Jane, since John is both Civil and Mechanical, and Jane is in Mechanical.
I think sub-selects are involved, but all the reading I've done so far have shown how to do subselects checking for one value (say, John's Civil Discipline). I need to be able to perform a query that runs a WHERE condition but matches any of John's Disciplines many-to-many with others' Disciplines.
I'm using the DataTables Editor .NET library to do the SQL, but I can translate an answer in regular SQL markup to that library. The only limitation of the library that I might encounter here is that everything would have to be done in one SQL statement. I appreciate any help!
Something like this?
SELECT DISTINCT [User].ID, [User].Name
FROM [User]
JOIN User_Discipline
ON [User].ID = User_Discipline.UserID
WHERE
User_Discipline.DisciplineID IN (
SELECT DisciplineID
FROM User_Discipline
WHERE UserID = <<John Doe's userID>>
)
You can do it all with inner joins:
declare #users table (id int, fullname varchar(50))
declare #disciplines table (id int, discname varchar(50))
declare #userdisciplines table (userid int, discid int)
insert into #users VALUES (3, 'John Doe')
insert into #users VALUES (7, 'Jane Smith')
insert into #users VALUES (12, 'Joe Jones')
insert into #disciplines VALUES (1, 'Civil')
insert into #disciplines VALUES (2, 'Mechanical')
insert into #disciplines VALUES (2, 'Piping')
insert into #userdisciplines VALUES (3, 2)
insert into #userdisciplines VALUES (3, 1)
insert into #userdisciplines VALUES (7, 2)
insert into #userdisciplines VALUES (12, 3)
SELECT distinct id, fullname from #users u
INNER JOIN #userdisciplines ud ON ud.userid = u.id
INNER JOIN
(SELECT ud.discid FROM #users u
inner join #userdisciplines ud on ud.userid = u.id
WHERE u.fullname = 'John Doe') d ON d.discid = ud.discid

Check duplicates in sql table and replace the duplicates ID in another table

I have a table with duplicate entries (I forgot to make NAME column unique)
So I now have this Duplicate entry table called 'table 1'
ID NAME
1 John F Smith
2 Sam G Davies
3 Tom W Mack
4 Bob W E Jone
5 Tom W Mack
IE ID 3 and 5 are duplicates
Table 2
ID NAMEID ORDERS
1 2 item4
2 1 item5
3 4 item6
4 3 item23
5 5 item34
NAMEID are ID from table 1. Table 2 ID 4 and 5 I want to have NAMEID of 3 (Tom W Mack's Orders) like so
Table 2 (correct version)
ID NAMEID ORDERS
1 2 item4
2 1 item5
3 4 item6
4 3 item23
5 3 item34
Is there an easy way to find and update the duplicates NAMEID in table 2 then remove the duplicates from table 1
In this case what you can do is.
You can find how many duplicate records you have.
In Order to find duplicate records you can use.
SELECT ID, NAME,COUNT(1) as CNT FROM TABLE1 GROUP BY ID, NAME
This is will give you the count and you find all the duplicate records
and delete them manually.
Don't forget to alter your table after removing all the duplicate records.
Here's how you can do it:
-- set up the environment
create table #t (ID int, NAME varchar(50))
insert #t values
(1, 'John F Smith'),
(2, 'Sam G Davies'),
(3, 'Tom W Mack'),
(4, 'Bob W E Jone'),
(5, 'Tom W Mack')
create table #t2 (ID int, NAMEID int, ORDERS varchar(10))
insert #t2 values
(1, 2, 'item4'),
(2, 1, 'item5'),
(3, 4, 'item6'),
(4, 3, 'item23'),
(5, 5, 'item34')
go
-- update the referencing table first
;with x as (
select id,
first_value(id) over(partition by name order by id) replace_with
from #t
),
y as (
select #t2.nameid, x.replace_with
FROM #t2
join x on #t2.nameid = x.id
where #t2.nameid <> x.replace_with
)
update y set nameid = replace_with
-- delete duplicates from referenced table
;with x as (
select *, row_number() over(partition by name order by id) rn
from #t
)
delete x where rn > 1
select * from #t
select * from #t2
Pls, test first for performance and validity.
Let's use the example data
INSERT INTO TableA
(`ID`, `NAME`)
VALUES
(1, 'NameA'),
(2, 'NameB'),
(3, 'NameA'),
(4, 'NameC'),
(5, 'NameB'),
(6, 'NameD')
and
INSERT INTO TableB
(`ID`, `NAMEID`, `ORDERS`)
VALUES
(1, 2, 'itemB1'),
(2, 1, 'itemA1'),
(3, 4, 'itemC1'),
(4, 3, 'itemA2'),
(5, 5, 'itemB2'),
(5, 6, 'itemD1')
(makes it a bit easier to spot the duplicates and check the result)
Let's start with a simple query to get the smallest ID for a given NAME
SELECT
NAME, min(ID)
FROM
tableA
GROUP BY
NAME
And the result is [NameA,1], [NameB,2], [NameC,4], [NameD,6]
Now if you use that as an uncorrelated subquery for a JOIN with the base table like
SELECT
keep.kid, dup.id
FROM
tableA as dup
JOIN
(
SELECT
NAME, min(ID) as kid
FROM
tableA
GROUP BY
NAME
) as keep
ON
keep.NAME=dup.NAME
AND keep.kid<dup.id
It finds all duplicates that have the same name as in the result of the subquery but a different id + it also gives you the id of the "original", i.e. the smallest id for that name.
For the example it's [1,3], [2,5]
Now you can use that in an UPDATE query like
UPDATE
TableB as b
JOIN
tableA as dup
JOIN
(
SELECT
NAME, min(ID) as kid
FROM
tableA
GROUP BY
NAME
) as keep
ON
keep.NAME=dup.NAME
AND keep.kid<dup.id
SET
b.NAMEID=keep.kid
WHERE
b.NAMEID=dup.id
And the result is
ID,NAMEID,ORDERS
1, 2, itemB1
2, 1, itemA1
3, 4, itemC1
4, 1, itemA2 <- now has NAMEID=1
5, 2, itemB2 <- now has NAMEID=2
5, 6, itemD1
To eleminate the duplicates from tableA you can use the first query again.

Join Query in Sql server

I am having trouble with a join in sql.
I have 3 tables.
1: Lists the user details
2: Lists the permissions the user group has
3: Lists the page that that group can access
Table1 users :
****************************************
username | group
****************************************
admin | administrator
Table2 groups :
*********************************************
user_group | create | view | system_admin
*********************************************
administrator | 1 | 0 | 1
Table3 urls:
*********************************************
create | view | system_admin
*********************************************
create.php | view.php | system.php
(apologies for my table drawing)
What I am doing via php , is grabbing the user_group they belong to.
I then need to check if they have access to the page they have just hit or redirect them back.
Can I accomplish this with the current table layout the way they are through a join?, Or shall I look to re-design these tables as they are not intuitive for this kind of thing.
I actually might redesign the tables to make them easier to query:
create table users
(
id int,
username varchar(10),
groupid int
);
insert into users values (1, 'admin', 1);
create table groups
(
groupid int,
groupname varchar(20)
);
insert into groups values (1, 'administrator');
create table permissions
(
permissionid int,
permissionname varchar(20)
);
insert into permissions values (1, 'create');
insert into permissions values (2, 'view');
insert into permissions values (3, 'system_admin');
create table urls
(
urlid int,
name varchar(10)
);
insert into urls values(1, 'create.php');
insert into urls values(2, 'view.php');
insert into urls values(3, 'system.php');
create table group_permission_urls
(
groupid int,
permissionid int,
urlid int
);
insert into group_permission_urls values(1, 1, 1);
insert into group_permission_urls values(1, 0, 2);
insert into group_permission_urls values(1, 3, 3);
Then your query would be similar to this:
select *
from users us
left join groups g
on us.groupid = g.groupid
left join group_permission_urls gpu
on us.groupid = gpu.groupid
left join permissions p
on gpu.permissionid = p.permissionid
left join urls u
on gpu.urlid = u.urlid
see SQL Fiddle with Demo
By comparing the $current_page with the results of an IN() subquery, you can do this in one query. If the page matches any listed in a column the user has permission for, this will return a row. It should not return any row if there is no match in an allowed column.
SELECT
groups.create,
groups.view,
groups.system_admin,
1 AS can_access
FROM
users
JOIN groups ON users.group = groups.user_group
WHERE
users.username = '$some_username'
AND (
/* Substitute the current page. Better, use a prepared statement placeholder if your API supports it */
(groups.create = 1 AND '$current_page' IN (SELECT DISTINCT create FROM urls))
OR
(groups.view = 1 AND '$current_page' IN (SELECT DISTINCT view FROM urls))
OR
(groups.system_admin = 1 AND '$current_page' IN (SELECT DISTINCT system_admin FROM urls))
)
This works by comparing the $current_page to the distinct set of possible values from each of your 3 columns. If it matches a column and also the user's group has permission on that type, a row is returned.
select case when count(1) > 0 then 'come in' else 'go away' end
from users, groups, urls
where
users.username = '$username' and
users.user_group = groups.user_group and
((urls.create = '$url' and groups.create = 1) or
(urls.view = '$url' and groups.view = 1) or
(urls.system_admin = '$url' and groups.system_admin = 1))