Remove results from SELECT by conditions on multiple columns - mysql

So I have this feed of products
id man sup product
1 1 1 MacBook
2 1 2 iMac
3 2 1 Windows
4 2 2 Office
and then tables with manufacturers
id manufacturer
1 Apple
2 Microsoft
and suppliers
id supplier
1 TechData
2 Westcoast
Then, for some reasons, I don't want to show a manufacturer's products by a certain supplier, i.e.:
id man sup comment
1 2 1 TechData aren't allowed to sell Microsoft
2 1 2 hide all Apple products from Westcoast
Is there a way, in pure SQL, to show only the products which fall through my filter, in this case MacBook and Office? I believe this isn's just a WHERE NOT (x AND y) as the result will list the remaining combinations.
Thanks a lot!

This is just a variation on Return row only if value doesn't exist, except you're joining on two columns.
SELECT p.product, m.manufacturer, s.supplier
FROM products AS p
JOIN manufacturers AS m ON m.id = p.man
JOIN suppliers AS s ON s.id = p.sup
LEFT JOIN filter AS f ON p.man = f.man AND p.sup = f.sup
WHERE f.id IS NULL
DEMO

You can try this, mate:
First, create the temporary/real container for your custom manufacturer-supplier filter:
-- pivot temp/real table for suppliers - manufacturers
DROP TEMPORARY TABLE IF EXISTS `manufacturers_suppliers`;
CREATE TEMPORARY TABLE `manufacturers_suppliers` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`manufacturer_id` INT(11) UNSIGNED,
`supplier_id` INT(11) UNSIGNED,
PRIMARY KEY (`id`),
UNIQUE KEY (`manufacturer_id` ASC, `supplier_id` ASC)
);
-- populate pivot table
INSERT INTO `manufacturers_suppliers` (`manufacturer_id`, `supplier_id`)
VALUES
-- TechData aren't allowed to sell Microsoft
(2, 1),
-- hide all Apple products from Westcoast
(1, 2);
After that, using the content of the container, you only need to create a standard query for your result set.
-- create result
SELECT
p.id, p.product, # show product detail
s.id, s.supplier, # show supplier detail
m.id, m.manufacturer # show manufacturer detail
FROM
products p
INNER JOIN suppliers s ON s.id = p.sup
INNER JOIN manufacturers m ON m.id = p.man
LEFT JOIN `manufacturers_suppliers` ms ON
ms.manufacturer_id = man.id
AND ms.supplier_id = sup.id
WHERE ms.id IS NULL;
So that every time you have an update to your filter, you only update records not the actual query script. Cheers

Related

Duplicate record issue on query

This is my sample data
-- schema
CREATE TABLE Cart (
Id_cart INT NOT NULL,
Id_product VARCHAR(25)
);
CREATE TABLE Orders (
Id INT,
Id_cart INT NOT NULL,
Id_vendor INT NOT NULL,
status VARCHAR(25),
order_no VARCHAR(25)
);
-- data
INSERT INTO Cart
(Id_cart, Id_product)
VALUES
(1, 'abc002'),
(1, 'abc003')
;
INSERT INTO Orders
(Id, Id_cart,Id_vendor,status,order_no)
VALUES
(1, 1,1, 'pending','aaa001'),
(2, 1,2, 'pending','aaa002')
;
I use this query to show record.
Select c.id_cart,order_no,id_product from cart as c
left join (SELECT id_cart,status,order_no FROM orders) o using(id_cart)
The result i get
id_cart order_no id_product
1 aaa002 abc002
1 aaa001 abc002
1 aaa002 abc003
1 aaa001 abc003
The result i expected
id_cart order_no id_product
1 aaa001 abc002
1 aaa002 abc003
Anything wrong with my query? How can i eliminate the duplicate record?
Let's say i added a column vendor_id to the orders table. Each order_no belong to one vendor and an id_cart will belong to many supplier. When i am trying to display my product, i want to display order_no and my id_product.
E.g. I have a cart belong to two order
an order belong to vendor A (1) and consist of product A and B.
an order belong to vendor B (2) and consist of product C and D.
During display the output should be
order_no | product_id
order1 | A
order1 | B
order2 | C
order2 | D
Now my problem is each order_no will be loop for each product_id. How can i overcome this?
You want to show cart products along with their orders. Carts contain products from different vendors for which exist separate orders. So join the product table to the cart in order to know the vendor and only then join the order table.
select
id_cart,
o.order_no,
id_product
from cart c
join product p using (id_product)
join orders o using (id_cart, id_vendor);
If you also want cart products for which no order has been written yet, make the orders join an outer join.
UPDATE: As you are reporting an issue with "Unknown column 'id_vendor' in 'from clause'", here is the query with ON clauses instead:
select
c.id_cart,
o.order_no,
c.id_product
from cart c
join product p on p.id_product = c.id_product
join orders o on o.id_cart = c.id_cart and o.id_vendor = p.id_vendor;
Simple add GROUP BY to your sql Query
SELECT c.id_cart,order_no,id_product FROM cart AS c
left join (SELECT id_cart,status,order_no FROM orders) o using(id_cart) GROUP BY o.order_no
Example :- http://sqlfiddle.com/#!9/b6b118/6/0

