SQL query, primary key to multiple foreign key - mysql

I'm having a problem which i cannot manage to fix.
I've been looking at some other posts but most of them were either to complex for me to understand/use or do not deal with the same problem.
First the tables:
Person
id | amt
=============
1 | 5
2 | 26
3 | 3
Goal
id | idPerson | goals
===========================
1 | 1 | "AAA"
2 | 1 | "AAA"
4 | 1 | "DDD"
5 | 2 | "CCC"
6 | 2 | "BBB"
7 | 3 | "AAA"
What I want:
I need all Person ids where the Person does not have the goal "AAA".
Currently a Person can only have between 1 and 3 goals in the Table Goal.
My Select:
SELECT Person.id, Person.amt
FROM Person
WHERE amt < 10
AND Person.id IN
(
SELECT Goal.idPerson
FROM Goal
WHERE Person.id = Goal.idPerson
AND Goal.goals != "AAA"
)
If possible could someone explain me why my SQL is not working and what i have to do different so it works? With an explanation i could avoid the same problem in the future. :)
Thank you.
EDIT:
I did not thought that this would make a problem but when i filter not only for 'AAA' but also for 'BBB' then it I will get results with 'AAA', 'BBB' and even some with both.
How the Where is now:
AND Person.id NOT IN
(
SELECT Goal.idPerson
FROM Goal
WHERE Person.id = Goal.idPerson
AND Goal.goals = 'AAA'
AND Goal.goals = 'BBB'
)

You probably looking for NOT EXISTS instead like
SELECT Person.id, Person.amt
FROM Person
WHERE amt < 10
AND NOT EXISTS
(
SELECT 1
FROM Goal
WHERE idperson = person.id AND goals = "AAA"
)

You don't need it to be correlated and you want to use NOT IN instead:
select Person.id,
Person.amt
from Person
where amt < 10
and id not in (
select idPerson
from Goal
where goals = 'AAA'
)

You need to use NOT IN rather than IN, e.g.:
SELECT *
FROM Person
WHERE amt < 10
AND Person.id NOT IN (
SELECT idPerson FROM Goal WHERE goals = 'AAA'
);

Also change this:
AND Goal.goals != "AAA"
to
AND Goal.goals != 'AAA'

Your subselect filters all rows that do not match "AAA". That includes "DDD". You can use
SELECT * FROM Person p
WHERE NOT EXISTS (SELECT g.id FROM Goal g WHERE g.goals="AAA" AND g.idPerson=p.id)

Related

MySQL select query with AND condition on same columns of same table

