find likewise data from two tables in mysql - php

i have two tables in my database one is A other one is B
A is having few fields in which three are id,name,group
B is having feilds like id,title,description, etc.
i have to search the id's of title and description that are having data similar to table A's name or group and then have to insert the id's in a field of table A.
For example,
if A is having 'Anna' in its name and 'girl' in its group then i have to search the title's and descriptions in table B that are containing this word 'Anna' or 'girl'.
I want to do this in one single query.
How can i do so?
Edit:
Iam explainng my tables here for a better understanding
table A
id name group matched_id
1 anna girl
2 sydney girl
3 max boy etc.
Table B
id title description
1 A good girl Anna is a very good girl
2 Max doesnt work hard Boys are always like that only
etc...
see i will first search for a match for 'anna' in the table B's title and description and if a match is found in either of them then i'll store that id in table A only in the field 'matched id'
I'll do the same procedure for 'girl' and then for 'sydney' and so on

This query should do the trick:
UPDATE a SET matched_id =
(SELECT b.id FROM b
WHERE b.title LIKE CONCAT(' % ',a.name,' % ')
OR b.description LIKE CONCAT('% ',a.name,' %')
OR b.title LIKE CONCAT('%',a.group,'%')
OR b.description LIKE CONCAT('%',a.group,'%')
LIMIT 1)
WHERE EXISTS
(SELECT a.id FROM b
WHERE b.title LIKE CONCAT('% ',a.name,' %')
OR b.description LIKE CONCAT('% ',a.name,' %')
OR b.title LIKE CONCAT('%',a.group,'%')
OR b.description LIKE CONCAT('%',a.group,'%')
LIMIT 1)
Some remarks to this:
LIMIT 1 was necessary, as each subquery can and will return more than 1 row
Not 100% sure if the order you want/need is used, you may need some further testing for that and use order by if needed
it may also be better to use an extra table for the groups (to reduce duplicate entries) and maybe one extra mapping table, so that you can map all results from B to A
EDIT:
if names need to match perfectly (unlike girl/girls), you can just add a space in front of the name: '% ',a.name,' %'. If it gets more complicated I would suggest using regular expressions (REGEX). I modified the query with the spaces (for names only), so feel free to try it again.

SELECT *
FROM A
JOIN B
ON b.title IN (a.name, a.group)
OR b.description IN (a.name, a.group)
WHERE a.name = 'Anna'
AND a.group = 'girl'
Since INDEX_UNION's are not very efficient in MySQL, it may be better to split the queries (especially if your tables are InnoDB):
SELECT *
FROM (
SELECT b.id
FROM A
JOIN B
ON b.title IN (a.name, a.group)
WHERE a.name = 'Anna'
AND a.group = 'girl'
UNION
SELECT b.id
FROM A
JOIN B
ON b.description IN (a.name, a.group)
WHERE a.name = 'Anna'
AND a.group = 'girl'
) bo
JOIN B
ON b.id = bo.id

Related

MySQL Join and create new column value

I have an instrument list and teachers instrument list.
I would like to get a full instrument list with id and name.
Then check the teachers_instrument table for their instruments and if a specific teacher has the instrument add NULL or 1 value in a new column.
I can then take this to loop over some instrument checkboxes in Codeigniter, it just seems to make more sense to pull the data as I need it from the DB but am struggling to write the query.
teaching_instrument_list
- id
- instrument_name
teachers_instruments
- id
- teacher_id
- teacher_instrument_id
SELECT
a.instrument,
a.id
FROM
teaching_instrument_list a
LEFT JOIN
(
SELECT teachers_instruments.teacher_instrument_id
FROM teachers_instruments
WHERE teacher_id = 170
) b ON a.id = b.teacher_instrument_id
my query would look like this:
instrument name id value
--------------- -- -----
woodwinds 1 if the teacher has this instrument, set 1
brass 2 0
strings 3 1
One possible approach:
SELECT i.instrument_name, COUNT(ti.teacher_id) AS used_by
FROM teaching_instrument_list AS i
LEFT JOIN teachers_instruments AS ti
ON ti.teacher_instrument_id = i.id
GROUP BY ti.teacher_instrument_id
ORDER BY i.id;
Here's SQL Fiddle (tables' naming is a bit different).
Explanation: with LEFT JOIN on instrument_id we'll get as many teacher_id values for each instrument as teachers using it are - or just a single NULL value, if none uses it. The next step is to use GROUP BY and COUNT() to, well, group the result set by instruments and count their users (excluding NULL-valued rows).
If what you want is to show all the instruments and some flag showing whether or now a teacher uses it, you need another LEFT JOIN:
SELECT i.instrument_name, NOT ISNULL(teacher_id) AS in_use
FROM teaching_instrument_list AS i
LEFT JOIN teachers_instruments AS ti
ON ti.teacher_instrument_id = i.id
AND ti.teacher_id = :teacher_id;
Demo.
Well this can be achieved like this
SELECT
id,
instrument_name,
if(ti.teacher_instrument_id IS NULL,0,1) as `Value`
from teaching_instrument_list as til
LEFT JOIN teachers_instruments as ti
on ti.teacher_instrument_id = til.id
Add a column and check for teacher_instrument_id. If found set Value to 1 else 0.