How to Select data trough 2 tables with ONE Query

Every client has their own project but not all of them has project.
I want to select all clients that has project but I do not know how to do it!
I get all clients with this query:
SELECT `clients` FROM `reps` WHERE `clients` != ''
My goal is to get data which has only project
These are my database tables:
Table Clients: (Table name = reps)
1 id varchar(12) // example: stckvrflw
2 ctitle varchar(100) // example: StackOverflow
Table Projects: (Table name = verkocht)
1 id varchar(11) // example: 1
2 title varchar(100) // example: This is an Example
Do you have a solution on my problem?
You have the client in the milestones table, so doesn't this do what you want?
SELECT DISTINCT m.client
FROM `milestones` m;
You have to join the projects and the milestones tables. Since you gave no information on your database shema, the field names have to be adjusted. You also have to check for deleted or hidden flags if you have some:
SELECT c.* FROM clients c
INNER JOIN projects p ON p.client = c.id
INNER JOIN milestones m ON m.project = p.id
GROUP BY c.id

MySQL query using different tables and filters

I have one table called 'vacancies' which has a 'vacancy_id' PK. It looks like this:
create table vacancies
(
vacancy_id int not null auto_increment,
org_id int not null,
name varchar(255) not null comment 'title',
vacancy_visibility_start_date datetime comment 'vacancy visibility date, when it needs to be active on the website',
vacancy_visibility_end_date datetime,
primary key (vacancy_id)
);
Following this I have a couple of other tables which are linked to this one.
create table vacancy_calendar
(
vacancy_calendar_id int not null auto_increment,
vacancy_id int,
date_from datetime not null,
date_to datetime not null,
primary key (vacancy_calendar_id)
);
create table vacancy_interests
(
vacancy_id int,
interest_id int
);
create table vacancy_skills
(
vacancy_id int,
skill_id int
);
All of these tables can contain multiple rows for the same vacancy_id.
My page has different filters which I want to process via AJAX.
I want to have one line per vacancy containing all data I need + it has to match my filtering criteria. However I am not sure how my query has to look like in order to retrieve the result I am looking for.
It is possible to filter on 'interest_id' , 'skill_id', 'date_from' and 'date_to'.
I started with the following query but I am stuck very fast:
SELECT v.*, vi.interest_id
FROM `vacancies` as v
INNER JOIN `vacancy_interests` as vi on v.vacancy_id = vi.vacancy_id
GROUP BY v.vacancy_id
This query will only return me 1 interest_id for a vacancy, even if the vacancy has 3 interest_id rows in the vacancy_interest table. If I remove the GROUP BY statement I will get 3 rows for the same vacancy which is not what I want either.
Ideally I would want the interest_id's to be each in a separate column or in the same field separated by comma's. Or if there are any other possibilities/suggestions feel free to share!
You can use group_concat for get interest_id separated by comma
SELECT v.*, group_concat(vi.interest_id)
FROM `vacancies` as v
INNER JOIN `vacancy_interests` as vi on v.vacancy_id = vi.vacancy_id
GROUP BY v.vacancy_id
Referring to you comment about add where eg:
You can add where condition
SELECT v.*, group_concat(vi.interest_id)
FROM `vacancies` as v
INNER JOIN `vacancy_interests` as vi on v.vacancy_id = vi.vacancy_id
INNER JOIN `vacancy_skills` as vs ON vs.vacancy_id = v.vacancy_id
WHERE vs.skill_id IN (4) AND vi.interest_id IN (1,3)
GROUP BY v.vacancy_id
In this case the gorup_concat is applied on the resulting rows .. because group by perform the related action on the selected resulting rows .