I have a table like this
itemid | propertyname | propertyvalue
___________|______________|_______________
1 | point | 12
1 | age | 10
2 | point | 15
2 | age | 11
3 | point | 9
3 | age | 10
4 | point | 13
4 | age | 11
I need a query to select all items where age greater than 10 and point less than 12.
I tried
`select itemid from table where (propertyname="point" and propertyvalue < 12)
and (propertyname="age" and propertyvalue >10)`
It gives no results. How can I make it work?
you can use an inner join
SELECT
a.itemid
FROM
yourTable a
INNER JOIN
yourTable b
ON
a.itemid=b.itemid
AND a.propertyname='point'
AND b.propertyname='age'
WHERE
a.propertyvalue<12
AND b.propertyvalue>10
ok so in table a youre lookin for all items with the name point and a value smaller 12 and in table b youre looking for all items with the name age and a value greater 10. Then you only have to look for items, which are in both tables. For this you connect the two tables over the itemid. To connect tables you use the join. Hope this will help you to understand. If not ask again :)
To join a table to itself in the same query you can include the table twice in the FROM clause, giving it a different alias each time. Then you simply proceed with building your query as if you were dealing with two separate tables that just happen to contain exactly the same data.
In the query below the table example is aliased as a and b:
SELECT a.itemid
FROM example a, example b
WHERE a.itemid = b.itemid
AND a.propertyname = 'point'
AND a.propertyvalue < 12
AND b.propertyname = 'age'
AND b.propertyname > 10
Try It:
SELECT itemid FROM test_table WHERE propertyname="point" AND propertyvalue < 12 AND itemid IN(SELECT itemid FROM test_table WHERE propertyname="age" AND propertyvalue >10)
http://sqlfiddle.com/#!9/4eafc6/1
PLs Try this
select itemid from table where (propertyname="point" and propertyvalue < 12)
or (propertyname="age" and propertyvalue >10);
Here's one idea...
SELECT item_id
, MAX(CASE WHEN propertyname = 'point' THEN propertyvalue END point
, MAX(CASE WHEN propertyname = 'age' THEN propertyvalue END age
FROM a_table
GROUP
BY item_id
HAVING age+0 > 10
AND point+0 < 12;
You can use an inner join. Meaning, it's like you're going to work with 2 tables: the first one you're going to select the name="age" and val>10, and the second one is where you're going to select name="point" and val<12.
It's like you're creating an instance of your table that doesn't really exist. It's just going to help you extract the data you need at the same time.

SQL - select rows that have the same value in two columns

The solution to the topic is evading me.
I have a table looking like (beyond other fields that have nothing to do with my question):
NAME,CARDNUMBER,MEMBERTYPE
Now, I want a view that shows rows where the cardnumber AND membertype is identical. Both of these fields are integers. Name is VARCHAR. Name is not unique, and duplicate cardnumber, membertype should show for the same name, as well.
I.e. if the following was the table:
JOHN | 324 | 2
PETER | 642 | 1
MARK | 324 | 2
DIANNA | 753 | 2
SPIDERMAN | 642 | 1
JAMIE FOXX | 235 | 6
I would want:
JOHN | 324 | 2
MARK | 324 | 2
PETER | 642 | 1
SPIDERMAN | 642 | 1
this could just be sorted by cardnumber to make it useful to humans.
What's the most efficient way of doing this?
What's the most efficient way of doing this?
I believe a JOIN will be more efficient than EXISTS
SELECT t1.* FROM myTable t1
JOIN (
SELECT cardnumber, membertype
FROM myTable
GROUP BY cardnumber, membertype
HAVING COUNT(*) > 1
) t2 ON t1.cardnumber = t2.cardnumber AND t1.membertype = t2.membertype
Query plan: http://www.sqlfiddle.com/#!2/0abe3/1
You can use exists for this:
select *
from yourtable y
where exists (
select 1
from yourtable y2
where y.name <> y2.name
and y.cardnumber = y2.cardnumber
and y.membertype = y2.membertype)
SQL Fiddle Demo
Since you mentioned names can be duplicated, and that a duplicate name still means is a different person and should show up in the result set, we need to use a GROUP BY HAVING COUNT(*) > 1 in order to truly detect dupes. Then join this back to the main table to get your full result list.
Also since from your comments, it sounds like you are wrapping this into a view, you'll need to separate out the subquery.
CREATE VIEW DUP_CARDS
AS
SELECT CARDNUMBER, MEMBERTYPE
FROM mytable t2
GROUP BY CARDNUMBER, MEMBERTYPE
HAVING COUNT(*) > 1
CREATE VIEW DUP_ROWS
AS
SELECT t1.*
FROM mytable AS t1
INNER JOIN DUP_CARDS AS DUP
ON (T1.CARDNUMBER = DUP.CARDNUMBER AND T1.MEMBERTYPE = DUP.MEMBERTYPE )
SQL Fiddle Example
If you just need to know the valuepairs of the 3 fields that are not unique then you could simply do:
SELECT concat(NAME, "|", CARDNUMBER, "|", MEMBERTYPE) AS myIdentifier,
COUNT(*) AS count
FROM myTable
GROUP BY myIdentifier
HAVING count > 1
This will give you all the different pairs of NAME, CARDNUMBER and MEMBERTYPE that are used more than once with a count (how many times they are duplicated). This doesnt give you back the entries, you would have to do that in a second step.

Select from one table but filtering other two

Let's say i've got this database:
book
| idBook | name |
|--------|----------|
| 1 |Book#1 |
category
| idCateg| category |
|--------|----------|
| 1 |Adventures|
| 2 |Science F.|
book_categ
| id | idBook | idCateg | DATA |
|--------|--------|----------|--------|
| 1 | 1 | 1 | (null) |
| 2 | 1 | 2 | (null) |
I'm trying to select only the books which are in category 1 AND category 2 something like this
SELECT book.* FROM book,book_categ
WHERE book_categ.idCateg = 1 AND book_categ.idCateg = 2
Obviously, this giving 0 results becouse each row has only one idCateg it does work width OR but the results are not what I need. I've also tried to use a join, but I just can't get the results I expect.
Here it's the SQLFiddle of my current project, with my current DB, the data at the begining is just a sample. SQLFiddle
Any help will be really appreciated.
Solution using EXISTS:
select *
from book b
where exists (select 'x'
from book_categ x
where x.idbook = b.idbook
and x.idcateg = 1)
and exists (select 'x'
from book_categ x
where x.idbook = b.idbook
and x.idcateg = 2)
Solution using join with an inline view:
select *
from book b
join (select idbook
from book_categ
where idcateg in (1, 2)
group by idbook
having count(*) = 2) x
on b.idbook = x.idbook
You could try using ALL instead of IN (if you only want values that match all criteria to be returned):
SELECT book.*
FROM book, book_categ
WHERE book_categ.idCateg = ALL(1 , 2)
One way to get the result is to do join to the book_categ table twice, something like
SELECT b.*
FROM book b
JOIN book_categ c1
ON c1.book_id = b.id
AND c1.idCateg = 1
JOIN book_categ c2
ON c2.book_id = b.id
AND c2.idCateg = 2
This assumes that (book_id, idCateg) is constrained to be unique in the book_categ table. If it isn't unique, then this query can return duplicate rows. Adding a GROUP BY clause or the DISTINCT keyword will eliminate any generated duplicates.
There are several other queries that can get generate the same result.
For example, another approach to finding book_id that are in two categories is to get all the rows with idCateg values of 1 or 2, and then GROUP BY book_id and get a count of DISTINCT values...
SELECT b.*
FROM book b
JOIN ( SELECT d.book_id
FROM book_categ d
WHERE d.idCateg IN (1,2)
GROUP BY d.book_id
HAVING COUNT(DISTINCT d.idCateg) = 2
) c
ON c.book_id = b.id

Finding duplicate names where first name can be an initial or full name

I am trying to find duplicates by comparing the first name and surname columns in a table. The first name can be a name or an initial.
Reading other posts I have managed to figure out how to get the duplicate surnames and list the first letter for first name. But I am unsure how to only show rows where there is a match of surname and the first letter of the first name.
SELECT *
FROM table AS a
INNER JOIN (
SELECT LEFT( firstname, 1 ) , surname
FROM table
GROUP BY surname
HAVING COUNT( * ) > 1
) AS b ON a.surname = b.surname
id | firstname | surname
**************************
1 | joe | bloggs
2 | j | bloggs
3 | s | bloggs
4 | f | doe
5 | frank | spencer
Currently this query would return
1 | joe | bloggs
2 | j | bloggs
3 | s | bloggs
Result I would like would just contain the possible duplicates.
1 | joe | bloggs
2 | j | bloggs
I don't quite get what you want. Yor provided a query, your current table and the expected result.
I've just created your table, run your query and got the expected result. What is wrong with this?
SELECT FROM table1 AS a
INNER JOIN (
SELECT surname FROM table1
GROUP BY surname
HAVING COUNT(*) > 1
) AS b ON a.surname = b.surname
This effectively result in your expected result:
joe | bloggs
j | bloggs
Or am I missing something?
After re-reading... are you expecting to get only this?
j | bloggs
If that is the case, use this:
SELECT * FROM table1 AS a
INNER JOIN (
SELECT surname FROM table1
GROUP BY surname
HAVING COUNT(*) > 1
) AS b ON a.surname = b.surname
WHERE CHAR_LENGTH(firstname) = 1
Edit:
After the expected result was properly explained I conclude the query should be:
SELECT a.firstname, a.surname FROM t1 AS a
INNER JOIN (
SELECT LEFT(firstname, 1) AS firstChar, surname FROM t1
GROUP BY surname, firstChar
HAVING COUNT(surname) > 1
) AS b ON a.surname = b.surname AND b.firstChar = LEFT(a.firstname, 1)
Working example
You probably don't want to use initials all the time, e.g., if you always strip to initials you might consider Bob X the same as Bill X.
So you need to check three cases.
both firstnames are initials
both firstnames are non initials
only one firstname is an intial
So you can work with string methods of Mysql to check the length of either firstname and check the proper case.
I would join the table to itself like so:
select * into #temp from (
SELECT 1, 'joe', 'bloggs' UNION
SELECT 2, 'j', 'bloggs' UNION
SELECT 3, 'f', 'doe' UNION
SELECT 4, 'frank', 'spencer' UNION
SELECT 5, 'steven', 'woo' UNION
SELECT 6, 'steve', 'woo' UNION
SELECT 7, 'stanley', 'woo'
) x (id, firstname, surname)
select
*
from
#temp l
inner join
#temp r
on
left(l.firstname, 1) = left(r.firstname, 1)
and
l.surname = r.surname
where
l.id < r.id
drop table #temp
the downside to this is that the steven and stanley match. I would suggest you think about creating a firstname alias table and use that to standardize the firstnames.

MYSQL join results set wiped results during IN () in where clause?

Editted heavily!
The original question was based on a misunderstanding of how IN() treats a column from a results set from a join. I thought IN( some_join.some_column ) would treat a results column as a list and loop through each row in place. It turns out it only looks at the first row.
So, the adapted question: Is there anything in MySQL that can loop through a column of results from a join from a WHERE clause?
Here's the super-simplified code I'm working with, stripped down from a complex crm search function. The left join and general idea are relics from that query. So for this query, it has to be an exclusive search - finding people with ALL specified tags, not just any.
First the DB
Table 1: Person
+----+------+
| id | name |
+----+------+
| 1 | Bob |
| 2 | Jill |
+----+------+
Table 2: Tag
+-----------+--------+
| person_id | tag_id |
+-----------+--------+
| 1 | 1 |
| 1 | 2 |
| 2 | 2 |
| 2 | 3 |
+-----------+--------+
Nice and simple. So, naturally:
SELECT name, GROUP_CONCAT(tag.tag_id) FROM person LEFT JOIN tag ON person.id = tag.person_id GROUP BY name;
+------+--------------------------+
| name | GROUP_CONCAT(tag.tag_id) |
+------+--------------------------+
| Bob | 1,2 |
| Jill | 2,3 |
+------+--------------------------+
So far so good. So what I'm looking for is something that would find only Bob in the first case and only Jill in the second - without using HAVING COUNT(DISTINCT ...) because that doesn't work in the broader query (there's a seperate tags inheritance cache and a ton of other stuff).
Here's my original sample queries - based on the false idea that IN() would loop through all rows at once.
SELECT DISTINCT name FROM person LEFT JOIN tag ON person.id = tag.person_id
WHERE ( ( 1 IN (tag.tag_id) ) AND ( 2 IN (tag.tag_id) ) );
Empty set (0.00 sec)
SELECT DISTINCT name FROM person LEFT JOIN tag ON person.id = tag.person_id
WHERE ( ( 2 IN (tag.tag_id) ) AND ( 3 IN (tag.tag_id) ) );
Empty set (0.00 sec)
Here's my new latest failed attempt to give an idea of what I'm aiming for...
SELECT name, GROUP_CONCAT(tag.tag_id) FROM person LEFT JOIN tag ON person.id = tag.person_id
GROUP BY person.id HAVING ( ( 1 IN (GROUP_CONCAT(tag.tag_id) ) ) ) AND ( 2 IN (GROUP_CONCAT(tag.tag_id)) );
Empty set (0.00 sec)
So it seems it's taking a GROUP_CONCAT string, of either 1,2 or 2,3, and is treating it as a single entity rather than an expression list. Is there any way to turn a grouped column into an expression list that IN () or =ANY() will treat as a list?
Essentially, I'm trying to make IN() loop iteratively over something that resembles an array or a dynamic expression list, which contains all the rows of data that come from a join.
Think about what your code is doing logically:
( 1 IN (tag.tag_id) ) AND ( 2 IN (tag.tag_id) )
is equivalent to
( 1 = (tag.tag_id) ) AND (2 = (tag.tag_id) )
There's no way tag.tag_id can satisfy both conditions at the same time, so the AND is never true.
It looks like the OR version you cited in your question is the one you really want:
SELECT DISTINCT name FROM person LEFT JOIN tag ON person.id = tag.person_id
WHERE ( ( 1 IN (tag.tag_id) ) OR ( 2 IN (tag.tag_id) ) );
Using the IN clause more appropriately, you could write that as:
SELECT DISTINCT name FROM person LEFT JOIN tag ON person.id = tag.person_id
WHERE tag.tag_id in (1,2);
One final note, because you're referencing a column from the LEFT JOINed table in your WHERE clause (tag.tag_id), you're really forcing that to behave like an INNER JOIN. To truly get a LEFT JOIN, you'd need to move the criteria out of the WHERE and make it part of the JOIN conditions instead:
SELECT DISTINCT name FROM person LEFT JOIN tag ON person.id = tag.person_id
AND tag.tag_id in (1,2);
WHERE ( ( 1 IN (tag.tag_id) ) AND ( 2 IN (tag.tag_id) ) );
This will never return any results since tag.tag_id cannot be 1 and 2 at the same time.
Additionally is there a reason you're using 1 IN (blah) rather than blah = 1?