Multiple Attributes from one Query - mysql

I am having a problem getting multiple attributes for one item. Below is my Table:
Table: product_attrib
id | product_id | name | value
------------------------------
0 | 33 | age | 25
1 | 33 | size | 25
My problem is when I join the query, I only get one of the attributes with such a query:
Query:
SELECT
p.*
,pa.name
,pa.value
FROM product AS p
LEFT OUTER JOIN product_attrib AS pa ON (
p.id = pa.product_id
)
My Results
"products_id":"0",
"products_price":"0.0000",
"products_name":null,
"products_description":null,
"attrib_name":"color",
"attrib_value":"red"
Do you see how I only get one attribute set?
Is there a way I can get all the attributes for a product?

Most likely, your original query is right as it is. You probably want the product, no matter if attributes can be found.
You can reverse the order of the tables in the JOIN to prevent losing rows from product_attrib like this (if product with product_id 33 does not exist):
SELECT
p.*
,pa.name
,pa.value
FROM product_attrib AS pa
LEFT JOIN product AS p ON p.id = pa.product_id
But that's probably not what you want.
A LEFT [OUTER] JOIN includes all rows from the left hand table and adds values from the right table where the JOIN condition can be fulfilled (potentially creating multiple rows if multiple matches are found in the right hand table.) If no matching row can be found in the right hand table NULL values are substituted for all columns of the right hand table.
Start by reading the manual here.
If you want "all attributes" per product in the same row, you need to aggregate values. Something like this:
SELECT p.*
,group_concat(pa.name) AS att_names
,group_concat(pa.value) AS att_values
FROM product AS p
LEFT JOIN product_attrib AS pa ON p.id = pa.product_id
WHERE p.product_id = 33
GROUP BY p.*;

i see so many people writing full joins when they aren't necessary. please correct me if i'm wrong.
SELECT
p.*
,pa.name
,pa.value
FROM product AS p, product_attrib AS pa
WHERE p.id = pa.product_id

Related

Filtering on a LEFT JOINED column

Is there a more efficient way to filter on a joined table as in the following example? Or is this a fine approach? This query returns the desired results, but I am an amateur at MySQL.
I have indexes on products.id, product_details.product_id and product_details.value
SELECT p.id
FROM products p
LEFT
JOIN product_details d
ON d.product_id = p.id
WHERE d.value = 1
OR p.id = 4
Simplified structure as follows:
products table
product_id (PRIMARY KEY) | name
--------------------------------
1 | Shirt
2 | Shoes
3 | Dress
4 | A product with no corresponding details row
product_details table
product_id (PRIMARY KEY) | value
---------------------------------
1 | 1
2 | 23
3 | 32
This is your query:
SELECT products.id
FROM products LEFT JOIN
product_details
ON product_details.product_id = products.id
WHERE product_details.value = 1 OR products.id = 4;
This is not a bad practice. I do think the query is easier to follow using EXISTS:
SELECT p.id
FROM products p
WHERE p.id = 4 OR
EXISTS (SELECT 1
FROM product_details pd
WHERE pd.product_id = p.id AND pd.value = 1
);
In addition EXISTS makes it clear that you don't want to return duplicates if there are duplicate matching rows in product_details.
If performance is you main consideration, then EXISTS is probably your best choice, with an index on product_details(product_id, value).
Couple of notes:
As a rule of thumb, a UNION ALL statement performs better than an OR operator. Also, this helps clear up the query.
Using both an implicit JOIN and a predicate in the WHERE clause on the same table can get you into trouble - especially if you're using a LEFT OUTER JOIN (the predicate in the WHERE clause has precedence over the LEFT OUTER JOIN).
Seems like you always want to pull back any records that has a products.id = 4, and also any products that have a product_details.value = 1. This seems like two separate queries to me, and splitting it would probably make it easier to maintain in the future.
SELECT
p.id
FROM
products p
WHERE
p.id = 4
UNION ALL
SELECT
p.id
FROM
product_details pd
JOIN
products p
ON
p.id = pd.product_id
WHERE
pd.value = 1
Source: https://bertwagner.com/posts/or-vs-union-all-is-one-better-for-performance/

Query to return sales excludes results that are 0

