0

I have two tables:
tblhobby

   +-------+-------+-------+-------+  
   | name  |hobby1 |hobby2 |hobby3 |  
   +-------+-------+-------+-------+   
   | kris  | ball  | swim  | dance |  
   | james | eat   | sing  | sleep |  
   | amy   | swim  | eat   | watch |  
   +-------+-------+-------+-------+

tblavailable_hobby

+----------------+ 
| available_hobby|
+----------------+
| ball           | 
| dance          | 
| swim           | 
| eat            | 
| watch          | 
+----------------+ 

the sql query should take all the columns in tblhobby and match it with tblavailable_hobby. If all the hobbies match to the available_hobby, then the person is selected

the query should produce

+--------+ 
| name   |
+--------+
| kris   | 
| amy    | 
+--------+

Please help

Thanks for the answers. I have inherited this database and not able to normalize it at the moment. however, I would like to add another twist to the question. Suppose:

   +-------+-------+-------+-------+  
   | name  |hobby1 |hobby2 |hobby3 |  
   +-------+-------+-------+-------+   
   | kris  | ball  | swim  | dance |  
   | james | eat   | sing  | sleep |  
   | amy   | swim  | eat   | watch | 
   | brad  | ball  |       | dance |
   +-------+-------+-------+-------+

I would like to get

+--------+ 
| name   |
+--------+
| kris   | 
| amy    | 
| brad   |
+--------+

how would i go about with it?

HansUp
  • 95,961
  • 11
  • 77
  • 135
