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

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.

Related

Make an alias column with custom string in MySQL

I have a MySQL tables like this:
##customer##
+-----------+----+---------+
|customer_id|name|telephone|
+-----------+----+---------+
| 1 |Andi|+62932011|
| 2 |Boby|+62928291|
| 3 |Jane|+62932212|
| 4 |John|+62999021|
| 5 |Beth|+62999021|
| 6 |Noel|+62999021|
+-----------+----+---------+
##plus_membership##
+-----------------+-----------+-------+------------+
|plus_membership_id|customer_id|status |requested_at|
+------------------+-----------+-------+------------+
| 1 | 1 | 1 | 2018-11-01 |
| 2 | 2 | 0 | 2018-11-03 |
| 3 | 4 | 2 | 2018-11-04 |
| 4 | 6 | 1 | 2018-11-05 |
+------------------+-----------+-------+------------+
there are two tables in above structure, the first is the customer with customer_id as the primary key and the second one is the plus_membership which has foreign key customer_id, the plus_membership table is a table to show a request if customer request to become a plus member, status 1 means the customer is apporved to be plus member. I need to select the customer table and add alias column lets say the alias column name is membership , that shows only regular or plus , plus means the customer in plus_membership status is 1, and regular if customer doesnt exist in plus_membership table or status is not 1 in membership table. for example:
SELECT *, .... AS membership FROM customer;
+-----------+----+---------+----------+
|customer_id|name|telephone|membership|
+-----------+----+---------+----------+
| 1 |Andi|+62932011| Plus |
| 2 |Boby|+62928291| Regular |
| 3 |Jane|+62932212| Regular |
| 4 |John|+62999021| Regular |
| 5 |Beth|+62999021| Regular |
| 6 |Noel|+62999021| Plus |
+-----------+----+---------+----------+
You can use Left Join between the two tables, and use Case .. When conditional expressions to evaluate membership accordingly.
Left Join would ensure that all the customer(s) from the customer table are considered, whether they have a corresponding matching row in the plus_membership table or not.
SELECT
c.customer_id,
c.name,
c.telephone,
(CASE WHEN pm.status = 1 THEN 'Plus' ELSE 'Regular' END) AS membership
FROM customer AS c
LEFT JOIN plus_membership AS pm
ON pm.customer_id = c.customer_id
Another approach can be using Correlated Subquery and Exists(). Generally, this would be less efficient than Left Join approach.
SELECT
c.customer_id,
c.name,
c.telephone,
CASE WHEN EXISTS (SELECT 1
FROM plus_membership AS pm
WHERE pm.customer_id = c.customer_id AND
pm.status = 1
)
THEN 'Plus'
ELSE 'Regular'
END AS membership
FROM customer AS c
We use EXISTS or IN to look up data in another table.
select customer_id, name, telephone,
case when customer_id in (select customer_id from plus_membership where status = 1)
then 'Plus' else 'Regular' end as membership
from customer
order by customer_id;

Get total count of records with a mysql join and 2 tables