I've searched this and found this problem, and the solution that worked for most people (using an outer join) is not working for me. I originally had an inner join, and switched it to an outer join but I am getting the same results. This is based off certain account numbers and it shows their total sales. If an account has 0 sales it does not show up, and I need it to show up. Here is my query.
Select a.accountnumber, SUM(a.totalsales) as Amount, c.companyname
FROM Sales a LEFT OUTER JOIN Accounts c on (a.Accountnumber = c.Accountnumber)
WHERE a.Salesdate between '1/1/2016' and '1/27/2016'
AND a.Accountnumber in ('1','2','3','4')
GROUP BY a.Accountnumber, c.companyname
And I'll get results like:
Accountnumber | Amount | Company
1 | 250.00 | A
3 | 500.00 | B
Since accountnumbers 2 and 4 dont have an amount, they are not showing up. I would like them to show up like
Accountnumber | Amount | Company
1 | 250.00 | A
2 | 0 | B
3 | 250.00 | C
4 | 0 | D
How can I achieve this? Any help would be appreciated. Thank you!
I think that RIGHT JOIN will not work, since there are conditions in WHERE.
Try this:
SELECT
c.accountnumber,
COALESCE(SUM(a.totalsales),0) AS Amount,
c.companyname
FROM Accounts c
LEFT OUTER JOIN Sales a
ON a.Accountnumber = c.Accountnumber
AND a.Salesdate BETWEEN '1/1/2016' AND '1/27/2016'
WHERE
c.Accountnumber IN ('1', '2', '3', '4')
GROUP BY c.Accountnumber, c.companyname
Just to clarify, the problem is not which JOIN is used, it can be either, but using WHERE condition ON non-existing (NULL) values, since all not matched values from outer joined table are NULL anyway, any condition applied, practically make those joins inner joins (unless they are IS NULL conditions), see: http://www.codeproject.com/Articles/33052/Visual-Representation-of-SQL-Joins
You should have two options.
Modify the query to select from the Accounts table first and then join the Sales table afterwards.
FROM Accounts c
LEFT OUTER JOIN Sales a on (a.Accountnumber = c.Accountnumber)
Use a RIGHT join instead of a LEFT one.
FROM Sales a
RIGHT OUTER JOIN Accounts c on (a.Accountnumber = c.Accountnumber)
Change it to a RIGHT OUTER JOIN and it should work.
A left join says keep everything in the left table, IE the table before the left join, even if it doesn't exist in the second table. In your case I sure hope there aren't any sales that don't have accounts. To keep all the Accounts even if they don't have a sale you need a RIGHT outer join. Alternatively you could change the order of the tables and do FROM Accounts c LEFT OUTER JOIN Sales a ...
Edit..
Blind got it. RIGHT OUTER JOIN is what you need to use given the way you wrote it but then you have a null for all the values in the columns from the sales table for 2 and 4 so the account number can't be in (1,2,3,4) or between the dates so they don't make it into the results.

MySql In an inner join does it matter which table comes first?

