Selecting first unique email address in SQL - sql

I have a list of UserIds (which is a uniqueidentifier), Names, email addresses and relationships.
Something like
SELECT UserId, Name, Email, Relationship FROM Users
Sample data would look like:
F87B7702-F0EE-11D3-B288-0000B4A488D3 Peter peter#peter.com Member
ZZZZZZZZ-F0EE-11D3-B288-0000B4A488D2 Joan peter#peter.com Principal
AAAAAAAA-F0EE-11D3-EEEE-0000B4A488D3 Bob bob#bob.com Principal
Relationship can be either be 'Principal' or 'Member', but a principal user can also be a member.
An email address isn't specific to a user (often a member user will have the same email address as a principal user).
I need to ensure that I only select 1 user per email address so that the same person won't be emailed twice. If there are 2 emails for the same user I need to select the principal record.
What's the easiest way to do this, bearing in mind that you can't do a max on a uniqueidentifier field? For the sample data I gave above I would need to return only the second and third record.
I was leaning towards ordering by Relationship, doing a group by and then max but I don't think that's possible.
Output required is UserId, Name, Email.

The ORDER BY in ROW_NUMBER() will allow you to choose how to prioritise your emails. Such as how to deal with an email with no Principle but multiple Members (maybe add , Name ASC so the member with the first alphabetically ordered name gets chosen?)
SELECT
*
FROM
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY Relationship DESC) AS sequence_id
FROM
yourTable
)
AS sequenced_table
WHERE
sequence_id = 1

I didn't quite understood you, but if you want to get email only once, preffering the principle user this code will do:
select UserId, Name, Email, Relationship
from Table
where Relationship='Principle' or ( Relationship='Member' and Email not in
(
select Email
from Table
where Relationship='Principle'
)
)
This will give you the second and third lines. If there are more conditions- expand your example.

Related

Grouping other fields by common email

I'd appreciate any assistance people could provide with this one. I've tried to follow a couple of other posts (GROUP BY to combine/concat a column), but when I do this, it combines everything.
I have a table in SQL that holds contact data. Some contacts in the database have the same email address, particularly admin#, accounts#, etc. We also have a list of contact preferences for each contact: Emergency Contact, Accounts, WHS, etc. I'm trying to select the results of the table, but only one row per email address.
My data looks like this:
However, I'd like it to group the rows together that have the same email address (I don't care about the names). I'd also like it to look at the contact preference fields and if even one of the joined fields had a yes, show a Y, otherwise show a N - see below example
As I mentioned, all my tests have been unsuccessful I'm afraid, so I would appreciate any assistance that you can provide.
You can use the following:
;WITH selected as (
select min(ContactID) as ContactID, Mail, max(EmergencyContact) as EmergencyContact,
max(AccountsCon) as AccountsCon, max(WHSContact) as WHSContact
from t
GROUP BY Mail
)
select t.ContactID, t.BusinessID, t.BusinessName, t. FirstName, t.LastName, t.Position, t.Mail, s.EmergencyContact, s.AccountsCon, s.WHSContact
from selected as s
inner join t as t ON t.ContactID = s.ContactID
This way you get the contactid, businessid, businessname, firstname, lastname and position from the first record found with each email and the last 3 columns you get using max (taking advantage that Y is greater than N, so if there is at least one Y it will get Y).
I think you can just use aggregation:
select businessid, businessname, max(firstname),
max(lastname), max(position),
email,
max(emergencycontact),
max(accountscor),
max(whcontact)
from t
group by businessid, businessname, email;

Setting a column value for only one user depending on results