SELECT all rows with a given ID if at least one of those rows has a given value

What I have is a mySQL query that will select some drinks and return basic information about the drink, as well as a list of the ingredients of that particular drink, as well as the rating for the particular drink. I have 3 tables drinks, drinks_ratings, drinks_ing
So, my issue is that say I want to get information about drinks that contain vodka, and are in a highball glass I would run the query below...
It works, its just my issue is that it doesnt return ALL the ingredients back. For Example, if I return "randomDrinkName1" and it happened to have vodka and soda in it....when i get the information back it leaves out the soda because i said WHERE ing = voda, so I understand why this is happeneing...but Is there some other type of WHERE clause I can do to check if it has "vodka" and return it along with all the other ingredient information that might also be there?
I know I could just do a query before this query that gets be back ids that have vodka in them from my drinks_ing table.
But this seems like it could be bad idea ...like if there were 1000s of drinks with vodka in them just to do a query on a select with a 1000 OR statements.
I'm interested if there is a way i can easily do this all in one query. thanks!
select dIngs.id,
dIngs.name,
dIngs.descrip,
dIngs.type,
dIngs.ing,
AVG(b.rating) as arating,
COUNT(b.id) as tvotes
from (
select a.id,
a.name,
a.descrip,
a.type,
concat (
'[',
GROUP_CONCAT('{\"ing\":', c.ing, ',\"parts\":', c.parts, '}'),
']'
) ing
from drinks a
left join drinks_ing c on a.id = c.did
where c.ing = "vodka"
and a.type = "highball"
group by a.id
) dIngs
left join drinks_ratings b on dIngs.id = b.id
group by dIngs.id
order by arating desc,
tvotes desc LIMIT 0,
50;
edit:
to illustrate a result that I would want to get is like this:
[0]
descrip = "the description text will be here"
arating = 0
id = 4
ing = [ {"ing": "vodka", "parts": 4}, {"ing": "soda", "parts": 2}, {"ing": "sprite", "parts": 2} ]
name = "awesomeDrink"
type = "highball"
tvotes = 0
but what im actually getting back just includes the vodka ing because thats what i was checking for
[0]
descrip = "the description text will be here"
arating = 0
id = 4
ing = [ {"ing": "vodka", "parts": 4} ]
name = "awesomeDrink"
type = "highball"
tvotes = 0
To be clear, if i dont supply something like where ing = vodka, i get all the ingredients back just fine. thats not the issue....
I need it to just check if one of the potential ingredients happens to be vodka, then basically return all the ing data...and if vodka isn't a potential ingredient, ignore that drink and NOT return it.
edit:
what my tables look like..
drinks_ing
---------------
did (which is the drink id its associated with)
id (the id of the ingredient aka "vodka")
parts
drinks
---------------
id
name
description
type
timestamp
drinks_ratings
-----------------
id
userid
rating
timestamp
Your best approach may be to use a self join. This is where you reference the same table twice, but by different names. Roughly this would look like:
SELECT d.stuff
FROM drinks d
INNER JOIN drinks v on d.id = v.id
WHERE v.ingredient = "vodka"
Update: Making this better correspond to the tables in question. This is saying: given all of the vodka ingredients, find all of the ingredients that are found in the same drink as that vodka ingredient.
SELECT d.*
FROM drinks_ing d
INNER JOIN drinks_ing v on d.did = v.did
WHERE v.ing = "vodka"
Yes, you can do it in one query. Your inn
SELECT a.id, a.name, a.descrip, a.type, CONCAT('[', GROUP_CONCAT('{\"ing\":', c.ing, ',\"parts\":', c.parts, '}'), ']') ing
FROM drinks a LEFT JOIN drinks_ing c
ON a.id = c.did
WHERE a.id in (select distinct a.id
from drinks_ing c
ON a.id = c.did
where c.ing = "vodka"
)
This finds the drinks with the ingredient you want and returns information about the drinks.

