I have two tables:
products product_eans
+--+-----------+ +----------+-------------+
|id|name | |product_id|ean |
+--+-----------+ +----------+-------------+
| 1|hello world| | 1|4053804303361|
+--+-----------+ +----------+-------------+
| 2|hello mars | | 1|4053804304788|
+--+-----------+ +----------+-------------+
| 2|4053804304825|
+----------+-------------+
I now want to count the (unique) products that has the string 4788 in their name or in one of their EANs. The result in the example would be 1 (one product has an EAN that contains the search string 4788)
I have managed this with
SELECT
COUNT(DISTINCT products.id) AS count
FROM
products
WHERE
products.name LIKE "%4788%" OR
(SELECT
GROUP_CONCAT(ean)
FROM
product_eans
WHERE
product_id = product.id) LIKE "%4788%"`
but it’s incredible slow with thousands of rows in both tables.
What is the most efficient way for a query like this?
Using "double-ended wildcards" is never going to be fast because you won't get use of indexing so the tables will be scanned. An inner join is probably the most efficient
SELECT COUNT(DISTINCT e.products_id)
FROM product_eans e
inner join products p on e.products_id = p.id
WHERE e.ean LIKE '%4788%'
OR p.name LIKE '%4788%'
but one other possibility is to avoid the OR in tha wheer clause by using a union query like this:
SELECT
COUNT(*)
FROM (
SELECT
product_id
FROM product_eans
WHERE ean LIKE '%4788%'
UNION
SELECT
id
FROM products
WHERE name LIKE '%4788%'
) d
After being inspired by Used_By_Already, I came across a simple idea:
SELECT
COUNT(DISTINCT products.id) AS count
FROM
products
WHERE
products.name LIKE "%4788%" OR
products.id in (SELECT product_id FROM product_eans WHERE ean "%4788%")
It's super fast now. So thanks to Used_By_Already.
Related
I need to find out users who have either made or received a booking.
I have two tables that look like this:
Users:
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
+----+
Bookings:
+----+-----+-----+
| id | rid | oid |
+----+-----+-----+
| 1 | 1 | 2 |
| 2 | 2 | 1 |
| 3 | 3 | 4 |
+----+-----+-----+
A booking has two users, a 'rider' (rid), and an 'owner' (oid).
The rider and owner can't be the same for each booking but riders can also be owners.
My output should be a list of user IDs that correspond with users who have made or received a booking.
So far I have written
select u.id, b1.rid, b2.oid
from users u
left join bookings b1
on u.id = b1.rid
left join bookings b2
on u.id = b2.oid;
And various other permutations, but I'm not getting the desired result. Any help would be appreciated.
You want all User IDs that are either in Bookings.rid or Bookdings.oid. So you could do something like:
select
users.id
from
users
where
users.id in (select bookings.rid from bookings)
or
users.id in (select bookings.oid from bookings);
You should be able to utilize a UNION clause here.
However, you don't define what the "time window" is, so I am not sure we can come up with a complete solution for you. However, try something like the following:
SELECT
users.id,
bookings.rid,
bookings.oid
FROM
users
LEFT JOIN bookings ON users.id = bookings.rid
UNION ALL
SELECT
users.id,
bookings.rid,
bookings.oid
FROM
users
LEFT JOIN bookings ON users.id = bookings.oid
My output should be a list of user IDs that correspond with users who have made or received a booking.
To do that, you only need to look at the bookings table :
SELECT DISTINCT rid id FROM bookings
UNION ALL SELECT DISTINCT oid FROM bookings
The DISTINCT removes the duplicates returned by each query, and the UNION ALL removes duplicates across both queries.
If you are looking to filter by time frame :
SELECT DISTINCT rid id FROM bookings WHERE some_date BETWEEN :start_date AND :end_date
UNION ALL SELECT DISTINCT oid FROM bookings WHERE some_date BETWEEN :start_date AND :end_date
Where some_date is the field that contains the booking date, and :start_date/end_date are the beginning and the end of the date interval.
I guess there is a name column in Users table.
If you want this too then:
select users.id, users.name from (
select rid userid from bookings
union
select oid userid from bookings
) t inner join users
on users.id = t.userid
group by users.id, users.name
See the demo
If not you only need to scan the bookings table:
select distinct userid from (
select rid userid from bookings
union
select oid userid from bookings
) t
See the demo
I am making a website to compare product prices between different stores. I have created a search function where I want to display the current lowest price and this is where I am a bit stuck.
I have a table Products with the basic information and a table product_store with the prizes in different stores for different products. Below is a basic schema of the database for these tables:
+-----------+ +---------------+
| products | | product_store |
+-----------+ +---------------+
| id | | product_id |
| name | | store_id |
+-----------+ | price |
| created_at |
+---------------+
The product_store table has multiple prices for the same product_id and store_id so I can create a price history.
Now I would like to create a query to get all products and their lowest price at the moment. So, you take the price for each store with the highest created_at, and from this collection I want to get the lowest value.
This is what I have tried so far:
select products.*, prices.price
from products
left join (
SELECT p1.product_id, min(p1.price) as price, p1.created_at
FROM product_store p1
WHERE p1.created_at = (SELECT max(created_at) FROM product_store p2 WHERE p2.store_id = p1.store_id
) as prices
on prices.product_id = products.id
I search for the highest created_at per store and product and get the lowest price for these rows. However this gives some very strange results where the prices get mixed up between the products and some products that have prices don't have any in the results.
Can someone help me to create a good query for this problem?
Thanks in advance. :)
Here is one method. It aggregates the product_store table to get the maximum created date for each store. Then it joins it back to that table to get price and finally does an aggregation in the outer query:
select p.*, min(ps.price)
from products p left join
(select ps.product_id, ps.store_id, max(created_at) as maxca
from product_store ps
group by ps.product_id, ps.store_id
) pssum
on pssum.product_id = p.id left join
product_store ps
on ps.product_id = pssum.product_id and
ps.store_id = pssum.store_id and
ps.created_at = pssum.maxca
group by p.id;
Your product_store table is an example of a slowly changing dimension. If it were set up with a eff_date and end_date (effective date and end date) columns, then the query would be easier to write and probably more efficient in terms of performance.
Let's say that I have the following tables in my MySQL database:
TABLE Products
| id | some_column1 | some_column2 |
TABLE ProductProperties
| id | product_id | name |
Oversimplified, but sufficient. Now I want to get all products with properties. I do:
SELECT * FROM `Products` JOIN `ProductProperties` ON Products.id = ProductProperties.product_id
What do I get?
| id | some_column1 | some_column2 | id | product_id | name |
It's not cool, and I want to make it cool in one of the two ways:
1) To get the array of objects like in Product table, but extended by one more member, which would be the array of properties which matched JOIN. I've sort of figured out already that it's impossible?
2) To get the array like this (I'd still have to iterate over it in PHP to join all properties in one product into one object):
| product_id | some_column1 | some_column2 | property_id | product_id | name |
So I'd like to rename the column ProductProperties.id into ProductProperties.property_id. If I could remove ProductProperties.product_id from the output too, that would be ideal, but for now, I only want the way to rename one column in the output. Or to prefix it by table name. Or something like that.
Doable?
You should explicitly name the columns and not use *. Then, don't return redundant columns:
SELECT p.id as productid, p.some_column1, p.some_column2,
pp.id as ProductPropertiesId, pp.name
FROM `Products` p JOIN `ProductProperties` pp
ON p.id = pp.product_id
Also, table aliases make such a query more readable.
SELECT Products.id product_id,
Products.some_column1,
Products.some_column2,
ProductProperties.id property_id,
ProductProperties.name
FROM `Products`
JOIN `ProductProperties`
ON Products.id = ProductProperties.product_id
Here's my simple SQL question...
I have two tables:
Books
-------------------------------------------------------
| book_id | author | genre | price | publication_date |
-------------------------------------------------------
Orders
------------------------------------
| order_id | customer_id | book_id |
------------------------------------
I'd like to create a query that returns:
--------------------------------------------------------------------------
| book_id | author | genre | price | publication_date | number_of_orders |
--------------------------------------------------------------------------
In other words, return every column for ALL rows in the Books table, along with a calculated column named 'number_of_orders' that counts the number of times each book appears in the Orders table. (If a book does not occur in the orders table, the book should be listed in the result set, but "number_of_orders" should be zero.
So far, I've come up with this:
SELECT
books.book_id,
books.author,
books.genre,
books.price,
books.publication_date,
count(*) as number_of_orders
from books
left join orders
on (books.book_id = orders.book_id)
group by
books.book_id,
books.author,
books.genre,
books.price,
books.publication_date
That's almost right, but not quite, because "number_of_orders" will be 1 even if a book is never listed in the Orders table. Moreover, given my lack of knowledge of SQL, I'm sure this query is very inefficient.
What's the right way to write this query? (For what it's worth, this needs to work on MySQL, so I can't use any other vendor-specific features).
Your query is almost right and it's the right way to do that (and the most efficient)
SELECT books.*, count(orders.book_id) as number_of_orders
from books
left join orders
on (books.book_id = orders.book_id)
group by
books.book_id
COUNT(*) could include NULL values in the count because it counts all the rows, while COUNT(orders.book_id) does not because it ignores NULL values in the given field.
SELECT b.book_id,
b.author,
b.genre,
b.price,
b.publication_date,
coalesce(oc.Count, 0) as number_of_orders
from books b
left join (
select book_id, count(*) as Count
from Order
group by book_id
) oc on (b.book_id = oc.book_id)
Change count(*) to count(orders.book_id)
You're counting the wrong thing. You want to count the non-null book_id's.
SELECT
books.book_id,
books.author,
books.genre,
books.price,
books.publication_date,
count(orders.book_id) as number_of_orders
from books
left join orders
on (books.book_id = orders.book_id)
group by
books.book_id,
books.author,
books.genre,
books.price,
books.publication_date
select author.aname,count(book.author_id) as "number_of_books"
from author
left join book
on(author.author_id=book.author_id)
GROUP BY author.aname;
I don't understand MySQL very well, here are the table structures I am using.
users
id | first_name | last_name | username
| password
categories
id | user_id | name | description
links
id | user_id | category_id | name |
url | description | date_added |
hit_counter
I am trying to return a result set like this, to give information about the category for a user that includes how many links are in it.
id | user_id | name | description | link_count
At the moment I have this query, but it only returns rows for categories that have links. It should return rows for categories that do not have any links (empty categories).
SELECT categories.*, COUNT(links.id)
FROM categories LEFT JOIN links ON
categories.id=links.category_id;
How to do this query? Thanks.
we can't do select table dot "star" with an aggregate.
what you wanna do is something like (pseudocode):
select
categories.field1,
categories.field2,
{etc.}
count(links.id)
from categories
left join links
on categories.id = links.category_id
group by
categories.field1,
categories.field2,
{etc.}
iow: you're missing the group by code-block to get the right aggregate in your query result set.
To mold alien052002's answer to fit your specific question, the following (untested) should work:
select c.id,
c.user_id,
c.name,
c.description,
count(l.link_count)
from categories c
left join links l on l.category_id = c.id
group by c.id, c.user_id, c.name, c.description
try this
SELECT categories.*, COUNT(links.id) FROM categories LEFT JOIN links ON categories.id=links.category_id group by categories.id;