Formating RIGHT JOIN query with multiple joins - mysql

I have SQL query which works:
SELECT table1.bike_id
FROM
(
SELECT bike_id
FROM `bike_filters`
WHERE (`bike_category_id` in (416,11111))
) as table1
RIGHT JOIN (
SELECT bike_id
FROM `bike_filters`
WHERE (`bike_category_id` in (5555,779))
) as table2 ON table1.bike_id = table2.bike_id
GROUP BY bike_id
But I need to add more RIGHT JOINs lines, maybe 5 or more. How to form the query in the right way? I'm searching in the same table, but joining several records in one query to get bike_id, which fits all conditions.
The purpose of this query is to get bike_ids, which has all parameters by the query - bike can have 20 filters, but if user searches by 5 and bike matches them, we get bike_id by this query.
Table Structure:
| id | bike_id | bike_category_id |
| 1 | 3 | 416 |
| 2 | 3 | 779 |
| 3 | 3 | 344 |
| 4 | 3 | 332 |
| 5 | 4 | 444 |
| 5 | 5 | 555 |
I need something like this, this one is incorrect:
SELECT table1.bike_id
FROM
(
SELECT bike_id
FROM `bike_filters`
WHERE (`bike_category_id` IN (416,11111))
) AS table1
RIGHT JOIN (
SELECT bike_id
FROM `bike_filters`
WHERE (`bike_category_id` IN (5555,779))
) AS table2
RIGHT JOIN (
SELECT bike_id
FROM `bike_filters`
WHERE (`bike_category_id` IN (5555,344))
) AS table3
RIGHT JOIN (
SELECT bike_id
FROM `bike_filters`
WHERE (`bike_category_id` IN (5555,332))
) AS table4
GROUP BY bike_id

You can use aggregation, and put all the conditions in the HAVING clause, as follows:
SELECT bike_id
FROM bike_filters
GROUP BY bike_id
HAVING
MAX(bike_category_id in (416,11111)) = 1
AND MAX(bike_category_id in (5555,779)) = 1
This will return all bike_ids that :
have category 416 or 11111
and have category 5555 or 779
You can extend the HAVING clause as per your requirements.

Related

How to join and give a default if a value is in one table but not another?

I am using MySQL, and I'm a newbie!
Hope you guys here can help me with a SQL question.
Say I have 2 tables, and I want to a simple join.
Table 1:
id | service_id | user_number
----------------------------------------------------------------------
0 | 1001 | 10
1 | 1002 | 20
2 | 1004 | 40
Table 2:
id | service_id | error_number
----------------------------------------------------------------------
0 | 1001 | 1000
1 | 1003 | 3000
2 | 1004 | 4000
I want to do a join on service_id and have default value of user_number and error_number to be 0 if it does not exist.
So:
id | service_id | user_number | error_number
----------------------------------------------------------------------
0 | 1001 | 10 | 1000
1 | 1002 | 20 | 0
3 | 1003 | 0 | 3000
2 | 1004 | 40 | 4000
I tried some queries, but they kept giving me null instead of 0.
Thanks a lot.
Here you should use union first, then do aggregation:
select t.`service_id`, sum(t.`user_number`) as `user_number`, sum(t.`error_number`) as `error_number`
from (
select `service_id`, `user_number`, 0 as `error_number` from t1
union
select `service_id`, 0 as `user_number`, `error_number` from t2
) t
group by `service_id`
demo here.
You can try this one, mate:
SELECT
t1.id,
t1.service_id,
COALESCE(tb1.user_number, 0) `user_number`,
COALESCE(tb2.error_number, 0) `error_number`
FROM
(
SELECT id, service_id
FROM table1
UNION
SELECT id, service_id
FROM table2
) t1
LEFT JOIN table1 tb1 ON tb1.service_id = t1.service_id
LEFT JOIN table2 tb2 ON tb2.service_id = t1.service_id;
Try this:
select COALESCE(t1_service,t2_service ) as service_id, COALESCE(user_number,0) as user_number , COALESCE(error_number,0) as error_number
from (
select t1.service_id as t1_service , t1.user_number , t2.error_number, t2.service_id as t2_service
from table_1 t1
LEFT OUTER JOIN table_2 t2
on t1.service = t2.service
union
select t1.service_id as t1_service , t1.user_number , t2.error_number, t2.service_id as t2_service
from table_1 t1
Right OUTER JOIN table_2 t2
on t1.service = t2.service
)z1
order by service_id

