Using a field to filter a selection on a second field in SQL Server - sql

I have a table ClientContacts, which holds basic information about a pairing of clients. Some of the details held in this table include P1Title, P2Title, P1FirstName, P2FirstName. For each row in this table there may be details of one or two clients, with a CustomerId that represents the pairing. Within this table is also ContactId, which is used to link to the table described below.
In a second table ContactDetails which contains rows that hold a specific contact detail that is associated with a client. Each client may have a number of rows in this table, each row holding a different detail such as HomeNumber, MobileNumber and Email. This table also contains a Type field which represents the type of contact detail held in the row. 1 = Home number, 2 = Mobile number and 3 = email. The Note field is also included, which may hold either Mr or Mrs denoting whether the mobile number held belongs to Person1 or Person2 in the client pairing.
Here is a visual structure of the tables.
ClientContacts
CustomerId ContactId Person1Title Person1FirstName Person1LastName Person2Title Person2FirstName Person2LastName
1 100 Mr Bob BobLastname Mrs Bobette BobetteLastname
2 101 Mr John JohnLastname Mrs Johnette JohnetteLastname
ContactDetails
ContactId Detail Type Note
100 012345 1
100 077777 2 P1
100 012333 1
100 088888 2 P2
101 099999 1
101 012211 1
101 066666 2
101 email#email.com 3
I want to construct a query that allows me to pull back the information of both of the clients, as well as figure out whether any of the mobile numbers stored in the ContactDetails table belongs to either of the two clients, if it does, I need to be able to tell which belongs to Person1 or Person2 in the pairing.
In addition, if the note field is null for a particular mobile number (type = 2), the first mobile number should be used for Person1 and the second should be used for Person2.
Below is my desired output:
Output
CustomerId Person1Firstname
Person1Lastname Person2Firstname Person2Lastname Home Person1Mobile Person2Mobile Person2Email
1 Bob BobLastname Bobette BobetteLastname 012211 077777 088888 null
I have a partially working query that manages to extract the mobile numbers and relates them to P1 or P2, however this only works if the Note field is not null.
select
cc.CustomerId,
cc.Person1Forename,
cc.Person1Surname,
cc.Person2Forename,
cc.Person2Surname,
max(case when cd.Type = 3 then cd.Detail end) as 'Home',
max(case when cd.Type = 4 and cd.Note = cc.P1Title then cd.Detail end) as 'Person1Mobile',
max(case when cd.Type = 4 and cd.Note = cc.P2Title then cd.Detail end) as 'Person2Mobile',
max(case when cd.Type = 5 then cd.Detail end) as 'Email'
from ClientContacts cc join
ContactDetails
cd on cc.ContactId = cd.ContactId
I'm unsure how to proceed from here. Any help would be appreciated.

Related

Use auxiliary table to define values for another table's column

I am working with a table that currently uses multiple CASE expressions to define the behavior of one of the columns, i.e.:
SELECT
Employee
,Company
,Department
,Area
,Flag = CASE
WHEN Company = 'Amazon' and Department in ('IT', 'HR')
THEN 0
WHEN Department = 'Legal'
THEN 1
WHEN Area = 'Cloud'
THEN 1
ELSE 0
END
FROM Table1
Which would result in something like the following dummy data:
Employee
Company
Department
Area
Flag
Cindy
Amazon
IT
Support
0
Jack
Amazon
HR
Support
0
Bob
Microsoft
Legal
Contracts
1
Joe
Amazon
Legal
Research
1
Lauren
Google
IT
Cloud
1
Jane
Apple
UX
Research
0
I am trying to simplify the Flag expression by using an auxiliary Mappings table that has the following structure, in order to get the value for the Flag column:
Company
Department
Area
Flag
Amazon
IT
NULL
0
Amazon
HR
NULL
0
NULL
Legal
NULL
1
NULL
NULL
Cloud
1
The NULL values mean the column could take any value. Is it possible to achieve this without falling into multiple CASE statements?

Using TOP 1 (or CROSS APPLY) within multiple joins