SQL query between 2 tables

I have a database with 2 tables that I am working with (SugarCRM).
I am querying the table cases_audit to get a row count of cases with a status changed to closed. All this works great.
What I am having trouble with is figuring out how to take the id from cases_audit and ensure that under table cases that same id has a type = "support"
// Query cases_aduit to find out how many cases were closed -0 days ago
$query_date_1_closed = "select * from cases_audit where after_value_string = 'Closed' and date_created LIKE '$date_1 %'";
$rs_date_1_closed = mysql_query($query_date_1_closed);
$num_rows_1_closed = mysql_num_rows($rs_date_1_closed);
Assuming the column id in cases_audit referes to id in cases (which is not very likely), this query gives you every column from the audit plus type from the appropriate case:
SELECT
A.*, C.type
FROM cases_audit A
INNER JOIN cases C ON A.id=C.id
WHERE A.after_value_string = 'Closed' AND A.date_created LIKE '$date_1 %'
If you want to count the closed support cases, add C.type to your WHERE condition:
SELECT
COUNT(*)
FROM cases_audit A
INNER JOIN cases C ON A.id=C.id
WHERE A.after_value_string = 'Closed' AND A.date_created LIKE '$date_1 %' AND C.type = 'support'
cases_audit.parent_id is the field which relates to cases.id

Join tables with comma values

I have a hard nut to crack with joing 3 tables.
I have a newsletter_items, newsletter_fields and newsletter_mailgroups which I want to be joined to get a list of newsletters.
The newsletter_items contains the fields:
letter_id, letter_date, receivers, template, status
That can look like
1, 1234567899, 1,2 (comma separated), standard.html, 1
newsletter_fields contains the fields:
field_uid, field_name, field_content, field_letter_uid
That can look like
1, letter_headline, A great headline, 1
where field_letter_uid is the newsletter for which the field belongs to.
and newsletter_mailgroups contains the fields:
mailgroup_id, mailgroup_name, number_of_members
That can look like
1, Group1, 233
2, Group2, 124
3, Group3, 54
What I want is to combine these 3 tables to that I can get a list of all the newsletter like this:
Letter date | Letter headline | Receivers | Status
2008-01-01 12:00:00 | A great headline | Group1, Group 2 | 1
So in short I want my SQL query to join the 3 tables and in that process select the receivers from the mailgroup table and display them comma separated like Group1, Group 2
This what I got now
SELECT A.*, B.* FROM newsletter_items A, newsletter_fields B, WHERE B.field_letter_uid = A.letter_id AND field_name = 'letter_headline' AND A.template = '". $template ."';
But I can't seem to figure out how to get the mailgroups into that.
I recommend that you make your joins explicit.
It makes it easier to debug your query and to change inner with left joins.
There is absolutely never a good reason to use SQL '89 implicit join syntax.
SELECT ni.*
, nf.*
, group_concat(nm.mailgroup_name) as mailgroups
FROM newsletter_items ni
INNER JOIN newsletter_fields nf
ON (nf.field_letter_uid = ni.letter_id)
INNER JOIN newsletter_mailgroups nm
ON (find_in_set(nm.mailgroup_id, ni.receivers))
WHERE
nf.field_name = 'letter_headline'
ni.template = '". $template ."'
GROUP BY ni.letter_id;
Regarding your database design.
I recommend you normalize your database, that means that you move the comma separated fields into a different table.
So you make a table receivers
Receivers
----------
id integer auto_increment primary key
letter_id integer not null foreign key references newsletter_items(letter_id)
value integer not null
You then remove the field receiver from the table newsletter_items
Your query then changes into:
SELECT ni.*
, group_concat(r.value) as receivers
, nf.*
, group_concat(nm.mailgroup_name) as mailgroups
FROM newsletter_items ni
INNER JOIN newsletter_fields nf
ON (nf.field_letter_uid = ni.letter_id)
INNER JOIN newsletter_mailgroups nm
ON (find_in_set(nm.mailgroup_id, ni.receivers))
LEFT JOIN receiver r ON (r.letter_id = ni.letter_id)
WHERE
nf.field_name = 'letter_headline'
ni.template = '". $template ."'
GROUP BY ni.letter_id;
This change should also speed up your query significantly.
If it's allowed, why don't you create a new table called newsletter_item_receivers where you could store letter_id, receiver_id fields?
Having comma separated values in a field like this usually means you're missing a table :)
Edit:
By using CSV, you are making your life miserable when you want to retrieve an answer to "give me all newsletters that receiver_id=5 receives" :)
Here's a good answer to a similar question on SO: Comma separated values in a database field
Edit2:
If I understand your table relationships correctly then it would be something like this:
SELECT
a.letter_date,
b.receiver_id,
a.status
FROM newsletter_items_receivers b
LEFT OUTER JOIN newsletter_items a ON (a.letter_id = b.letter_id)
LEFT OUTER JOIN newsletter_mailgroups m ON (m.mailgroup_id = b.receiver_id)
NOTE! This query WILL NOT return a newsletter when there are no receivers of that newsletter.
If you need that functionality you can try something like this:
SELECT
x.letter_date,
y.mailgroup_name,
x.status
FROM (
SELECT
a.letter_date,
b.receiver_id,
a.status
FROM newsletter_items a
LEFT OUTER JOIN newsletter_items_rec b ON (b.letter_id = a.letter_id)) x
LEFT OUTER JOIN newsletter_mailgroups y ON (y.mailgroup_id = x.receiver_id)
I don't have access to SQL right now so I might have made some syntax errors (hopefully not logical ones :)).
As for why we are doing it like this, as #Konerak pointed out, you'd be well advised to read up on database normalization and why it's important.
You can start with this article from about.com, just glanced over it seems an OK read
http://databases.about.com/od/specificproducts/a/normalization.htm
Also, it would be good if you'd keep fields names the same across multiple tables.
For example you have letter_id in newsletter_items, but you have field_letter_uid in newsletter_fields. Just a thought :)
Try to use
SELECT A.*, B.*, group_concat(C.mailgroup_name SEPARATOR ',')
FROM newsletter_items A, newsletter_fields B, newsletter_mailgroups C
WHERE B.field_letter_uid = A.letter_id
AND field_name = 'letter_headline'
AND A.template = '". $template ."'
and find_in_set(c.mailgroup_id, A.receivers)
group by A.letter_id;

