possible to join a table on only one row - sql

I have a temporary table I'm creating in a sproc that houses my user information. I need to join this table to another table that has SEVERAL rows for that particular user but I only want to return one result from the "many" table.
something like this
SELECT u.firstname, u.lastname
FROM #users AS u
INNER JOIN OtherTable AS ot on u.userid = (top 1 ot.userid)
obviously that wont' work but that's the gist of what I'm trying to do for two reasons, one I only want one row returned (by a date field descending) and two for optimaztion purposes. The query has to scan several thousand rows as it currently is..

SELECT
u.firstname, u.lastname, t.*
FROM
#users AS u
CROSS APPLY
(SELECT TOP 1 *
FROM OtherTable AS ot
WHERE u.userid = ot.userid
ORDER BY something) t

Use the ROW_NUMBER() function to order your rows by datetime and then filter by row_num = 1
;WITH otNewest
AS
(
SELECT *
FROM othertable
WHERE ROW_NUM() OVER(partition by userid order by datetime DESC) = 1
)
SELECT u.firstname, u.lastname, o.*
FROM #users U
INNER JOIN otNewest O
ON U.userid = O.userid

So, if you're joining but not returning any columns from OtherTable, then you're only interested in checking for existence?
SELECT u.firstname, u.lastname
FROM #users AS u
WHERE EXISTS(SELECT 1
FROM OtherTable ot
WHERE u.userid = ot.userid)

Related

How to select three columns from two tables and group by only two columns in SQL