I've reviewed multiple Q&A involving TOP 1 and CROSS APPLY (including the very informative 2043259), but I still can't figure out how to solve my issue. If I had a single join I'd be fine, but fitting TOP 1 into the middle of a chain of joins has stumped me.
I have four tables and one of the tables contains multiple matches when joining due to a previous bug (since fixed) that created new records in the table instead of updating existing records. In all cases, where there are multiple records, it is the top-most record that I want to use in one of my joins. I don't have access to the table to clean up the extraneous data, so I just have to deal with it.
The purpose of my query is to return a list of all "Buildings" managed by a particular person (user choses a person's name and they get back a list of all buildings managed by that person). My tables are:
Building (a list of all buildings):
BuildingId BuildingName
1 Oak Tree Lane
2 Lighthoue Court
3 Fairview Lane
4 Starview Heights
WebBuildingMapping (mapping of BuidingId from Building table, that is part of an old system, and corresponding WebBuildingId in another piece of software):
BuildingId WebBuildingId
1 201
2 202
3 203
4 204
WebBuildingContacts (list of ContactID for the building manager of each building). This is the table with duplicate values - where I want to choose the TOP 1. In sample data below, there are two references to WebBuidingId = 203 (row 3 & row 5) - I only want to use row 3 data in my join.
Id WebBuildingId ContactId
1 201 1301
2 202 1301
3 203 1303
4 204 1302
5 203 1302
Contacts (list of ContactIds and corresponding property manager Names)
ContactId FullName
1301 John
1302 Mike
1303 Judy
As noted, in the example above, the table WebBuildingContact has two entries for the building with a WebBuidingId = 203 (row 3 and row 5). In my query, I want to select the top one (row 3).
My original query for a list of buildings managed by 'Mike' is:
SELECT BuildingName
FROM Building bu
JOIN WebBuildingMapping wbm ON wbm.BuildingId = bu.BuildingId
JOIN WebBuildingContact wbc ON wbc.WebBuildingId = wbm.WebBuildingId
JOIN Contacts co ON co.ContactId = wbc.ContactId
WHERE co.FullName = 'Mike'
This returns 'Fairview Lane' and 'Starview Heights'; however, Judy manages 'Fairview Lane' (she's the top entry in the WebBuildingContacts table). To modify the query and eliminate row 5 in WebBuildingContacts from the join, I did the following:
SELECT BuildingName
FROM Building bu
JOIN WebBuildingMapping wbm ON wbm.BuildingId = bu.BuildingId
JOIN WebBuildingContact wbc ON wbc.WebBuildingId =
(
SELECT TOP 1 WebBuildingId
FROM WebBuildingContact
WHERE WebBuildingContact.WebBuildingId = wbm.WebBuildingId
)
JOIN Contacts co ON co.ContactId = wbc.ContactId
WHERE co.FullName = 'Mike'
When I try this; however, I get the same result set (ie it returns 'Mike' as manager for 2 buildings). I've also made various attempts to use CROSS APPLY but I just end up with 'The multi-part identifier could not be bound', which is a whole other rabbit hole to go down.
You could try this:
SELECT bu2.BuildingName
FROM building bu2
WHERE bu2.BuildingId IN
(SELECT MAX(bu.BuildingId)
FROM Building bu
JOIN WebBuildingMapping wbm ON wbm.BuildingId = bu.BuildingId
JOIN WebBuildingContact wbc ON wbc.WebBuildingId = wbm.WebBuildingId
JOIN Contacts co ON co.ContactId = wbc.ContactId
WHERE co.FullName = 'Mike'
);

Query identifying duplicate records with different values [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I have a table in which I have loaded records from 4 different sources. There is no common factor between the records from each source except for FName, LName, Addr1, Addr2, City, State and Zip. Each record is assigned a FileID by me based upon which source file they were loaded from. I need to construct a query in which I can identify which person/household was found to be in all 4 files, 3 files, 2 files, etc. I only need to maintain one record for each duplicate person/household.
The other tricky part is that I have email address on 2 of the 4 files and also an Emailable field field that is coming in on the other two files. This is a factor which I need to consider also when maintaining the single selected record.
For example, one group will be indicated by: “individuals who are in all of the following lists: DMA , Vehicle Ownership and Lifestyle (Wealth Engine) lists. These individuals must have an email address.” But then another group needs to be identified as “individuals who are in all of the following lists: DMA , Vehicle Ownership and Lifestyle (Wealth Engine) lists. These individuals DO NOT have an email address”
Example Data:
ID FirstName LastName FullName Address1 Address2 City State Zip Zip4 EmailAddress FILE EMAILABLE
06925901SNDCR44110G6520 S Nylah Watson NULL 1234 Main NULL Anytown ST 10000 2000 NULL DMA Y
1641189779 Nylah Watson NULL 1234 Main Anytown ST 10000 2000 nylahwatson#gmail.com LST
06925901SNDCR44110G6520 S Nylah Watson NULL 1234 Main NULL Anytown ST 10000 2000 NULL VEH Y
374977111 Nylah Watson NULL 1234 Main NULL Anytown ST 10000 2000 nylahwatson#gmail.com V12 NULL
48770181SBRNT 1345M6352 S Watson Nylah NULL 4321 Main NULL HOUSTON TX 20000 3000 NULL DMA N
48770181SBRNT 1345M6352 S Watson Nylah NULL 4321 Main NULL HOUSTON TX 20000 3000 NULL VEH N
1933990731 Watson Nylah NULL 4321 Main Houston TX 20000 3000 LST
Depending on how many/how flexible the groups you need are, you may want to do something like this:
Select Name, Address... -- Fields you want to group on (consider to identify the same person)
, max(case when File = 'DMA' then 1 else 0 end) as HasDMAFile
, max(case when File = 'Veh' then 1 else 0 end) as HasVEHFile --repeat for your other file types
, max(case when emailaddress is not null then 1 else 0 end) as HasEmail
From MyTable
Group by Name, Address...--same list of fields as you use at the beginning of your select
Then use that result set to create your combination groups, for example:
; with CTE as (Query from Above)
Select *
, case when HasDMAFile = 1
and HasVEHFile = 1
and HasLSTFile = 1
and HasEmail = 0
then 'Group 1'
when HasDMAFile = 1
and HasVEHFile = 1
and HasLSTFile = 1
and HasEmail = 1
then 'Group 2' --etc for more groups here; make sure your groups are either mutually exclusive or in a logical order, since the case statement is evaluated in the order it's written
end as Grp
from CTE
SELECT
FName, LName, Addr1, Addr2, City, State, Zip,count(distinct File)
FROM <TABLE>
GROUP BY FName, LName, Addr1, Addr2, City, State, Zip
Having count(distinct File)=4
The emails could be coalesced, but you need to figure out an order of preference.

Get data using JOIN

I have two tables having following data-
Social_Tbl
ID Name Value
------------------------
1 Facebook FB
2 Orkut OR
3 Google GL
4 Other OT
And Organization_tbl
ID Organization Name
-----------------------------
1 1234 Facebook
2 1234 Google
3 146 Other
4 126 Other
5 126 Facebook
6 77 Google
Here, 'Name' is the foreign key (Not ID).
I want to join these tables and get the 'Name' columns data which does not belong to organization id 1234. As follows-
Name
----
Orkut
Other
Here, 'Orkut' and 'Other' does not belong to 1234 organization.
I tried following query for this-
select * from Social_Tbl st
join Organization_tbl ot
on st.Name = ot.Name
where Organization = 1234
This query fetches Names related to 1234 i.e Facebook and Google. I want result
Orkut and Other. If I replace Organization = 1234 with Organization != 1234 it returns all data from Organization_tbl.
Can somebody help me on this. This should be pretty simple, just npt able to find it out.
Could be done with a subquery:
select st.Name
from Social_Tbl st
where not exists (
select *
from Organization_tbl ot
where st.Name = ot.Name
and ot.Organization = 1234
)
(This also returns names that don't have an entry in Organization_tbl at all.)

How to design db for holding Dominion cards?

I'd like to store some information about games of the card game Dominion. You don't need to know much about this game, except that:
There are around 200 unique cards
Each game includes only ten of these cards, or on occasion eleven
I'll be tracking lots more about each game (who played, who won, etc), but what I'm having trouble with is working with the "supply" (the ten included cards for this game).
I'm thinking I want three tables, something like card_name, supply, and game:
card_name supply game
id | name supply | card game | supply | player1 | player2 | ...
----+--------- --------+------ ------+--------+---------+---------+-----
1 | Village 1 | 1 301 | 1 | 'Mike' | 'Tina' | ...
2 | Moat 1 | 3
3 | Witch 1 | 200
... | ... ... | ...
200 | Armory
I think this is a reasonable way to represent "Mike and Tina played a game which contained Village, Witch, Armory, and some other cards I didn't bother typing into this example". Given this structure (or some other one, if you think mine is no good), I'd like to run queries like "which games had Witch and Village, but not Moat?" That is, I want to specify some arbitrary number of "these X cards included, these Y cards excluded" and search the game table for games satisfying the criteria.
I think this is a classic one-to-many relation, where a supply has multiple cards, but I don't understand the right way to search for a supply by multiple cards.
Your data structure is reasonable. I might suggest that you would want a game_users table as well, so the users are not listed in separate columns. This would be particularly important if games had different numbers of users. However, this aspect is not relevant to your question.
You want to solve "set-within-sets" subqueries. Your structure is useful and the supply table provides the basic information needed for this.
So, a query to get the appropriate "supply" records for "Witch", "Village", and not "Moat" would look like:
select supplyid
from supplies s join
cards c
on s.cardid = c.cardid
group by supplyid
having sum(case when cardname = 'Witch' then 1 else 0 end) > 0 and
sum(case when cardname = 'Village' then 1 else 0 end) > 0 and
sum(case when cardname = 'Moat' then 1 else 0 end) = 0;
First note that I changed the name, so the id columns contain the word "id" and the table names are in plural.
Each condition in the having clause is representing one condition on the cards. You can tweak this to get the game information by joining in games:
select g.gameid
from supplies s join
cards c
on s.cardid = c.cardid join
games g
on g.supplyid = s.gameid
group by g.gameid
having sum(case when cardname = 'Witch' then 1 else 0 end) > 0 and
sum(case when cardname = 'Village' then 1 else 0 end) > 0 and
sum(case when cardname = 'Moat' then 1 else 0 end) = 0;