1

My use case is similar to the here. For some reason my code is not working All I want to do is, fetch products, along with it a single product image

Below is the response I receive when I run the query, but I want single row (here id=10 is returned twice because there is two image for the product)

[
  {
        "id": "10",
        "title": "Horlicks Health & Nutrition Drink Jar",
        "image": "url-8.1"
    },
    {
        "id": "10",
        "title": "Horlicks Health & Nutrition Drink Jar",
        "image": "url-8.2"
    }
]

I tried to run this, it works but without GROUP BY

SELECT p.*,ph.url
FROM  products AS p
LEFT JOIN images AS ph 
ON p.id=ph.product_id 
WHERE (p.title LIKE '%hor%')
GROUP BY p.id

Error : Trying to get property 'num_rows' of non-object

Schema

CREATE TABLE `images` (
  `id` int(11) NOT NULL,
  `product_id` int(11) DEFAULT NULL,
  `url` varchar(255) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

INSERT INTO `images` (`id`, `product_id`, `url`) VALUES
(1, 8, 'url-8.1'),
(8, 10, 'url-8.11'),
(9, 10, 'url-8.000');



CREATE TABLE `products` (
  `id` int(11) NOT NULL,
  `title` varchar(150) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

INSERT INTO `products` (`id`, `title`) VALUES
(8, 'Bbetter Vitamin'),
(9, 'Liveasy Essentials'),
(10, 'Horlicks Health');


ALTER TABLE `images`
  ADD PRIMARY KEY (`id`);

ALTER TABLE `products`
  ADD PRIMARY KEY (`id`);

This query also works but it uses subquery, it be cause perfomance bottleneck as its a search API having lots of search conditions

SELECT p.*, ph.url as image
FROM products AS 
LEFT JOIN images AS ph
ON ph.id=(SELECT id FROM images AS pi WHERE pi.product_id = p.id LIMIT 1)
WHERE (p.title LIKE '%hor%')

This query also works but I need ph.url, if I add it then it gives me error. This works SELECT p.*, MIN(ph.url) as image but I want to fetch the first image which matched

SELECT p.*, MIN(ph.id) as image_id
FROM products AS p
LEFT JOIN images AS ph
ON p.id=ph.product_id 
WHERE (p.title LIKE '%hor%')
GROUP BY p.id
Ashiq Dey
  • 309
  • 5
  • 18
  • 1
    Instead of `GROUP BY` you can hackishly do it with a `LIMIT 1` – Zak Sep 13 '22 at 16:52
  • Your GROUP BY is invalid. You typically GROUP BY the same columns as you SELECT, _except those who are arguments to set functions_. – jarlh Sep 13 '22 at 16:53
  • 2
    Your error is not from SQL. It is from whatever programming language you're using to perform the query. We'd need to see that code, but you're probably trying to use an error as a result. Always check for query errors. We'd also need to see the schema for products and images. – Schwern Sep 13 '22 at 16:55
  • @Schwern I have added the schema, can you please check it once – Ashiq Dey Sep 13 '22 at 17:19
  • @Zak cant use LIMIT as I am fetching multiple products, (product search query) – Ashiq Dey Sep 13 '22 at 17:19
  • @AshiqDey Thank you for adding your schema. We also need to see your query code; the error is from your code. Where are you calling `num_rows`? – Schwern Sep 13 '22 at 17:36
  • @AshiqDey "*I want to fetch the first image which matched*" SQL has no inherent order. You need to add an `order by` clause to determine which is "first". – Schwern Sep 13 '22 at 17:40
  • @Schwern I fixed the error by using this `if($result && $result->num_rows !=null)`. now the only goal is to get a single image selected for every product. – Ashiq Dey Sep 13 '22 at 17:46
  • @AshiqDey You've "fixed" the error by ignoring it. Don't just throw out the error. It contains the explanation why the query failed. Print it to a log file. – Schwern Sep 13 '22 at 17:58

1 Answers1

2

Error : Trying to get property 'num_rows' of non-object

This is coming from the code you are using to run the query. The query is invalid, so it returns an error. You're trying to use that error as a result. Probably something like...

result = query.execute(...)
print result.num_rows

You need to check the query execution for an error. I can't tell you how to do that, you'll need to consult the documentation for your query library.


I want to fetch the first image which matched

SQL has no inherent order. If you have an idea of which is "first" you need to add an order by clause.

If you just want one result and it doesn't matter which one, use limit 1.


Now, why doesn't this work...

SELECT p.*,ph.url
...
GROUP BY p.id

And why does this work?

SELECT p.*, MIN(ph.id) as iid
...
GROUP BY p.id

When you use a group by, all non-grouped columns are aggregated into a single result for each thing in the group. You need to tell SQL how to do that aggregation using aggregate functions like min, max, sum and so on.

The first doesn't work because for each id there are multiple urls and you didn't tell SQL how to aggregate the urls. This is the SQL error which you'll see once you check your queries for errors in your code.

Expression #3 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'ph.url' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

You can fix this by applying an aggregate function to ph.url. min will work and will return the first URL alphabetically.

SELECT p.*, MIN(ph.url)
...
GROUP BY p.id

However, group by is not actually returning the first row. It's returning a all rows with the same ID smashed together. If you just want one row, use a limit.

SELECT p.*, ph.url
FROM products AS p
LEFT JOIN images AS ph
ON p.id=ph.product_id 
WHERE (p.title LIKE '%hor%')
limit 1

Because SQL has no inherent order, this will return any matching row. You cannot rely on the ordering. If you need it to be in a specific order, add an order by.

For example, order by ph.id asc will get the image with the lowest ID; presumably this is the first.

SELECT p.*, ph.url
FROM products AS p
LEFT JOIN images AS ph
ON p.id=ph.product_id 
WHERE (p.title LIKE '%hor%')
order by ph.id asc
limit 1

While the order of IDs are somewhat reliable for the order in which the row was inserted, it is not reliable for the ordering according to business rules. For example, if one imports data from another source often those items might have been created in a different order than they're inserted.

Typically one adds a created_at timestamp to their tables, defaulting to current_timestamp, to allow ordering.


Demonstration of all that.

Schwern
  • 153,029
  • 25
  • 195
  • 336
  • you are awesome. thank you for your explanation. But for my case I cant use limit as I want to fetch multiple products. – Ashiq Dey Sep 13 '22 at 18:10
  • @AshiqDey Please add more detail about what result you want to your question, and expand your example to include multiple products. I'm guessing you'll need a [window function](https://www.mysqltutorial.org/mysql-window-functions/). – Schwern Sep 14 '22 at 00:10
  • I changed my mind and decided not to do this way. instead maintain image_id along with product details so i can simply join (Its simpler, better, faster). My query `SELECT p.*, i.url FROM products as p JOIN images AS i ON i.id=p.image_id WHERE p.id=12` – Ashiq Dey Sep 16 '22 at 16:06