What kind of JOIN should I use to join these tables? - sql-server

I have the following three tables that I am trying to join together and create an overview of all desktops and whoever has one assigned to their UserID if any.
dbo.Users
ID Name Lastname JobTitle
118 Ryan Doe Field Engineer
119 Jessica Braun Technical Consultant
120 Daniel Sous Web developer
121 Amy Amyson Intern
.. etc
dbo.LightDesktops
ID Model MACAddress UserID
1 HP1234 AA:AA:AA:AA:AA:AA 118
2 HP1234 BB:BB:BB:BB:BB:BB 121
3 HP1234 AA:BB:BB:AA:BB:AA NULL
4 HP1234 BB:AA:BB:AA:AA:BB 124
dbo.MediumDesktops
ID Model MACAddress UserID
1 HP12PRO AA:AB:AA:BB:AA:BA 132
2 HP12PRO BB:BA:AB:BA:BB:AA 119
3 HP12PRO AA:BA:BA:AB:AA:BA 123
4 HP12PRO BB:BB:BB:AB:BA:BB 241
I managed to figure out how to do it per type of desktop, for example LightDesktops:
SELECT * FROM LightDesktops LEFT OUTER JOIN Users ON LightDesktops.UserID = Users.UserID
That will show me a nice overview of the light desktops with their information as well as whoever has one assigned to if any.
If I'd like to have an overview of the light desktops that are not used and therefore in stock I can do
SELECT * FROM LightDesktops LEFT OUTER JOIN Users ON LightDesktops.UserID = Users.UserID WHERE LightDesktops.UserID IS NULL
How can I accomplish the same results, but for both tables containing information of our desktops? I tried to use an UNION but returned me lots of duplicate values.

Using UNION to bring the light and medium desktops tables together as a single dataset shouldn't give you duplicates unless the tables contain duplicate values in rows accross all columns in your SELECT clauses and you use UNION ALL. If you know your tables have unique values use UNION ALL to give a performance boost.
I would combine the two tables in a common table expression (cte) and then join the resultant table with a LEFT OUTER JOIN to your user table which can also be filtered to find entries where there is no match WHERE [user].[UserID] IS NULL, NB, that would return orphaned rows in your desktops tables where the user has been deleted; alternatively drop the left outer join and use WHERE [desktop].[UserID] IS NULL to return only dekstops without assiged users.
You could try the following code;
WITH cte_Desktop AS
(
SELECT
[ID] as [DesktopID],
'Light Desktop' as [DekstopType],
[Model],
[MACAddress],
[UserID]
FROM [dbo].[LightDesktops]
UNION
SELECT
[ID],
'Medium Desktop',
[Model],
[MACAddress],
[UserID]
FROM [dbo].[MediumDesktops]
)
SELECT
[desktop].*
FROM cte_Desktop AS [desktop]
LEFT OUTER JOIN [dbo].[Users] AS [user]
on [user].[UserID] = [desktop].[UserID]
WHERE [user].[UserID] IS NULL

Use Full join to get report of all users present. it will give you the full report. On top of this results Query for the userid is NULL
SELECT U.ID userid,
U.NAME,
LD.USERID LD_USERID,
LD.MODEL LIGHT_MODEL,
LD.MACADDRESS LIGHT_MAC,
LM.USERID LM_USERID,
LM.MODEL MEDIUM_MODEL,
LM.MACADDRESS MEDIUM_MAC
FROM #USERS U
FULL OUTER JOIN #LIGHTD LD
ON (U.ID = LD.USERID )
FULL OUTER JOIN #LIGHTM LM
ON (LM.USERID = U.ID)

While you can achieve what you need in a single query, it may be more supportable to break the table union out into a view which allows that logic to be reused across multiple queries and also allows for easier refactoring at a later date.
Nod to destination-data for reminding me of this.
View
CREATE VIEW [dbo].[Desktops] AS
(
SELECT
[ID] as [DesktopID],
'Light Desktop' as [DekstopType],
[Model],
[MACAddress],
[UserID]
FROM [dbo].[LightDesktops]
UNION
SELECT
[ID],
'Medium Desktop',
[Model],
[MACAddress],
[UserID]
FROM [dbo].[MediumDesktops]
)
Query
SELECT
[desktop].*
FROM [dbo].[Desktops] AS [desktop]
LEFT OUTER JOIN [dbo].[Users] AS [user]
on [user].[UserID] = [desktop].[UserID]
WHERE [user].[UserID] IS NULL

