Mysql Query - Joins causing confusion for me in this query - mysql

I have a query that returns results related to items that match a specific category...
There are 3 mysql tables that results to this, items, categories and item_categories.
These i assume are self explanatory, but the latter, is a linking table that links any specific item to any specific category, using a match of id's.
The items table contains one row, with an id value of 1.
The categories table is filled with 15 rows, with id values of 1-15.
the item_categories table contains one row, the item_id value is 1 and the category_id value is 5.
This is the mysql query in its php form:
$catResultQuery = "
SELECT i.id, name, price
FROM items i
INNER JOIN item_categories
ON i.id = item_id
INNER JOIN categories c
ON category_id = c.id
WHERE MATCH (c.id)
AGAINST ('{$_SESSION['input']}' IN BOOLEAN MODE)
ORDER BY name
";
The session variable has a value of 5, but for some reason, this query displays a 0 result set.
Even when i run the query in php myadmin, it returns 0 rows.
And i am confused, because in my head, the logic behind all of this seems fairly simple, but for some reason i get 0? Does anyone have any idea where i have gone wrong with this?
Any advice and input would be greatly appreciated, thank you!!

Ok, I see now that you're building the SQL dynamically. If that's the case, then this should work:
SELECT i.id, name, price
FROM items i
INNER JOIN item_categories
ON i.id = item_id
INNER JOIN categories c
ON category_id = c.id
WHERE c.id
IN ('{$_SESSION['input']}')
ORDER BY name
Just make sure '{$_SESSION['input']}' is comma delimited and be aware that this carries the risk of SQL injection because you're constructing the SQL on the fly.

Related

How to Include/Exclude array of IDs from a relationship/pivot table and avoid duplicates?

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.

WHERE clause with INNER JOIN and Sub Query

I have achieved my desired query but I want to know how this one worked. I have multiple tables on my database and my requirements was to take the id from table called product and using this id, I want to retrieve some data from multiple tables and product id is a foreign key to the other tables. The query below works fine (by the way I was just experimenting and luckily got this query).
SELECT ponsfdp.*, product.pName, product.pImage, product.productSizes FROM product
INNER JOIN priceOnSizesForDigitalPrinting AS ponsfdp ON ponsfdp.pId_fk =
(SELECT pId FROM product WHERE pName LIKE "%booklet%")
WHERE pName LIKE "%booklet%";
But when I tried this query,
SELECT ponsfdp.*, product.pName, product.pImage, product.productSizes FROM product
INNER JOIN priceOnSizesForDigitalPrinting AS ponsfdp ON ponsfdp.pId_fk =
(SELECT pId FROM product WHERE pName LIKE "%booklet%");
It contains all the data even with null fields too. Can someone explain to me how it works? My personal opinion is both query should return same data because on the second query, I am using a subquery and it returns only one id, on the other hand, first query has a WHERE clause which generates the same id but by the help of name. How does the first query returns very specific columns and second return all columns even null columns too? I need an explanation for both queries.
Your first query also returning all rows as returned from your second query. But, when you are adding the last filter-
WHERE pName LIKE "%booklet%"
It's just keeping one single row from all rows where pName is like 'booklet'. You can consider the output from your second query as a single table and your logic working as below-
SELECT * FROM (
SELECT ponsfdp.*, product.pName, product.pImage, product.productSizes
FROM product
INNER JOIN priceOnSizesForDigitalPrinting AS ponsfdp
ON ponsfdp.pId_fk = (SELECT pId FROM product WHERE pName LIKE "%booklet%")
)A
WHERE pName LIKE "%booklet%"
Hope this will at least give you some insight of your query.
I don't see any need for a subquery here. You should be using the where condition to select rows from your FROM table, then use the ON clause of your join to find the right record(s) in your joined table for each row of the FROM table:
SELECT ponsfdp.*, product.pName, product.pImage, product.productSizes
FROM product
INNER JOIN priceOnSizesForDigitalPrinting AS ponsfdp
ON ponsfdp.pId_fk = pId
WHERE pName LIKE "%booklet%";

MySQL: Returning More than 1 Row When Referencing Through Several Tables

