MYSQL query that selects from filters - php

I am trying to add a product filter feature. Each product could have an arbitrary number of filters.
I have set up my table like so:
row_id products_id filters_id
1 1 1
2 2 2
3 3 3
4 4 3
5 1 3
So the table has a list of product_ids and corresponding filter_ids. As this seemed like such a simple table, I assumed that the script would be easy. But I am really struggling to get my head around it.
There query I got to, was:
SELECT p.products_id
, p.products_name
, p.products_short_desc
, p.products_price
, p.products_url
, p.products_image
, p.products_short_desc
, p.contact_pricing
, GROUP_CONCAT(ptc.categories_id) category_id
FROM products p
LEFT
JOIN prod_to_cat ptc
ON ptc.products_id = p.products_id
AND ptc.categories_id IN (3)
WHERE p.products_status = 1
GROUP
BY p.products_id
HAVING COUNT(DISTINCT ptc.categories_id) = 1
ORDER
BY p.products_price
As an example, if just one filter was selected, and it was id 3. This failed to work properly, even with much tweaking. It also seems fair to complex, for something I thought would have been so simple.
So essentially, I am trying to work out how, with this table, can I select all the products, that have all the match filters_id's?
So if if filter 1 and 3 were selected, I would want to yield the results of all products that match, i.e. are in the table with their product id and corresponding filter_ids. In this example, it would just return products_id 1, if just filters id 3 was selected it would return product 3,4 and 1. How is this achieved?

It seems fairly straightforward to me:
DROP TABLE IF EXISTS product_filters;
CREATE TABLE product_filters
(product_id INT NOT NULL
,filter_id INT NOT NULL
,PRIMARY KEY(product_id,filter_id)
);
INSERT INTO product_filters VALUES
(1,1),
(2,2),
(3,3),
(4,3),
(1,3);
SELECT product_id
FROM product_filters
WHERE filter_id IN(1,3)
GROUP
BY product_id
HAVING COUNT(*) = 2;
+------------+
| product_id |
+------------+
| 1 |
+------------+
SELECT product_id
FROM product_filters
WHERE filter_id IN(3)
GROUP
BY product_id
HAVING COUNT(*) = 1;
+------------+
| product_id |
+------------+
| 1 |
| 3 |
| 4 |
+------------+

Related

select first row against an id from table mysql query with php [duplicate]