Kris Adidarma
  • 45
  • 1
  • 2
  • 6
  • The design of your tables violates 3nf, you need to normalize tblHobby. – JonH Sep 29 '11 at 18:55
  • 3
    @JonH It violates [1NF](http://en.wikipedia.org/wiki/First_normal_form). :) – Shef Sep 29 '11 at 18:57
  • @Shef: you are mistaken, all tables appear to be in 1NF. Review [this article](http://www.simple-talk.com/content/print.aspx?article=712): "Many writers misunderstand the concept of a repeating group and use it to claim that a certain table is in violation of 1NF. Some people [mistakenly] believe that a set of columns, usually similarly named, that are placed adjacent to each other in a table, and have the same data type constitute a ‘repeating group’." – onedaywhen Sep 30 '11 at 13:19
  • @JonH: 3NF is not very useful. They should be aiming for 5NF. – onedaywhen Sep 30 '11 at 13:23
  • @onedaywhen Sorry pal, but you are the one misunderstanding it. This is not the case the article describes, here the repeating groups are in violation of the 1NF, because it's the same type of data related to the person. In this case, the person can stand fine without a hobby, with one, or two hobbies. Not all three columns are required. If you refer to the example the article presents all the columns are required. If you look closely the same hobbies are interchanged between the columns, so `hobby_1` does not talk about a type of hobby, rather the columns are repeated to accommodate values. – Shef Sep 30 '11 at 17:22
  • @Shef: what I am saying is that they are not "repeating groups". If you are calling NFNF because of the presence of nulls then good for you, I won't disagree. But hang on: didn't I see you using an outer join the other day...? – onedaywhen Sep 30 '11 at 21:56
  • @onedaywhen They are indeed repeating groups, because the exact same domain values are scattered among different columns just to accommodate the different values it could have related to this particular person. There can't be a clearer case of 1NF violation than this one. I don't think there is anything wrong with out joins. They have their own application. Frankly, I don't recall recommending them, but if I have answered a question where it was not appropriate/best to use outer joins, feel free to bring that to my attention. :) – Shef Sep 30 '11 at 22:26
  • @Shef: it is clear you have fallen fowl of one of the fallacies of 1NF. Consider if the domain in question was 'dates' and the columns were `eligibility_date`, `hire_date` and `termination_date` (as per the article I linked to) then your statement would hold true ("the exact same domain values are scattered among different columns just to accommodate the different values it could have related to this particular person['s employment]") but they are clearly not repeating groups. – onedaywhen Oct 03 '11 at 07:43
  • @Shef: Try [this](http://www.dbdebunk.com/page/page/622301.htm), it may make things clearer: "The commonly used example [`child1`, `child2`, `child3`] is wrong: such a table may be poorly designed, but it is in 1NF... R-tables are in 1NF by definition, because they do not have the table equivalent of repeating groups, **multivalued columns**. If your table contained a column called `CHILDREN` that represented multiple child values, then it would not be in 1NF and, thus, it would not be a R-table." I can provide similar citations but feel I can say no more on the subject to convince you. – onedaywhen Oct 03 '11 at 07:46
  • @Shef: "I don't recall recommending them, but if I have answered a question where it was not appropriate/best to use outer joins, feel free to bring that to my attention. :) " -- I note the smilie but [this](http://stackoverflow.com/questions/6845307/fetch-column-if-row-exists-using-mysql/6845352#6845352) (http://stackoverflow.com/questions/6845307/fetch-column-if-row-exists-using-mysql/6845352#6845352)is an answer where you recommended an `OUTER JOIN` that would expressly generate nulls in the resultset. If you believe (as I do) that nulls violate 1NF then your answer is not appropriate/best. – onedaywhen Oct 03 '11 at 07:48
  • @onedaywhen This is going fairly long for nothing. `NULL`s in the resultset do not violate anything, not even indicate a violation. They are an _indication_ of a violation on the records themselves. This is what I know and believe. `LEFT JOIN` is NOT an `OUTER JOIN`, get your records straight. Yes, I would recommend `LEFT JOIN` over `UNION` anytime anywhere. I am done here. – Shef Oct 03 '11 at 08:14
  • @Shef: Nulls violate 1NF: [citation from Wikipedia](http://en.wikipedia.org/wiki/First_normal_form): "tables (or views) that would not meet this definition of 1NF [include] A table with at least one nullable attribute". `LEFT JOIN` is indeed an outer join. `LEFT JOIN` is a contraction of `LEFT OUTER JOIN` i.e. the `OUTER` keyword is optional. Check the [MySQL documentation](http://dev.mysql.com/doc/refman/5.0/en/join.html) or [citation from Wikipedia](http://en.wikipedia.org/wiki/Join_(SQL)#Left_outer_join): "The result of a left outer join (or simply left join)…" I agree this is hopeless! – onedaywhen Oct 03 '11 at 09:39
  • @onedaywhen Yes, you are right about the `LEFT JOIN`, my bad. However, a table is different from a resultset. You are misquoting about the null attributes. It explicitly says _"A table with at least one nullable attribute."_ not a view. Yes, on the list header it says _"(or views)"_, but that's because there is also an explicit case for a view there. – Shef Oct 03 '11 at 09:47
  • @onedaywhen I asked this as a question here on SO, so we can get a broader range of opinions whether those are repeating groups or not. You may check the question [Which normal form does this table violate?](http://stackoverflow.com/questions/7633108/which-normal-form-does-this-table-violate). – Shef Oct 03 '11 at 09:59

6 Answers6

3

Poor DB design, but, assuming you have to live with it:

SELECT h.name
    FROM tblhobby h
        INNER JOIN tblavailable_hobby ah1
            ON h.hobby1 = ah1.available_hobby
        INNER JOIN tblavailable_hobby ah2
            ON h.hobby2 = ah2.available_hobby
        INNER JOIN tblavailable_hobby ah3
            ON h.hobby3 = ah3.available_hobby

EDIT: Answering the twist proposed in the comments below.

SELECT h.name
    FROM tblhobby h
        LEFT JOIN tblavailable_hobby ah1
            ON h.hobby1 = ah1.available_hobby
        LEFT JOIN tblavailable_hobby ah2
            ON h.hobby2 = ah2.available_hobby
        LEFT JOIN tblavailable_hobby ah3
            ON h.hobby3 = ah3.available_hobby
    WHERE (h.hobby1 IS NULL OR ah1.available_hobby IS NOT NULL)
        AND (h.hobby2 IS NULL OR ah2.available_hobby IS NOT NULL)
        AND (h.hobby3 IS NULL OR ah3.available_hobby IS NOT NULL)
Joe Stefanelli
  • 132,803
  • 19
  • 237
  • 235
  • if one of the hobbies is empty, how would I still be able to select the person? – Kris Adidarma Sep 29 '11 at 19:31
  • You would do a left outer join. So just change all of the `INNER JOIN`s in the example above to `LEFT OUTER JOIN` This means: include all data from the left-hand table regardless of whether it matches any data in the right-hand table. – Paul Sasik Sep 29 '11 at 19:49
  • hi, sorry the additional query does not work. Still produces kris and amy – Kris Adidarma Sep 29 '11 at 20:05
  • @KrisAdidarma The additional query assumes your "empty" values are NULL. Are your "empty" values `NULL` or empty strings `''`? – Joe Stefanelli Sep 29 '11 at 20:07
  • thats another thing. the database is using access 2003 and I am trying to ascertain if it is null or empty. I still cannot figure it out. I think access does not differentiate. Do you have any experience in this? – Kris Adidarma Sep 29 '11 at 20:12
  • @KrisAdidarma Try replacing the three `h.hobbyx IS NULL` segments with `h.hobbyx = ''` and see if that makes a difference. – Joe Stefanelli Sep 29 '11 at 20:14
  • but anyways, I tried replacing is null with ="" and is not null with <>"" and the result is still the same – Kris Adidarma Sep 29 '11 at 20:17
  • also tried replacing the three h.hobbyx IS NULL segments with h.hobbyx = '' but result still same – Kris Adidarma Sep 29 '11 at 20:20
  • @JoeStefanelli sorry it is my mistake. My joins were inner joins and not left joint. It works now. THANKS A LOT!! you are a wizard – Kris Adidarma Sep 29 '11 at 20:34
1

I know this doesn't answer your question directly, and others have pointed out that your table design is problematic. What it should look like is this:

Table: Person
Id    Name
-------------
1     Kris
2     James
3     Amy

table: PersonHobby (Join table)
PersonId HobbyId
----------------
1        1 -- Kris likes to ball
1        2 --   "           dance
1        3 --   "           swim
2        4  -- James likes to eat

Table: Hobby
Id   Name
--------------
1    Ball
2    Dance
3    Swim
4    Eat
etc.

This design uses the concept of a Join or Junction table that allows you make many-to-many relationships between data. In this case people and hobbies.

You then query the data like this:

SELECT * 
FROM Person p 
JOIN PersonHobby AS ph on p.Id = ph.PersonId
JOIN Hobby       AS h  on h.Id = ph.HobbyId

WHERE ... -- filter as you need to

The PersonHobbies table in my example takes a table of Persons and a table of Hobbies and enables relationships between Persons and Hobbies. I know this will probably look like more work to you... extra tables, extra columns. But trust us, this design will make your life much simpler in the near future. In fact, you're already feeling the pain of your design by trying to figure out a query which should be much simpler than it is against your current db.

I would like to produce a WHERE filter to match your requirements but I don't quite understand what you're after. Could you explain in some more detail?

Paul Sasik
  • 79,492
  • 20
  • 149
  • 189
  • In fixing the 1NF problem, you've neglected to be able to accommodate the data as presented - i.e. that there are hobbies assigned to people which are not "available". In your model (while I'm not disputing the benefits), the query joins can actually become different or you can have a quite lengthy WHERE clause, because then you add back in the "available" hobbies and all the hobbies linked for a person need to be "available" hobbies. i.e. the person cannot be linked to any "unavailable" hobbies. – Cade Roux Sep 29 '11 at 19:35
  • hi paul, Joe's answer is what I am looking for in general but I have edited my question because now I have a missing hobby in one of the columns. Therefore, I need to modify joe's query. This is the result of not normalizing the database, I know but i need to figure a way around it. Maybe you can figure our a solution. Thanks – Kris Adidarma Sep 29 '11 at 19:40
  • @Kris: My sincere condolences on your inherited data model. Unfortunately my suggested solution cuts all the way through the app and would indeed change the behavior. For example, a user would not be able to enter a non-existent hobby. It would have to be created first, or, created on-the-fly when a non-existent hobby is detected. etc. This would require a redoing of muc h of the logic... – Paul Sasik Sep 29 '11 at 19:51
  • @PaulSasik thanks. a lot of logic and programming has gone into this. Actually the database is a bit more complex than the hobby problem but it presented it as such because it is easier to understand. Thanks for your support – Kris Adidarma Sep 29 '11 at 20:32
1

You can use a query to transform your existing table into a "virtual table", which I think should be easier to work with. Save this SQL statement as qryHobbiesUnion.

SELECT [name] AS person, hobby1 AS hobby
FROM tblhobby
WHERE (((hobby1) Is Not Null))
UNION
SELECT [name], hobby2
FROM tblhobby
WHERE (((hobby2) Is Not Null))
UNION
SELECT [name], hobby3
FROM tblhobby
WHERE (((hobby3) Is Not Null));

I enclosed "name" in square brackets because it's a reserved word. And I aliased [name] as person to avoid problems with square brackets when using qryHobbiesUnion in a subquery later.

I assumed any "empty" values for hobby will be Null. If blanks could also be empty strings (""), change the WHERE clauses to a pattern like this:

WHERE Len(hobby1 & "") > 0

After you determine which version of the WHERE clause returns the correct rows, save the query and use it in another query.

SELECT sub.person
FROM
    [SELECT qh.person, qh.hobby, ah.available_hobby
    FROM
        qryHobbiesUnion AS qh
        LEFT JOIN tblavailable_hobby AS ah
        ON qh.hobby = ah.available_hobby
        ]. AS sub
GROUP BY sub.person
HAVING (((Count(sub.hobby))=Count([sub].[available_hobby])));

Using your second set of sample data, that query returns the 3 person names you wanted: amy; brad; and kris.

If tblhobby contained a row for a person with all the hobby fields empty, this query would not include that person's name. That makes sense to me because it seems your intention is to identify the people whose hobby choices are all matched in tblavailable_hobby. So a person with no hobby selections has no matches. If you want different behavior, this will probably get uglier. :-)

HansUp
  • 95,961
  • 11
  • 77
  • 135
  • "I enclosed "name" in square brackets because..." -- but why did you use so many parentheses when none was required e.g. `(((hobby3) Is Not Null))` ?! – onedaywhen Sep 30 '11 at 15:21
  • 1
    @onedaywhen I used the query designer, which goes overboard (IMO) with parentheses. Notice it also added square brackets in the HAVING clause. Those "features" used to annoy me and I'd discard the extraneous ones. I've since grown more complacent, and don't clean the SQL as often. And I think it might sometimes help Access beginners to show them how the SQL will look when the query designer transforms it. – HansUp Sep 30 '11 at 15:32
0

Really you must learn more about relational databases. Your design isn't good. You should have table with people and a table with hobbies. Then you should have a table the relates the two tables by an ID.

Your tables should look likes this

TABLE: People COLUMNS: PID (INT, Primary Key), NAME

TABLE: Hobbies COLUMNS: HID (INT, Primary Key), Hobby

TABLE: PeoplesHobbies COLUMNS: ID, PID, HID

THEN your query would look something like this

select * from people `p` inner join PeoplesHobbies `ph` on p.PID = ph.PID inner join on Hobbies `h` on ph.HID = h.HID where p.NAME = 'JOHN'
Caimen
  • 2,623
  • 2
  • 26
  • 43
  • 1
    While I believe the original design needs work, it does express a need for people to be linked to hobbies which are not "available", which your solution does not. The problem with the original design of 1NF (array) is a problem. Viewing the hobbies as natural keys does not mean that it needs to be replaced with a surrogate key - it could still be considered normalized with natural keys. – Cade Roux Sep 29 '11 at 19:06
  • thanks. unfortunately this is a grandfathered database I inherited. – Kris Adidarma Sep 29 '11 at 19:16
0
SELECT name
FROM tblhobby AS h
WHERE EXISTS
        ( SELECT *
          FROM tblavailable_hobby AS ah1
          WHERE h.hobby1 = ah1.available_hobby
        )
  AND EXISTS
        ( SELECT *
          FROM tblavailable_hobby AS ah2
          WHERE h.hobby2 = ah2.available_hobby
        )
  AND EXISTS
        ( SELECT *
          FROM tblavailable_hobby AS ah3
          WHERE h.hobby3 = ah3.available_hobby
        )
ypercubeᵀᴹ
  • 113,259
  • 19
  • 174
  • 235
0

Borrowing from Joe's answer:

SELECT h.name
    FROM tblhobby h
        LEFT JOIN tblavailable_hobby ah1
            ON h.hobby1 = ah1.available_hobby
        LEFT JOIN tblavailable_hobby ah2
            ON h.hobby2 = ah2.available_hobby
        LEFT JOIN tblavailable_hobby ah3
            ON h.hobby3 = ah3.available_hobby
WHERE (h.hobby1 IS NULL OR ah1.available_hobby IS NOT NULL)
    AND (h.hobby2 IS NULL OR ah2.available_hobby IS NOT NULL)
    AND (h.hobby3 IS NULL OR ah3.available_hobby IS NOT NULL)

ypercube's answer can be similarly extended.

Community
  • 1
  • 1
Cade Roux
  • 88,164
  • 40
  • 182
  • 265