I have 2 tables that I am trying to join but I am not sure how to make it the most time efficient.
Tasks Table:
nid | created_by | claimed_by | urgent
1 | 11 | 22 | 1
2 | 22 | 33 | 1
3 | 33 | 11 | 1
1 | 11 | 43 | 0
1 | 11 | 44 | 1
Employee Table:
userid | name
11 | EmployeeA
22 | EmployeeB
33 | EmployeeC
Result I am trying to get:
userid | created_count | claimed_count | urgent_count
11 | 3 | 1 | 3
22 | 1 | 1 | 2
33 | 1 | 1 | 2
created_account column will show total # of tasks created by that user.
claimed_count column will show total # of tasks claimed by that user.
urgent_count column will show total # of urgent tasks (created or claimed) by that user.
Thanks in advance!
I would start by breaking this up into pieces and then putting them back together. You can get the created_count and claimed_count using simple aggregation like this:
SELECT created_by, COUNT(*) AS created_count
FROM myTable
GROUP BY created_by;
SELECT claimed_by, COUNT(*) AS claimed_count
FROM myTable
GROUP BY claimed_by;
To get the urgent count for each employee, I would join the two tables on the condition that the employee is either the created_by or claimed_by column, and group by employee. Instead of counting, however, I would use SUM(). I am doing this because it appears each row will be either 0 or 1, so SUM() will effectively count all non-zero rows:
SELECT e.userid, SUM(t.urgent)
FROM employee e
JOIN task t ON e.userid IN (t.created_by, t.claimed_by)
GROUP BY e.userid;
Now that you have all the bits of data you need, you can use an outer join to join all of those subqueries to the employees table to get their counts. You can use the COALESCE() function to replace any null counts with 0:
SELECT e.userid, COALESCE(u.urgent_count, 0) AS urgent_count, COALESCE(crt.created_count, 0) AS created_count, COALESCE(clm.claimed_count, 0) AS claimed_count
FROM employee e
LEFT JOIN(
SELECT e.userid, SUM(t.urgent) AS urgent_count
FROM employee e
JOIN task t ON e.userid IN (t.created_by, t.claimed_by)
GROUP BY e.userid) u ON u.userid = e.userid
LEFT JOIN(
SELECT claimed_by, COUNT(*) AS claimed_count
FROM task
GROUP BY claimed_by) clm ON clm.claimed_by = e.userid
LEFT JOIN(
SELECT created_by, COUNT(*) AS created_count
FROM task
GROUP BY created_by) crt ON crt.created_by = e.userid;
Here is an SQL Fiddle example.

MySQL Filtering rows from three tables

Let's say i've got this database:
book
| idBook | name |
|--------|----------|
| 1 |Book#1 |
category
| idCateg| category |
|--------|----------|
| 1 |Adventures|
| 2 |Science F.|
book_categ
| id | idBook | idCateg | DATA |
|--------|--------|----------|--------|
| 1 | 1 | 1 | (null) |
| 2 | 1 | 2 | (null) |
I'm trying to select only the books which are in category 1 AND category 2
This is what I've got so far:
SELECT book.* FROM book,book_categ
WHERE book_categ.idCateg = 1 AND book_categ.idCateg = 2
Obviously, this giving 0 results becouse each row has only one idCateg it does work width OR but the results are not what I need. I've also tried to use a join, but I just can't get the results I expect.
Here it's the SQLFiddle of my current project, the data at the begining is just a sample.
SQLFiddle
Any help will be really appreciated.
You could double join with a constraint on the category id:
SELECT a.* FROM book AS a
INNER JOIN book_categ AS b ON a.idBook = b.idBook AND b.idCateg = 1
INNER JOIN book_categ AS c ON a.idBook = c.idBook AND c.idCateg = 2
You could use a subquery:
SELECT a.* FROM book AS a
WHERE
(SELECT COUNT(DISTINCT idCateg) FROM book_categ AS b
WHERE b.idBook = a.idBook AND b.idCateg IN (1,2)) = 2
If you are on MySQL as your fiddle implies, you should prefer the join variant, since most joins are much faster in MySQL than subqueries.
edit
This one should also work:
SELECT a.* FROM book a
INNER JOIN book_categ AS b ON a.idBook = b.idCateg
WHERE b.idCateg IN (5, 6)
GROUP BY idBook
HAVING COUNT(DISTINCT b.idCateg) = 2
and should be faster than the two above, although you have to change the last number according to the number of category ids you are requesting.

Finding the MIN value that appears for each unique value in either of two other columns