Try
WITH all as (
select model , userid from LightDesktops
union
select model , userid from MediumDesktops
)
select * FROM all where UserId IS NULL

Related

getting properties from table fails if I join the same table twice

I am trying to combine data from two tables but running into issues because of the odd way the contacts table is formatted. When I run my query it returns no results, Why cant I get both phone and email? If I try to get them separately it works perfectly.
I have a Users table:
id Name email
1 Bill abc#gmail.com
And a Contacts table:
id Name Data
1 phone 1234
1 email abc#gmail.com
My MSSQL query:
select co.Data as phone,
co2.Data as Email
from Users u
left join Contacts co on c.id=u.id
left join Contacts co2 on c2.id=u.id
where co.Name='email'
and co2.Name='phone' --if i dont add this emails and ebverything show up
You need to add more predicates to your join. I agree that the table is not a great architecture. It is an EAV style which is a pain to work with.
select c.Data as phone
, co2.Data as Email
from Users u
left join Contacts c on c.id = u.id
AND co.Name = 'email'
left join Contacts co2 on c2.id = u.id
AND and co2.Name='phone'
Your issue is not reproducible. I ran this script:
DECLARE #Users TABLE (
id tinyint
);
INSERT INTO #Users (id) VALUES (1);
DECLARE #Contacts TABLE (
id tinyint
, [Name] varchar(31)
, [Data] varchar(31)
);
INSERT INTO #Contacts (id, Name, Data) VALUES (1,'Phone','1234'),(1,'email','abc#gmail.com');
select co.Data as phone,
co2.Data as Email
from #Users u
left join #Contacts co on co.id=u.id
left join #Contacts co2 on co2.id=u.id
where co.Name='phone'
and co2.Name='email'
And got:
phone Email
1234 abc#gmail.com
So you had some typo or something else that you left of out your post causing your query not to work.
For instance, I corrected your aliases to use co and co2 throughout. There are places in your query where you used c and c2.
Also I corrected the where clause to associate co with "phone" and co2 with "email", since that's how they are associated in the SELECT list. You have them backwards in the query in your question.
You can create a view pivoting your contacts data which will make many other uses of your contacts data much simpler.
CREATE VIEW vContactsPivoted
AS
SELECT
id, email, phone
FROM
Contacts
PIVOT (MAX(data) FOR [name] IN (email, phone)) pivoted
After that you can use simple joins like any other traditional table:
SELECT
u.id,
u.[Name],
c.phone,
c.email
FROM
[Users] u
JOIN vContactsPivoted c ON u.id = p.id

Return latest record from 1 to Many Relationship table

