SQL help selecting customer information from two tables - sql

I have an assignment where I had to create two tables named Customer and Address. These tables are located within a database called HandsOnOne.
Customer has columns titled:
CustomerID, CustomerName, CustomerAddressID
Address has columns titled:
AddressID, Street, City, State, ZipCode
There is a foreign key relationship in which AddressID in the Address Table is the primary key and CustomerAddressID in the Customer Table is the foreign key.
I used the following code to insert values into each table:
USE HandsOnOne;
INSERT INTO Address (AddressID, Street, City, State, ZipCode)
VALUES (1, '2400 Broadway Drive', 'Missoula', 'MT', '59802'),
(2, '320 21st Street', 'Billings', 'MT', '59101'),
(3, '439 Skyline Blvd', 'Denver', 'CO', '80002'),
(4, '56 Park Avenue', 'New York', 'NY', '10001');
USE HandsOnOne;
INSERT INTO Customer (CustomerID, CustomerName, CustomerAddressID)
VALUES (1, 'Western Supply Company', 1),
(2, 'Nick Harper', 3),
(3, 'Alice Harper', 3),
(4, 'Abacus Consulting', 4);
From there, I have to sort based on certain specifications. The first specification was to list all customers with city and state sorted ascending by ZipCode then ascending by CustomerName.
Here is the code I used for this part:
USE HandsOnOne;
SELECT CustomerName, City, State
FROM Customer, Address
ORDER BY ZipCode ASC, CustomerName ASC;
When I execute this code, my return is 16 items instead of 4. Somehow, each customer is being assigned each address, giving me 4 items at each address.
The next question asks me to list the Street, City, State and ZipCode of all address without a customer associated with them. This query should return the address of 320 21st St Billings, MT 59101 because its AddressID value is 2 and there is no CustomerAddressID value of 2 in the Address table. However, I do not receive any results when I execute this query.
I have verified that there is a foreign key relationship. What am I doing wrong?

You aren't restricting your join. Also, the implicit join is no longer supported in most DBMS (especially recent versions of SQL server), so an explicit Inner Join is best.
Try this:
select CustomerName, City, State
from Customer
inner join Address
on AddressID = CustomerAddressID -- I assume this is the foreign key
order by ZipCode asc, CustomerName asc
Ah, question 2 (missed that due to formatting)
select A1.*
from Address A1
left join Customer C2
on A1.AddressID = C2.CustomerAddressID
where C2.CustomerAddressID is null

For your 2nd question: use NOT EXISTS to get all addresses without a customer. See below:
SELECT *
FROM Address A
WHERE NOT EXISTS (SELECT 1
FROM Customer C
WHERE C.CustomerAddressID = A.AddressID)

Related

Get records from table with join where records in join not contain specific value

I have two tables:
Table user:
create table user (
id bigserial not null primary key,
username varchar(256),
active boolean not null default true
);
And table address:
create table address (
id bigserial not null primary key,
user_id integer not null,
country varchar(256),
city varchar(256),
street varchar(256)
);
And some data as example:
insert into user(id, username, active) values (1, 'john', true);
insert into user(id, username, active) values (2, 'alex', true);
insert into user(id, username, active) values (3, 'alice', true);
insert into user(id, username, active) values (4, 'tom', true);
insert into user(id, username, active) values (5, 'dave', true);
insert into address(id, user_id, country, city, street) values (1, 1, 'Germany', 'Berlin', '');
insert into address(id, user_id, country, city, street) values (2, 2, 'Germany', 'Berlin', '');
insert into address(id, user_id, country, city, street) values (3, 2, 'Great Britain', 'London', '');
insert into address(id, user_id, country, city, street) values (4, 3, 'France', 'Paris', '');
insert into address(id, user_id, country, city, street) values (5, 4, 'USA', 'New York', '');
insert into address(id, user_id, country, city, street) values (6, 5, 'South Korea', 'Seoul', '');
Every user can have several addresses. I need to get all users who doesn't have in their set of addresses address with specific country, for example 'Germany'.
What I tried:
select u.* from user u
left join address a on u.id=a.user_id where a.country is not like '%Germany%'
But it returns users, who have address with specific country but also have some other address, which country is different from the specific one, for example with the data used above this is alex, who has two addresses Germany and Great Britain:
id username active
--------------------
2 alex True
3 alice True
4 tom True
5 dave True
Any suggestions how can I do such query?
Your code checks whether each user has at least one address outside of Germany, while you want to ensure that they have none.
I would recommend not exists:
select c.*
from client c
where not exists (
select 1
from address a
where a.user_id = c.id and a.country = 'Germany'
)
This query would take advantage of an index on address(user_id, country).
Note that it is unclear whether your table is called user or client... I used the latter.
Note that this also returns clients that have no address at all. If that's not what you want, then an alternative uses aggregation:
select c.*
from client c
inner join address on a.user_id = c.id
group by c.id
having not bool_or(a.country = 'Germany')
This is the query:
select user_id from address where country = 'Germany'
that returns all the users that you want to filter out.
Use it with NOT IN:
select u.*
from user u
where id not in (select user_id from address where country = 'Germany')
See the demo.
Results:
> id | username | active
> -: | :------- | :-----
> 3 | alice | t
> 4 | tom | t
> 5 | dave | t

