How to get prestashop product attributes through SQL - mysql

I need to build a report for a prestashop site showing product id, name, and stock. So far I've done just that through the following query:
SELECT product.id_product, product_lang.name, stock_available.quantity
FROM ps_product product, ps_product_lang product_lang, ps_stock_available stock_available
WHERE stock_available.id_product = product.id_product
AND stock_available.id_product = product.id_product
AND product_lang.id_lang =1
AND product.reference = ''
AND product_lang.id_product = product.id_product
which outputs something like:
|===============================|
| id | name | stock |
| 1 | earring | 45 |
|===============================|
The problem is that some products have the same name and same ID but different attributes, so i'm getting stuff like this:
|===============================|
| id | name | stock |
| 1 | earring | 45 |
| 1 | earring | 76 |
| 1 | earring | 9 |
|===============================|
What i need is to add a new field showing the attributes that differentiate them, so the final output would be something like this:
|================================================|
| id | name | attributes | stock |
| 1 | earring | yellow, short | 45 |
| 1 | earring | red, short | 76 |
| 1 | earring | red, long | 9 |
|================================================|
But I can't figure out how the attributes tables (because there are several of them) relate to the products table in order to add the proper statements to the query and get the desired behaviour.
If there are any prestashop database experts around, or anyone that could help, I would really appreciate it.

Try this:
SELECT DISTINCT
p.id_product, pl.name, psa.quantity
FROM ps_product p
LEFT JOIN ps_product_lang pl ON (pl.id_product = p.id_product)
LEFT JOIN ps_stock_available psa ON (p.id_product = psa.id_product)
GROUP by p.id_product

Related

INNER and LEFT JOIN on mysql

I have the needing to chain some joins queries, and I am not able to get the result that I want.
I have many tables to save some product info:
-Products: Name and description in many languages.
-Products_attributes: Some common attributes of the products like the provider or the buying price.
-Products_locations: Info concerning the locations that sell a certain product, like stock, or the selling price.
-Other important table is the Companies one: some of this companies could be a provider.
Well, this is my query:
SELECT p.id
, p.name nameProduct
, p.reference refProduct
, a.buy_price priceProduct
, l.active
, l.sell_price
, l.stock stockProduct
, c.title p_name
FROM products p
LEFT JOIN products_location l
ON l.product_reference = p.reference
AND l.active = 1
AND l.location_id = 4
JOIN products_attributes a
ON a.product_reference = p.reference
AND p.lang = 'es'
AND a.provider = 6
JOIN companies c
ON a.provider = c.id
AND c.id = 6
What I want to do is get all products of a certain provider, if the location from where is executing the query has this product, the row of the result must return too the concerning at this product<->location, in contrary it must return NULL in the column related to this relation.
At the moment, with this query, all what I am getting is the products of a provider where has a relation between the product<->location (through products_location table).
Any way to do that??
Thank you.
EDIT
An example about what I trying to get could be something like this:
TABLE: Companies
id | Title
1 | SomeName
6 | ProviderName
TABLE: Products
id | reference | name | lang
1 | 11111 | 1_es | es
2 | 11111 | 1_en | en
3 | 22222 | 2nam | es
4 | 33333 | 3nam | es
5 | 44444 | 4nam | es
6 | 55555 | 5nam | es
TABLE: Products_atributte
id | product_reference | buy_price | provider
1 | 11111 | 10 | 6
1 | 22222 | 15 | 6
1 | 33333 | 20 | 6
1 | 44444 | 12 | 1
1 | 55555 | 13 | 1
TABLE: Products_locations
id | product_reference | location_id | sell_price | stock | active
1 | 11111 | 4 | 26 | 10 | 1
1 | 11111 | 5 | 25 | 13 | 1
1 | 22222 | 5 | 20 | 13 | 1
1 | 44444 | 5 | 21 | 1 | 1
1 | 55555 | 5 | 22 | 2 | 1
AND THE RESULT MUST BE SOMETHING LIKE THIS:
nameProduct | refProduct | priceProduct | active | sell_price | stockProduct | p_name
1_es | 11111 | 10 | 1 | 26 | 10 | ProviderName
2nam | 22222 | 15 | NULL | NULL | NULL | ProviderName
3nam | 33333 | 20 | NULL | NULL | NULL | ProviderName
If I use a LEFT JOIN only in the products_locations table, I donĀ“t get the two last rows, and if I use LEFT JOIN with all the tables I get duplicates product references, also I get products provided by other providers (in the example 1-> SomeName).
You were correct to LEFT JOIN the products and products_location tables. However, you used INNER JOIN for the other two tables in the query and I believe that this may be the reason why you are only seeing records which have a relation between product and location. The logic would be that a product which does not have a location also does not have an entry in, for example, the products_attributes table. Hence, the non matching records you want to retain would be filtered off downstream by an INNER JOIN. To remedy this, use LEFT JOIN everywhere:
SELECT products.id,
products.name AS nameProduct,
products.reference AS refProduct,
products_attributesbuy_price AS priceProduct,
products_location.active,
products_location.sell_price,
products_location.stock AS stockProduct,
provider.title AS p_name
FROM products
LEFT JOIN products_location
ON products_location.product_reference = products.reference AND
products_location.active = 1 AND
products_location.location_id = 4
LEFT JOIN products_attributes
ON products_attributes.product_reference = products.reference AND
products.lang = 'es' AND
products_attributes.provider = 6
LEFT JOIN companies AS provider
ON products_attributes.provider = provider.id AND
provider.id = 6