SELECT * FROM table WHERE field IN (SELECT id FROM table ORDER BY field2)

I have 4 tables:
categories - id, position
subcategories - id, categories_id, position
sub_subcategories - id, subcategories_id, position
product - id, sub_subcategories_id, prod_pos
Now I'm doing tests to find out what's wrong with my query.
So i want to select sub_subcategories, and to get someting like that:
[[1,2,3,4,5,6], [1,2,3,4,5,6,7]], [[1,2,3,4,5,6], [1,2,3,4]]
Each [] means: big - categories, small - subcategory, and the numbers are position in sub_subcategories. I want the [] to order by their "position" field, so query:
SELECT id FROM sub_subcategories_id
WHERE subcategories_id IN (
SELECT id
FROM subcategories_id
WHERE categories_id IN (
SELECT id FROM categories
WHERE id = 'X' ORDER BY position)
ORDER BY position)
ORDER BY position
is somehow wrong, because I get:
1,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,6,6,6,7
Dunno why - does last "ORDER BY position" destroy everything?
You need to apply all of your desired ordering in the outermost query - ORDERing within subqueries doesn't make any sense - the question "is this ID in <this list>?" has the same answer, no matter what order the list is in (indeed, more property, <this list> is a set, which has no order).
So you'll need to get all of the columns you need to order by in your outermost query.
Something like:
SELECT ssi.ID
from
sub_subcategories_id ssi
inner join
subcategories_id si
on
ssi.subcategories_id = si.id
inner join
categories c
on
si.categories_id = c.id
where
c.id = 'X'
order by
c.position,
si.position,
ssi.position
As it stands now, your query would never return a 'set' of numbers as is. If you ignore all the subselects, you're essentially doing:
SELECT id FROM sub_subcategories_id
ORDER BY position
which would only return one column: the sub_sub_categories_id. You'd be better off doing something like:
SELECT cat.id, subcat.id, subsubcat.id
FROM sub_sub_categories AS subsubcat
LEFT JOIN sub_categories AS subcat ON subcat.id = subsubcat.subcategories.id
LEFT JOIN categories AS cat ON cat.id = subcat.category_id
WHERE (cat.id = 'X')
ORDER BY cat.id, subcat.id, subsubcat.id
That'll return 3 columns ordered by the various IDs. If you don't need the individual sub_sub_categories values, and just want them as a single string value, you can mess around with GROUP_CONCAT() and do various bits of grouping:
SELECT cat.id, subcat.id, GROUP_CONCAT(subsubcat.id)
FROM ...
...
WHERE (cat.id = 'X')
GROUP BY cat.id, subcat.id, subsubcat.id
ORDER BY ...

Categories