MySQL get data from three tables using one id - mysql

I have three different tables, which have following structure:
Food
ID | title
---+----------
1 | sandwich
2 | spaghetti
Ingridients
ID | food_reference | type | location | bought
----+----------------+------+----------+----------
100 | 1 | ham | storeA | 11-1-2013
101 | 1 | jam | storeB | 11-1-2013
102 | 2 | tuna | storeB | 11-6-2013
Tags
ID | food_reference | tag
----+----------------+-----
1000| 1 | Tag
1001| 1 | Tag2
1002| 2 | fish
and using one select I want to get all information from these three tables (title,type,location,bought,tag) for one specific ID.
I have tried something like
SELECT food.*,ingridients.*,tags.* FROM food
JOIN ingridients
ON :id=ingridients.food_reference
JOIN tags
ON :id=tags.food_reference
WHERE id=:id
BUT this query returns for id=1 only one row from ingridients and tags even though there are two matching rows (ham and jam, Tag and Tag2). Could you tell me what am I doing wrong?
EDIT: I tried LolCoder's solution, but I still got only one result, even though in fiddle it seems to work. However I tried :
SELECT F.*, group_concat(I.type), group_concat(I.location),
group_concat(I.bought), group_concat(T.tag)
FROM feeds F
INNER JOIN ingridients I
ON :id = I.food_reference
INNER JOIN tags T
ON :id=T.food_reference
WHERE F.id=:id
This finds data from ALL matching rows, but several times, i.e. I get (for id=1)
sandwich,ham,ham,ham,jam,jam,jam,tag,tag,tag,tag2,tag2,tag2
EDIT2: magic happened and LolCoder's solution works, so thank you :-)

Try with this query:
SELECT food.*,ingridients.*,tags.* FROM food
JOIN ingridients
ON food.id=ingridients.food_reference
JOIN tags
ON food.id=tags.food_reference
WHERE food.id=1
Check SQLFIDDLE

use alias with joins as.column name

Related

Removing Records with String Contained in Other Records using 3 tables and Joins

