SQL: Compare 2 tables and state if data was found - sql

I'm a novice when it comes to SQL, so forgive me if this is a dumb question.
I have 2 tables, one with a list of users, and one that holds email history data.
Users Table:
userID fName lName ...
1 John Smith
2 Jane Doe
3 Kevin Cooper
Email History Table:
emailID userID subject sendDate ...
1 6 welcome 2020-10-17
2 3 hello 2020-10-20
3 7 welcome 2020-10-23
I am wanting to do some sort of select statement that would compare every customer in table 1, to every email in table 2 based on some sort of search query (in this case where subject = "hello" and sendDate = "2020-10-20" and would return something like this:
Returned Query:
userID fName lName ... emailSent?
1 John Smith ... No
2 Jane Doe ... No
3 Kevin Cooper ... Yes

One option uses exists and a correlated subquery:
select u.*,
exists (
select 1
from emailhistory eh
where eh.userid = u.userid and eh.subject = 'hello' and eh.senddate = '2020-20-20'
) emailSent
from users u
This gives you 0/1 values in column emailSent, where 1 indicates that a match exists. As compared to the left join approach, the upside is that it does not "multiplies" the user rows if more than one match is found in the history table.
For performance, consider an index on emailhistory(userid, subject, senddate).

You can left join the email table on, putting your date and subject criteria in the where clause:
SELECT
u.userid,
u.fname,
u.lname,
case when eh.emailid is null then 'No' else 'Yes' end as emailsent
FROM
users u
LEFT JOIN
emailhistory eh
ON
u.userid = eh.emailid AND
eh.subject = 'hello' AND
eh.senddate = '2020-10-20'
This conceptually filters the email table down to just that subject and day, then joins those records onto the users table. You get every row from users and only rows from emailhistory that match on userid, and also had that subject/date. You can then examine whether the emailid (a key of the join) is null or not; the only way it can be null is if no email was sent to that user on that date with that subject

Related

Is it possible to select data from multiple rows for output in a single line?

I have 2 databases. I want to write a query that will pull data from both and tie them into the same result.
user database:
id username group
1 steve group1
2 joe group1
3 tom group2
data database:
id userid fieldname fieldresult
1 2 phone 867-5309
2 2 address 123 elm st
3 1 address 666 park avenue
If I just want steve's address, I could write:
select user.username, data.fieldresult from user, data where user.id = data.userid and data.fieldname = 'address' and user.username = 'steve';
The result would be:
username fieldresult
steve 666 park avenue
But what if I want all of the fieldresults for joe in a single row? Is that possible to do from the query itself, or do I have to handle that in code?
Basically, I'd like to see:
username phone address
joe 867-5309 123 elm st
Is this wishful thinking? I wouldn't even need "phone" and "address" as the headers, as long as I could get their values in the same result.
NOTE: I'm working with an existing database and this is how the information is currently stored.
EDIT:
Also, I need to do this on a much larger basis. Instead of querying by the username, can I get this information in the same way for each user in an entire group.
Try
select a.username,
b.fieldresult as phone,
c.fieldresult as address
from #user a
left join #data b on a.id=b.userid and b.fieldname='phone'
left join #data c on a.id=c.userid and c.fieldname='address'
where a.id = 2
You can try pivoting contact info in datadb table and then join it with user
select * from userdb..[user] u join
(
select userid,phone, address from
(
select userid,fieldname,fieldresult from datadb..data
) as a pivot
(
max(fieldresult) for fieldname in (phone, address)
) piv ) as a
on a.userid = u.id
this would result in something as follows.
id username group userid phone address
1 steve group1 1 NULL park aven
2 joe group1 2 867-5309 123 elm st
The two tables i used are user_ and data.
Hope,this might help you
DECLARE
user_name user_.username%type:='joe';
phone_no data.fieldresult%type;
full_address data.fieldresult%type;
user_id user_.id%type;
CURSOR c_name IS select * from data;
result c_name%rowtype;
BEGIN
select id into user_id from user_ where username=user_name;
open c_name;
LOOP
fetch c_name into result;
IF ( result.userid=user_id AND result.fieldname='phone') THEN
phone_no:=result.fieldresult;
END IF;
IF(result.userid=user_id AND result.fieldname='address') THEN
full_address:=result.fieldresult;
END IF;
EXIT when c_name%NOTFOUND;
END LOOP;
close c_name;
dbms_output.put_line(user_name||' '||phone_no||' '||full_address);
END;
/
OUTPUT: joe 867-5309 123 elm st

Return all dates that do not have a matching entry

I've tried some of the other solutions I've found on SO, but they don't seem to work with my schema, or I just suck at SQL. So let's say I have two tables, table 1 is something like this:
LastName | FirstName | Date
Doe John 7/07/14
Doe John 7/07/14
Doe John 7/08/14
Bond James 7/07/14
Bond James 7/09/14
Jane Mary 7/08/14
Essentially, a person will have an entry for a certain date. they can also have multiple entries for one date. table 2 is a range of dates, such as:
Date
7/06/14
7/07/14
7/08/14
7/09/14
What I want to do is get a result set that shows for what days in table 2 is a person missing an entry, ideally with the person's name as well. Any leads? Thanks!
Try this,
;WITH CTE AS
(
--create list of all names and date combinations from both table
SELECT DISTINCT A.LastName, A.FirstName, B.Date_col
FROM Table1 A, Table2 B
)
--select rows that are missing dates in your first table
SELECT X.* FROM CTE X
LEFT OUTER JOIN Table1 Y
ON X.LastName = Y.LastName
AND X.FirstName = Y.FirstName
AND X.Date_col = Y.Date_Col
WHERE Y.LastName IS NULL

SQL Server - Columns without a value 'H'

My SQLServer table 'Users' is like
User Usertype
---------------------------
Mike S
Sally S
Alan S
Sally H
Alan S
Mike S
Mike H
I am trying to build a query on this table which should return the user that has no 'H' usertype. For e.g., the above table should return 'Alan' as this user has no 'H' pair, while Mike and Sally has at least one 'H'.
Kindly help
Group by the user and then check how many H types each user has. That number must be 0 for the ones you are looking for.
select [user]
from your_table
group by [user]
having sum(case when usertype = 'H' then 1 else 0 end) = 0
Try this.It return the result 'Alas'.
select distinct User from Users where User not IN(
select a.User from Users a join Users b
on a.User=b.User and a.Usertype!=b.Usertype)
Demo SQL FIDDLE: http://sqlfiddle.com/#!2/2f175/24

How to select rows with exactly 2 values in a column fast within a table that has 10 million records?

I have a table (TestFI) with the following data for instance
FIID Email
---------
null a#a.com
1 a#a.com
null b#b.com
2 b#b.com
3 c#c.com
4 c#c.com
5 c#c.com
null d#d.com
null d#d.com
and I need records that appear exactly twice AND have 1 row with FIID is null and one is not. Such for the data above, only "a#a.com and b#b.com" fit the bill.
I was able to construct a multilevel query like so
Select
FIID,
Email
from
TestFI
where
Email in
(
Select
Email
from
(
Select
Email
from
TestFI
where
Email in
(
select
Email
from
TestFI
where
FIID is null or FIID is not null
group by Email
having
count(Email) = 2
)
and
FIID is null
)as Temp1
group by Email
having count(Email) = 1
)
However, it took nearly 10 minutes to go through 10 million records. Is there a better way to do this? I know I must be doing some dumb things here.
Thanks
I would try this query:
SELECT EMail, MAX(FFID)
FROM TestFI
GROUP BY EMail
HAVING COUNT(*)=2 AND COUNT(FIID)=1
It will return the EMail column, and the non-null value of FFID. The other value of FFID is null.
With an index on (email, fid), I would be tempted to try:
select tnull.*, tnotnull.*
from testfi tnull join
testfi tnotnull
on tnull.email = tnotnull.email left outer join
testfi tnothing
on tnull.email = tnothing.email
where tnothing.email is null and
tnull.fid is null and
tnotnull.fid is not null;
Performance definitely depends on the database. This will keep all the accesses within the index. In some databases, an aggregation might be faster. Performance also depends on the selectivity of the queries. For instance, if there is one NULL record and you have the index (fid, email), this should be much faster than an aggregation.
Maybe something like ...
select
a.FIID,
a.Email
from
TestFI a
inner join TestFI b on (a.Email=b.Email)
where
a.FIID is not null
and b.FIID is null
;
And make sure Email and FIID are indexed.
I need records that appear exactly twice AND have 1 row with FIID is null and one is not
1
On the innermost select, group by email having count = 2:
select email, coalesce(fiid,-1) as AdjusteFIID from T
group by email having count(email) =2
2
select email, AdjustedFIID
from
(
select email, coalesce(fiid,-1) as AdjusteFIID from T
group by email having count(email) =2
) as X
group by email
having min(adjustedFIID) = -1 and max(adjustedFIID) > -1

Select Top row of 2nd table in SQL Join

I have to tables called USERS and COMM_HISTORY
USERS(user_id, name)
COMM_HISTORY(comm_history_id, comm_date, comm_by, user_id)
USERS table will have a unique list of users and COMM_HISTORY can have duplicates of user_id. When I join them I would like to grab the name of user and the most recent comm_date from COMM_HISTORY.
Something similar with this post sql join - only select top row from 2nd table but it seems there's no correct answers in that post.
I would like to have the ff result when I join them
1 John Doe 2012-01-29 Jane Doe
Instead of
1 John Doe 2011-10-20 Jane Lee
2 John Doe 2012-01-29 Jane Doe
3 John Doe 2011-09-08 Jane Doe
Anyone has a solution for this?
#Andomar's solution in the other question should work. Adapted to your use case, it would be:
SELECT USERS.*, COMM_HISTORY.*
FROM USERS
INNER JOIN COMM_HISTORY
ON USERS.user_id = COMM_HISTORY.user_id
LEFT JOIN COMM_HISTORY LATER_HISTORY
ON COMM_HISTORY.user_id = LATER_HISTORY.user_id
AND LATER_HISTORY.comm_date > COMM_HISTORY.comm_date
WHERE LATER_HISTORY.user_id IS NULL
Join the history table back onto itself, and set a condition saying no later history rows should be selected.
an approach using cte
with recent_comms as (
select
row_number() over (partition by user_id, order by comm_date decs) as rn,
comm_date,
comm_by,
user_id
FROM comm_history)
select
users.name,
recent_comms.comm_date,
recent_comms.comm_by
from users
join recent_comms on users.user_id = recent_comms.user_id
and recent_comms.rn = 1
changing the rn = 1 to rn <= 5 would get the last 5 per user, for example.