I have an SQL Query that's working, but I need to add one more column. This column is on a different table and I can't figure out how to bring it all together.
Here's the code that works... it's selecting all active users who share a first and last name with another user (we're looking for duplicates)
SELECT UserTable.LastName,
UserTable.FirstName
FROM UserTable
INNER JOIN InfoTable
ON InfoTable.ID=UserTable.ID
WHERE InfoTable.Number > 2500 and Infotable.Inactive = 0
GROUP BY FirstName,
LastName
HAVING COUNT(*) > 1;
What I need to do is modify this code so that I can display the column NUMBER which is located on InfoTable, however I do not want to include NUMBER in the GROUP BY, because NUMBER is always going to be unique, so it would return no results.
If your RDBMS supports windows functions (aka OLAP functions), you can simply do :
SELECT x.*
FROM (
SELECT
u.LastName,
u.FirstName,
i.Number,
COUNT(u.ID) OVER (PARTITION BY u.LastName, u.FirstName) cnt
FROM
UserTable u
INNER JOIN InfoTable i
ON u.ID = I.ID
AND i.Number > 2500
AND i.Inactive = 0
) x WHERE x.cnt > 1
ORDER BY 1, 2, 3
The inner query uses COUNT ... OVER to add to each record the count of users that have the same first and last name , and the outer query filters in homonyms.
You can't SELECT columns that are not in the GROUP BY. A GROUP BY clause tells the engine to create groups out of the specified columns. Once the rows are grouped, you can only SELECT columns in the GROUP BY clause, or aggregates of the non-grouped columns.
Something like this should work. First the subquery gets the duplicate FirstName/LastName pairs, then the main query JOINs back to the original tables to show you the output you want.
SELECT I.Number, U.LastName, U.FirstName
FROM InfoTable I
JOIN UserTable U
ON I.ID = U.ID
JOIN
(
SELECT U.LastName, U.FirstName
FROM UserTable U
JOIN InfoTable I
ON U.ID = I.ID
WHERE I.Number > 2500 AND I.Inactive = 0
GROUP BY U.LastName, U.FirstName
HAVING COUNT(*) > 1
) T
ON U.FirstName = T.FirstName AND U.LastName = T.LastName
ORDER BY I.Number, U.LastName, U.FirstName

SQL conditional field, first match JOIN

Lets imagine I have two tables:
user
--userid
--fname
--lname
widget
--id
--userid
--value
user.userid = widget.userid
I want to see the full list of users with the Widget.value if they have one, AND(!) the first match if there are more than 1 widget. No widget = null field
id fname lname value
1 John Doe X8
I can not do simple joins, cos if there is no 'widget.value' for some 'user' user won't be displayed
CROSS APPLY doesn't work as well
I need
1 widget = value
2 widgets = first one
0 widgets = null field
using top with ties:
select top 1 with ties
u.*, w.id, w.value
from dbo.user u
left join dbo.widget w
on u.userid = w.userid
order by row_number() over (partition by u.userid order by w.id);
using common table expression with row_number()
;with cte as (
select u.*, w.id, w.value
, rn = row_number() over (partition by u.userid order by w.id)
from dbo.user u
left join dbo.widget w
on u.userid = w.userid
)
select *
from cte
where rn = 1;
outer apply should do what you want:
select u.*, w.value
from user u outer apply
(select top 1 w.*
from widgets w
where w.userid = u.userid
order by id -- or however you define the first one
) w;
Try this:
SELECT
u.userid, u.fname, u.lname, w.value
FROM user as u
LEFT JOIN
(
SELECT w1.*
FROM widget as w1
INNER JOIN
(
SELECT userid, MAX(id) AS LatestId
FROM widget
GROUP BY userid
) AS w2 ON w1.userid = w2.userid and w1.id = w2.latestid
) AS w ON u.userid = w.userid;
The inner join with subquery with max and group by, will give you the latest row for each userid if any. So for those with more than 1 row you will get the latest one.
There is no date, so I assumed the max id is the latest one, which might not always the case.
LEFT JOIN will include those rows with un matched rows form the widget table, so if there is a user with no widget you will get a value null.
SELECT u.userid, w.value
FROM user u
OUTER APPLY (
SELECT TOP 1 w.value
FROM widget w
WHERE w.userid = u.userid
ORDER BY w.id --order by whatever makes a widget the first one
) w

Left join without multiple rows from right table

I have two tables (User and Salary). I want to do a left join from User to Salary. For each user I want their name and salary. In case they have no salary that field can be left empty. So far a left join is all we need. But I only want one row per user. Due to some defects there can be several salaries for one user (see table salary). I only want one row per user which can be selected randomly (or top 1). How do I do that? The expected output is presented in the bottom.
User Table:
User Name
1 Adam
2 Al
3 Fred
Salary Table
User Salary
1 1000
2 2000
2 2000
Expected table:
User Name Salary
1 Adam 1000
2 Al 2000
3 Fred null
Changed User to Userid as User is a reserved word in SQL
SELECT u.Userid, u.Name, MAX(S.Salary)
FROM Usertable u
LEFT JOIN Salarytable s ON u.Userid = s.userid
GROUP BY u.userid, u.name
SQL Fiddle: http://sqlfiddle.com/#!6/ce4a8/1/0
Try this:
select U.User, U.Name, min(S.Salary)
from UserTable U
left join SalaryTable S on S.User = U.User
group by U.User, U.Name
You can utilize a ROW_NUMBER to get the max (or min) salary:
SELECT *
FROM Usertable u
LEFT JOIN
(
select Userid, Salary,
row_number()
over (partition by Userid
order by Salary desc) as rn
from Salarytable
) as s
ON u.Userid = s.userid
AND rn = 1
And in Teradata you could apply the rn = 1filter using QUALIFY within the Derived Table:
SELECT *
FROM Usertable u
LEFT JOIN
(
select Userid, Salary,
row_number()
over (partition by Userid
order by Salary desc) as rn
from Salarytable
qualify rn = 1
) as s
ON u.Userid = s.userid
Use a derived table to get distinct rows from salaries table.
select u.userid, u.username, s.salary
from users u left join (select distinct userid, salary from salaries) s
on u.userid = s.userid
Also, renamed tables and columns. Table names should normally end with s (since pluralis.) Columns should not.
Or, do a GROUP BY:
select u.userid, u.username, max(s.salary)
from users u left join salaries s
on u.userid = s.userid
group by u.userid, u.username
Or skip the left join, instead do a correlated sub-query:
select u.userid, u.username, (select max(s.salary) from salaries s
where u.userid = s.userid)
from users
Try this
select distinct U.User, U.Name, S.Salary
from UserTable U
left join SalaryTable S on S.User = U.User

Select all values from first table and ONLY first value from second table

I want to select all values from Users and min of dateUsed in Code table. How can I do that?
I've tried this:
SELECT u.firstName, u.lastName, u.fbId, q.dateUsed, u.codesLeft
FROM Users u
inner join Code q on u.Id = q.userId
But it's selecting all values from the Code and Users tables.
P.S. Adding distinct has no effect
Adding distinct has no effect
As a rule of thumb, DISTINCT helps for a single-column SELECTs. With multiple columns you need to go for "big guns" - the GROUP BY clause.
In order for that to work you need to make sure that each item that you select is either a GROUP BY column, or has a suitable aggregation function:
SELECT u.firstName, u.lastName, u.fbId, MIN(q.dateUsed) as dateUsed, u.codesLeft
FROM Users u
INNER JOIN Code q ON u.Id = q.userId
GROUP BY u.firstName, u.lastName, u.fbId, u.codesLeft
Scalar Sub Query
SELECT u.firstName,
u.lastName,
u.fbId,
(SELECT TOP 1 dateUsed
FROM Code as q WHERE u.Id = q.userId
ORDER BY dateUsed ASC),
u.codesLeft
FROM Users u
OR CTE
;WITH SEQ as (
SELECT userId, MIN(DateUsed) as FirstDate, MAX(DateUsed) as lastDate
FROM Code GROUP BY userID)
SELECT... u.*, q.FirstDate, q.LastDate
FROM Users as u
JOIN SEQ as q ON u.ID = q.userID
If you want to see more from the Code table than just the date, you can use a correlated subquery in the join:
SELECT u.firstName, u.lastName, u.fbId, q.dateUsed, u.codesLeft,
q.codeVal, q.ipUsed
FROM Users u
join Code q
on q.userId = u.Id
and q.dateUsed =(
select Min( dateUsed )
from Code
where userID = q.userID );

How can I take the first match from table B to table A when there's multiple matches?

If I have a User table, like this:
UserId Name
------ ----
1 Jim
2 Mark
and an Order table like this:
OrderId UserId
------- ------
1 1
2 1
3 1
4 2
How can I just take any single Order of Jim's? It doesn't matter which, I just want one.
I have this:
Joining isn't the issue, it's just limiting it to one result as there are obviously 3.
SELECT * FROM User u
LEFT OUTER JOIN Order o on u.UserId = o.UserId
Thanks in advance.
This will select the first matched row:
SELECT TOP 1 * FROM User u
LEFT OUTER JOIN Order o ON u.UserID = o.UserID
If you know which user you're looking for ahead of time, you can add that to the WHERE clause:
WHERE u.UserID = #userID
And, if you ever need to make sure you're getting the First, or Most Recent order, you can use :
ORDER BY o.OrderID ASC/DESC
Select u.userid, name, min (orderID)
FROM User u
LEFT JOIN Order o on u.UserId = o.UserId
group by u.userid, name
To filter just for Jim
Select min (orderID)
FROM User u
LEFT JOIN Order o on u.UserId = o.UserId
where name='Jim'
the following query will give you one order per user (the lowest order number of each user)
select name, order_id
from users u,
orders o
where u.user_id = o.user_id
and o.order_id = (select min(order_id) from orders c where c.user_id=o.user_id)
select u.userid,
u.name,
o.orderid
from users u
join (select orderid,
userid,
row_number() over (partition by userid order by orderid) as rn
from orders) o
on o.userid = u.userid and o.rn = 1;
You can control which orders is being taken by tweaking the order by part of the window definition.