Here's what I'm trying to do. Let's say I have this table t:
key_id | id | record_date | other_cols
1 | 18 | 2011-04-03 | x
2 | 18 | 2012-05-19 | y
3 | 18 | 2012-08-09 | z
4 | 19 | 2009-06-01 | a
5 | 19 | 2011-04-03 | b
6 | 19 | 2011-10-25 | c
7 | 19 | 2012-08-09 | d
For each id, I want to select the row containing the minimum record_date. So I'd get:
key_id | id | record_date | other_cols
1 | 18 | 2011-04-03 | x
4 | 19 | 2009-06-01 | a
The only solutions I've seen to this problem assume that all record_date entries are distinct, but that is not this case in my data. Using a subquery and an inner join with two conditions would give me duplicate rows for some ids, which I don't want:
key_id | id | record_date | other_cols
1 | 18 | 2011-04-03 | x
5 | 19 | 2011-04-03 | b
4 | 19 | 2009-06-01 | a
How about something like:
SELECT mt.*
FROM MyTable mt INNER JOIN
(
SELECT id, MIN(record_date) AS MinDate
FROM MyTable
GROUP BY id
) t ON mt.id = t.id AND mt.record_date = t.MinDate
This gets the minimum date per ID, and then gets the values based on those values. The only time you would have duplicates is if there are duplicate minimum record_dates for the same ID.
I could get to your expected result just by doing this in mysql:
SELECT id, min(record_date), other_cols
FROM mytable
GROUP BY id
Does this work for you?
To get the cheapest product in each category, you use the MIN() function in a correlated subquery as follows:
SELECT categoryid,
productid,
productName,
unitprice
FROM products a WHERE unitprice = (
SELECT MIN(unitprice)
FROM products b
WHERE b.categoryid = a.categoryid)
The outer query scans all rows in the products table and returns the products that have unit prices match with the lowest price in each category returned by the correlated subquery.
I would like to add to some of the other answers here, if you don't need the first item but say the second number for example you can use rownumber in a subquery and base your result set off of that.
SELECT * FROM
(
SELECT
ROW_NUM() OVER (PARTITION BY Id ORDER BY record_date, other_cols) as rownum,
*
FROM products P
) INNER
WHERE rownum = 2
This also allows you to order off multiple columns in the subquery which may help if two record_dates have identical values. You can also partition off of multiple columns if needed by delimiting them with a comma
This does it simply:
select t2.id,t2.record_date,t2.other_cols
from (select ROW_NUMBER() over(partition by id order by record_date)as rownum,id,record_date,other_cols from MyTable)t2
where t2.rownum = 1
If record_date has no duplicates within a group:
think of it as of filtering. Simpliy get (WHERE) one (MIN(record_date)) row from the current group:
SELECT * FROM t t1 WHERE record_date = (
select MIN(record_date)
from t t2 where t2.group_id = t1.group_id)
If there could be 2+ min record_date within a group:
filter out non-min rows (see above)
then (AND) pick only one from the 2+ min record_date rows, within the given group_id. E.g. pick the one with the min unique key:
AND key_id = (select MIN(key_id)
from t t3 where t3.record_date = t1.record_date
and t3.group_id = t1.group_id)
so
key_id | group_id | record_date | other_cols
1 | 18 | 2011-04-03 | x
4 | 19 | 2009-06-01 | a
8 | 19 | 2009-06-01 | e
will select key_ids: #1 and #4
SELECT p.* FROM tbl p
INNER JOIN(
SELECT t.id, MIN(record_date) AS MinDate
FROM tbl t
GROUP BY t.id
) t ON p.id = t.id AND p.record_date = t.MinDate
GROUP BY p.id
This code eliminates duplicate record_date in case there are same ids with same record_date.
If you want duplicates, remove the last line GROUP BY p.id.
This a old question, but this can useful for someone
In my case i can't using a sub query because i have a big query and i need using min() on my result, if i use sub query the db need reexecute my big query. i'm using Mysql
select t.*
from (select m.*, #g := 0
from MyTable m --here i have a big query
order by id, record_date) t
where (1 = case when #g = 0 or #g <> id then 1 else 0 end )
and (#g := id) IS NOT NULL
Basically I ordered the result and then put a variable in order to get only the first record in each group.
The below query takes the first date for each work order (in a table of showing all status changes):
SELECT
WORKORDERNUM,
MIN(DATE)
FROM
WORKORDERS
WHERE
DATE >= to_date('2015-01-01','YYYY-MM-DD')
GROUP BY
WORKORDERNUM
select
department,
min_salary,
(select s1.last_name from staff s1 where s1.salary=s3.min_salary ) lastname
from
(select department, min (salary) min_salary from staff s2 group by s2.department) s3

How to count the number of grouped rows in mysql when I already count the total rows

I have the below table and I want to do the following:
Count the number of times each item appears in the table
Count the DISTINCT number of items
Group the items by name
+-------+---------+
| id | names |
+-------+---------+
| 1 | Apple |
| 2 | Orange |
| 3 | Grape |
| 4 | Apple |
| 5 | Apple |
| 6 | Orange |
| 7 | Apple |
| 8 | Grape |
+-------+---------+
For the 1. and 3. points I have the following query which works quite well:
SELECT * ,
COUNT(names) as count_name,
FROM tbl_products WHERE type = '1'
GROUP BY names
So I get:
Apple (4)
Orange (2)
Grape (2)
Now I want to also count the number of grouped by rows and added a line to count the distinct elements, however there is some problem, since MySQL accepts the query but cannot output a result:
SELECT * ,
COUNT(names) as count_name,
COUNT(DISTINCT names) as count_total
FROM tbl_products WHERE type = '1'
GROUP BY names
Can anyone advice what might be the problem?
EDIT: For more clearance I want to get a table like this:
+-------+---------+------------+-------------+
| id | names | count_ctg | count_total |
+-------+---------+------------+-------------+
| 1 | Apple | 4 | 3 |
| 2 | Orange | 2 | 3 |
| 3 | Grape | 2 | 3 |
+-------+---------+------------+-------------+
Why not just use the query you are using:
SELECT * ,
COUNT(names) as count_name,
FROM tbl_products WHERE type = '1'
GROUP BY names
This query achieves all three objectives.
1) You get a count of the number of each name value in count_name.
2) The number of distinct names values will be equal to the number of rows in the result set , since you are grouping by names. Pretty much any client-side MySQL DB connection library will enable you to retrieve this value.
3) You meet your third criteria of grouping by name by explictly using GROUP BY names
Of course the value for id in the result set is meaningless, you may want to only select names and count_names.
1-.Count the number of times each item appears in the table:
SELECT names, count(names) FROM tbl_products WHERE type = '1' group by names
2-. How many distinct items exist in the table:
SELECT DISTINCT names FROM tbl_products WHERE type = '1'
3-. Group the items by name:
SELECT count(DISTINCT names) as Total FROM tbl_products WHERE type = '1'
As your last EDIT (ALL IN ONE):
SELECT id, names, count(names), total FROM tbl_products, (select count(distinct names) as total from tbl_products) as total WHERE type = '1' group by names
You can get the count of distinct names in a subquery, then OUTER JOIN that thing back into your main query where you already solved for 1 and 3:
SELECT names ,
COUNT(names) as count_name,
Total
FROM tbl_products
OUTER JOIN (SELECT count(DISTINCT names) as Total FROM tbl_products) t2
WHERE type = '1'
GROUP BY names
You can use the SQL Windowing OVER()
This query returns the row_number() function as the id column in the results, and the over(...) for row_number requires an order by clause. You could order by whatever you want, but it most be ordered by something.
;WITH vwGroups (name, Quantity) AS
(
SELECT name
, COUNT(*)
FROM tbl_products
GROUP BY name
)
SELECT ROW_NUMBER() OVER(ORDER BY Quantity DESC, name) AS id
, name
, Quantity AS count_name
, COUNT(*) OVER () AS count_total
FROM vwGroups