Postgresql insert if does not exist

I have the following query
INSERT INTO address (house_number, street, city_id)
values(11, 'test st', (select id from city where LOWER(city) = LOWER('somecity')))
Is there anyway to insert "somecity" in the city table if "somecity" does not exist in city then after inserting, it would return the ID for the inserted row?
I did find this answer that says upsert can be used to achieve this
https://stackoverflow.com/a/31742830/492015
but I can't find an example that inserts if select does not return the row.
Instead of nesting the INSERTs, you could use a CTE
to perform the INSERTs one after the other but as a single statement:
WITH tmp AS (
INSERT INTO test_city (city) VALUES ('somecity')
ON CONFLICT (lower(city)) DO UPDATE SET city = excluded.city
RETURNING id, city
)
INSERT INTO test_address (house_number, street, city_id)
SELECT house_number, street, id
FROM (VALUES (11, 'test st', 'somecity')) val (house_number, street, city)
LEFT JOIN tmp USING (city)
RETURNING *
Using this setup:
DROP TABLE IF EXISTS test_address;
DROP TABLE IF EXISTS test_city;
CREATE TABLE test_address (
house_number int
, street text
, city_id int
);
CREATE TABLE test_city (
id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
, city text
);
CREATE UNIQUE INDEX test_city_uniq_idx ON test_city USING btree (lower(city));
INSERT INTO test_city (city) VALUES ('Somecity');
and with the INSERT above, the query
SELECT * FROM test_address;
yields
| house_number | street | city_id |
|--------------+---------+---------|
| 11 | test st | 1 |
and
SELECT * FROM test_city;
yields
| id | city |
|----+----------|
| 1 | somecity |
Note that the CTE replaces
(select id from city where LOWER(city) = LOWER('somecity'))
with an INSERT .. ON CONFLICT .. DO UPDATE statement:
INSERT INTO test_city (city) VALUES ('somecity')
ON CONFLICT (lower(city)) DO UPDATE SET city = excluded.city
RETURNING id, city
I used DO UPDATE instead of DO NOTHING so that RETURNING id, city will always return something. If you use DO NOTHING, then nothing is returned when there is a conflict.
Note however that a consequence of using city = excluded.city is that the original 'Somecity'
gets replaced by 'somecity'. I'm not sure you'll find that behavior acceptable, but unfortunately I haven't figured out how to do nothing when there is a conflict and yet return id and city at the same time.
Another issue you may have with the above solution is that I used a unique index on lower(city):
CREATE UNIQUE INDEX test_city_uniq_idx ON test_city USING btree (lower(city));
This allows you to use the identical condition in the INSERT statement:
INSERT ... ON CONFLICT (lower(city))
as a substitute for the condition LOWER(city) = LOWER('somecity') which appeared in your SELECT statement. It produces the desired effect, but the trade-off is that now you have a unique index
on (lower(city)).
Regarding the followup question
of how to insert into more than 2 tables:
You can chain together more than one CTE, and the subsequent CTEs can even reference the prior CTEs. For example,
CREATE UNIQUE INDEX city_uniq_idx ON city USING btree (lower(city));
CREATE UNIQUE INDEX state_uniq_idx ON state USING btree (lower(state_code));
WITH tmpcity AS
(
INSERT INTO
city (city)
VALUES
(
'Miami'
)
ON CONFLICT (lower(city)) DO
UPDATE
SET
city = excluded.city RETURNING id, city
)
, tmpstate as
(
INSERT INTO
state (state_code)
VALUES
(
'FL'
)
ON CONFLICT (lower(state_code)) DO
UPDATE
SET
state_code = excluded.state_code RETURNING id, state_code
)
INSERT INTO
address (house_number, street, city_id, state_id)
SELECT
house_number,
street,
tmpcity.id,
tmpstate.id
FROM
(
VALUES
(
12,
'fake st.',
'Miami',
'FL'
)
)
val (house_number, street, city, state_code)
LEFT JOIN
tmpcity USING (city)
LEFT JOIN
tmpstate USING (state_code)
ON CONFLICT (street) DO NOTHING