I previously got a great answer (thank you #Paul Spiegel) on removing records from a table whose string was contained at the end of another record. For example, removing 'Farm' when 'Animal Farm' existed) and grouped by a Client Field.
The problem is, in fact, a little more complex and spans three tables, I'd hoped I could extend the logic easily but it turns out to also be challenging (for me). Instead of one table with Client and Term, I have three tables:
Terms
Clients
Look-up-Table (LUT) where I store pairs of TermID and ClientID
I have made some progress since initially posting this question so where I stand is I made the Joins and resultant Select return the fields I want to delete from the Look-up-Table (LUT):
http://sqlfiddle.com/#!9/479c72/45
The final select being:
Select Distinct(C.Title),T2.Term From LUT L
Inner Join Terms T
On L.TermID=T.ID
Inner Join Terms T2
On T.Term Like Concat('% ', T2.Term)
Inner Join Clients C
On C.ID=L.ClientID;
I am in the process of trying to turn this into a Delete with little success.
Append this to your query:
Inner Join LUT L2
On L2.ClientID = L.ClientID
And L2.TermID = T2.ID
That will ensure, that the clients do match and you will get the following result:
| ClientID | TermID | ID | Term | ID | Term | ID | Title | ClientID | TermID |
|----------|--------|----|---------------|----|-----------|----|-------|----------|--------|
| 1 | 2 | 2 | Small Dog | 1 | Dog | 1 | Bob | 1 | 1 |
| 2 | 5 | 5 | Big Black Dog | 3 | Black Dog | 2 | Alice | 2 | 3 |
To delete the corresponding rows from the LUT table, replace Select * with Delete L2.
But deleting the terms is more tricky. Since it's a many-to-many relation, the term may belong to multiple clients. So you can't just delete them. You will need to cleanup up the table in a second statement. That can be done with the following statement:
Delete T
From Terms T
Left Join LUT L
On L.TermID = T.ID
Where L.TermID Is Null
Demo: http://sqlfiddle.com/#!9/b17659/1
Note that in this case the term Medium Dog will also be deleted, since it doesn't belong to any client.

return multiple rows in subquery mysql

i am new in mysql and stuck on a query.i want all the records of one table and multiple tags from other table that match with one column of first table.
select A.*,(select B.tag from crm_tags B where tag_id in (A.tags)) from crm_stores A;
there are multiple stores in crm_stores and each store has multiple tags in crm_tags. i want all details of store and each tag of store in one query.
when i tried the above query, it generates error : subquery returns multiple rows. please help, how to solve that problem
The tags in crm_stores are like "2098,2063",means multiple tags are comma seprated. While crm_tags has seprated entries
Thanks in advance
this is my first table crm_stores
+----------+-------+--------+-----------+----------+-
| store_id | guest | budget | tags | discount |
+----------+-------+--------+-----------+----------+-
| 23 | 5 | 1000 | 2098,2063 | 50% |
+----------+-------+--------+-----------+----------+-
this is my second table crm_tags
+--------+--------------+
| tag_id | tag |
+--------+--------------+
| 2063 | Chinese |
| 2098 | North Indian |
+--------+--------------+
use a join:
SELECT A.*, B.tag
FROM crm_stores A
LEFT JOIN crm_tags B ON A.tags LIKE concat(concat('%',B.tag_id),'%')
new mysql fiddle:
http://sqlfiddle.com/#!9/dedeb1/7/0

MySQL IN() Operator not working

How to use IN() Operator not working it's.
Those table are example and look the same as the real database I have.I don't have the permitting to add tables or change
Those are the tables:
students
+------+------+
| id | name |
+------+------+
| 1 | ali |
| 2 | man |
| 3 | sos |
+------+------+
Classes
+------+---------+
| c_id | students|
+------+---------+
| 1 | 1,2,3,4 |
| 2 | 88,33,55|
| 3 | 45,23,72|
+------+---------+
When I use this query it return me only the student with id =1
because "id IN (students)" return 1 when the first value are equal.
select name,c_id from students,classes where id IN (students);
when I get the list out on PHP than add it. it work fine.But, this solution need a loop and cost many queries.
select name,c_id from students,classes where id IN (1,2,3,4);
FIND_IN_SET()
the same happened, it's only return 1 but if the value on other position it return 0.
The IN operator works just fine, where it's applicable for what it does.
First, consider restructuring your data to be normalized, and avoid storing values as comma separated lists.
Second, if you absolutely have to deal with columns containing comma separated lists of values, MySQL provides the FIND_IN_SET() function.
FOLLOWUP
Ditch the old-school comma syntax for the join operation, and use the JOIN keyword instead. And relocate the join predicates from the WHERE clause to the ON clause. Fully qualify column references, eg.
SELECT s.name
, c.c_id
FROM students s
JOIN classes c
ON FIND_IN_SET(s.student_id,c.students)
ORDER BY s.name, c.c_id
To reiterate, storing a "comma separated list" in a column is an anti-pattern; it flies against relational theory and normalization, and disregards the best practices around relational databases. O
One might argue for improved performance, but this pattern doesn't improve performance; rather it adds unnecessary complexity in query and DML operations.
You need three tables.
One table students, one table classes, and then one table, say, students_to_classes containing something like
c_id | student_id
1 | 1
1 | 2
1 | 3
1 | 4
2 | 88
and so on.
Then you can query
select c_id from students_to_classes where student_id in (1,2,3,4)
Google "n:m relationship" for background on this.
EDIT
I know you're not specifically asking for another table structure, but this is a way of having a data type (a single number) that works with IN. Please believe me that this is the right way to do it, the reason you run into trouble with something as simple as IN is that you're using a non-standard approach, which, for such a standard problem, is typically not a good idea.
That's not how the function IN is supposed to work. You use IN when you have a list of possible matches like:
instead of:
WHERE id=1 or id=2 or id=3 or id=4
you use:
WHERE id IN (1,2,3,4)
Anyhow, your logic is not correct. The relation of Class and Student is Many-to-Many, thus a third table is needed. Let's call it studend_class, where you can store the students of each class.
student
+------+------+
| id | name |
+------+------+
| 1 | ali |
| 2 | man |
| 3 | sos |
+------+------+
class
+------+---------+
| id | name |
+------+---------+
| 1 | math |
| 2 | english |
| 3 | science |
+------+---------+
student_class
+------------+-------------+
| class_id | student_id |
+------------+-------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 3 | 3 |
+--------------+-----------+
In the example above all students are in math class and ali is also in science class.
Finally, if you whant to know which students are in what class, let's say Math, you can use:
SELECT s.id, s.name, c.name
FROM student s
INNER JOIN student_class sc ON sc.student_id=s.id
INNER JOIN class c ON sc.class_id = c.id
WHERE c.name="math";

Multi-conditional join through a link table

Bear with me, this needs a lot of up front info to explain what I am trying to do. I have tried to genericize it as much as possible to make things clearer. In a single query I am hoping to pull out a list of pages which match against tags linked in another table, and these tags are in groups. I am hoping to use the textual representation of the item instead of it's id, but if nothing else I could do 2 up front queries to get the tag_id and taggroup_id - just hoping not to have to do that.
DB Schema:
+-----------------------------------+
| taggroups |
+------------------+----------------+
| taggroup_id | group_name |
+------------------+----------------+
| 1 | fruits |
+------------------+----------------+
+-----------------------------------------------+
| tags |
+-------------+-----------------+---------------+
| tag_id | taggroup_id | tag_name |
+-------------+-----------------+---------------+
| 1 | 1 | apple |
| 2 | 1 | orange |
| 3 | 1 | grape |
+-------------+-----------------+---------------+
+--------------------------------------+
| pages |
+------------------+-------------------+
| page_id | title |
+------------------+-------------------+
| 99 | Doctor a day |
+------------------+-------------------+
+--------------------------------------------------+
| tags_to_pages |
+------------+----------+---------------+----------+
| join_id | tag_id | taggroup_id | page_id |
+------------+----------+---------------+----------+
| 1 | 1 | 1 | 99 |
| 2 | 2 | 1 | 99 |
+------------+----------+---------------+----------+
Test Query:
Got this far and can't seem to get it to work.
SELECT
pages.*, tags.tag_name, taggroups.group_name
FROM
tags_to_pages
INNER JOIN taggroups as grp ON (
grp.group_name = 'fruits'
AND
tags_to_pages.taggroup_id = grp.taggroup_id
)
INNER JOIN tags as val ON (val.tag_name = 'apple' AND tags_to_pages.tag_id = val.tag_id)
LEFT JOIN pages ON (tags_to_pages.page_id = pages.page_id)
Additionally, what tables should have indexes and what should the indexes be for be optimization?
I'd do it this way:
SELECT
pages.*, tags.tag_name, taggroups.group_name
FROM
tags_to_pages
JOIN taggroups AS grp ON grp.taggroup_id = tags_to_pages.taggroup_id
JOIN tags AS val ON val.taggroup_id = grp.taggroup_id
JOIN pages ON tags_to_pages.page_id = pages.page_id
WHERE
grp.group_name='fruits'
AND val.tag_name = 'apple'
This isn't that different to what you have, but I'm putting the join criteria in the JOIN clauses and the selection criteria in the WHERE clauses, which seems tidier to me.
While re-typing this query I spotted that you were using a tag_id somewhere I thought should have been a taggroup_id, so I changed it, but I regret I can't see it again now.
I'd also worry about the selection criteria - what if an apple doesn't happed to be a fruit? Obviously it is in this case, and indeed in reality :-), but I think you should only be specifying the fruit name in your query, not fruit and group names, and leave the database to sort it out for itself.
Also, why use an INNER JOIN for tags, tags_to_pages and taggroups and an OUTER JOIN for pages? Surely if there are no pages, you're better of getting no rows returned rather than one row, half full on NULLS?
I'd index the id columns only.
Just my 2p worth, really.
EDIT
I have set up a demo of this on www.sqlfiddle.com. Your original query worked fine once I changed the aliases in the SELECT list. My attempt above didn't work terribly well :-(. I had the same problems with the aliases and once I fixed them the query returned the same row twice.
I re-wrote it again from scratch as
SELECT pages.*, tags.tag_name, taggroups.group_name
FROM pages
JOIN tags_to_pages AS ttp ON ttp.page_id = pages.page_id
JOIN tags ON tags.tag_id = ttp.tag_id
JOIN taggroups ON taggroups.taggroup_id = ttp.taggroup_id
WHERE taggroups.group_name = 'fruits' AND tags.tag_name='apple';
and it works fine link.
Setting up this demo, it struck me to wonder why you were saving the taggroup_id in the tags_to_pages table. I'm "self-taught" in SQL and databases (translate that to: "I make it up as I go along, rely on doing things that 'work' and trust to intuition to find out what's 'right'") but doesn't this break the idea of normalisation? Shouldn't the connection between tags and taggroups be defined only via the taggroup_id column in the tags table? Perhaps someone who really understands databases will come along and put me right.
Finally, I've no idea why PHPMyAdmin just hung up when you tried your query. Good luck!