I'm selecting data to output posts a user has book marked.
The main table which holds the ids of the posts a user has bookMarked, is called bookMarks. This is the table based on which posts will be selected from the posts table, to display to the user.
bookMarks
id | postId | userId
--------------------------
1 | US01 | 1
2 | US02 | 1
3 | US01 | 2
4 | US02 | 2
posts
id | postId | postTitle
--------------------------
1 | US01 | Title 1
2 | US02 | Title 2
3 | US03 | Title 3
4 | US04 | Title 4
My sql is currently like this:
select a.postsTitle
from posts a
inner join bookmarks b
on b.userId = a.userId
and b.userId = :userId
Notice, I have the table posts put first before the table bookmarks. But, since I'm selecting based on whats there in bookmarks, is it necessary I declare the table bookmarks first instead of post in the sql statement? Will doing it the way I'm doing it cause and problems in data selection or efficiency?
Or should I do it like:
select b.postsTitle
from bookmarks a
inner join posts b
on a.userId = b.userId
and a.userId = :userId
Notice, I have table bookmarks put first here.
Instead of the following:
select a.postsTitle
from posts a
inner join bookmarks b
on b.userId = a.userId
and b.userId = :userId
You should consider formatting your JOIN in this format, using the WHERE clause, and proper capitalization:
SELECT p.postsTitle
FROM bookmarks b
INNER JOIN posts p
ON p.userId = b.userId
WHERE b.userId = :userId
While it makes no difference (performance wise) to MySQL which order you put the tables in with INNER JOIN (MySQL treats them as equal and will optimize them the same way), it's convention to put the table that you are applying the WHERE clause to first. In fact, assuming proper indexes, MySQL will most likely start with the table that has the WHERE clause because it narrows down the result set, and MySQL likes to start with the set that has the fewest rows.
It's also convention to put the joined table's column first in the ON clause. It just reads more logically. While you're at it, use logical table aliases.
The only caveat is if you don't name your columns and instead use SELECT * like the following:
SELECT *
FROM bookmarks b
INNER JOIN posts p
ON p.userId = b.userId
WHERE b.userId = :userId
You'll get the columns in the order they're listed in the query. In this case, you'll get the columns for bookmarks, followed by the columns for posts.
Most would say never use SELECT * in a production query, but if you really must return all columns, and you needed the columns from posts first, you could simply do the following:
SELECT p.*, b.*
FROM bookmarks b
INNER JOIN posts p
ON p.userId = b.userId
WHERE b.userId = :userId
It's always good to be explicit about the returned result set.
There is no effect on query performance or final resultset with respect to placement of table on either side of JOIN clause if INNER JOIN is used .
The only difference observed is in order of columns returned and that too only if SELECT * is used . Suppose you have tableA(aid,col1,col2) and tableB(bid,col3)
SELECT *
FROM tableA
INNER JOIN tableB
ON tableA.aid=tableB.bid
returns column in order
aid|col1|col2|bid|col3
On otherhand
SELECT *
FROM tableB
INNER JOIN tableA
ON tableA.aid=tableB.bid
returns column in order
bid|col3|aid|col1|col2|
But it matters in case of LEFT JOIN or RIGHT JOIN.

how to get orphans from a join table in MySQL

Imagine 2 tables, the first one is a list of products (products), the second one a join table between products and another table (categories), called products-categories
products:
id | name
------------
1 Lorem
2 Ipsum
3 Dolor
4 Sit
products-categories
product_id | categories_id
---------------------------
1 3
1 6
4 1
2 2
How to get the orphan elements, I mean the elements in no category, so in this case: 3, in a efficient way (+30k records) using MyISAM?
This is somehow like showing all rows that are not joinable, but this syntax seams weird to me...
select * from products p
left join product_categories pc on p.id=pc.product_id
where pc.product_id is null
will return all products in table products that are not found in product_Category. LEft join and where is very fast. 30k records is also very little, so don't worry there.
Using SubQuery:
SELECT name FROM products
WHERE id NOT IN (SELECT product_id FROM products-categories);
Using JOIN
SELECT name FROM products
LEFT JOIN products_categories ON (id=product_id)
WHERE product_id IS NULL;
Better to go with join
sqlfiddle demo : http://sqlfiddle.com/#!2/684c1/8
select p.id from products p left join product-categories c on p.id=c.product_id
where c.id is NULL
I had a similar problem once i used something like following
select p.id from products p left join productscategories pc where pc.categories_id is null
-hj

Select only rows where count of rows in other table is greather than 0

I have 2 tables in my database:
Products:
--------------------------------------------------
| id | product_name | manufacturer |
--------------------------------------------------
Products_photos:
-----------------------------------------------
| id | product_id | image_name |
-----------------------------------------------
I want select all Products, where Product_photos count is greater than 0.
How I can do that?
#Edit:
I don't want to add results from Products_photos for my output. I want only show entries from Products, where are any images. Sorry for my english :)
Thanks for help
I think the joining solutions already offered are the best bet, in terms of query efficiency. But for clarity - in terms of expressing exactly what you ask for - I would choose an approach like this:
select * from products p
where exists (select * from products_photos pp where pp.product_id = p.id)
SELECT p.id, p.product_name, p.manufacturer
FROM Products p
INNER JOIN Products_photos i on i.product_id = p.id
you can do
Select P.id, P.product_name, P.manufacturer
from Products P
INNER JOIN Products_photos Pp on P.Id = Pp.product_id
For the Inner Join, it will only return rows where it's posible the joining, which means that you have at least one value in the Products_photos table.
SELECT P.* FROM Products AS P INNER JOIN Products_Photos AS PP ON P.id=PP.id
Another method, more inefficient but maybe better for you to understand, would be
SELECT P.* FROM Products AS P
WHERE P.id IN (SELECT DISTINCT id FROM Product_photos)