I have an entity called Path, which is linked to another entity Product using two different fields.
/**
* #ORM\Entity
*/
class Path
{
/**
* #ORM\ManyToOne(targetEntity="Product")
*/
private $product;
/**
* #ORM\ManyToMany(targetEntity="Product")
*/
private $products;
}
Links in different fields are done due to the need to perform some logic on "main" product, while allowing attaching to multiple products in some other parts of the app.
EDIT: Product in product field is not present in products field.
I'm looking to build a query that will do some other checks against Product, possibly joining with other entities related to Product. I'd like database to perform only one join with product table (as I would if it weren't done in Doctrine).
Currently I've got a QueryBuilder set up like so:
$qb->leftJoin("path.products", 'path_product');
$qb->join(Product::class, 'products', 'WITH', "path.product = products OR path_product.id = products.id")
Generated SQL:
SELECT path.id AS id_0
FROM path path
LEFT JOIN path_product path_product ON path.id = path_product.path_id
LEFT JOIN product product_1 ON product_1.id = path_product.product_id
LEFT JOIN product product_2 ON (path.product_id = product_2.id OR product_1.id = product_2.id)
(It's a simplified SQL generated from Doctrine Query Builder with unnecessary fields removed and expanded aliases)
...but this generates one additional join with Product's table, which feels "dirty". Is there some other way to do a join like this?
Related
When querying one table using the doctrine query builder a partial select can be written like this:
$queryBuilder = $this->createQueryBuilder('person');
$queryBuilder->addSelect('partial person.{id, name}');
How can one write a partial select be written for a left joined table? I tried something like this, but can't figure out the correct syntax:
$queryBuilder = $this->createQueryBuilder('person');
$queryBuilder->join('person.address');
$queryBuilder->addSelect('partial person.{id, name} person.address.city'); // ???
My goal would be to select only parts of the Person and the Address object when executing the query to be more memory efficient.
Your syntax is off for your join operation. You have to give an alias when using join. From there, you can just use the same syntax to query your partial Address object:
// In a method of PersonRepository
$qb = $this->createQueryBuilder('person')
->select(['partial person.{id, name}', 'partial address.{id, city}'])
->join('person.address', 'address');
Notice that I added id to the fields retrieved for Address. If you don't, Doctrine will give you the following error:
Error: The partial field selection of class Path\To\Entity\Address must contain the identifier
As a side note, you said you wanted to write this select for a left joined table. If you want to perform a LEFT JOIN, you need to use leftJoin instead of join (the signature of both methods is the same).
A previous developer at my work assigned all products an attribute called "delivery_text", which holds a string of html. For each product, the main body of this string is unique. However each one contains a link to a specific url (the link is the same for every product). Due to changes in our site layout, we now need to update this link for every product, without impacting the rest of the attribute text.
Obviously, I don't want to have to manually edit thousands of products just to change the url of the link in each product's delivery_text string.
Is there a way I can programmatically parse the contents of this attribute for every product, and replace instances of links to oldsite.com/old-url with links to newsite.com/new-url?
I presume I can do it with some kind of database trickery, and have access to phpmyadmin via cpan, but I have no idea how to actually go about doing so.
Note that I don't consider this a duplicate question, as while there are many "how do i mass update attribute values for all products" type questions, I have yet to find one that asks "how do i search and replace a specific character sequence within the attribute text for each product".
If you want to do this from the database, you could do something like this:
Find the attribute values
SELECT at.* FROM catalog_product_entity_varchar AS at
INNER JOIN eav_attribute AS ea
ON ea.attribute_id = at.attribute_id
WHERE ea.attribute_code = 'delivery_text';
This should show you a list of all of the attribute values in your database currently.
Update the attribute values
Once you've found all the values, you can use the same query to replace the URL within the values:
UPDATE catalog_product_entity_varchar AS at
INNER JOIN eav_attribute AS ea
ON ea.attribute_id = at.attribute_id
SET at.value = REPLACE(at.value, 'http://oldurl.com', 'http://newurl.com')
WHERE ea.attribute_code = 'delivery_text';
Doing it without SQL
You could also do this using the Magento model APIs. Start by gathering the data you need, the same way as above:
Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
$collection = Mage::getModel('catalog/product')
->getCollection()
->addAttributeToSelect('delivery_text');
foreach ($collection as $product) { /** #var Mage_Catalog_Model_Product $product */
var_dump($product->getDeliveryText());
}
Then you can update the attribute within your loop. Here you need to specify a store ID, so I've assumed you'll do this at the admin (global) scope:
$old = 'http://oldurl.com';
$new = 'http://newurl.com';
foreach ($collection as $product) { /** #var Mage_Catalog_Model_Product $product */
Mage::getModel('catalog/product_action')
->updateAttributes(
array($product->getId()),
array('delivery_text' => $new),
Mage_Core_Model_App::ADMIN_STORE_ID
);
}
Please note: Using Mage_Catalog_Model_Product_Action::updateAttributes will work quickly and efficiently to update product attributes at a given store level, but if you are not using the admin store level then you may not be able to retrieve the value in a collection because this method doesn't create a default value - it just does what it says it will; create/update a value at a given store level.
I'm just copy & pasting some of the introductory text from one of my questions, since the same table relationship is involved in this question also.
I have three of many tables in Oracle (10g) database as listed below. I'm using Hibernate Tools 3.2.1.GA with Spring version 3.0.2.
Product - parent table
Colour - parent table
ProductColour - join table - references colourId and prodId of Colour and Product tables respectively
Where the table ProductColour is a join table between Product and Colour. As the table names imply, there is a many-to-many relationship between Product and Colour which is mapped by PrductColour. I think, the relationship in the database can easily be imagined and is clear with only this much information. Therefore, I'm not going to explore this relationship at length unnecessarily.
An entity (row) in Product is associated with any number entities in Colour and an entity (row) in Colour can also be associated with any number of entities in Product.
Since, it is a many-to-many relationship, it is mapped in the Product and the Colour entity classes (POJOs) with their respective java.util.Set and no direct POJO class for the product_colour table is available.
The class Product looks like the following.
public class Product implements java.io.Serializable
{
private BigDecimal prodId;
private Set<Colour> colours = new HashSet<Colour>(0);
.
.
.
//Other properties with setters and getters.
}
The class Colour looks like the following.
public class Colour implements java.io.Serializable
{
private BigDecimal colourId;
private Set<Product> products = new HashSet<Product>(0);
.
.
.
//Other properties with setters and getters.
}
The actual mapping between entities is available in xxx.hbm.xml files, regarding this question which is unnecessary, I think .
What I want to do is to retrieve only those rows from the Colour table which don't match the colour rows in the ProductColour table for a particular product at a time. In this regard, the native Oracle SQL statement would look something like the following.
SELECT colour_id, colour_name, colour_hex
FROM colour
WHERE colour_id not in (SELECT colour_id FROM product_colour WHERE prod_id=81)
ORDER BY colour_id DESC
Where prod_id can be any valid BigDecimal number in Java which is dynamic.
As noted earlier, the relationship is available as a many-to-many relationship in Hibernate, no POJO class for the database table product_colour is available and therefore, I'm fumbling in writing such an HQL statement in Hibernate. I have tried to write such an HQL statement but no attempts were succeeded.
[The code presented in the rest of the part may completely be unnecessary to review]
I'm therefore following a traditional way. What I'm doing is... I'm first retrieving a single product row from the Product the entity class based on a dynamic value of prodId such as,
List<Product>list=session.createQuery("from Product where prodId=:prodId")
.setParameter("prodId", prodId).list();
and then using a loop, I'm getting the entire Colour set - java.util.Set corresponding to the product_colour table in Oracle which is available in the Product entity for this product such as,
Set<Colour>colours=new HashSet<Colour>(0);
for(Product p:list)
{
if(p!=null)
{
colours=p.getColours();
}
}
As can be seen, the colours Set is being populated with all of the colour rows available (reference rows) in the product_colour table in Oracle.
After getting all of these rows, I'm getting the entire Colour entity class itself (all the row in it) that corresponds to the colour table in Oracle and then removing those rows which match the rows retrieved from the product_colour Oracle table (available in the colours Set in the preceding snippet) satisfying the condition as mentioned earlier such as,
List<Colour>colourList=session.createQuery("from Colour order by colourId desc").list();
Iterator<Colour>it=colourList.iterator();
while(it.hasNext())
{
Colour c=(Colour)it.next();
for(Colour pc:colours) //colours is available in the preceding snippet.
{
if(c==pc)
{
it.remove();
}
}
}
This can do what is intended but doing so, may imply some overhead on the system. Additionally, what I want to achieve doesn't seem possible with this approach which is pagination. I can't use the setFirstResult(int) and the setMaxResults(int) methods to accomplish the task of pagination which is the case otherwise like the one shown below regarding the Product entity class,
List<Product> products=session.createQuery("from product order by prodId desc")
.setMaxResults(0).setFirstResult(4);
So the question is again, regarding this relationship, is this possible to write such an HQL statement that can retrieve only those rows from the Colour entity class which don't match the colour rows in the product_colour Oracle table like the native SQL statement shown above?
How can I achieve the concept of pagination otherwise (in case, it is not possible)?
Short answer to a veeeeery long question:
select colour from Colour colour
where colour.id not in (
select colour2.id from Product product
inner join product.colours colour2
where product.id = :productId)
I have a simple data model: a Product can have many Tag objects.
The Tag model has a tag field and a value field, both strings (value is not important here).
The following DQL query gets me all Products and their tags:
SELECT p, t FROM Product p LEFT JOIN p.tags t
However, how do I only select Products that have a certain tag (e.g. "blue")? It is important that I get all the tags for the returned product, so I cannot simply do a WHERE t.tag = 'blue'.
As a side question; do you have any thoughts on whether it would be better to implement this using a separate Tag table, and then having a ProductTag table knitting them together (proper many-to-many relation)?
You can use a DQL query like this:
"SELECT p, t FROM Product p LEFT JOIN p.tags t WHERE p.id IN (
SELECT sp.id FROM Product sp INNER JOIN sp.tags st WHERE st.tag = 'blue'
)"
This will return all product object where a Tag.tag = 'blue' is found in their collection of tags, but also other tags are joined.
I tried this out in a Symfony2 project and it worked like this. Because I got all the information used for this from the Doctrine Documentation I think it will work too in the standalone version.
If it doesn't work tell me the error and I will take a close look at this problem.
I am trying to show Grouped Products list based on attributes of its associated simple products. Right now i am doing like below
- Create a collection of simple products and add attribute filters like color,brand etc., like below
$productCollection = Mage::getModel('catalog/product')->getCollection()
->addStoreFilter(Mage::app()->getStore())
->addAttributeToFilter($aname,$avalue)
->addAttributeToFilter('type_id', array('eq' => 'simple'))
->addAttributeToFilter(ATTRIBUTE_CODE,ATTRIBUTE_VALUE_ID);
(->addAttributeToFilter('color',5))
- Obtain all resultant ids and get its parent ids using the below
Mage::getModel('catalog/product_type_grouped')->getParentIdsByChild($productCollection->getAllIds());
- Read the parent ids from above object and show the result in a custom grid that i created
- Created a paging logic for parent ids and do paging in view file
This logic really consumes more time, is there any way that i can do all these in a single collection? may be inner join sort of method between simple and grouped products!
Please suggest.
Thanks,
Balan
It depends of attributes you want to filter with.
Magento already have prepared data for filtering grouped products by simple product attributes. It is stored in catalog_product_index_eav.
But these attributes should be marked as 'Filterable' in magento admin.
/** #var $productCollection Mage_Catalog_Model_Resource_Product_Collection */
$productCollection = Mage::getModel('catalog/product')->getCollection();
$productCollection->addStoreFilter(Mage::app()->getStore())
->addAttributeToFilter('type_id', 'grouped');
$resource = Mage::getModel('core/resource');
$productCollection->getSelect()
->join(array('filter_eav' => $resource->getTableName('catalog/product_index_eav')),
"e.entity_id = filter_eav.entity_id AND filter_eav.attribute_id = {$attributeId} AND value = {$valueId}",
array(''));
Originally this table is used in Mage_Catalog_Model_Resource_Layer_Filter_Attribute function applyFilterToCollection. But it requires filter object as param.
This might be what you are looking for. It works well.
http://www.commerceextensions.com/filterable-grouped-products.html