I have two tables named Visit and VisitMovement that are in a 1-to-many relationship, i.e. 1 Visit can have many VisitMovement rows. I've taken out all the detail fields but these are the table definitions:
CREATE TABLE [dbo].[Visit]
(
[VisitID] [int] IDENTITY(1,1) NOT NULL
)
CREATE TABLE [dbo].[VisitMovement](
[VisitMovementID] [int] IDENTITY(1,1) NOT NULL,
[VisitID] [int] NOT NULL
)
A lot of the time I need to return the Visit and the details from the latest VisitMovement row.
Currently what I do is have these two views, but it is getting slower as time goes by.
For example if I do select * from vwGetVisit where VisitID = 1245 it takes a few seconds to return, and the cost is mostly over on the vwLatestVisitMovement object.
What would a better way be to achieve this?
CREATE VIEW [dbo].[vwGetVisit] as
SELECT
V.*
VM.*
FROM
Visit V
INNER JOIN VisitMovement VM ON
V.VisitID = VM.VisitID
INNER JOIN vwLatestVisitMovement LVM on
VM.VisitMovementID = LVM.VisitMovementID
....many other joins including left joins....
CREATE View [dbo].[vwLatestVisitMovement]
AS
SELECT
VisitID,
max(VisitMovementID) as VisitMovementID
FROM
dbo.VisitMovement
GROUP BY
VisitID
You join visitMovement with vwLatestVisitMovement on VisitMovementID. It souds strange to me... vwLatestVisitMovement .VisitMovementId is calculated using MAX and cost a lot join with this field. I think could be more naturally join vwLatestVisitMovement with Visit on VisitID, you should get same result but a better query plan.
If you need alla data from VisitMOvement you can use CROSS APPLY or ROW_NUMBER() OVER.
Usually I prefer ROW_NUMBER() OVER.
Try this and see if performance are better:
CREATE VIEW [dbo].[vwGetVisit] as
SELECT
V.*
, LVM.*
FROM
Visit V
INNER JOIN vwLatestVisitMovement LVM on
V.VisitID = LVM.VisitID
....many other joins including left joins....
CREATE View [dbo].[vwLatestVisitMovement]
AS
WITH LVM AS (
-- replace * with fields!
SELECT VM.*
, ROW_NUMBER() OVER(PARTITION BY VM.VisitID ORDER BY VM.VisitMovementID DESC) AS CheckLastId
FROM dbo.VisitMovement AS VM
)
SELECT
*
FROM
LVM
WHERE CheckLastId = 1
GO
With cross appply:
CREATE VIEW [dbo].[vwGetVisit] as
SELECT
V.*
, LVM.*
FROM
Visit V
CROSS APPLY (SELECT TOP (1) LM.* FROM dbo.VisitMovement AS LM WHERE LM.VisitID = V.VisitID ORDER BY LM.VisitMovementID DESC ) AS LVM
....many other joins including left joins....
Indexes on VisitID should help solve your problem:
CREATE UNIQUE CLUSTERED INDEX IDX_vwGetVisit_VisitID
ON [vwGetVisit] (VisitID);
CREATE UNIQUE CLUSTERED INDEX IDX_vwLatestVisit_VisitID
ON [vwLastestVisitMovement] (VisitID);
Views aren't known for great performance however, you may be better off repeating the query as needed if you continue to have performance issues.

TSQL Group By Issues

I have a TSQL query that I am trying to group data on. The table contains records of users and the access keys they hold such as site admin, moderator etc. The PK is on User and access key because a user can exist multiple times with different keys.
I am now trying to display a table of all users and in one column, all of the keys that user holds.
If bob had three separate records for his three separate access keys, result should only have One record for bob with all three of is access levels.
SELECT A.[FirstName],
A.[LastName],
A.[ntid],
A.[qid],
C.FirstName AS addedFirstName,
C.LastName AS addedLastName,
C.NTID AS addedNTID,
CONVERT(VARCHAR(100), p.TIMESTAMP, 101) AS timestamp,
(
SELECT k.accessKey,
k.keyDescription
FROM TFS_AdhocKeys AS k
WHERE p.accessKey = k.accessKey
FOR XML PATH ('key'), TYPE, ELEMENTS, ROOT ('keys')
)
FROM TFS_AdhocPermissions AS p
LEFT OUTER JOIN dbo.EmployeeTable as A
ON p.QID = A.QID
LEFT OUTER JOIN dbo.EmployeeTable AS C
ON p.addedBy = C.QID
GROUP BY a.qid
FOR XML PATH ('data'), TYPE, ELEMENTS, ROOT ('root');
END
I am trying to group the data by a.qid but its forcing me to group on every column in the select which will then not be unique so it will contain the duplicates.
Whats another approach to handle this?
Currently:
UserID | accessKey
123 | admin
123 | moderator
Desired:
UserID | accessKey
123 | admin
moderator
Recently, I was working on something and had a similar problem. Like your query, I had an inner 'for xml' with joins in the outer 'for xml'. It turned out it worked better if the joins were in the inner 'for xml'. The code is pasted below. I hope this helps.
Select
(Select Institution.Name, Institution.Id
, (Select Course.Courses_Id, Course.Expires, Course.Name
From
(Select Course.Courses_Id, Course.Expires, Courses.Name
From Institutions Course Course Join Courses On Course.Courses_Id = Courses.Id
Where Course.Institutions_Id = 31) As Course
For Xml Auto, Type, Elements) As Courses
From Institutions Institution
For Xml Auto, Elements, Root('Institutions') )
As I don't have the definitions for the other tables you have I just make a sample test data and you can follow this to answer yours.
Create statement
CREATE TABLE #test(UserId INT, AccessLevel VARCHAR(20))
Insert sample data
INSERT INTO #test VALUES(123, 'admin')
,(123, 'moderator')
,(123, 'registered')
,(124, 'moderator')
,(124, 'registered')
,(125, 'admin')
By using ROW_NUMBER() you can achieve what you need
;WITH C AS(
SELECT ROW_NUMBER() OVER(PARTITION BY UserId ORDER BY UserId) As Rn
,UserId
,AccessLevel
FROM #test
)
SELECT CASE Rn
WHEN 1 THEN UserId
ELSE NULL
END AS UserId
,AccessLevel
FROM C
Output
UserId AccessLevel
------ -----------
123 admin
NULL moderator
NULL registered
124 moderator
NULL registered
125 admin

