I have a couple of tables:
table items: id, title
table properties: id_item, name, value
So an item has multiple properties (is an EAV).
Now i need to find which item has some properties, so i try to join the same relation multiple times:
$queryBuilder = $this->createQueryBuilder('i')
->join('i.properties', 'p');
$i = 0;
foreach ($properties as $name=>$value) {
$queryBuilder->join('i.properties', 'p'.$i)
->andWhere("p{$i}.name = :name".$i)
->setParameter(':name'.$i, $name)
->andWhere("p{$i}.value = :value".$i)
->setParameter(':value'.$i, $value);
$i = $i + 1;
}
return $queryBuilder->getQuery()->getResult();
But this doesn't work because the join is not repeated by doctrine, it uses always the same.
UPDATE:
To be more clear, if I join only once the table item and properties I get:
id | title | name | value
1 | t shirt | color| red
1 | t shirt | size | large
But if i need to search the items that have property color=red and size=large, i need to join it twice, so that i can make more where condition on different columns:
id | title | name1 | value1 | name2 | value2
1 | t shirt | color | red | size | large
The SQL generate actually is something like:
SELECT m0_.id AS id0
FROM item m0_
INNER JOIN properties m1_ ON m0_.id = m1_.item_id
WHERE m1_.name = 'Color' AND m1_.value = 'red' AND m1_.name = 'Size' AND m1_.value = 'Large'
But obviously m1_.name cannot be Color and Size at the same time.
Related
Consider a database with three tables:
goods (Id is the primary key)
+----+-------+-----+
| Id | Name | SKU |
+----+-------+-----+
| 1 | Nails | 123 |
| 2 | Nuts | 456 |
| 3 | Bolts | 789 |
+----+-------+-----+
invoiceheader (Id is the primary key)
+----+--------------+-----------+---------+
| Id | Date | Warehouse | BuyerId |
+----+--------------+-----------+---------+
| 1 | '2021-10-15' | 1 | 223 |
| 2 | '2021-09-18' | 1 | 356 |
| 3 | '2021-07-13' | 2 | 1 |
+----+--------------+-----------+---------+
invoiceitems (Id is the primary key)
+----+----------+--------+-----+-------+
| Id | HeaderId | GoodId | Qty | Price |
+----+----------+--------+-----+-------+
| 1 | 1 | 1 | 15 | 1.1 |
| 2 | 1 | 3 | 7 | 1.5 |
| 3 | 2 | 1 | 12 | 1.5 |
| 4 | 3 | 3 | 3 | 1.3 |
+----+----------+--------+-----+-------+
What I'm trying to do is to get the MAX(invoiceheader.Date) for every invoiceitems.GoodId. Or, in everyday terms, to find out, preferably in a single query, when was the last time any of the goods were sold, from a specific warehouse.
To do that, I'm using a derived query, and the solution proposed here . In order to be able to do that, I think that I need to have a way of giving multiple (well, two) aliases for a derived table.
My query looks like this at the moment:
SELECT tmp.* /* placing the second alias here, before or after tmp.* doesn't work */
FROM ( /* placing the second alias, tmpClone, here also doesn't work */
SELECT
invoiceheader.Id,
invoiceheader.Date,
invoiceitems.HeaderId,
invoiceitems.Id,
invoiceitems.GoodId
FROM invoiceheader
LEFT JOIN invoiceitems
ON invoiceheader.Id = invoiceitems.HeaderId
WHERE invoiceheader.Warehouse = 3
AND invoiceheader.Date > '0000-00-00 00:00:00'
AND invoiceheader.Date IS NOT NULL
AND invoiceheader.Date > ''
AND invoiceitems.GoodId > 0
ORDER BY
invoiceitems.GoodId ASC,
invoiceheader.Date DESC
) tmp, tmpClone /* this doesn't work with or without a comma */
INNER JOIN (
SELECT
invoiceheader.Id,
MAX(invoiceheader.Date) AS maxDate
FROM tmpClone
WHERE invoiceheader.Warehouse = 3
GROUP BY invoiceitems.GoodId
) headerGroup
ON tmp.Id = headerGroup.Id
AND tmp.Date = headerGroup.maxDate
AND tmp.HeaderId = headerGroup.Id
Is it possible to set multiple aliases for a single derived table? If it is, how should I do it?
I'm using 5.5.52-MariaDB.
you can use both (inner select) and left join to achieve this for example:
select t1.b,(select t2.b from table2 as t2 where t1.x=t2.x) as 'Y' from table as t1 Where t1.y=(select t3.y from table3 as t3 where t2.a=t3.a)
While this doesn't answer my original question, it does solve the problem from which the question arose, and I'll leave it here in case anyone ever comes across a similar issue.
The following query does what I'd intended to do - find the newest sale date for the goods from the specific warehouse.
SELECT
invoiceheader.Id,
invoiceheader.Date,
invoiceitems.HeaderId,
invoiceitems.Id,
invoiceitems.GoodId
FROM invoiceheader
INNER JOIN invoiceitems
ON invoiceheader.Id = invoiceitems.HeaderId
INNER JOIN (
SELECT
MAX(invoiceheader.Date) AS maxDate,
invoiceitems.GoodId
FROM invoiceheader
INNER JOIN invoiceitems
ON invoiceheader.Id = invoiceitems.HeaderId
WHERE invoiceheader.Warehouse = 3
AND invoiceheader.Date > '0000-00-00 00:00:00'
AND invoiceheader.Date IS NOT NULL
AND invoiceheader.Date > ''
GROUP BY invoiceitems.GoodId
) tmpDate
ON invoiceheader.Date = tmpDate.maxDate
AND invoiceitems.GoodId = tmpDate.GoodId
WHERE invoiceheader.Warehouse = 3
AND invoiceitems.GoodId > 0
ORDER BY
invoiceitems.GoodId ASC,
invoiceheader.Date DESC
The trick was to join by taking into consideration two things - MAX(invoiceheader.Date) and invoiceitems.GoodId - since one GoodId can only appear once inside a specific invoiceheader / invoiceitems JOINing (strict limit imposed on the part of the code which inserts into invoiceitems).
Whether this is the most optimal solution (ignoring the redundant conditions in the query), and whether it would scale well, remains to be seen - it has been tested on tables with ~5000 entries for invoiceheader, ~60000 entries for invoiceitems, and ~4000 entries for goods. Execution time was < 1 sec.
I am storing product attributes in a relational table in a MariaDB database the following way:
I have a main table, called Products which provide the name, description, and other simple information about a product, and another table, ProductAttributes, with the following structure: Id|ProductId|Attribute|Value where Id is an autoincremented primary key, and ProductId is a reference to a row in the Products table.
I can store simple attribute value relations to a product in this way, say ie, height, weight, length of a product. My problems start, when a product's attribute, ie color can have multiple possible values.
I could add multiple lines to the ProductAttributes table when storing multi-valued attributes, ie:
1|yy|color|red
2|yy|color|blue
and from this schema, I could easily retrieve a single product's attributes, but I am having trouble on how to go forward when trying to compare two products based on their attributes.
Is there any other way to store multiple values for a single attribute in a relational database to maintain their searchability?
As of now, to find similar attributed products I am doing a similar query:
SELECT * FROM ProductAttributes base
INNER JOIN ProductAttributes compare ON compare.ProductId != base.ProductId
WHERE base.Attribute = compare.Attribute
AND base.Value = compare.Value
AND base.ProductId = 'x'
GROUP BY compare.ProductId
My problem is, that this query will return the products with a red and blue color, as similar to products with a blue color.
Btw, I can not change my attributes tables to a one attribute per column representation, because I do not know from the get-go how many attributes will I have, and even if I knew, I have way too many possible attributes and differences on each product category, to represent this in a traditional table.
A possible pitfall is, that I also want to compare products to one another with missing attributes. Ie, if a product has a length attribute specified, but another one has no length attribute, they could still be similar. Right now, to make this kind of comparison, in the background, I am transposing my attributes table, to a simple table, and on that table, perform this query:
SELECT b.ProductId as BaseProduct, s.ProductId as SimProduct
FROM tmp_transposed_product_attributes b
CROSS JOIN tmp_transposed_product_attributes s ON b.ProductId != s.ProductId
WHERE (b.attribute1 = s.attribute1 OR b.attribute1 IS NULL OR s.attribute1 IS NULL)
AND (b.attribute2 = s.attribute2 OR b.attribute2 IS NULL OR s.attribute2 IS NULL) ...
If I'm following correctly for the product comparison, I like to use EXISTS or NOT EXISTS to help find things like that, which may also help avoid having to transpose the data.
For example, given this sample table data:
MariaDB [test]> select * from productattributes;
+----+-----------+-----------+-------+
| id | productID | attribute | value |
+----+-----------+-----------+-------+
| 1 | yy | height | 5 |
| 2 | yy | color | red |
| 3 | yy | weight | 10 |
| 4 | yy | length | 6 |
| 5 | yy | color | blue |
| 6 | zz | color | white |
| 7 | zz | height | 5 |
| 8 | zz | length | 8 |
+----+-----------+-----------+-------+
8 rows in set (0.00 sec)
To find all similar attributes between the two, but has different values (removes attribute/values pairs that are the same) use a NOT EXISTS query to same table like so:
MariaDB [test]> SELECT * FROM `productattributes` pA
-> WHERE productID IN ('yy', 'zz')
-> AND NOT EXISTS (SELECT * FROM productattributes pB
-> WHERE pA.attribute = pB.attribute
-> AND pA.value = pB.value
-> AND pA.productID != pB.productID)
-> ORDER BY productID, attribute;
+----+-----------+-----------+-------+
| id | productID | attribute | value |
+----+-----------+-----------+-------+
| 2 | yy | color | red |
| 5 | yy | color | blue |
| 4 | yy | length | 6 |
| 3 | yy | weight | 10 |
| 6 | zz | color | white |
| 8 | zz | length | 8 |
+----+-----------+-----------+-------+
6 rows in set (0.00 sec)
Then to find attribute/value pairs that ARE the same between the two, simply remove the NOT portion of the query:
MariaDB [test]> SELECT * FROM `productattributes` pA
-> WHERE productID IN ('yy', 'zz')
-> AND EXISTS (SELECT * FROM productattributes pB
-> WHERE pA.attribute = pB.attribute
-> AND pA.value = pB.value
-> AND pA.productID != pB.productID)
-> ORDER BY productID, attribute;
+----+-----------+-----------+-------+
| id | productID | attribute | value |
+----+-----------+-----------+-------+
| 1 | yy | height | 5 |
| 7 | zz | height | 5 |
+----+-----------+-----------+-------+
2 rows in set (0.00 sec)
Here's the query without the command line junk:
SELECT * FROM `productattributes` pA
WHERE productID IN ('yy', 'zz')
AND NOT EXISTS (SELECT * FROM productattributes pB
WHERE pA.attribute = pB.attribute
AND pA.value = pB.value
AND pA.productID != pB.productID)
ORDER BY productID, attribute;
EDIT:
To cover the case where there is an attribute that is in one but not the other, then the value check of the query can be removed:
MariaDB [test]> SELECT * FROM `productattributes` pA
-> WHERE productID IN ('yy', 'zz')
-> AND NOT EXISTS (SELECT * FROM productattributes pB
-> WHERE pA.attribute = pB.attribute
-> AND pA.productID != pB.productID)
-> ORDER BY productID, attribute;
+----+-----------+-----------+-------+
| id | productID | attribute | value |
+----+-----------+-----------+-------+
| 3 | yy | weight | 10 |
+----+-----------+-----------+-------+
1 row in set (0.00 sec)
Given three tables with one table serving as a junction table which contains two foreign key columns, I'm trying to make an insert so that, given a TableA.prefix, TableA.number, TableB.prefix, and TableB.number, I can update the JunctionTable.is_archived column for the matching row in JunctionTable:
So while the matching row in JunctionTable currently looks like:
+----------------------------------------------------------------------+
| id | tblA_id | tblB_id | is_archived |
| 3 | 7 | 98 | 0 |
+----------------------------------------------------------------------+
And matching rows in TableA and TableB look like:
TableA
+----------------------------------------------+
| id | prefix | number |
| 7 | CLA | 754 |
+----------------------------------------------+
TableB
+----------------------------------------------+
| id | prefix | number |
| 98 | RED | 221 |
+----------------------------------------------+
I'd like to UPDATE the is_archived value like so:
+----------------------------------------------------------------------+
| id | tblA_id | tblB_id | is_archived |
| 3 | 7 | 98 | 1 |
+----------------------------------------------------------------------+
I've tried a few different statements based on information found here but they aren't valid:
UPDATE JunctionTable
SET is_archived = "1"
WHERE tblAid =
(SELECT id FROM TableA WHERE prefix = "CLA" AND number = 754)
AND tblB.id =
(SELECT id FROM TableB WHERE prefix = "RED" AND number = 221)
UPDATE JunctionTable
SET is_archived = "1"
WHERE (
LEFT JOIN TableA ON JunctionTable.tblA_id=TableA.id
WHERE TableA.course_prefix = "CLA" AND TableA.course_number = 754
LEFT JOIN TableB ON JunctionTable.tblB_id=TableB.id
WHERE TableB.course_prefix = "RED" AND TableB.course_number = 221)
In the first query, it looks like the problems are the names of the ID columns in your Junction table ("tblAid" and "tblB.id"), and you're using double quotes instead of single quotes. This should work:
UPDATE JunctionTable
SET is_archived = 1
WHERE tblA_id =
(SELECT id FROM TableA WHERE prefix = 'CLA' AND number = 754)
AND tblB_id =
(SELECT id FROM TableB WHERE prefix = 'RED' AND number = 221)
I have a entity Article20000Information, with a few fields like: id, description, manufacturer_id, supplier_id
I have another entity, Organisation. It has a list of companies (both manufacturers & suppliers) each with an id.
I also have a page which renders a list of Article20000Information data. Currently, it simply displays the data in the table so:
| id | Description | Manufacturer | Supplier | Price |
|----|-------------|--------------|----------|--------|
| 1 | thing1 | 2 | 5 | 34 |
| 2 | thing2 | 5 | 2 | 23 |
| 3 | thing3 | 3 | 4 | 25 |
What I need is for the manufacturer and supplier column to display the name value from the organisation table, based on the id shown.
What is the best way to go about this?
Got it!
I needed multiple aliases, which I'd guessed, but I also needed to give them AS so that they come out with different column names. This in turn lets twig render the tags.
<?php
namespace Regenerys\QMSBundle\Entity;
use Doctrine\ORM\EntityRepository;
class Article20000InformationRepository extends EntityRepository
{
public function findStuff()
{
return $this->getEntityManager()
->createQuery(
'SELECT
A.id,
A.articleNumber,
A.description,
B.name as manufacturer,
C.name as supplier
FROM
RegenerysQMSBundle:Article20000Information A
LEFT OUTER JOIN RegenerysQMSBundle:Organisation B WITH B.id = A.manufacturer
LEFT OUTER JOIN RegenerysQMSBundle:Organisation C WITH C.id = A.supplier '
)
->getResult();
}
}
Thanks to #Alexandru for his DQL help.
You need to join the two tables based on id condition.
select A.id, A.Description, B.ManufacturName, B.supplierName
from Article20000Information A
left outer join Organisation B
ON B.id = A.id
More info on table joins.
If you are using doctrine, a proper way is to create a Repository class, and there write your joined code proposed by #K139 but in DQL:
class Article20000InformationRepository extends EntityRepository
{
public function findAll()
{
return $this->getEntityManager()
->createQuery(
'SELECT A.id, A.Description, B.ManufacturName, B.supplierName FROM AppBundle:Article20000Information A
LEFT OUTER JOIN AppBundle:Organisation B ON B.id = A.id '
)
->getResult();
}
}
Then in your controller you will use it:
$articles = $em->getRepository('AppBundle:Article20000Information')->findAll();
I have an android sqllite database. It has a text column called chainid.
I'd like to return all columns from rows with DISTINCT chainids || or where chainid is equal to: "none".
So e.g.:
| ID| Name | chainid |
| 1 | widgetname1 | 12345 |
| 2 | widgetname2 | 12345 |
| 3 | widgetname3 | "none" |
| 4 | widgetname4 | 49390 |
| 5 | widgetname5 | 49390 |
Given the above table I would like my query to return 3 rows with all columns for row 2, row3 and row5. -- So DISTINCT on chainid OR where chainid = "none" with the max id selected as the distinct row
Can I achieve this in one query?
I could return all and then process afterwards in java, but this is inefficient.
What about
select *
from table where id in
( select max(id)
from table
group by chainid
where chainid != 'none'
union
select id
from table
where chainid = 'none'
)