mysql subquery not producing all results

I have two tables: contacts and client_profiles. A contact has many client_profiles, where client_profiles has foreign key contact_id:
contacts:
mysql> SELECT id,first_name, last_name FROM contacts;
+----+-------------+-----------+
| id | first_name | last_name |
+----+-------------+-----------+
| 10 | THERESA | CAMPBELL |
| 11 | donato | vig |
| 12 | fdgfdgf | gfdgfd |
| 13 | some random | contact |
+----+-------------+-----------+
4 rows in set (0.00 sec)
client_profiles:
mysql> SELECT id, contact_id, created_at FROM client_profiles;
+----+------------+---------------------+
| id | contact_id | created_at |
+----+------------+---------------------+
| 6 | 10 | 2014-10-09 17:17:43 |
| 7 | 10 | 2014-10-10 11:38:01 |
| 8 | 10 | 2014-10-10 12:20:41 |
| 9 | 10 | 2014-10-10 12:24:19 |
| 11 | 12 | 2014-10-10 12:35:32 |
+----+------------+---------------------+
I want to get the latest client_profiles for each contact. That means There should be two results. I want to use subqueries to achieve this. This is the subquery I came up with:
SELECT `client_profiles`.*
FROM `client_profiles`
INNER JOIN `contacts`
ON `contacts`.`id` = `client_profiles`.`contact_id`
WHERE (client_profiles.id =
(SELECT `client_profiles`.`id` FROM `client_profiles` ORDER BY created_at desc LIMIT 1))
However, this is only returning one result. It should return client_profiles with id 9 and 11.
What is wrong with my subquery?
It looks like you were trying to filter twice on the client_profile table, once in the JOIN/ON clause and another time in the WHERE clause.
Moving everything in the where clause looks like this:
SELECT `cp`.*
FROM `contacts`
JOIN (
SELECT
`client_profiles`.`id`,
`client_profiles`.`contact_id`,
`client_profiles`.`created_at`
FROM `client_profiles`
ORDER BY created_at DESC
LIMIT 1
) cp ON `contacts`.`id` = `cp`.`contact_id`
Tell me what you think.
Should be something like maybe:
SELECT *
FROM `client_profiles`
INNER JOIN `contacts`
ON `contacts`.`id` = `client_profiles`.`contact_id`
GROUP BY `client_profiles`.`contact_id`
ORDER BY created_at desc;
http://sqlfiddle.com/#!2/a3f21b/9
You need to prequery the client profiles table grouped by each contact.. From that, re-join to the client to get the person, then again to the client profiles table based on same contact ID, but also matching the max date from the internal prequery using max( created_at )
SELECT
c.id,
c.first_name,
c.last_name,
IDByMaxDate.maxCreate,
cp.id as clientProfileID
from
( select contact_id,
MAX( created_at ) maxCreate
from
client_profiles
group by
contact_id ) IDByMaxDate
JOIN contacts c
ON IDByMaxDate.contact_id = c.id
JOIN client_profiles cp
ON IDByMaxDate.contact_id = cp.contact_id
AND IDByMaxDate.maxCreate = cp.created_at

MySQL: Group By multiple fields and count