SQL query - find where a value (where there could be multiple) does not exist

I need some help in identifying records which do not have a specific value associated with it.
Need:
Each distinct customer record can have multiple methods of contact, for example:
Cheryl Hubert has the following contact records:
Code value: 1.
Description: home phone
CustomerData:. 123-456-7890
Code value: 2
Description: work phone
CustomerData: 000-123-4567
Code value:3
Description: email
CustomerData: chubert#xxx.xxx
Customers may have none of these, or some of these.
I need to write a query to find all those customer records which DO NOT have an email address (code value 3). I've seen queries with 'not exists' but not sure that would be the right way. Keep in mind that the same field name is used for all contact data (CustomerData).
The code value/description provides what is within the CustomerData field.
Any help appreciated.
Let's say the contact info is in a table contactRecords, which looks something like this:
customerId int,
codeValue int,
description varchar,
customerData varchar
To get all of the customers who do not have an email record (where codeValue = 3), try something like this:
select distinct customerId
from contactRecords
where customerId not in (
select distinct customerId
from contactRecords
where codeValue = 3)
The inner query finds all customers who have an email record. The outer query finds all but those customers.
As you posted almost no data i will try guessing your structure. Assuming you have clients in one table and contacts on another one with the client id, usually when you want to find something non relational between two tables, you select on your client, left join on your contact and put a where clause on any of the contact column is null. If you want specifically the value 3, put it directly in join clause.
Try this query:
select *
from customers c
where not exists(select 1 from contact_method
where customer_id = c.id
and description = 'email');
I assumed such schema:
create table customers(id int, name varchar(20));
insert into customers values (1, 'Cheryl Hubert');
create table contact_method (id int, customer_id int, code_value int, description varchar(20), customer_data varchar(20));
insert into contact_method values (1, 1, 1, 'home phone', '123-456-7890');
insert into contact_method values (2, 1, 2, 'work phone', '000-123-4567');
insert into contact_method values (3, 1, 3, 'email', 'chubert#xxx.xxx');
Demo
You can use the GROUP BY and HAVING clauses to check:
Oracle Setup:
CREATE TABLE contact_details ( code_value, customerid, description, customerdata ) AS
SELECT 1, 1, 'home phone', '123-456-7890' FROM DUAL UNION ALL
SELECT 2, 1, 'work phone', '000-123-4567' FROM DUAL UNION ALL
SELECT 3, 1, 'email', 'chubert#xxx.xxx' FROM DUAL UNION ALL
SELECT 4, 2, 'home phone', '012-345-6789' FROM DUAL;
Query:
SELECT customerid
FROM contact_details
GROUP BY customerid
HAVING COUNT( CASE description WHEN 'email' THEN 1 END ) = 0
Output:
| CUSTOMERID |
|------------|
| 2 |

How to detect duplicate records with sub table records