Given the following (simplified) tables:
People p
id name registered
-----------------------------------
1 Geoff 2011-03-29 12:09:08
2 Phil 2011-04-29 09:03:54
3 Tony 2011-05-29 21:22:23
4 Gary 2011-06-21 22:56:08
...
Items i
date p1id p2id
----------------------------------------
2011-06-29 20:09:44 1 2
2011-06-26 10:45:00 1 3
2011-06-23 12:22:43 2 3
2011-06-22 13:07:12 2 4
...
I'd like:
The earliest single i.date that each p.id appears in either column p1id or p2id; or p.registered if they feature in neither.
So far, I've tried:
CREATE TEMPORARY TABLE temp (id INT);
INSERT INTO temp (id)
SELECT DISTINCT u FROM (
SELECT p1id AS u FROM Items UNION ALL
SELECT p2id AS u FROM Items
)tt;
SELECT registered,id FROM People
WHERE id NOT IN (SELECT id FROM temp);
Which gets me as far as the second part, albeit in a fairly clumsy way; and I'm stuck on the first part beyond some sort of external, scripted iteration through all the values of p.id (ugh).
Can anyone help?
I'm on MySQL 5.1 and there's ~20k people and ~100k items.
One more solution:
SELECT id, name, IF(min_date1 IS NULL AND min_date2 IS NULL, registered, LEAST(COALESCE(min_date1, min_date2), COALESCE(min_date2, min_date1))) date FROM (
SELECT p.id, p.name, p.registered, MIN(i1.date) min_date1, MIN(i2.date) min_date2 FROM people p
LEFT JOIN items i1
ON p.id = i1.p1id
LEFT JOIN items i2
ON p.id = i2.p2id
GROUP BY id
) t;
OR this:
SELECT p.id, p.name, COALESCE(MIN(i.date), p.registered) FROM people p
LEFT JOIN (
SELECT p1id id, date FROM items
UNION ALL
SELECT p2id id, date FROM items
) i
ON p.id = i.id
GROUP BY id;
Result:
+------+-------+---------------------+
| id | name | date |
+------+-------+---------------------+
| 1 | Geoff | 2011-06-26 10:45:00 |
| 2 | Phil | 2011-06-22 13:07:12 |
| 3 | Tony | 2011-06-23 12:22:43 |
| 4 | Gary | 2011-06-22 13:07:12 |
+------+-------+---------------------+
This is tested in Postgres, but I think it ought to work in MySQL with few or no changes:
SELECT p.id,COALESCE(MIN(x.date),p.registered) AS date
FROM p
JOIN (
SELECT p.id,MIN(i.date) AS date
FROM p
JOIN i ON (p.id=i.p1id)
GROUP BY p.id
UNION
SELECT p.id,MIN(i.date) AS date
FROM p
JOIN i ON (p.id=i.p2id)
GROUP BY p.id
) AS x ON x.id = p.id
GROUP BY p.id,p.registered;
Output (given your sample data):
id | date
----+---------------------
3 | 2011-06-23 12:22:43
1 | 2011-06-26 10:45:00
2 | 2011-06-22 13:07:12
4 | 2011-06-22 13:07:12
(4 rows)

How to get total occurrence of a value within its own query?

Let's say we have this query
SELECT * FROM table
And this result from it.
id | user_id
------------
1 | 1
------------
2 | 1
------------
3 | 2
------------
4 | 1
How could I get the count of how often a user_id appears as another field (without some major SQL query)
id | user_id | count
--------------------
1 | 1 | 3
--------------------
2 | 1 | 3
--------------------
3 | 2 | 1
--------------------
4 | 1 | 3
We have this value currently in code, but we are implementing sorting to this table and I would like to be able to sort in the SQL query.
BTW if this is not possible without some major trick, we are just going to skip sorting on that field.
You'll just want to add a subquery on the end, I believe:
SELECT
t.id,
t.user_id,
(SELECT COUNT(*) FROM table WHERE user_id = t.user_id) AS `count`
FROM table t;
SELECT o.id, o.user_id, (
SELECT COUNT(id)
FROM table i
WHERE i.user_id = o.user_id
GROUP BY i.user_id
) AS `count`
FROM table o
I suspect this query as not being a performance monster but it should work.