Mysql group-concat is giving wrong output

My purchaseproduct table
+------------+------------+
| productids | quantities |
+------------+------------+
| 1,3,4,5 | 1,1,1,1 |
| 2,3,4,5 | 1,1,1,1 |
+------------+------------+
My product table
productsid | productsname |
+------------+-----------------------------+
| 1 | Phone |
| 2 | Laptop |
| 3 | Charger |
| 4 | Earphone |
| 5 | Camera |
I want to get product name based on productids in purchaseproduct table
Like below Out put is needed
Phone,Charger,Earphone,Camera (In row one)
Laptop,Charger,Earphone,Camera (In row two)
I tried this below statement and many other
select group_concat(p.productsname) from purchaseproducts as pp join products as p on find_in_set(p.productsid,pp.productids);
But the output I get is
Phone,Charger,Earphone,Camera,Laptop,Charger,Earphone,Camera (All in one row)
How can I achieve the output I need?
You can simply use DISTINCT inside the GROUP_CONCAT :
select pp.productsid , group_concat(DISTINCT p.productsname)
from purchaseproducts pp
join products p
on find_in_set(p.productsid,pp.productids);
GROUP BY pp.productsid

Mysql count per distinct user with a join

currently i have two tables with some data. the first table has the following:
+----------------+-----------+
| name | member_id |
+----------------+-----------+
| Juice Box | 49432 |
| Rainsurge | 49631 |
| spiderpigrider | 50482 |
+----------------+-----------+
The second table has the following:
+------------+-----------+
| recruit_id | bin(refs) |
+------------+-----------+
| 49432 | 1 |
| 49631 | 1 |
| 49432 | 1 |
| 49631 | 1 |
| 49432 | 1 |
| 49631 | 1 |
| 49432 | 1 |
| 49631 | 1 |
| 49432 | 1 |
| 49631 | 1 |
+------------+-----------+
I would like to return the name, total refs and member_id/recruit_id like so (listing only users with at least 1 ref)
+------------+-----------+------------+
| recruit_id | name | total_refs |
+------------+-----------+------------+
| 49631 | Rainsurge | 5 |
| 49432 | Juice Box | 5 |
+------------+-----------+------------+
select r.recruit_id,bin(r.refs),ipb.name from refs as r
inner join syndicate_ipb.core_members as ipb on ipb.member_id=r.recruit_id;
this returned my data but obviously without a total count and repeated names/ids
select r.recruit_id,count(bin(r.refs)),ipb.name from refs as r
inner join syndicate_ipb.core_members as ipb on ipb.member_id=r.recruit_id;
this returned data with the total count of everyone but only one id/name
+------------+--------------------+-----------+
| recruit_id | count(bin(r.refs)) | name |
+------------+--------------------+-----------+
| 49432 | 10 | Juice Box |
+------------+--------------------+-----------+
this returns the data but again without a count
select distinct r.recruit_id,bin(r.refs),ipb.name from refs as r
inner join syndicate_ipb.core_members as ipb on ipb.member_id=r.recruit_id;
+------------+-------------+-----------+
| recruit_id | bin(r.refs) | name |
+------------+-------------+-----------+
| 49432 | 1 | Juice Box |
| 49631 | 1 | Rainsurge |
+------------+-------------+-----------+
Any help or guidance is greatly appreciated. I feel like i'm close here but just not competent enough with SQL to get it. thanks!
You were almost there. You just missed the GROUP BY clause at the end.
Query:
SELECT
r.recruit_id,
count(bin(r.refs)),
ipb.name
FROM refs AS r
INNER JOIN syndicate_ipb.core_members AS ipb
ON ipb.member_id = r.recruit_id
GROUP BY r.recruit_id;
Note:
If bin(refs) column always contains value 1 then actually you don't need to keep that column. In that case you can use count(*) or count(r.recruit_id) to get the count.
And if bin(refs) column contains any value then count will not give you the right answer. In that case you need to use sum like Sum( bin(refs)).
You have to use the group by clause:
select r.recruit_id, ipb.name, count(bin(refs)) as total_refs
from refs as r
inner join syndicate_ipb.core_members as ipb
on ipb.member_id=r.recruit_id
group by r.recruit_id, ipb.name
having count(bin(refs)) >= 1
This group by r.recruit_id, ipb.name will group the results and this having count(bin(refs)) >= 1 will garante that it only returns members with at least one ref
Do not only group your columns just by the ones you want. Even though MySql allows it, it is not SQL Ansi pattern and even MySql now is complying with it. Use an aggregation function grouping with your entire columns on the select statement.
SELECT ipb.*, COUNT(`r`.`recruit_id`) AS cid FROM `ipb`
INNER JOIN `r` ON `r`.`join_id` = ipb.`member_id`
GROUP BY ipb.`member_id`