Let's say I'm creating an address book in which the main table contains the basic contact information and a phone number sub table -
Contact
===============
Id [PK]
Name
PhoneNumber
===============
Id [PK]
Contact_Id [FK]
Number
So, a Contact record may have zero or more related records in the PhoneNumber table. There is no constraint on uniqueness of any column other than the primary keys. In fact, this must be true because:
Two contacts having different names may share a phone number, and
Two contacts may have the same name but different phone numbers.
I want to import a large dataset which may contain duplicate records into my database and then filter out the duplicates using SQL. The rules for identifying duplicate records are simple ... they must share the same name and the same number of phone records having the same content.
Of course, this works quite effectively for selecting duplicates from the Contact table but doesn't help me to detect actual duplicates given my rules:
SELECT * FROM Contact
WHERE EXISTS
(SELECT 'x' FROM Contact t2
WHERE t2.Name = Contact.Name AND
t2.Id > Contact.Id);
It seems as if what I want is a logical extension to what I already have, but I must be overlooking it. Any help?
Thanks!
In my question, I created a greatly simplified schema that reflects the real-world problem I'm solving. Przemyslaw's answer is indeed a correct one and did what I was asking both with the sample schema and, when extended, with the real one.
But, after doing some experiments with the real schema and a larger (~10k records) dataset, I found that performance was an issue. I don't claim to be an index guru, but I wasn't able to find a better combination of indices than what was already in the schema.
So, I came up with an alternate solution which fills the same requirements but executes in a small fraction (< 10%) of the time, at least using SQLite3 - my production engine. In hopes that it may assist someone else, I'll offer it as an alternative answer to my question.
DROP TABLE IF EXISTS Contact;
DROP TABLE IF EXISTS PhoneNumber;
CREATE TABLE Contact (
Id INTEGER PRIMARY KEY,
Name TEXT
);
CREATE TABLE PhoneNumber (
Id INTEGER PRIMARY KEY,
Contact_Id INTEGER REFERENCES Contact (Id) ON UPDATE CASCADE ON DELETE CASCADE,
Number TEXT
);
INSERT INTO Contact (Id, Name) VALUES
(1, 'John Smith'),
(2, 'John Smith'),
(3, 'John Smith'),
(4, 'Jane Smith'),
(5, 'Bob Smith'),
(6, 'Bob Smith');
INSERT INTO PhoneNumber (Id, Contact_Id, Number) VALUES
(1, 1, '555-1212'),
(2, 1, '222-1515'),
(3, 2, '222-1515'),
(4, 2, '555-1212'),
(5, 3, '111-2525'),
(6, 4, '111-2525');
COMMIT;
SELECT *
FROM Contact c1
WHERE EXISTS (
SELECT 1
FROM Contact c2
WHERE c2.Id > c1.Id
AND c2.Name = c1.Name
AND (SELECT COUNT(*) FROM PhoneNumber WHERE Contact_Id = c2.Id) = (SELECT COUNT(*) FROM PhoneNumber WHERE Contact_Id = c1.Id)
AND (
SELECT COUNT(*)
FROM PhoneNumber p1
WHERE p1.Contact_Id = c2.Id
AND EXISTS (
SELECT 1
FROM PhoneNumber p2
WHERE p2.Contact_Id = c1.Id
AND p2.Number = p1.Number
)
) = (SELECT COUNT(*) FROM PhoneNumber WHERE Contact_Id = c1.Id)
)
;
The results are as expected:
Id Name
====== =============
1 John Smith
5 Bob Smith
Other engines are bound to have differing performance which may be quite acceptable. This solution seems to work quite well with SQLite for this schema.
The author stated the requirement of "two people being the same person" as:
Having the same name and
Having the same number of phone numbers and all of which are the same.
So the problem is a bit more complex than it seems (or maybe I just overthought it).
Sample data and (an ugly one, I know, but the general idea is there) a sample query which I tested on below test data which seems to be working correctly (I'm using Oracle 11g R2):
CREATE TABLE contact (
id NUMBER PRIMARY KEY,
name VARCHAR2(40))
;
CREATE TABLE phone_number (
id NUMBER PRIMARY KEY,
contact_id REFERENCES contact (id),
phone VARCHAR2(10)
);
INSERT INTO contact (id, name) VALUES (1, 'John');
INSERT INTO contact (id, name) VALUES (2, 'John');
INSERT INTO contact (id, name) VALUES (3, 'Peter');
INSERT INTO contact (id, name) VALUES (4, 'Peter');
INSERT INTO contact (id, name) VALUES (5, 'Mike');
INSERT INTO contact (id, name) VALUES (6, 'Mike');
INSERT INTO contact (id, name) VALUES (7, 'Mike');
INSERT INTO phone_number (id, contact_id, phone) VALUES (1, 1, '123'); -- John having number 123
INSERT INTO phone_number (id, contact_id, phone) VALUES (2, 1, '456'); -- John having number 456
INSERT INTO phone_number (id, contact_id, phone) VALUES (3, 2, '123'); -- John the second having number 123
INSERT INTO phone_number (id, contact_id, phone) VALUES (4, 2, '456'); -- John the second having number 456
INSERT INTO phone_number (id, contact_id, phone) VALUES (5, 3, '123'); -- Peter having number 123
INSERT INTO phone_number (id, contact_id, phone) VALUES (6, 3, '456'); -- Peter having number 123
INSERT INTO phone_number (id, contact_id, phone) VALUES (7, 3, '789'); -- Peter having number 123
INSERT INTO phone_number (id, contact_id, phone) VALUES (8, 4, '456'); -- Peter the second having number 456
INSERT INTO phone_number (id, contact_id, phone) VALUES (9, 5, '123'); -- Mike having number 456
INSERT INTO phone_number (id, contact_id, phone) VALUES (10, 5, '456'); -- Mike having number 456
INSERT INTO phone_number (id, contact_id, phone) VALUES (11, 6, '123'); -- Mike the second having number 456
INSERT INTO phone_number (id, contact_id, phone) VALUES (12, 6, '789'); -- Mike the second having number 456
-- Mike the third having no number
COMMIT;
-- does not meet the requirements described in the question - will return Peter when it should not
SELECT DISTINCT c.name
FROM contact c JOIN phone_number pn ON (pn.contact_id = c.id)
GROUP BY name, phone_number
HAVING COUNT(c.id) > 1
;
-- returns correct results for provided test data
-- take all people that have a namesake in contact table and
-- take all this person's phone numbers that this person's namesake also has
-- finally (outer query) check that the number of both persons' phone numbers is the same and
-- the number of the same phone numbers is equal to the number of (either) person's phone numbers
SELECT c1_id, name
FROM (
SELECT c1.id AS c1_id, c1.name, c2.id AS c2_id, COUNT(1) AS cnt
FROM contact c1
JOIN contact c2 ON (c2.id != c1.id AND c2.name = c1.name)
JOIN phone_number pn ON (pn.contact_id = c1.id)
WHERE
EXISTS (SELECT 1
FROM phone_number
WHERE contact_id = c2.id
AND phone = pn.phone)
GROUP BY c1.id, c1.name, c2.id
)
WHERE cnt = (SELECT COUNT(1) FROM phone_number WHERE contact_id = c1_id)
AND (SELECT COUNT(1) FROM phone_number WHERE contact_id = c1_id) = (SELECT COUNT(1) FROM phone_number WHERE contact_id = c2_id)
;
-- cleanup
DROP TABLE phone_number;
DROP TABLE contact;
Check at SQL Fiddle: http://www.sqlfiddle.com/#!4/36cdf/1
Edited
Answer to author's comment: Of course I didn't take that into account... here's a revised solution:
-- new test data
INSERT INTO contact (id, name) VALUES (8, 'Jane');
INSERT INTO contact (id, name) VALUES (9, 'Jane');
SELECT c1_id, name
FROM (
SELECT c1.id AS c1_id, c1.name, c2.id AS c2_id, COUNT(1) AS cnt
FROM contact c1
JOIN contact c2 ON (c2.id != c1.id AND c2.name = c1.name)
LEFT JOIN phone_number pn ON (pn.contact_id = c1.id)
WHERE pn.contact_id IS NULL
OR EXISTS (SELECT 1
FROM phone_number
WHERE contact_id = c2.id
AND phone = pn.phone)
GROUP BY c1.id, c1.name, c2.id
)
WHERE (SELECT COUNT(1) FROM phone_number WHERE contact_id = c1_id) IN (0, cnt)
AND (SELECT COUNT(1) FROM phone_number WHERE contact_id = c1_id) = (SELECT COUNT(1) FROM phone_number WHERE contact_id = c2_id)
;
We allow a situation when there are no phone numbers (LEFT JOIN) and in outer query we now compare the number of person's phone numbers - it must either be equal to 0, or the number returned from the inner query.
The keyword "having" is your friend. The generic use is:
select field1, field2, count(*) records
from whereever
where whatever
group by field1, field2
having records > 1
Whether or not you can use the alias in the having clause depends on the database engine. You should be able to apply this basic principle to your situation.

SQL Server 2005 Full Text Search over multiple tables and columns

I'm looking for a good solution to use the containstable feature of the SQL Serve r2005 effectivly. Currently I have, e.g. an Employee and an Address table.
-Employee
Id
Name
-Address
Id
Street
City
EmployeeId
Now the user can enter search terms in only one textbox and I want this terms to be split and search with an "AND" operator. FREETEXTTABLE seems to work with "OR" automatically.
Now lets say the user entered "John Hamburg". This means he wants to find John in Hamburg.
So this is "John AND Hamburg".
So the following will contain no results since CONTAINSTABLE checks every column for "John AND Hamburg".
So my question is: What is the best way to perform a fulltext search with AND operators across multiple columns/tables?
SELECT *
FROM Employee emp
INNER JOIN
CONTAINSTABLE(Employee, *, '(JOHN AND Hamburg)', 1000) AS keyTblSp
ON sp.ServiceProviderId = keyTblSp.[KEY]
LEFT OUTER JOIN [Address] addr ON addr.EmployeeId = emp.EmployeeId
UNION ALL
SELECT *
FROM Employee emp
LEFT OUTER JOIN [Address] addr ON addr.EmployeeId = emp.EmployeeId
INNER JOIN
CONTAINSTABLE([Address], *, '(JOHN AND Hamburg)', 1000) AS keyTblAddr
ON addr.AddressId = keyTblAddr.[KEY]
...
This is more of a syntax problem. How do you divine the user's intent with just one input box?
Are they looking for "John Hamburg" the person?
Are they looking for "John Hamburg Street"?
Are they looking for "John" who lives on "Hamburg Street" in Springfield?
Are they looking for "John" who lives in the city of "Hamburg"?
Without knowing the user's intent, the best you can hope for is to OR the terms, and take the highest ranking hits.
Otherwise, you need to program in a ton of logic, depending on the number of words passed in:
2 words:
Search Employee data for term 1, Search Employee data for term 2, Search Address data for term 1, Search address data for term 2. Merge results by term, order by most hits.
3 words:
Search Employee data for term 1, Search Employee data for term 2, Search employee data for term 3, Search Address data for term 1, Search address data for term 2, Search address data for term 3. Merge results by term, order by most hits.
etc...
I guess I would redesign the GUI to separate the input into Name and Address, at a minimum. If that is not possible, enforce a syntax rule to the effect "First words will be considered a name until a comma appears, any words after that will be considered addresses"
EDIT:
Your best bet is still OR the terms, and take the highest ranking hits. Here's an example of that, and an example why this is not ideal without some pre-processing of the input to divine the user's intent:
insert into Employee (id, [name]) values (1, 'John Hamburg')
insert into Employee (id, [name]) values (2, 'John Smith')
insert into Employee (id, [name]) values (3, 'Bob Hamburg')
insert into Employee (id, [name]) values (4, 'Bob Smith')
insert into Employee (id, [name]) values (5, 'John Doe')
insert into Address (id, street, city, employeeid) values (1, 'Main St.', 'Springville', 1)
insert into Address (id, street, city, employeeid) values (2, 'Hamburg St.', 'Springville', 2)
insert into Address (id, street, city, employeeid) values (3, 'St. John Ave.', 'Springville', 3)
insert into Address (id, street, city, employeeid) values (4, '5th Ave.', 'Hamburg', 4)
insert into Address (id, street, city, employeeid) values (5, 'Oak Lane', 'Hamburg', 5)
Now since we don't know what keywords will apply to what table, we have to assume they could apply to either table, so we have to OR the terms against each table, UNION the results, Aggregate them, and compute the highest rank.
SELECT Id, [Name], Street, City, SUM([Rank])
FROM
(
SELECT emp.Id, [Name], Street, City, [Rank]
FROM Employee emp
JOIN [Address] addr ON emp.Id = addr.EmployeeId
JOIN CONTAINSTABLE(Employee, *, 'JOHN OR Hamburg') AS keyTblEmp ON emp.Id = keyTblEmp.[KEY]
UNION ALL
SELECT emp.Id, [Name], Street, City, [Rank]
FROM Employee emp
JOIN [Address] addr ON emp.Id = addr.EmployeeId
JOIN CONTAINSTABLE([Address], *, 'JOHN OR Hamburg') AS keyTblAdd ON addr.Id = keyTblAdd.[KEY]
) as tmp
GROUP BY Id, [Name], Street, City
ORDER BY SUM([Rank]) DESC
This is less than ideal, here's what you get for the example (in your case, you would have wanted John Doe from Hamburg to show up first):
Id Name Street City Rank
2 John Smith Hamburg St. Springville 112
3 Bob Hamburg St. John Ave. Springville 112
5 John Doe Oak Lane Hamburg 96
1 John Hamburg Main St. Springville 48
4 Bob Smith 5th Ave. Hamburg 48
But that is the best you can do without parsing the input before submitting it to SQL to make a "best guess" at what the user wants.
I had the same problem. Here is my solution, which worked for my case:
I created a view that returns the columns that I want. I added another extra column which aggregates all the columns I want to search among. So, in this case the view would be like
SELECT emp.*, addr.*, ISNULL(emp.Name,'') + ' ' + ISNULL(addr.City, '') AS SearchResult
FROM Employee emp
LEFT OUTER JOIN [Address] addr ON addr.EmployeeId = emp.EmployeeId
After this I created a full-text index on SearchResult column. Then, I search on this column
SELECT *
FROM vEmpAddr ea
INNER JOIN CONTAINSTABLE(vEmpAddr, *, 'John AND Hamburg') a ON ea.ID = a.[Key]