This type of question is answered in post "MySQL: Group By & Count Multiple Fields"
EDIT : Sample Query Used
SELECT actors.id AS actor_id, actors.act_name AS actor_name, details.registration_id AS
registration from games INNER JOIN actors ON actors.id = games.actor_id INNER JOIN
details ON details.id = games.detail_id WHERE 'some cond' GROUP BY registration, actor_id;
But, I'm unable to achieve it in my case. My table data is little different (I'm grouping the table by registration, actor_id). eg:
actor_id | actor_name | registration
----------------------------------------
189 | ABC | 1234-1234
189 | ABC | 4567-1234
189 | ABC | 7890-4321
169 | DEF | 1111-5643
169 | DEF | 1111-5643
and I expect the output as below
actor_id | actor_name | registration | actor_count
------------------------------------------------------
189 | ABC | 1234-1234 | 3
189 | ABC | 4567-1234 | 3
189 | ABC | 7890-4321 | 3
169 | DEF | 1111-5643 | 2
169 | DEF | 1111-5643 | 2
That is actor ABC has 3 occurrences in table and DEF has 2 occurrences, etc
Instead when I use count(*) I get an expected count of 1 in each row
But, Is there a way to achieve the above output?
You could achieve this by doing a sub query to the same table. Maybe something like this:
SELECT
actors.actor_id,
actors.actor_name,
actors.registration,
(
SELECT
COUNT(*)
FROM
actors AS innerActors
WHERE innerActors.actor_id=innerActors.actor_id
) AS actor_count
FROM
actors
You can achive your goal by joining your base table to an aggregation subquery (in mysql).
For example:
SELECT
A.actor_id, A.actor_name, A.registration, B.actor_count
FROM
YourTable AS A
INNER JOIN (
SELECT
actor_id, COUNT(1) AS actor_count
FROM
YourTable
GROUP BY
actor_id
) B
ON A.actor_id = B.actor_id
Write a subquery that gets the count for each actor. Then join this with the original table to put the count on each of their rows.
SELECT t1.actor_id, t1.actor_name, t1.registration, t2.actor_count
FROM YourTable AS t1
JOIN (SELECT actor_id, COUNT(*) AS actor_count
FROM YourTable
GROUP BY actor_id) AS t2 ON t1.actor_id = t2.actor_id
DEMO
If you include registration in the grouping, you'll get counts of 1 because the registration is different on each row.

How to replace a UNION in a query for a view?

