views:

156

answers:

9

alt text

For a particular name i want to fetch other names who have lived in three or more cities lived by this person.

+1  A: 

Yeah.

You send the table back to whoever designed it with a comment to learn hwo to design tables. First normal form, normalization.

Once the table follows SQL rules, the query is pretty easy.

TomTom
A: 

You need to normalize your database.

Doing that you will get the columns

Name, City (optionally CityOrder).

After that you will need to find a way to combine these results into what you need. Doing this you'll need to understand Join, Count and Group by.

Thirler
+2  A: 

You need to redesign your table instaed of

name , city1 , city2 , city3 ,city4 , city5 ,city6 , city7 , city8 , city9 city10

it should be more like

Person, City, rank
------------------
name , city1 ,1
name , city2 ,2 
name , city3 ,3 
name , city4 ,4 
name , city5 ,5 
name , city6 ,6 
name , city7 ,7 
name , city8 ,8 
name , city9 ,9 
name , city10,10

and take TomTom's advice and learn about data normalization!

lexu
A person's name usually makes a bad key.
Ignacio Vazquez-Abrams
Yepp, that's why it say's 'more like' in the text above. I was afraid to confuse him to no end if I split the data into three tables: person, city and person_city . It's hard to draw a line when deciding how far into details one goes in an answer. But, you are correct about the bad key!
lexu
+12  A: 

this is what you should do:

redesign your database to have a city table:

city(id int, name varchar)

and a user table:

user(id int, name varchar, ...)

and a user_city table:

user_city(user_id int, city_id int)

that alone will eliminate the limit of 10 cities per user.

to find the cities lived in by a user:

select city_id form user_city where user_id = ?

now how you would find users that lives in 3 or more cities from that list? one way to do it would be to count the number of cities from the list each user lived in, something like:

select user_id,count(*) n 
from user_city 
where city_id in (select city_id 
                  from user_city 
                  where user_id = ?) 
group by user_id having n >= 3;

I didn't really test this, but it should work.

you will also have to figure out how to index those tables.

Omry
aaah I took to long to edit .... this is what you should do
Ankur
edited, if you feel it can be improved - please feel free to further edit it.
Omry
Bravo! +1 for a good, compact answer!
lexu
hi omry ... i respect your answer , but actually i have a limitation and i can't change the database design ....is it impossible ....note that name is unique and primary key of the table. can't it be solved with help of php ?
saurav
I'd go for >= 3, or > 2, personally... ;)
Benjol
me too, thanks :)
Omry
saurav it depends on the amount of data we are talking about. also - performance will probably be much worse if you implement this in php.as you noticed, almost all of the answers were that you need to redesign your database. I would consider this seriously. the normalized design I presented has more advantages beyond being good for the query you need.
Omry
saurav - one more thing: there ARE cases where an un-normalized database is okay, mainly when dealing with HUGE amounts of data that needed to be sharded between a few servers. this is probably not one of those cases.
Omry
A: 

Try this:

< table > Person
< fields > PersonId, PersonName |

< table > City
< fields > CityId, CityName |

< table > LivedIn
< fields > LivedInId, PersonId, CityId

Logically you would do the following things for each scenario:

  1. Find the person who has lived in the maximum number of different cities:
    Make a list of the PersonId's (all people)
    Iterate over that and count the number of cities each person lived in
    Find the maximum cities lived in by anyone person
    Find the person name related to the personId that had the max cities

  2. Find all people that lived in 3 or more cities as a give person
    Let's call the person Bob
    Make a list of all cities (the CityIds) that Bob lived in.
    Make a list which includes personId, and common cities (maybe a HashMap in Java)
    Iterate over the LivedIn table and update the count of how many cities are common
    Find all the people who have a count greater than 3

I would do this with a combination of Java and SQL but I am not that good with either so can't give you the code here without having to look a lot of stuff up.

Ankur
A: 

Breaking this data out into three tables to provide a more flexible many-to-many relationship.

person table to store names
city table to store cities
person_city to relate the two (many to many)

To retrieve other people who have lived in 3 or more cities that navin has:

SELECT name FROM (
    SELECT
        p.name, COUNT(DISTINCT(city_id)) AS lived
    FROM person p 
    JOIN person_city pc ON (pc.person_id = p.person_id) 
    JOIN city c ON (c.city_id = pc.city_id) 
    WHERE city_id IN (
        SELECT c2.city_id 
        FROM city c2 
        JOIN person_city pc2 ON (c2.city_id = pc2.city_id) 
        JOIN person p2 ON (p2.person_id = pc2.person_id) 
        WHERE p2.name = 'navin' 
    )
    GROUP BY person_id HAVING lived >= 3
) AS multihome 
WHERE name <> 'navin';
Greg K
Doh, answered in the other thread. Nevermind.
Greg K
+4  A: 

You'd need binomial(10,3)^2 OR conditions to do your query. Thats 14 400. You do not want to do that.

Jens
+2  A: 