Trouble with my SQL join/group by/cursor question

I've made database design for a small CRM system. It comprises of Companies and Meetings (amongst others).
Companies has the fields:
ID (primary, auto_inc)
Name (text)
Meetings has the fields:
ID (primary, auto_inc)
CompanyId (link to Companies.ID)
WhenTime (datetime, to store when the meeting was)
Notes (text about the meeting)
What I want to accomplish is a query that gives me a list of all Companies (all fields in the table), AND the WhenTime and Notes of the latest meeting with that company (latest is max(WhenTime), and if there is none, a NULL will do fine).
I think I can solve this with cursors, but I'm afraid of speed.
I've tried several Group By formulations, but I fear I lack the finesse required.
My last attempt was this:
select Companies.ID, Companies.name, mts.whentime, mts.notes
from Companies
left outer join (
select top(1) *
from Meetings
order by [whentime] desc
) mts
on Companies.ID = mts.companyID
order by Companies.name asc
but this code only takes one tuple from Meetings, not one per company in the join, so it's no good.
Any ideas?
Try:
select Companies.ID, Companies.name, mts.whentime, mts.notes
from Companies
cross apply
(
select top(1) *
from Meetings
where Companies.ID = Meetings.companyID
order by [whentime] desc
) mts
order by Companies.name asc;
I would start by creating a view of the latest meetings as I find creating views makes complex queries easier to read and maintain and can introduce an element of reusability (if done right).
CREATE VIEW [dbo].[LatestCompanyNotes]
AS
SELECT [CompanyId], [WhenTime], [Notes]
FROM [Meetings] AS M1
INNER JOIN
(
SELECT [CompanyId], MAX([Id]) AS [MaxId]
FROM [Meetings]
GROUP BY [CompanyId]
) AS M2 ON M2.[CompanyId] = M1.[CompanyId] AND M2.[MaxId] = M1.[Id]
Now you should be able to join to this view in your query as you've previously done.
SELECT Companies.[ID], Companies.[Name], mts.[WhenTime], mts.[Notes]
FROM [Companies]
LEFT OUTER JOIN [dbo].[LatestCompanyNotes] AS mts ON mts.[CompanyId] = Companies.[ID]
ORDER BY Companies.[Name] ASC
Please note that I've not tested the code (I don't even have SQL Server installed) and it may require a few small changes to work.
You don't need a cross-apply here, just a correlated sub-query to find the most recent meeting date:
SELECT Companies.ID, Companies.name, mts.whentime, mts.notes
FROM Companies
LEFT OUTER JOIN Meetings mts
ON Companies.ID = mts.companyID
AND mts.WhenTime =
(SELECT MAX(WhenTime) FROM Meetings mtshist WHERE mtshist.companyID = mts.companyID)
ORDER BY Companies.name
Note that this will retrieve all companies, including those which have never had a meeting:
1 Alexander and co. 2010-01-04 some more notes
2 Barnard Partnership 2010-01-03 NULL
3 Collingwood Ltd. 2010-01-07 recent meeting
4 Dimitri and sons NULL NULL