Joining a variable number of tables based on column value

I have a few Models in my code which is modeled in my MySQL database in this structure:
Properties
+----+------+---------+
| id | name | address |
|----+------+---------|
| 1 | p1 | 123 st |
| 2 | p2 | 123 st |
| 2 | p3 | 123 st |
+----+------+---------+
Tenants (belongs to property)
+----+-------------+-------+
| id | property_id | suite |
|----+-------------+-------|
| 1 | 1 | s1 |
| 2 | 1 | s2 |
| 3 | 2 | s3 |
+----+-------------+-------+
Costs (can belong to property or tenants)
+----+--------------+-----------+--------------+
| id | parent_model | parent_id | name |
|----+--------------+-----------+--------------+
| 1 | property | 1 | gardening |
| 2 | property | 2 | construction |
| 3 | tenant | 1 | renovation |
+----+--------------+-----------+--------------+
Files (can belong to any model)
+----+--------------+-----------+--------------+
| id | parent_model | parent_id | name |
|----+--------------+-----------+--------------+
| 1 | property | 1 | file1.jpg |
| 2 | tenant | 2 | file2.pdf |
| 3 | costs | 3 | file3.doc |
+----+--------------+-----------+--------------+
As you can see from the table structure all models can be linked back to a property record (either directly or via one or more intermediary tables).
In my code I want to write one query that will get the property.id of a file
After looking over this question: Joining different tables based on column value I realized finding a "link" from a file to a property can be done via a few joins.
The number of joins needed is different based on whatever the parent_model is. For file.id = 1 its a matter of joining in the properties table. For file.id = 3 we must join in costs, tenants, and properties
How should a query be written that can get a property.id for all of my files records?
Edit:
This would be a sample output:
+---------+-------------+
| file_id | property_id |
|---------+-------------|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
+---------+-------------+
In this case all the files worked out to be associated with property_id 1 but this may not always be the case.
I don't think there's a shortcut. You need to traverse all the paths something along these lines
select -- property files
FileID, parent_id
from Files
where parent_model='property'
union
select -- property costs
FileID, c.parent_id
from
Files inner join costs C on Files.parent_id=c.id and c.parent_model='property'
where Files.parent_model='costs'
union
select -- tenant costs
FileID, t.parent_id
Files inner join costs C on Files.parent_id=c.id and c.parent_model='tenant'
inner join tenant t on t.id=c.parent_id
where Files.parent_model='costs'
.... etc.
i.e. just string together all the variations then UNION
You should be able to do this like so:
SELECT
P.id,
P.name,
P.address,
F.name,
...
FROM
Files F
LEFT OUTER JOIN Costs C ON
F.parent_model = 'Cost' AND C.id = F.parent_id
LEFT OUTER JOIN Tenants T ON
(F.parent_model = 'Tenant' AND T.id = F.parent_id) OR
(C.parent_model = 'Tenant' AND T.id = C.parent_id)
LEFT OUTER JOIN Properties P ON
(F.parent_model = 'Property' AND P.id = F.parent_id) OR
(C.parent_model = 'Property' AND P.id = C.parent_id) OR
(P.id = T.property_id)
Depending on your data, this might break down. I don't think that I like the table design in this case.

Selecting other side of many to many relationship