Respecting your request to not redesign the database

My untried idea, no way to test it right now

Make a view (name, city) by unioning select name, c1, select name, c2 etc...

Then:

select m2.name from myview m1
inner join myview m2 on m1.city = m2.city
where m1.name = @Name AND m2.Name!=@Name
group by m2.name
having count(m2.name) > 2 
Benjol
`@` looks like sql server and not mysql. However, you need to add `AND m2.Name!=@Name` so the *searched for person* is not included in the results. This is basically the same solution as in my answer http://stackoverflow.com/questions/2469551/please-help-me-design-a-sql-query-for-this-problem/2469917#2469917 but I forgo the use of Views and just do the UNIONS in the query, might be easier for the OP. If this were SQL Server I'd just do a CTE.
KM
@KM, yup, you're right. But one of his parallel questions was tagged mssql, so who knows?
Benjol
+1  A: 

Try something like this:

SELECT PersonName,COUNT(*) AS CountOf
    FROM (SELECT PersonName,city1 FROM PersonCities WHERE city1 IS NOT NULL
          UNION SELECT PersonName,city2 FROM PersonCities WHERE city2 IS NOT NULL
          UNION SELECT PersonName,city3 FROM PersonCities WHERE city3 IS NOT NULL
          UNION SELECT PersonName,city4 FROM PersonCities WHERE city4 IS NOT NULL
          UNION SELECT PersonName,city5 FROM PersonCities WHERE city5 IS NOT NULL
          ...
         ) dt
    WHERE dt.city1 IN (SELECT city1 FROM PersonCities WHERE PersonName=..SearchPerson.. AND city1 IS NOT NULL
                       UNION SELECT city2 FROM PersonCities WHERE PersonName=..SearchPerson.. AND city2 IS NOT NULL
                       UNION SELECT city3 FROM PersonCities WHERE PersonName=..SearchPerson.. AND city3 IS NOT NULL
                       UNION SELECT city4 FROM PersonCities WHERE PersonName=..SearchPerson.. AND city4 IS NOT NULL
                       UNION SELECT city5 FROM PersonCities WHERE PersonName=..SearchPerson.. AND city5 IS NOT NULL
                       ...
                       )
        AND PersonName!=@SearchPerson
    GROUP BY PersonName
    HAVING COUNT(*)>=3

I don't have mysql, so here it is running using SQL Server:

DECLARE @PersonCities table(PersonName varchar(10), city1 varchar(10), city2 varchar(10), city3 varchar(10), city4 varchar(10), city5 varchar(10))
INSERT INTO @PersonCities VALUES ('Joe','AAA','BBB','CCC', NULL, NULL)
INSERT INTO @PersonCities VALUES ('Pat','BBB','DDD','EEE','FFF','GGG')
INSERT INTO @PersonCities VALUES ('Sam','FFF','BBB', NULL, NULL, NULL)
INSERT INTO @PersonCities VALUES ('Ron','HHH','DDD','EEE','FFF', NULL)
INSERT INTO @PersonCities VALUES ('Don','FFF','ZZZ','QQQ', NULL, NULL)

DECLARE @SearchPerson varchar(10)
SET @SearchPerson='Pat'

SELECT PersonName,COUNT(*) AS CountOf
    FROM (SELECT PersonName,city1 FROM @PersonCities WHERE city1 IS NOT NULL
          UNION SELECT PersonName,city2 FROM @PersonCities WHERE city2 IS NOT NULL
          UNION SELECT PersonName,city3 FROM @PersonCities WHERE city3 IS NOT NULL
          UNION SELECT PersonName,city4 FROM @PersonCities WHERE city4 IS NOT NULL
          UNION SELECT PersonName,city5 FROM @PersonCities WHERE city5 IS NOT NULL
         ) dt
    WHERE dt.city1 IN (SELECT city1 FROM @PersonCities WHERE PersonName=@SearchPerson AND city1 IS NOT NULL
                       UNION SELECT city2 FROM @PersonCities WHERE PersonName=@SearchPerson AND city2 IS NOT NULL
                       UNION SELECT city3 FROM @PersonCities WHERE PersonName=@SearchPerson AND city3 IS NOT NULL
                       UNION SELECT city4 FROM @PersonCities WHERE PersonName=@SearchPerson AND city4 IS NOT NULL
                       UNION SELECT city5 FROM @PersonCities WHERE PersonName=@SearchPerson AND city5 IS NOT NULL
                       )
        AND PersonName!=@SearchPerson
    GROUP BY PersonName
    HAVING COUNT(*)>=3

OUTPUT:

PersonName 
---------- -----------
Ron        3

(1 row(s) affected)
KM
Union query sounds the best bet. For extra points: Store it as a View!
MPelletier
@MPelletier , I'd prefer to use a CTE, which is available in a SQL Server but not in mysql, which this question is all about. I thought the OP might like the single query and not have to worry about creating a view, which would be better than duplicating the code.
KM