Combine two tables that have no common fields

I want to learn how to combine two db tables which have no fields in common. I've checked UNION but MSDN says :
The following are basic rules for combining the result sets of two queries by using UNION:
The number and the order of the columns must be the same in all queries.
The data types must be compatible.
But I have no fields in common at all. All I want is to combine them in one table like a view.
So what should I do?
There are a number of ways to do this, depending on what you really want. With no common columns, you need to decide whether you want to introduce a common column or get the product.
Let's say you have the two tables:
parts: custs:
+----+----------+ +-----+------+
| id | desc | | id | name |
+----+----------+ +-----+------+
| 1 | Sprocket | | 100 | Bob |
| 2 | Flange | | 101 | Paul |
+----+----------+ +-----+------+
Forget the actual columns since you'd most likely have a customer/order/part relationship in this case; I've just used those columns to illustrate the ways to do it.
A cartesian product will match every row in the first table with every row in the second:
> select * from parts, custs;
id desc id name
-- ---- --- ----
1 Sprocket 101 Bob
1 Sprocket 102 Paul
2 Flange 101 Bob
2 Flange 102 Paul
That's probably not what you want since 1000 parts and 100 customers would result in 100,000 rows with lots of duplicated information.
Alternatively, you can use a union to just output the data, though not side-by-side (you'll need to make sure column types are compatible between the two selects, either by making the table columns compatible or coercing them in the select):
> select id as pid, desc, null as cid, null as name from parts
union
select null as pid, null as desc, id as cid, name from custs;
pid desc cid name
--- ---- --- ----
101 Bob
102 Paul
1 Sprocket
2 Flange
In some databases, you can use a rowid/rownum column or pseudo-column to match records side-by-side, such as:
id desc id name
-- ---- --- ----
1 Sprocket 101 Bob
2 Flange 101 Bob
The code would be something like:
select a.id, a.desc, b.id, b.name
from parts a, custs b
where a.rownum = b.rownum;
It's still like a cartesian product but the where clause limits how the rows are combined to form the results (so not a cartesian product at all, really).
I haven't tested that SQL for this since it's one of the limitations of my DBMS of choice, and rightly so, I don't believe it's ever needed in a properly thought-out schema. Since SQL doesn't guarantee the order in which it produces data, the matching can change every time you do the query unless you have a specific relationship or order by clause.
I think the ideal thing to do would be to add a column to both tables specifying what the relationship is. If there's no real relationship, then you probably have no business in trying to put them side-by-side with SQL.
If you just want them displayed side-by-side in a report or on a web page (two examples), the right tool to do that is whatever generates your report or web page, coupled with two independent SQL queries to get the two unrelated tables. For example, a two-column grid in BIRT (or Crystal or Jasper) each with a separate data table, or a HTML two column table (or CSS) each with a separate data table.
This is a very strange request, and almost certainly something you'd never want to do in a real-world application, but from a purely academic standpoint it's an interesting challenge. With SQL Server 2005 you could use common table expressions and the row_number() functions and join on that:
with OrderedFoos as (
select row_number() over (order by FooName) RowNum, *
from Foos (nolock)
),
OrderedBars as (
select row_number() over (order by BarName) RowNum, *
from Bars (nolock)
)
select *
from OrderedFoos f
full outer join OrderedBars u on u.RowNum = f.RowNum
This works, but it's supremely silly and I offer it only as a "community wiki" answer because I really wouldn't recommend it.
SELECT *
FROM table1, table2
This will join every row in table1 with table2 (the Cartesian product) returning all columns.
select
status_id,
status,
null as path,
null as Description
from
zmw_t_status
union
select
null,
null,
path as cid,
Description from zmw_t_path;
try:
select * from table 1 left join table2 as t on 1 = 1;
This will bring all the columns from both the table.
If the tables have no common fields then there is no way to combine the data in any meaningful view. You would more likely end up with a view that contains duplicated data from both tables.
To get a meaningful/useful view of the two tables, you normally need to determine an identifying field from each table that can then be used in the ON clause in a JOIN.
THen in your view:
SELECT T1.*, T2.* FROM T1 JOIN T2 ON T1.IDFIELD1 = T2.IDFIELD2
You mention no fields are "common", but although the identifying fields may not have the same name or even be the same data type, you could use the convert / cast functions to join them in some way.
why don't you use simple approach
SELECT distinct *
FROM
SUPPLIER full join
CUSTOMER on (
CUSTOMER.OID = SUPPLIER.OID
)
It gives you all columns from both tables and returns all records from customer and supplier if Customer has 3 records and supplier has 2 then supplier'll show NULL in all columns
Select
DISTINCT t1.col,t2col
From table1 t1, table2 t2
OR
Select
DISTINCT t1.col,t2col
From table1 t1
cross JOIN table2 t2
if its hug data , its take long time ..
SELECT t1.col table1col, t2.col table2col
FROM table1 t1
JOIN table2 t2 on t1.table1Id = x and t2.table2Id = y
Joining Non-Related Tables
Demo SQL Script
IF OBJECT_ID('Tempdb..#T1') IS NOT NULL DROP TABLE #T1;
CREATE TABLE #T1 (T1_Name VARCHAR(75));
INSERT INTO #T1 (T1_Name) VALUES ('Animal'),('Bat'),('Cat'),('Duet');
SELECT * FROM #T1;
IF OBJECT_ID('Tempdb..#T2') IS NOT NULL DROP TABLE #T2;
CREATE TABLE #T2 (T2_Class VARCHAR(10));
INSERT INTO #T2 (T2_Class) VALUES ('Z'),('T'),('H');
SELECT * FROM #T2;
To Join Non-Related Tables , we are going to introduce one common joining column of Serial Numbers like below.
SQL Script
SELECT T1.T1_Name,ISNULL(T2.T2_Class,'') AS T2_Class FROM
( SELECT T1_Name,ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS S_NO FROM #T1) T1
LEFT JOIN
( SELECT T2_Class,ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS S_NO FROM #T2) T2
ON t1.S_NO=T2.S_NO;
select * from this_table;
select distinct person from this_table
union select address as location from that_table
drop wrong_table from this_database;
Very hard when you have to do this with three select statments
I tried all proposed techniques up there but it's in-vain
Please see below script. please advice if you have alternative solution
select distinct x.best_Achiver_ever,y.Today_best_Achiver ,z.Most_Violator from
(SELECT Top(4) ROW_NUMBER() over (order by tl.username) AS conj, tl.
[username] + '-->' + str(count(*)) as best_Achiver_ever
FROM[TiketFollowup].[dbo].N_FCR_Tikect_Log_Archive tl
group by tl.username
order by count(*) desc) x
left outer join
(SELECT
Top(4) ROW_NUMBER() over (order by tl.username) as conj, tl.[username] + '-->' + str(count(*)) as Today_best_Achiver
FROM[TiketFollowup].[dbo].[N_FCR_Tikect_Log] tl
where convert(date, tl.stamp, 121) = convert(date,GETDATE(),121)
group by tl.username
order by count(*) desc) y
on x.conj=y.conj
left outer join
(
select ROW_NUMBER() over (order by count(*)) as conj,username+ '--> ' + str( count(dbo.IsViolated(stamp))) as Most_Violator from N_FCR_Ticket
where dbo.IsViolated(stamp) = 'violated' and convert(date,stamp, 121) < convert(date,GETDATE(),121)
group by username
order by count(*) desc) z
on x.conj = z.conj
Please try this query:
Combine two tables that have no common columns:
SELECT *
FROM table1
UNION
SELECT *
FROM table2
ORDER BY orderby ASC

Resources