I've got three tables.
Table 1 is packages.
Table 2 is package_to_keyword.
Table 3 is keywords.
Packages can be connected to multiple keywords. So if I query package_to_keyword Joining keywords and packages I can search for all packages that relate to a certain keyword. That's the part I understand.
NOW... my question is, how do I retrieve packages that match a LIST of keywords? Right now, in php, I loop a sql statement and then loop through all the results for each keyword and do an array_intersect() to filter down to the packages that show up in all the result sets. However, I'm sure this is bad, because I'm running a query for each keyword when I'm certain SQL is built to handle this type of relationship, I'm just not sure what type of query to perform.
The key is the list of keywords can be any number of words. If I use something like IN ('keyword','array','returns','all','results') I just get a list for all the packages that have a relationship with ANY of the keywords when I just want packages that have a relationship with ALL of the keywords.
Thoughts?
select title
from packages p
inner join pack_to_tag pt on p.index = pt.pIndex
inner join keyworks w on w.index = pt.kindex
where word in ('keyword','array','returns','all','results')
group by title
having count(*) = 5
First, the "PreQuery" (qualified products result) looks only the products joined to keywords that have ANY of the keywords you are looking for, and will ultimately return 1 or more entries. The GROUP BY then confirms however many you ARE EXPECTING... Then join to products for final results.
select
p.*
from
( select ptt.pIndex
from pack_to_tag ptt
join keywords k
on ptt.kindex = k.index
and k.word in ( 'word1', 'word2', 'word3' )
group by
ptt.pIndex
having
count(*) = 3 ) QualifiedProducts
join Products p
on QualifiedProducts.pIndex = p.index
Related
Let's say you have
records table with id and name
tags table with id and name
records_tags with record_id and tags_id (relationship table)
Now you want to run a query to include records that have X tags and exclude records that have X tags.
You could do INNER JOIN, but the challenge here is, when there are many tags to a record, it creates duplicates within the results.
Example:
inner join `records_tags` on `records_tags`.`record_id` = `records`.`id`
and `records_tags`.`tag_id` in (?) and `records_tags`.`tag_id` not in (?)
As for the Laravel side, Ive used:
$records->join('records_tags', function ($join) use($include, $exclude) {
$join->on('records_tags.record_id','=','records.id');
if ($include) $join->whereIn('records_tags.tag_id',$include);
if ($exclude) $join->whereNotIn('records_tags.tag_id',$exclude);
});
Could there be a better solution to handle this or a way to ask for it to create unique or distinct rows, the goal of the join is only to include or exclude the actual records themselves from the results?
Edit:
The only other thing I can think of is doing something like this, still have to run tests to see accuracy, but for a crude solution
Edit 2: This doesn't appear to work on NOT IN as it creates duplicates.
$records->join(\DB::raw('(SELECT tag_id, record_id FROM records_tags WHERE records_tags.tag_id IN ('.implode(',',$include).'))'),'records_tags.record_id','=','records.id');
The conditions in the ON clause:
... and `records_tags`.`tag_id` in (?) and `records_tags`.`tag_id` not in (?)
do not exclude from the results the ids of the records that you want to exclude.
Any id that is linked to any of the wanted tags will be returned even if it is also linked to an unwanted tag, because the joins return 1 row for each of the linked tags.
What you can use is aggregation and the conditions in the HAVING clause:
SELECT r.id, r.name
FROM records r INNER JOIN records_tags rt
ON rt.record_id = r.id
GROUP BY r.id -- I assume that id is the primary key of records
HAVING SUM(rt.tag_id IN (?)) > 0
AND SUM(rt.tag_id IN (?)) = 0;
or, if you want the ids that are linked to all the wanted tags, use GROUP_CONCAT():
SELECT r.id, r.name
FROM records r INNER JOIN records_tags rt
ON rt.record_id = r.id
GROUP BY r.id
HAVING GROUP_CONCAT(rt.tag_id ORDER BY rt.tag_id) = ?;
In this case you will have to provide for the wanted tags ? placeholder a sorted comma separated list of ids.
I’ve got quite a few tables with product information. The columns on each table that I’m pulling from in this particular query have the exact same column names. I’ve been attempting to do it via a UNION ALL but for some reason it is throwing an error saying non-object but all the column names are correct.
I’m using a format that I found online. But obviously something is wrong. There are more tables; however, this is how it starts (with 2). I’d prefer not to have to code each select statement in the union with unique table abbreviations if I don’t have to.
I don’t have to use union if there is a better method.
All tables share data on Product_Categories and Product_Sub_Category.
The only thing unique to each table is id and part_number.
SELECT f.id,f.part_number,f.cat,f.subcat,f.table_name FROM
(
SELECT t.id,t.part_number,psc.name as subcat,c.name as cat, c.table_name FROM Steel_Strapping as t JOIN Product_Sub_Category as psc ON t.subcat = psc.id JOIN Product_Categories as c ON psc.category = c.id ORDER BY c.sort_order,psc.sort_order,t.sort_order
UNION ALL
SELECT t.id,t.part_number,psc.name as subcat,c.name as cat, c.table_name FROM Product as t JOIN Product_Sub_Category as psc ON t.subcat = psc.id JOIN Product_Categories as c ON psc.category = c.id ORDER BY c.sort_order,psc.sort_order,t.sort_order
) f
My end result is one full list of all products sharing column names. Ex: $result[‘part_number’] will pull part numbers from ALL tables listed in union.
I found the solution when playing around with code. I had to add parenthesis (select...) UNION JOIN (select...) inside the parent select statement
I have 3 tables,Products, Languages and Products_translation.
How can I retrieve one Product and all translation at same time in one query ?
I have this
SELECT p.*, pt.description FROM Products p
LEFT JOIN Products_translation pt ON p.id=pt.product_id
LEFT JOIN Languages l ON pt.language =l.code
I have 3 languages, but it only retrieves one field name 'description' and I wanted it to return 3 (the number of languages), with something like description_en, description_es, description_fr
It's possible to make something like pt.description AS 'description'+'l.code' ?
This is a very common question and I'm fairly sure it's been answered many times (for example here: Mysql: joining tables for translation records). Anyway, if you have only 3 languages, just do this:
SELECT p.*, pt_en.description as description_en, pt_es.description as description_es, pt_fr.description as description_fr FROM Products p
LEFT JOIN Products_translation pt_en ON (p.id=pt.product_id and pt.language = 'en')
LEFT JOIN Products_translation pt_es ON (p.id=pt.product_id and pt.language = 'es')
LEFT JOIN Products_translation pt_fr ON (p.id=pt.product_id and pt.language = 'fr')
If you have more than 3, or a varying number, search pivoting or pivot table for more info. It's not easy to do in SQL so usually it's faster to just select your products, select all the translations of those products in a separate query, and construct the result you want outside of the database.
I am pretty new to sql and searching the forum did not give me the right answer, so I place a new one. I have three tables:
Products
product_id
product_description
Keywords
keyword_id
Keyword_description
And a reference table keywords_to_products:
reference_id
keyword_id-> relates to keywords table
product_id -> relates to products table
I am looking for two queries :
One that gives all products matching one OR more specific keywords (I think I already have this)
ie: all products having the keywords 'bolt' OR 'screw'
One that gives all products matching multiple keywords (AND) this is the one I don't get...
ie: all products having the keywords 'bolt' AND 'nut'
Although I think it is not the most exciting thing to do, I just cannot figure it out.
Besides, I am wondering if I do the right thing anyway.
I am writing an application having appr. 1.000.000 different products in it with an article number and a description. I add important words from the description to the keyword database and also add additional keywords like EAN number or equivalent productnumber which are not in the product table.
These are all connected through the keyword-to-product table.
Every keyword, even EAN numbers for example, can refer to multiple products.
The idea is searching through the keyword_to_product_table only instead of searching through multiple fields in the products table. Good idea ??
Thanks in advance for your replies..
Regards, Arno Verkley
Is this what you want?
select distinct p.Id, p.Description
from Product p
join ProductKeyword pk
on p.id = pk.ProductId
join Keyword k
on k.id = pk.KeyWordId
and k.description in ("Red", "Yellow")
http://sqlfiddle.com/#!2/b6053/3
Searching directly in your main table, say in the description column, you would have a where looking like this
WHERE
description like "%bolt%"
and description like "%nut%"
Now using a keyword table (product_id, keyword), I can suggest
SELECT
DISTINCT(p.*)
FROM Product p
JOIN ProductKeyword pk1
ON p.id = pk1.productId
JOIN ProductKeyword pk2
ON p.id = pk2.productId
WHERE
pk1.keyword = "bolt"
AND
pk2.keyword = "nut"
And yes, a keyword_to_product table sounds a good idea. You can use an index on keyword, that will keep your queries fast (when the 'like' operator will slow down a lot queries on a big table).
First of all, thanks a lot for the answers !
A little bit of both answers seem to do the trick (on the fiddle; by the way thanks for this very usefull trial and error tool, although it is not very fast ;o( )
SELECT distinct p.id, p.descriptionFROM Product p
JOIN ProductKeyword pk1
ON p.id = pk1.ProductId
join Keyword k1
on k1.id = pk1.KeyWordId
JOIN ProductKeyword pk2
ON p.id = pk2.ProductId
join Keyword k2
on k2.id = pk2.KeyWordId
WHERE
k1.description = "Yellow"
AND
k2.description = "Red"
With indexes on the id's and keyword.description will this be the fastest method ?? And what if I will be using more then two keywords ?? just add more joins and where's ??
Regards, Arno
I've searched a bit but haven't found exactly what I'm looking for, so far. Basically I have a MySQL database with a couple tables (keywords, company and link tables). I want to be able to supply an array of keywords and find the associated companies. When I run the second query without the WHERE IN clause, it works. When I supply an array, it doesn't.
select keywords.idkeywords into #keyId
from keywords where
keywords.keyword IN ('Entertainment', 'Public');
select distinct company.company_name
from keywords, keyword_to_company, company
where keyword_to_company.keywordId = #keyId
and keyword_to_company.compId = company.idcompany;
Your query just doesn't make sense. First, you are trying to put multiple values in #keyid, but you can't do that. And, MySQL doesn't have the concept of table variables. You could use a temporary table.
Then the second query is worse. Does this query work for you?
select distinct c.company_name
from keywords k natural join
keyword_to_company k2c natural join
company c
where k.keyword IN ('Entertainment', 'Public') and
k2c.compId = company.idcompany;
I'm only using natural join because you don't specify the join keys. In general, you should always specify the columns being joined, using either on or using.
Thanks...
Your query didn't work..
This query does work however. Althought #keyId returns multiple rows, the query succeeds and results in a listing of associated companies. i agree that it shouldn't work, but it does.
select keywords.idkeywords into #keyId from keywords where
keywords.keyword = 'Entertainment'
and
keywords.keyword = 'Public';
select distinct company.company_name from keywords, keyword_to_company, company
where
keyword_to_company.keywordId = #keyId
and
keyword_to_company.compId = company.idcompany;