Background
I have three tables: (SQL fiddle: http://sqlfiddle.com/#!2/f7b33/11)
products
+----+-----------+
| id | product |
+----+-----------+
| 1 | product_1 |
| 2 | product_2 |
| 3 | product_3 |
+----+-----------+
products_features
+------------+------------+
| product_id | feature_id |
+------------+------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 1 | 4 |
| 2 | 1 |
| 2 | 3 |
| 3 | 4 |
+------------+------------+
features
+----+-----------+
| id | feature |
+----+-----------+
| 1 | feature_1 |
| 2 | feature_2 |
| 3 | feature_3 |
| 4 | feature_4 |
+----+-----------+
I'm then selecting like this:
SELECT products.product,
GROUP_CONCAT(features.feature) AS features
FROM products
LEFT JOIN products_features
ON product_id = products.id
LEFT JOIN features
ON products_features.feature_id = features.id
GROUP BY products.id
to get something like:
+-----------+-----------------------------------------+
| product | features |
+-----------+-----------------------------------------+
| product_1 | feature_1,feature_2,feature_3,feature_4 |
| product_2 | feature_1,feature_3 |
| product_3 | feature_4 |
+-----------+-----------------------------------------+
Question
So, everything is great, However, what I'd like to do is to only have things that have feature_1 and feature_3, whilst still getting the other features.
In other words, I'd like to write a query that would get me:
+-----------+-----------------------------------------+
| product | features |
+-----------+-----------------------------------------+
| product_1 | feature_1,feature_2,feature_3,feature_4 |
| product_2 | feature_1,feature_3 |
+-----------+-----------------------------------------+
I've tried:
SELECT products.product,
GROUP_CONCAT(features.feature) AS features
FROM products
LEFT JOIN products_features
ON product_id = products.id
RIGHT JOIN features
ON products_features.feature_id = features.id AND features.feature in ('feature_1','feature_3')
GROUP BY products.id
but of course I get:
+-----------+---------------------+
| product | features |
+-----------+---------------------+
| (null) | feature_4,feature_2 |
| product_1 | feature_1,feature_3 |
| product_2 | feature_3,feature_1 |
+-----------+---------------------+
So though I now know product_1 and product_2 are the ones with those features, I can't see the rest of the features they have.
What query will give me allow me to specify feature_1 and feature_3 and get the following response?
+-----------+-----------------------------------------+
| product | features |
+-----------+-----------------------------------------+
| product_1 | feature_1,feature_2,feature_3,feature_4 |
| product_2 | feature_1,feature_3 |
+-----------+-----------------------------------------+
I think the most generalizable way to approach this is with a having clause:
SELECT products.product,
GROUP_CONCAT(features.feature) AS features
FROM products
LEFT JOIN products_features
ON product_id = products.id
LEFT JOIN features
ON products_features.feature_id = features.id
GROUP BY products.id
HAVING sum(features.feature = 'feature_1') > 0 and
sum(features.feature = 'feature_3') > 0;
Each clause in the having statement is counting the number of times that a given feature appears. The and is requiring that both features be in the final result set.
EDIT:
Given the structure of your statement, you could also do:
HAVING find_in_set('feature_1', features) > 0 and
find_in_set('feature_3', features) > 0;
This works because you are producing a column with the list of features and you are using a comma as a separator for that list.
Returning data from a group_concat and then splitting the results back in your front-end is a no go. Not only will it result in inefficient use of resources but also might result in errors in the splitting (eg: imagine what would happen when a feature appears and it happens to contain a ,).
The third and main reason not to use a group_concat is that it has a limit on the length. I'm not re-explaining the wheel but check this question for more information.
The best approach will be to return all the matching features for a given product and then just process them in a loop. It should be pretty simple to do (actually, most UI components, web or not, would expect to receive a collection to display them and this is what you're sending to them).
Additionally, it is most likely that you already have the ids of the features that you want to check so it would be more efficient to check for them instead of strings.
My last comment is that you don't actually need left joins in there. A left join will return all elements from the left regardless of whether they have a match in the right. However, you need the right side to have 2 elements (the 2 features) which makes the query contradictory. You just need an inner join in there.
This is the query I would use:
SELECT p.product, f.feature FROM products p
JOIN (
SELECT product_id FROM products_features
WHERE feature_id IN (1, 3)
GROUP BY product_id
HAVING count(*) = 2
) pf ON p.id = pf.product_id
JOIN products_features pf2 ON p.id = pf2.product_id
JOIN features f ON pf2.feature_id = f.id
This is the fiddle for that query.