I'm currently playing around with SQL and trying to find the best way to accomplish this:
I currently have a user table that has a user_id, organisation_id, registered_datetime. There are
a number of users in this table with different organisations. There may be 3 different users in
1 organisation, or 1 in 1 organisation, etc.
I have added a new column called admin_user and I am trying to string up an SQL statement together
to update the admin user column. There can only be one admin user per organisation, and I want
the user who registered the earliest for that organisation to be the admin.
I could do this manually but it would take time if I had a lot of users. What would be the best
way to accomplish this?
EDIT:
So I have a number of users like this with the columns. The ones highlighted are the users that has registered the earliest. I want to be able to set those users as an admin user. The only admin user within their organisation and set the rest to 0. 1 (Admin) 0 (Not Admin)
This SQL query will mark users which registered_datetime are lowest in its organisation_id as admin.
UPDATE users SET admin_user = 1
WHERE user_id IN (
SELECT u.user_id FROM users u
WHERE u.registered_datetime IS NOT NULL AND NOT EXISTS(
SELECT 1 FROM users iu
WHERE iu.organisation_id = u.organisation_id AND iu.registered_datetime < u.registered_datetime
)
)
You might want to update all users to admin_user = 0 before this code, so all your users will have their values set.
One caveat here, if two users in one organisation were registered in exact same time, then both of them will be marked as administrators.
Update
I have added u.registered_datetime IS NOT NULL into the WHERE clause to filter out users with NULL in registered_datetime.
MSSQL
In MsSql server I usually solve this problem a in another way, by using ROW_NUMBER():
WITH base AS (
SELECT user_id, ROW_NUMBER() OVER ( PARTITION BY organisation_id ORDER BY registered_datetime ASC ) AS rn
FROM user
WHERE registered_datetime IS NOT NULL
)
UPDATE user SET is_admin = 1
WHERE user_id IN (
SELECT base.user_id FROM base WHERE rn = 1
)
This is too long for a comment.
You are describing three different tables:
Users
Organizations
UserOrganizations
The last has one row per user and per organization. This provides the mapping between the two. This can be called a "mapping" table, "junction" table, or "association" table.
How you implement one admin per organization depends on the database you are using.
You do not need the admin_user column. You need a column isadmin.
When a user is registered, if he is the first in the organization, then the isadmin column has the value 1, otherwise 0
Also, you can use the AAA query to find the administrator
SELECT `table`.`user_id`, MIN(`table`.`registered_datetime`) WHERE `organisation_id`=...

Unique sorted list of column values from 2 tables

I need to get a list of unique email addresses across 2 table. For example I have the selects:
select distinct
email
from
contacts
order by
email
select distinct
email
from
customers
order by
email
If I only needed one of those, piece of cake. If I wanted them as 2 columns side by side, also piece of cake.
But how do I get them as a single column, no duplicates, sorted? This will be running on Azure Sql Database if that is useful.
What about something like:
select distinct email from contacts order by email // Your first query
union
select distinct email from customers order by email // Your second query
The union creates a single column email with data from both queries and already eliminates duplicates, unlike union all.
For the ordering, just add ORDER BY email in the end.
select distinct
email
from (
select distinct email from contacts order by email
union all
select distinct email from customers order by email
) as emails

mysql query, two select