mysql logic for results spanning multiple rows

I have a product search query which returns rows similar to this:
| id | name | search tag |
| 1 | cat | furry |
| 1 | cat | ginger |
| 2 | dog | furry |
What I need to do is be able to get the single row that matches more than one search tag - for this example, a search for furry AND ginger returns no rows because there is only one search tag per row (this is because to get these results I'm using an INNER JOIN).
My question is, what do I need to do to be able to test for furry AND ginger in the query and return the cat but not the dog?
I'm doing this in PHP.
UPDATE
In order to cope with users putting in duplicate tags, a simple fix to the HAVING condition will return rows that have duplicate tags rather than ignoring them:
mysql> select id, name, group_concat(tag) as tags,
count(tag) as cnt from animals where tag in ('furry', 'ginger')
group by id HAVING cnt>=2;
You could first select the rows that have the desired tags, then group them by animal ID. Count how many results you get per animal and if it's the same as the number of tags you wanted you got a match.
mysql> select id, name, group_concat(tag) as tags,
count(tag) as cnt from animals where tag in ('furry', 'ginger')
group by id HAVING cnt=2;
+----+------+--------------+-----+
| id | name | tags | cnt |
+----+------+--------------+-----+
| 1 | cat | furry,ginger | 2 |
+----+------+--------------+-----+