I am trying to list all of the stores that sell products that contain specific guitar parts for a guitar rig.
I have a guitarRig database. Guitar rigs have parts (I.e. amplifier, cabinet, microphone, guitarType, guitarStringType, patchCord, effectsPedal) which come from products, which are purchased from stores.
Products are purchased for a price as dictated by my PurchaseInformation table.
Here are my tables:
Table Part:
name
guitarRig references GuitarRig(name)
product references Product(name)
Table Product:
name
part references Part(name)
barcodeNumber
Table PurchaseInformation:
price
product references Product(name)
purchasedFrom references Store(name)
Table Store:
name
storeLocation
So far what I have is this:
SELECT p.name AS Part, prod.name AS Product, sto.name AS Store
FROM Part p, ProductInformation prod, Store sto, PurchaseInfo purch
WHERE sto.storeNumber = purch.storeNumber
AND purch.product = prod.name
AND prod.Part = p.name
AND p.name =
(
SELECT name
FROM Part
WHERE name LIKE '%shielded%'
)
GROUP BY p.name;
The error I get is that it returns more than 1 row, however, this is what I want! I want to list the stores that sell products that contain the part I am searching for.
The quick fix is to replace the equality comparison operator ( = ) with the IN operator.
AND p.name IN
(
SELECT name ...
I say that's the quick fix, because that will fix the error, but this isn't the most efficient way to write the query. And it's not clear your query is going return the result set you specified or actually expect.
I strongly recommend you avoid the old-school comma join operator, and use the JOIN keyword instead.
Re-structuring your query into an equivalent query yields this:
SELECT p.name AS Part
, prod.name AS Product
, sto.name AS Store
FROM Part p
JOIN ProductInformation prod
ON prod.Part = p.name
JOIN PurchaseInfo purch
ON purch.product = prod.name
JOIN Store sto
ON sto.storeNumber = purch.storeNumber
WHERE p.name IN
(
SELECT name
FROM Part
WHERE name LIKE '%shielded%'
)
GROUP BY p.name;
Some notes. The GROUP BY clause is going to collapse all of the joined rows into a single row for each distinct part name. That is, you are only going to get one row back for each part name.
It doesn't sound like that's what you want. I recommend you remove that GROUP BY, and add an ORDER BY, at least until you figure out what resultset you are getting, and if that's the rows you want to return.
Secondly, using the IN (subquery) isn't the most efficient approach. If p.name matches a value returned by that subquery, since p is a reference to the same Part table, this:
WHERE p.name IN
(
SELECT name
FROM Part
WHERE name LIKE '%shielded%'
)
is really just a more complicated way of saying this:
WHERE p.name LIKE '%shielded%'
I think you really want something more like this:
SELECT p.name AS Part
, prod.name AS Product
, sto.name AS Store
FROM Part p
JOIN ProductInformation prod
ON prod.Part = p.name
JOIN PurchaseInfo purch
ON purch.product = prod.name
JOIN Store sto
ON sto.storeNumber = purch.storeNumber
WHERE p.name LIKE '%shielded%'
ORDER BY p.name, prod.name, sto.name
That's going to return all rows from Part that include the string 'shielded' somewhere in the name.
We're going to match those rows to all rows in ProductInformation that match that part. (Note that with the inner join, if a Part doesn't have at least one matching row in ProductInformation, that row from Part will not be returned. It will only return rows that find at least one "matching" row in ProductionInformation.
Similarly, we join to matching rows in PurchaseInfo, and then to Store. Again, if there's not matching row from at least one Store, we won't get those rows back. This query is only going to return rows for Parts that are related to at least on Store. We won't get back any Part that's not in a Store.
The rows can be returned in any order, so to make the result set deterministic, we can add an ORDER BY clause. It's not required, it doesn't influence the rows returned, it only affects the sequence the rows that get returned.

Storing an array of id's in one db field VS breaking out into a joining table - what sql to use?

I have concluded that my first quick fix of storing an array of ids in a singular database field (1,5,48) is probably not best but if I break them out into a joining table of 'parent item id' (singular) and then sub related item id (multiple) which links to a separate table it would be better.
But now I am unsure what mysql query to use to get matches.
So a search form is submitted where "related_item" array is "1,5,8" and one of my "parent_items" has related item matches of "1" and "8" in the joining table....
So what mysql query would return these matches?
UPDATE:
I have one table 'companies' maybe HSBC and TOPSHOP as example records.
There is a separate table of 'industries' maybe 'banking' and 'retail'
There is a joining table which is company_id and industry_id which pairs them both together
So if someone submits a search form for where industry = 'banking' or 'retail' how would I return the company records for 'topshop' and 'hsbc'
Something like this... its unclear to me form you description if you are "searching" children or parents. I think you mean to search children so:
-- SEARCH CHILDREN --
SELECT p.*, c.* FROM child_table c
LEFT JOIN linking_table l ON (p.id = l.child_table_id)
LEFT JOIN parent_table p ON (l.parent_item_id = p.id)
WHERE c.id IN (1,5,4,8)
-- OPTIONAL AND CLAUSE for p.id = ? --
If you mean to search parents then you can just rework the order, although you could use the exact same query with the exception that your WHERE IN clause would be on p.id instead of c.id.
Be aware though that this will get you 1 row per match for the children so you will have the same parent multiple times. If you dont need the actual child data then you could use DISTINCT to only return one instance.

mysql if/else scenario

Ok, here's a fun one. I have 2 tables: tbl_notes, tbl_notes_categories
Simply, the tbl_notes has a categoryid, and I correlate the 2 tables with that ID. So, nothing too complicated.
I force users to choose a category, from a dropdown input, and stop them from submitting if they don't select something.
However, I want to change this, primarily for learning JOINs and how far I can go with them.
Sooooooo, I am not going to force a user to select a category, and instead, I will default the categoryid to zero, in the tbl_notes. (most users will select a category, but this is for other instances)
In the query, I am locked to showing only the notes that have a categoryid that exists in the tbl_notes_categories table. But, I would like to have a condition if the categoryid is not recognized OR is equal to zero, then specify another String. Like "--Unassigned--", or "--Category does not exist--"
Here's my original query:
SELECT n.notesubject, c.categoryname
FROM `tbl_notes` n, `tbl_notes_categories` c
WHERE n.categoryid = c.categoryid
This will not let me see the notes without a categoryid, so I pulled this one:
SELECT n.notesubject, c.categoryname
FROM `tbl_notes` n
LEFT JOIN `tbl_notes_categories` c ON n.categoryid = c.categoryid
And that helps, but I'm stuck at the 'condition' of displaying alternate text, in the case of a missing category record from the categories table.
In MySQL you can use IFNULL:
SELECT
n.notesubject,
IFNULL(c.categoryname, 'Unknown') AS categoryname
FROM tbl_notes AS n
LEFT JOIN tbl_notes_categories AS c
ON n.categoryid = c.categoryid
This will work if the category is not found, but it will also work if the category id is zero assuming that you don't have a matching row in your category table because then it will also not be found. If for some reason you do want a row in the categroy table with id zero then you can just set its name to 'Unknown'.
Note that IFNULL is MySQL specific. The function COALESCE will also work and is supported by more databases.
For IF/ELSE statements in general in MySQL can use IF or for a more general solution use a CASE expression: CASE WHEN condition THEN expr1 ELSE expr2 END.