As soon as I apologize because I do not know or be able to explain exactly trouble.
How get value from table user_address.
how to pass user ID in the second "select".
select id, name, age,
(select address
from user_address
where user_id = ??user.id
ORDER BY address_name
LIMIT 1) AS address
from user
As an addendum to what already exists, you should probably not be relying on the specific order of rows in the database to give some sort of semantic meaning. If you have some better way of identifying which address you're after, you could use a join, such as:
select id, name, age, address
from user
inner join user_address
on user.id=user_address.user_id
where address_type='Home'
(adjust the where clause to whatever)
I assumed that you want to get something like the first address for a user
(each user may have a couple of addresses)
-there is another option that you want to find the first persone that lives in a given address (The solution below doesn't address this case)
SELECT u.id,u.name,u.age,a.ua as address
FROM
(
SELECT * FROM users
) u
INNER JOIN
(
SELECT userID, MIN(address) AS ua
FROM user_address
GROUP BY userID
) a
on u.id = a.userID
The syntax is for SQLServer - if you use MSAccess(you can use First and not min)
Hope it helps
Asaf

distinct sql query

I have a simple table with just name and email called name_email.
I am trying to fetch data out of it so that:
If two rows have the same name, but one has an email which is ending with ‘#yahoo.com’ and the other has a different email, then the one with the ‘#yahoo.com’ email should be discarded.
what would be best way to get this data out?
Okay, I'm not going to get involved in yet another fight with those who say I shouldn't advocate database schema changes (yes, you know who you are :-), but here's how I'd do it.
1/ If you absolutely cannot change the schema, I would solve it with code (either real honest-to-goodness procedural code outside the database or as a stored procedure in whatever language your DBMS permits).
This would check the database for a non-yahoo name and return it, if there. If not there, it would attempt to return the yahoo name. If neither are there, it would return an empty data set.
2/ If you can change the schema and you want an SQL query to do the work, here's how I'd do it. Create a separate column in your table called CLASS which is expected to be set to 0 for non-yahoo addresses and 1 for yahoo addresses.
Create insert/update triggers to examine each addition or change of a row, setting the CLASS based on the email address (what it ends in). This guarantees that CLASS will always be set correctly.
When you query your table, order it by name and class, and only select the first row. This will give you the email address in the following preference: non-yahoo, yahoo, empty dataset.
Something like:
select name, email
from tbl
where name = '[name]'
order by name, class
fetch first row only;
If your DBMS doesn't have an equivalent to the DB2 "fetch first row only" clause, you'll probably still need to write code to only process one record.
If you want to process all names but only the specific desired email for that name, a program such as this will suffice (my views on trying to use a relational algebra such as SQL in a procedural way are pretty brutal, so I won't inflict them on you here):
# Get entire table contents sorted in name/class order.
resultSet = execQuery "select name, email from tbl order by name, class"
# Ensure different on first row
lastName = resultSet.value["name"] + "X"
# Process every single row returned.
while not resultSet.endOfFile:
# Only process the first in each name group (lower classes are ignored).
if resultSet.value["name"] != lastName:
processRow resultSet.value["name"] resultSet.value["email"]
# Store the last name so we can detect next name group.
lastName = resultSet.value["name"]
select ne.*
from name_email ne
where ne.email not like '%#yahoo.com' escape '\' or
not exists(
select 1 from name_email
where name = ne.name and
email not like '%#yahoo.com' escape '\'
)
You could use something like the following to exclude invalid email addresses:
SELECT name, email
FROM name_email
WHERE email NOT LIKE '%#yahoo.com' // % symbol is a wildcard so joe#yahoo.com and guy#yahoo.com both match this query.
AND name = 'Joe Guy';
Or do it like this to include only the valid email address or domain:
SELECT name, email
FROM name_email
WHERE email LIKE '%#gmail.com'
AND name = 'Joe Guy';
This works well if you know ahead of time what specific names you are querying for and what email addresses or domains you want to exclude or include.
Or if you don't care which email address you return but only want to return one, you could use something like this:
SELECT DISTINCT (name, email)
FROM name_email;
You could do
SELECT TOP 1 email
FROM name_email
WHERE name = 'Joe Guy'
ORDER BY case when email like '%yahoo.com' then 1 else 0 end
So sort them by *#yahoo.com last and anything else first, and take the first one.
EDIT: sorry, misread the question - you want a list of each name, with only one email, and a preference for non-yahoo emails. Probably can use the above along with a group by, I'll have to rethink it.
Grabbing all the rows from the database, knowing not what the names are (and not needing to care about that really), but just want them to show, and if matching, skip a match if the email contains, in this case, #yahoo.com
SELECT DISTINCT name, email FROM name_email
WHERE email NOT LIKE '%#yahoo.com'
GROUP BY name;
Doing that will grab all the rows, but only one of a record if the names match with another row. But then, if there are two rows with matching names, junk the one with #yahoo.com in the email.
Not very pretty, but I believe it should work
select
ne.name
,ne.email
from
name_email ne
inner join (
select
name
,count(*) as emails_per_name
from
name_email
group by name
) nec
on ne.name = nec.name
where
nec.emails_per_name = 1
or (nec.emails_per_name > 1 and ne.email not like ('%#yahoo.com'))
That is assuming that the duplicate emails would be in yahoo.com domain - as specified in your question, and those would be excluded if there is more than one email per name
If you are working with SQL Server 2005 or Oracle, you can easily solve your problem with a ranking (analytical) function.
select a.name, a.name_email
from (select name, name_email,
row_number() over (partition by name
order by case
when name_email like '%#yahoo.com' then 1
when name_email like '%#gmail.com' then 1
when ... (other 'generic' email) then 1
else 0
end) as rn) as a
where a.rn = 1
By assigning different values to the various generic email names you can even have 'preferences'. As it is written here, if you have both a yahoo and a gmail address, you can't predict which one will be picked up.
You could use a UNION for this. Select everything without the yahoo.com and then just select the records that have yahoo.com and is not in the first list.
SELECT DISTINCT (name, name_email) FROM TABLE
WHERE name_email NOT '%yahoo.com'
UNION
SELECT DISTINCT (name, name_email) FROM TABLE
WHERE name NOT IN (SELECT DISTINCT (name, name_email) FROM TABLE
WHERE name_email NOT '%yahoo.com')