Need a MySQL JOIN to return all categories a product is in, per product, even if only one category is searched for

I have the following tables defined:
CREATE TABLE products (
product_id INTEGER(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
section VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (product_id)
) ENGINE=MyISAM;
CREATE TABLE categories (
category_id INTEGER(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
PRIMARY KEY (category_id)
) ENGINE=MyISAM;
CREATE TABLE product_categories (
product_id INTEGER(11) NOT NULL,
category_id INTEGER(11) NOT NULL,
PRIMARY KEY (product_id, category_id)
) ENGINE=MyISAM;
There are actually many more, and this is part of optimizing much larger, more complex queries. Part of it is moving some slow subqueries into views, which have helped a lot so far.
Queries became very slow when I added the categories/product_categories tables and joined against them when allowing users to search by products.section or categories.category_id. The UI passes those in as search parameters, and I'm trying to get a row per product with its id, name, section, and a comma-separated list of category names associated with it. I was able to make that much faster with the following view and query:
CREATE OR REPLACE
ALGORITHM = MERGE
VIEW view_products_with_categories
AS
SELECT
p.product_id,
p.name,
p.section,
pc.name AS category
products p
LEFT JOIN product_categories pc on p.product_id = pc.product_id
LEFT JOIN categories c ON pc.category_id = c.category_id;
SELECT
product_id,
name,
section,
GROUP_CONCAT(DISTINCT category ORDER BY category) AS categories
FROM view_products_with_categories
GROUP BY product_id;
Let's say we have the following rows:
product_id name section category_id category
332913 Model Train Engine child-and-baby 1160 child-and-baby>baby-and-pre-schooltoys>playsets
332913 Model Train Engine child-and-baby 1308 toys>baby-and-preschool>playsets
332913 Model Train Engine child-and-baby 1312 toys>carstrains-and-planes>cars-and-vehicles
The simple query above gives me the following:
product_id name section categories
332913 Model Train Engine child-and-baby child-and-baby>baby-and-pre-schooltoys>playsets,toys>baby-and-preschool>playsets,toys>carstrains-and-planes>cars-and-vehicles
That's fine, and as expected. However, I'd like the user to be able to search by category_id. Currently, our UI does some auto-complete magic on category names and adds a filter to the dynamically-generated SQL with the category_id in it. Had I left the category_id in the GROUP_CONCAT query, it'd be 1160. Let's say they wanted to search for the second one (1308), so we modify the query like so:
SELECT
product_id,
name,
section,
GROUP_CONCAT(DISTINCT category ORDER BY category) AS categories
FROM view_products_with_categories
WHERE category_id = 1308
GROUP BY product_id;
Now we get the following:
product_id name section categories
332913 Model Train Engine child-and-baby toys>baby-and-preschool>playsets
Again, exactly what you'd expect. However, the customer wants to see all categories associated with the product that has one or more of the categories they're searching for. So, let's make some simplified sample data to show you what I'm looking for:
product_id name section category_id category
1 product_1 section_1 1 category_1
1 product_1 section_1 2 category_2
1 product_1 section_1 3 category_3
2 product_2 section_2 3 category_3
2 product_2 section_2 4 category_4
2 product_2 section_2 5 category_5
If the user searches for category_id = 3, I'd like them to get the following:
product_id name section categories
1 product_1 section_1 category_1, category_2, category_3
2 product_2 section_2 category_3, category_4, category_5
But I'm currently only getting:
product_id name section categories
1 product_1 section_1 category_3
2 product_2 section_2 category_3
I just can't figure out a way to do it without a subquery, whose slowness is the reason I'm moving to views in the first place. I'm hoping there's something blindingly obvious I'm missing, probably due to sleep deprivation, so any help would be appreciated.
UPDATE: I also should mention that it's possible that a product is not in any categories, hence the LEFT JOINs in my code.
The following query works: (although I am not using the view)
select pc1.product_id, products.name, products.section,
group_concat(categories.name)
from products
inner join product_categories pc1
on (pc1.product_id = products.product_id and pc1.category_id = 3)
inner join product_categories pc2
on (pc2.product_id = products.product_id)
inner join categories
on (categories.category_id = pc2.category_id)
group by pc1.product_id, products.name, products.section
If you want to use the view the following will work:
SELECT vpc.product_id,vpc.name,vpc.section,
GROUP_CONCAT(DISTINCT category ORDER BY category) AS categories
FROM view_products_with_categories vpc
inner join product_categories pc
on (pc.product_id = vpc.product_id and pc.category_id=3)
GROUP BY vpc.product_id, vpc.name, vpc.section;

Most efficient way in MySQL to get all associated values from a relation table while filtering after the same values

Simplified I have two tables in MySQL: One holding a sort of person entity and one relation table which associates multiple rights two the person entity. The tables look like:
person
person_id | person_name
1 | Michael
2 | Kevin
person2right
person_id | right_id
1 | 1
1 | 2
1 | 4
2 | 1
2 | 2
What I want to achieve now is getting all persons including all associated rights, which have at least the defined rights - right_id 1 and 4 in this example.
What I have so far is a query with subselect, but I wonder if there is a more efficient way to achieve my goal without the subselect, because of MySQL not being able to use an index when joining a subselect. Here is my query:
SELECT person_name, GROUP_CONCAT(`person2right`.`right_id`) as `all_rights`
FROM `person`
LEFT JOIN `person2right` ON `person`.`person_id` = `person2right`.`person_id`
LEFT JOIN (
SELECT `person_id` FROM `person2right` WHERE `right_id` IN (1, 4)
GROUP BY `person_id` HAVING COUNT(`person2right`.`right_id`) >= 2
) as `p2r` ON `person`.`person_id` = `p2r`.`person_id`
WHERE `p2r`.`person_id` IS NOT NULL GROUP BY `person_id`
Maybe someone has an idea to do it without the subquery. Appriciate your help.
Thx in advance!
This will only select persons (and all their associated rights) who have both rights 1 and 4. Note that it's different from your query in that your query selects all persons (regardless of their rights) and only selects their associated rights if they have rights 1 and 4.
SELECT person_name, GROUP_CONCAT(`person2right`.`right_id`) as `all_rights`
FROM `person`
JOIN `person2right` ON `person`.`person_id` = `person2right`.`person_id`
GROUP BY `person`.`person_id`
HAVING SUM(`right_id` = 4) > 0 AND SUM(`right_id` = 1) > 0
Edit: if the rows in person2right are unique, then you can change your having clause to
HAVING SUM(`right_id` IN (1,4)) = 2
Let's see if additional joins can do the trick:
select person_name, group_concat(distinct p2r.right_id) as all_rights
from person as p
inner join person2right as p2r using (person_id) -- You don't need LEFT JOIN, because you'll only return persons with rights
-- The new stuff starts here: Two new LEFT JOINs to track the rights you want
left join person2right as p2r_1 using (person_id)
left join person2right as p2r_4 using (person_id)
where
-- Here is where you check if the rights exist
(p2r_1.right_id = 1 and p2r_4.right_id = 4)
group by p.person_id;
Take a look to this SQL fiddle example.
you can try checking the rights in the where clause (avoiding the second left join)
...
WHERE 2 = (select count(*)
from person2right
where person_id = person.person_id
and right_id in (1, 4))