How can I do a nested select when doing a find in CakePHP?
Here is a simplified data that I have currently.
Listings
id | title | content
----------------------
1 | Listing 1 | ...
Media
id | listing_id | file
----------------------------------
1 | 1 | file 1
2 | 1 | file 2
So when I do this.
$this->Listing->find('all');
This is the output that I get.
id | title | content | id | listing_id | file
------------------------------------------------------------------
1 | Listing 1 | ... | 1 | 1 | file 1
1 | Listing 1 | ... | 1 | 1 | file 2
And the query is as follow
SELECT * FROM `listings` AS `Listing`
LEFT JOIN `media` AS `Image` ON (`Listing`.`id` = `Image`.`listing_id`)
However, I just would love to join it with the first row of the Media table.
Essentially, this is the query that I'm looking for
SELECT * FROM `listings` AS `Listing`
LEFT JOIN `media` AS `Image` ON (`Image`.`id` = (SELECT MIN(`Image`.`id`) FROM `media` AS `Image` WHERE `Listing`.`id` = `Image`.`listing_id`))
How can I achieve that query in CakePHP?
Thanks,
Tee
You could do that using a hasMany association with a custom find query.
In your Listing model you would do something like this (not tested).
public $hasMany = array(
'Image'=>array(
'finderQuery'=>'SELECT MIN(`Image`.`id`) FROM `media` AS `Image` WHERE `Listing`.`id` = `Image`.`listing_id`)'
)
);
Read more -> Custom Finder Query
This is how I got it to work.
$this->Listing->find('all', 'contain' => array('Image' => array('conditions' => array(SELECT MIN(`Image`.`id`) FROM `media` AS `Image` WHERE `Listing`.`id` = `Image`.`listing_id`)))));
Related
I have an Images, Orders and OrderItems table, I want to match for any images, if any has already been bought by the User passed as parameters by displaying true or false in an IsBought column.
Select Images.Id,
Images.Title,
Images.Description,
Images.Location,
Images.PriceIT,
Images.PostedAt,
CASE WHEN OrderItems.ImageId = Images.Id THEN CAST(1 AS BIT)
ELSE CAST(0 AS BIT) END
AS 'IsBought'
FROM Images
INNER JOIN Users as u on Images.UserId = u.Id
LEFT JOIN Orders on Orders.UserId = #userId
LEFT JOIN OrderItems on Orders.Id = OrderItems.OrderId and OrderItems.ImageId = Images.Id
Group By Images.Id,
Images.Title,
Images.Description,
Images.Location,
Images.PriceIT,
Images.PostedAt,
OrderItems.ImageId,
Orders.UserId
When I use this CASE WHEN I have duplicates when the item has been bought where IsBought is True and the duplicate is False.
In the case where the Item has never been bought, there is no duplicates, IsBought is just equal to False
----------------------------------
| User | type |
----------------------------------
| Id | nvarchar(450) |
----------------------------------
| .......|
----------------------------------
----------------------------------
| Orders | type |
----------------------------------
| Id | nvarchar(255) |
----------------------------------
| UserId | nvarchar(450) |
----------------------------------
| ........................... |
----------------------------------
----------------------------------
| OrderItems | type |
----------------------------------
| Id | nvarchar(255) |
----------------------------------
| OrderId | nvarchar(255) |
----------------------------------
| ImageId | int |
----------------------------------
----------------------------------
| Images | type |
----------------------------------
| Id | int |
----------------------------------
| UserId | nvarchar(450) |
----------------------------------
| Title | nvarchar(MAX) |
----------------------------------
| Description| nvarhar(MAX) |
----------------------------------
| ......................... |
----------------------------------
Any ideas on how I could just have one row per Images with IsBought set to true or false but not duplicates?
I would like something like this:
----------------------------------------------------------------------------
| Id | Title | Description | Location | PriceIT | Location | IsBought |
----------------------------------------------------------------------------
| 1 | Eiffel Tower | .... | ...... | 20.0 | Paris | true |
----------------------------------------------------------------------------
| 2 | Tore di Pisa | .... | ...... | 20.0 | Italia | false |
---------------------------------------------------------------------------
| etc ......
---------------------------------------------------------------------------
Your query logic looks suspicious. It is unusual to see a join that consists only of a comparison of a column from the unpreserved table to a parameter. I suspect that you don't need a join to users at all since you seem to be focused on things "bought" by a person and not things "created" (which is implied by the name "author") by that same person. And a group by clause with no aggregate is often a cover-up for a logically flawed query.
So start over. You want to see all images apparently. For each, you simply want to know if that image is associated with any order of a given person.
select img.*, -- you would, or course, only select the columns needed
(select count(*) from Sales.SalesOrderDetail as orddet
where orddet.ProductID = img.ProductID) as [Order Count],
(select count(*) from Sales.SalesOrderDetail as orddet
inner join Sales.SalesOrderHeader as ord
on orddet.SalesOrderID = ord.SalesOrderID
where orddet.ProductID = img.ProductID
and ord.CustomerID = 29620
) as [User Order Count],
case when exists(select * from Sales.SalesOrderDetail as orddet
inner join Sales.SalesOrderHeader as ord
on orddet.SalesOrderID = ord.SalesOrderID
where orddet.ProductID = img.ProductID
and ord.CustomerID = 29620) then 1 else 0 end as [Has Ordered]
from Production.ProductProductPhoto as img
where img.ProductID between 770 and 779
order by <something useful>;
Notice the aliases - it is much easier to read a long query when you use aliases that are shorter but still understandable (i.e., not single letters). I've included 3 different subqueries to help you understand correlation and how you can build your logic to achieve your goal and help debug any issues you find.
This is based on AdventureWorks sample database - which you should install and use as a learning tool (and to help facilitate discussions with others using a common data source). Note that I simply picked a random customer ID value - you would use your parameter. I filtered the query to a range of images to simplify debugging. Those are very simple but effective methods to help write and debug sql.
Please, can someone help me with what is possibly a simple query?
We have two tables with below structure.
Customer table:
+----+-----------+
| id | name |
+----+-----------+
| 1 | customer1 |
| 2 | customer2 |
| 3 | customer3 |
+----+-----------+
Customer role mapping table:
+-------------+-----------------+
| customer_id | customerRole_id |
+-------------+-----------------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 1 |
+-------------+-----------------+
I want to select customers with role id 1 only NOT with role id 1 AND 2.
So, in this case, it would be customer id 2,3,4 & 5. ignoring 1 as it has multiple roles.
Is there a simple query to do this?
Many thanks, for any help offered.
Hmmm, there are several ways to do this.
select c.*
from customers c
where exists (select 1 from mapping m where m.customerid = c.id and m.role = 1) and
not exists (select 1 from mapping m where m.customerid = c.id and m.role <> 1);
If you just want the customer id, a perhaps simpler version is:
select customerid
from mapping
group by customerid
having min(role) = 1 and max(role) = 1;
This solution assumes that role is never NULL.
I have this tables:
products -> hasMany -> categories
Table: products
+----+--------+
| id | title |
+----+--------+
| 1 | Prod 1 |
| 2 | Prod 2 |
| 3 | Prod 3 |
+----+--------+
Table: categories
+----+-------+------------+
| id | title | product_id |
+----+-------+------------+
| 1 | Cat 1 | 1 |
| 2 | Cat 2 | 1 |
| 3 | Cat 3 | 2 |
| 4 | Cat 1 | 1 |
+----+-------+------------+
How can i query the products which are in both categories "Cat 1" AND "Cat 2" in my example i want only find "Prod 1"
My intuition is that you should change the database, so you instead have a habtm relation.
So your tables should looks like this:
Products (id, title)
ProductCategories (product_id, category_id)
Categories (id, title)
So the product can belong to many categories.
Then you can look up your table for products belonging to category with ID = 1 and category with ID = 3, by doing following query:
MySQL query
SELECT Products.id, Products.title
FROM Products
WHERE
Products.id IN
(
SELECT c1.product_id
FROM ProductCategories AS c1
INNER JOIN ProductCategories AS c2
ON c1.product_id = c2.product_id
WHERE c1.category_id = 1 AND c2.category_id = 3
);
Question is similar to this one How to write a MySQL query that returns a temporary column containing flags for whether or not an item related to that row exists in another table
Except that I need to be more specific about which rows exists
I have two tables: 'competitions' and 'competition_entries'
Competitions:
ID | NAME | TYPE
--------------------------------
1 | Example | example type
2 | Another | example type
Competition Entries
ID | USERID | COMPETITIONID
---------------------------------
1 | 100 | 1
2 | 110 | 1
3 | 110 | 2
4 | 120 | 1
I want to select the competitions but add an additional column which specifies whether the user has entered the competition or not. This is my current SELECT statement
SELECT
c.[ID],
c.[NAME],
c.[TYPE],
(CASE
WHEN e.ID IS NOT NULL AND e.USERID = #userid THEN 1
ELSE 0
END
) AS 'ENTERED'
FROM competitions AS c
LEFT OUTER JOIN competition_entries AS e
ON e.COMPETITIONID = c.ID
My desired result set from setting the #userid parameter to 110 is this
ID | NAME | TYPE | ENTERED
-------------------------------------
1 | Example | example type | 1
2 | Another | example type | 1
But instead I get this
ID | NAME | TYPE | ENTERED
-------------------------------------
1 | Example | example type | 0
1 | Example | example type | 1
1 | Example | example type | 0
2 | Another | example type | 1
Because it's counting the entries for all user ids
Fixing your query
SELECT
c.[ID],
c.[NAME],
c.[TYPE],
MAX(CASE
WHEN e.ID IS NOT NULL AND e.USERID = #userid THEN 1
ELSE 0
END
) AS 'ENTERED'
FROM competitions AS c
LEFT OUTER JOIN competition_entries AS e ON e.COMPETITIONID = c.ID
GROUP BY
c.[ID],
c.[NAME],
c.[TYPE]
An alternative is to rewrite it using EXISTS which is pretty much the same but may be easier to understand.
BTW, using single quotes on the column name is deprecated. Use square brackets.
SELECT
c.[ID],
c.[NAME],
c.[TYPE],
CASE WHEN EXISTS (
SELECT *
FROM competition_entries AS e
WHERE e.COMPETITIONID = c.ID
AND e.USERID = #userid) THEN 1 ELSE 0 END [ENTERED]
FROM competitions AS c
I can't figure out any good way to get a list of tag.names and lang which isn't already inserted in combination inside images_urls.
My database looks something like this.
tags
name user_id
+-------+---------+
|hi | 1 |
|friend | 1 |
|friend | 2 |
|people | 2 |
+-------+---------+
users
id lang
+---+------+
| 1 | en |
| 2 | sv |
+---+------+
images_urls
name lang
+--------+------+
| hi | en |
| friend | sv |
+--------+------+
What I would like to have returned would be:
result
name lang
+-------+------+
|friend | en |
|people | sv |
+-------+------+
I have tried something like this:
SELECT tags.name, users.lang
FROM tags, users
WHERE tags.user_id = users.id
AND CONCAT(tags.name, ',', users.lang) NOT IN(
SELECT DISTINCT CONCAT(name, ',', lang) FROM images_urls
)
GROUP BY CONCAT(name, ',', lang)
ORDER BY SUM(tags.count) DESC
LIMIT 20;
I'm not sure why images_urls has a name instead of a user_id, but this should work:
SELECT t.name, u.lang
FROM tags t
JOIN users u ON ( u.id = t.user_id )
LEFT JOIN images_urls iu ON ( iu.name=t.name AND iu.lang=u.lang )
WHERE iu.name IS NULL
Using the LEFT JOIN returns NULL for rows that do not exist in images_urls, so I check for that.
SELECT tags.name, users.lang
FROM tags, users
WHERE tags.user_id = users.id
AND users.name NOT IN (SELECT name from images_urls);
I would suggest images_urls should contain user_id not name, but thats a different issue.
column NOT IN ('name`','name1','name2','name3')
is also valid for testing purposes.