mysql getting max min values across multiple tables

I want to get a price range for some products from two tables.
Table1 (products):
pid | products_name | products_model
1 | Product 1.....| model 1
2 | Product 2.....| model 2
Table2 (products_prices):
pid | nerchant_id | price | sale_price | sale_start_date | sale_expire_date
1 | RVTBV | 11.90 | 0 | NULL | NULL
1 | QNUNU | 11.90 | 9.90 | 2013-05-01 | 2013-12-31
1 | YOLOR | 12.90 | 10.90 | 2013-04-01 | 2013-12-31
2 | RVTBV | 20.90 | 0 | NULL | NULL
2 | QNUNU | 29.90 | 25.90 | 2013-04-01 | 2013-12-31
2 | YOLOR | 29.90 | 0 | NULL | NULL
How do I get a result with price range to look like this:
pid | products_name | products_model | min_price | max_price
1 | Product 1.... | model 1 ...... | 10.90 ... | 12.90
2 | Product 2.... | model 2 ...... | 20.90 ... | 29.90
I am using a main query to get products data from table1 then a loop with php foreach product to get the min max values depending on sale start and expiry dates.
It does the work but I don't like subqueries with php. I prefer one MySQL query for performance reasons.
Thanks for helping.
Until now the following statement the best
SELECT p.pid,
p.manufacturers_id,
p.products_image,
p.products_name,
(select min(if(CURRENT_DATE BETWEEN pp.sale_start_date AND pp.sale_expire_date and pp.sale_price>'0', pp.sale_price, pp.price)) from products_prices pp where p.pid = pid) as min_price,
(select max(if(CURRENT_DATE BETWEEN pp.sale_start_date AND pp.sale_expire_date and pp.products_sale_price>'0', pp.sale_price, pp.price)) from products_prices pp where p.pid = pp.pid) as max_price
FROM products p
WHERE p.products_status = '1'
AND p.categories_id = '1'
ORDER BY min_price ASC LIMIT 0, 100
is it possible to optimize it a little bit?
Resumé:
sometimes is the solution so simple that i don´t see it;)
ok the project is an price comparison plattform. Products will be updated hourly or something like that, but not all prices will change. So let´s say 10% will be updated.
But the data must be retrieverd with each visit of the website.
In this case it will be more reads than writes (80-20).
I can add two extra columns to the products table (min_price and max_price) that i update only once if price_data changes.
on one Hand the update will be a little bit more complicated but that´s not a drama. On the other hand the data will be retrieved very fast.
I have testet 3 options based on 15000 products to retrieve 100 rows:
worst: the group by approch (over 1 sec)
good: the approach of arheops (0,12 sec)
best: update once with two extra colums (0,07 sec)
I go with the third option.
thanks for your help anyway!
That depend of you query.
If you query only some values from product, this will be optimal:
select pid,products_name,products_model,
(select min(price) from price where price.pid=product.pid) as min_price,
(select max(price) from price where price.pid=product.pid) as max_price
from product where some_filter_here;
If you need got FULL table, this one is best:
select a.pid,products_name,products_model,min_price,max_price
from product as a
left join (
select pid,min(price) as min_price, max(price) as max_price
from price group by pid
) as b on b.pid=a.pid
SELECT products.*,
MIN(IF(CURRENT_DATE BETWEEN sale_start_date AND sale_expire_date, sale_price, price)) min_price,
MAX(price) max_price
FROM products JOIN products_prices USING (pid)
GROUP BY pid
See it on sqlfiddle.
The below should work for your requirements.
Update:
The first query now also considers the start/end date for the sale price
SELECT
p.pid,
p.products_name,
p.products_model,
pp.price as min_price,
pp.sale_price as max_price
FROM
products p
JOIN products_prices pp ON ( p.pid = pp.pid )
LEFT JOIN products_prices pp2 ON ( pp2.pid = pp.pid AND pp2.price > pp.price AND pp.sale_start_date BETWEEN pp2.sale_start_date AND pp2.sale_expire_date )
WHERE
pp2.pid IS NULL AND NOW() BETWEEN pp.sale_start_date and pp.sale_expire_date
The below one, gets the max, min of the prices avaliable for a product
SELECT
p.pid,
p.products_name,
p.products_model,
MIN( LEAST( pp.price, pp.sale_price) ) as min_price,
MAX( GREATEST( pp.price, pp.sale_price) ) as max_price
FROM
products p
JOIN products_prices pp ON ( p.pid = pp.pid )
WHERE
pp.sale_price <> 0
GROUP BY
p.pid
SQLFIDDLE
I think you're actually after this. I've amended sale_price to NULL in the event that there is no sale which is just as it should be if you're going to insist on including sale information in the products_prices table...
DROP TABLE IF EXISTS products;
CREATE TABLE products
(pid INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,products_name VARCHAR(12) NOT NULL
,products_model VARCHAR(12) NOT NULL
);
INSERT INTO products VALUES
(1 ,'Product 1','model 1'),
(2 ,'Product 2','model 2');
DROP TABLE IF EXISTS products_prices;
CREATE TABLE products_prices
(pid INT NOT NULL
,merchant_id CHAR(5) NOT NULL
,price DECIMAL(5,2)NOT NULL
,sale_price DECIMAL(5,2) NULL
,sale_start_date DATE
,sale_expire_date DATE
,PRIMARY KEY(pid,merchant_id)
);
INSERT INTO products_prices VALUES
(1,'RVTBV',11.90,NULL,NULL,NULL),
(1,'QNUNU',11.90,9.90,'2013-05-01','2013-12-31'),
(1,'YOLOR',12.90,10.90,'2013-04-01','2013-12-31'),
(2,'RVTBV',20.90,NULL,NULL,NULL),
(2,'QNUNU',29.90,25.90,'2013-04-01','2013-12-31'),
(2,'YOLOR',29.90,NULL,NULL,NULL);
SELECT p.*
, MIN(CASE WHEN CURDATE() BETWEEN sale_start_date AND sale_expire_date THEN pp.sale_price ELSE pp.price END) min_price
, MAX(CASE WHEN CURDATE() BETWEEN sale_start_date AND sale_expire_date THEN pp.sale_price ELSE pp.price END) max_price
FROM products p
JOIN products_prices pp
ON pp.pid = p.pid
GROUP
BY p.pid;
+-----+---------------+----------------+-----------+-----------+
| pid | products_name | products_model | min_price | max_price |
+-----+---------------+----------------+-----------+-----------+
| 1 | Product 1 | model 1 | 10.90 | 11.90 |
| 2 | Product 2 | model 2 | 20.90 | 29.90 |
+-----+---------------+----------------+-----------+-----------+