I have created the following query to use in a view
SELECT
*
FROM
customers c
JOIN
customer_business cb
ON
c.customer_id = cb.customer_id
union
SELECT
*
FROM
customers c
LEFT JOIN
customer_business
ON
business_id=NULL;
It makes his work perfectly. It shows all customers with the business associated, and at the end, shows all customers with the info of the business in null.
customer_id | business_id
--------------------------------
1 | 1
2 | 1
2 | 2
1 | NULL
2 | NULL
3 | NULL
But the problem es that the UNION makes the view has very poor performace.
I tryed to do it with LEFT JOIN but doesnt shows al the customers with business in null, just the ones without any businesses associated
I know that the solution to speed up my view is to remove that UNION, but i cant figure out how.
Can anyone help me?
Thanks
EDIT
Here's an example
Customer Table
customer_id | name
--------------------------------
1 | test1
2 | test2
3 | test3
Customer_business Table
customer_business_id | customer_id | business_id
----------------------------------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 2 | 1
5 | 2 | 2
Expected query result:
name | customer_id | business_id
----------------------------------------------------------
test1 | 1 | 1
test1 | 1 | 2
test1 | 1 | 3
test2 | 2 | 1
test2 | 2 | 2
test1 | 1 | NULL
test2 | 2 | NULL
test3 | 3 | NULL
Updating it based on the comments below and the output you want.
Note that I have used UNION ALL which is faster than UNION as UNION uses DISTINCT to get unique records which in your case doesn't apply. Also, make sure customer_id is PK in Customer table and try adding non-unique index on customer_id in Customer_Business table and it should help with performance.
SELECT name,
C.customer_id,
business_id
FROM Customer C
INNER JOIN Customer_Business CB
ON C.customer_id = CB.customer_id
UNION ALL
SELECT name,
C.customer_id,
NULL
FROM Customer C
Excluding the union which we know that is not performant the other thing that slows down you query is the statement in the second query ON idbusiness = NULL.
I propose to edit you query like this and see the performance as a view:
SELECT c.customer_id, idbusiness
FROM customers c
JOIN customer_business cb ON c.customer_id = cb.customer_id
UNION
SELECT customer_id, NULL
FROM customers c
EDIT:
Looking for an alternative you could try this, it should return the same output (i've changed null values with 0) but i don't think it's faster:
SELECT c.customer_id, idbusiness
FROM customers c
INNER JOIN (
SELECT customer_id, idbusiness
FROM customer_business
UNION
SELECT 0 , 0
)b ON ( c.customer_id = b.customer_id )
OR (
b.idbusiness =0
)
Eventually you could try to put into a view only the subquery b or delete the union by putting the values 0,0 as a record in table customer_business.

MySQL same Query multiple tables

I need to query different tables that have the same columns but different content.
Table A:
ID DocDate Type
1 2013-05-01 A
2 2013-05-01 B
3 2013-05-02 D
4 2013-05-04 D
Table B:
ID DocDate Type
1 2013-05-01 F
2 2013-05-03 G
3 2013-05-03 G
4 2013-05-05 H
What I need:
COUNT(Tablea.ID) COUNT(Tableb.ID) DocDate
2 1 2013-05-01
1 NULL 2013-05-02
NULL 2 2013-05-03
1 NULL 2013-05-04
NULL 1 2013-05-05
Any help would be really appreciated.
Try
SELECT d.docdate, a.total totala, b.total totalb
FROM
(
SELECT docdate
FROM tablea
UNION
SELECT docdate
FROM tableb
) d LEFT JOIN
(
SELECT docdate, COUNT(*) total
FROM tablea
GROUP BY docdate
) a ON d.docdate = a.docdate LEFT JOIN
(
SELECT docdate, COUNT(*) total
FROM tableb
GROUP BY docdate
) b ON d.docdate = b.docdate
 ORDER BY d.docdate
Output:
| DOCDATE | TOTALA | TOTALB |
--------------------------------
| 2013-05-01 | 2 | 1 |
| 2013-05-02 | 1 | (null) |
| 2013-05-03 | (null) | 2 |
| 2013-05-04 | 1 | (null) |
| 2013-05-05 | (null) | 1 |
Here is SQLFiddle demo
There are a couple of ways to get this result.
The most efficient query to return the specified rows is likely going to be:
SELECT NULLIF(SUM(c.cnt_a_id),0) AS cnt_a_id
, NULLIF(SUM(c.cnt_b_id),0) AS cnt_b_id
, c.DocDate
FROM (
SELECT COUNT(a.ID) AS cnt_a_id
, 0 AS cnt_b_id
, a.DocDate AS DocDate
FROM Table_A a
GROUP BY a.DocDate
UNION ALL
SELECT 0
, COUNT(b.ID)
, b.DocDate
FROM Table_B b
GROUP BY b.DocDate
) c
GROUP BY c.DocDate
Suitable covering indexes on (DocDate, ID) of each table will benefit performance on large sets.
Another simpler to understand, but more expensive, would be create the UNION of the tables, and then perform the GROUP BY.
SELECT NULLIF(COUNT(c.a_id)) AS cnt_a_id
, NULLIF(COUNT(c.b_id)) AS cnt_b_id
, c.DocDate
FROM (
SELECT a.ID AS a_id
, NULL + 0 AS b_id
, a.DocDate AS DocDate
FROM Table_A a
UNION ALL
SELECT NULL + 0 AS a_id
, b.ID AS b_id
, b.DocDate AS DocDate
FROM Table_B b
) c
GROUP BY c.DocDate
(This second query is less efficient, because of the way MySQL materializes the query in the inline view as a temporary MyISAM table; this second query basically creates a copy of Table_A and Table_B concatenated together, and runs a query against that.
The first query is little different, in that it produces smaller sets to be concatenated together.