Mysql Count to return zero if no match

I have a set of approx 9000 tutor ids in an array and i have put them in a string like:
(1,2, 3, 4,5,6,7,....9000,9001,9002)
so that i can use them in the following query:
select count(student_assignment.assignment_id) as total_assignment from
student_assignment, assigned_tutor_fk where assignment_status = 'closed'
and assigned_tutor_fk in (1,2, 3, 4,5,6,7,..100,101,103...9000,9001,9002)
group by assigned tutor_fk.
I want to calculate total number of rows associated with each tutor(assigned_tutor_fk), and those tutors which do not have an assignment ie those which do not have assignment
record in the table i want to show their assignment count as 0, and i just want my query to return count and assigned_tutor_fk
my table structure is:
assignment_id | assigned_tutor_fk | assignment_date | student_id |
| 1 | 2 | 22-01-2011 | 4 |
| 2 | 3 | 14-03-2011 | 5 |
Im trying to get my output to be like this:
|total_assignment | assigned_tutor_fk |
| 5 | 4 |
| 2 | 7 |
| 0 | 8 |
Update: I tthink i have not been able to express myself properly,i already have a list of tutors filtered on another criteria, it was very complex to combine these two queries so now i have a set of the tutor id's and i want the sum to be displayed as zero in case the tutors does not have assignment record. please help me on this as i don know wht to do now
SELECT t.id, COUNT(sa.assignment_id)
FROM tutor t
LEFT JOIN
student_assignement sa
ON sa.assignment_tutor_fk = t.id
WHERE t.id IN (1, 2, ..., 9002)
GROUP BY
t.id
dont put the tutors in a string. Select them from a table and do a LEFT JOIN with the assignment and FK table. Without knowing all of your tables, i'm guessing it would look like this:
select
t.tutorId,
count(sa.assignment_id) as total_assignment
from
tutor t
LEFT JOIN
assigned_tutor_fk fk
ON
fk.assigned_tutor_fk = tutor.tutorId
LEFT JOIN
student_assignment sa
ON
fk.assignment_id = sa.id
where
sa.assignment_status = 'closed' OR
ISNULL(sa.assignment_status) -- if join fails.
group by
t.tutorId
Left Join retrieves all your values from the tutor table and merges it with the joined table IF there is a match. If not, NULL is inserted.
SELECT
count(*) as total_assignment,
assigned_tutor_fk
FROM assignmentTable
GROUP BY assigned_tutor_fk

how to select sum, and count from diffrent tables using multiple joins

I need to generate some big data from many tables, regarding filters, at there also i need to get the sum of some columns, and also counts of rows like example
i have 5 records
ID | NAME | DELETED
1 | A | 1
2 | A | 0
3 | A | 1
4 | B | 1
5 | C | 1
I have the query,
SELECT p.name, sum(p.deleted) as del, count(p.id) as numbers from products as p
join other AS b ON p.id=b.id
The output i need is,
The sum of deleted records
NAME | Deletion | Count
A | 2 | 3
B | 1 | 1
C | 1 | 1
Try this ::
SELECT
p.name,
sum(p.deleted) as del,
count(id) as numbers
from products as p
join other AS b ON p.id=b.id
group by p.name
You should not need to join to get your result. This should work:
SELECT name, sum(deleted), count(1)
FROM products
GROUP BY name
SELECT name,
SUM(CASE WHEN deleted = 1 THEN 1 ELSE 0 END) Deletion,
COUNT(*) `COunt`
FROM products
GROUP BY name
OR
SELECT name,
SUM(deleted) Deletion,
COUNT(*) `COunt`
FROM products
GROUP BY name;
SQLFiddle Demo (both queries)

Categories