How to refactor this MySQL code? - mysql

SELECT AVG(`col5`)
FROM `table1`
WHERE `id` NOT IN (
SELECT `id` FROM `table2`
WHERE `col4` = 5
)
group by `col2` having sum(`col3`) > 0
UNION
SELECT MAX(`col5`)
FROM `table1`
WHERE `id` NOT IN (
SELECT `id` FROM `table2`
WHERE `col4` = 5
)
group by `col2` having sum(`col3`) = 0
For readability and performance reasons, I think this code could be refactored. But how?
EDITIONS
removed the outer select
made the first select to return a sum and the second one to return another value
replaced the SUM by AVG

SELECT *
FROM table1 t1
left outer join table2 t2 on t1.id = t2.id and t2.col4 = 5
where t2.id is null
group by t1.col2
having sum(col3) >= 0
The outer select is missing the FROM clause and was not adding anything so I removed it. The NOT IN is inefficient compared to the LEFT OUTER JOIN method so I replaced that. The two UNIONs were easily combined into one by using >=.
Update:
Note the use of UNION ALL rather than UNION. I don't think you want to remove duplicates, and it will perform faster this way.
SELECT AVG(t1.col5)
FROM table1 t1
left outer join table2 t2 on t1.id = t2.id and t2.col4 = 5
where t2.id is null
group by t1.col2
having sum(t1.col3) > 0
UNION ALL
SELECT MAX(t1.col5)
FROM table1 t1
left outer join table2 t2 on t1.id = t2.id and t2.col4 = 5
where t2.id is null
group by t1.col2
having sum(t1.col3) = 0

SELECT t1.* FROM table1 t1 LEFT OUTER JOIN table2 t2 ON t1.id = t2.id
WHERE t2.col4 <> 5 AND SUM(t1.col3) > 0 GROUP BY t1.col2

I'm guessing that you want this:
SELECT * FROM `table`
WHERE col2 IN
(SELECT col2
FROM `table1`
WHERE `id` NOT IN (
SELECT `id` FROM `table2`
WHERE `col4` = 5
)
group by `col2` having sum(`col3`) >= 0
)
When using GROUP BY, you should only return columns that are named in the GROUP BY clause or that include an aggregate function. Therefore, the inner SELECT here gets the col2 values where the sum is greater than or equal to zero, then the outer SELECT grabs the entire row for those values.

SELECT
IF(SUM(`table1`.`col3`) > 0, AVG(`table1`.`col5`), MAX(`table1`.`col5`))
FROM `table1`
LEFT JOIN `table2` ON `table2`.`id` = `table1`.`id` AND `table2`.`col4` = 5
WHERE `table2`.`id` IS NULL
GROUP BY `table1`.`col2`
HAVING SUM(`table1`.`col3`) >= 0
Also * is considered harmful. If you want to make your query forward-compatible with possible future changes to your DB model, specify columns by their names.

Related

How do I make aggregate query return empty set instead of NULL row?

I have a SQL query like this:
SELECT t1.name, MAX(t2.value)
FROM t2
JOIN t1 ON t1.id = t2.t1_id
WHERE t2.t1_id = 1 AND t2.text_id = 16;
However, when t2 selection is empty, it returns a row containing NULL values (because of MAX function returning NULL when called on an empty set). I would like it to return an empty set instead. How can I achieve it?
Having clause fits perfectly here:
SELECT
t1.name, MAX(t2.value)
FROM
t2 JOIN t1 ON t1.id = t2.t1_id
WHERE
t2.t1_id = 1 AND t2.text_id = 16
-- GROUP BY something?
HAVING
MAX(t2.value) IS NOT NULL
Try this in sql server ...
with cte as
(
SELECT t1.name, MAX(t2.value) a
FROM t2
JOIN t1 ON t1.id = t2.t1_id
WHERE t2.t1_id = 1 AND t2.text_id = 16;
)
select * from cte where a is not null
try this in Mysql
select p.* from
(
SELECT t1.name, MAX(t2.value) a
FROM t2
JOIN t1 ON t1.id = t2.t1_id
WHERE t2.t1_id = 1 AND t2.text_id = 16;
) p where p.a is not null
Simply RETURN when you don't want to get a resultset.
IF ((SELECT MAX(t2.Value) FROM t2) > 0)
SELECT t1.name, MAX(t2.value)
FROM t2
JOIN t1 ON t1.id = t2.t1_id
WHERE t2.t1_id = 1 AND t2.text_id = 16
ELSE
RETURN
Totally agree with comment by Gordon Linoff.
To wrap select query in another select query is good idea but when we have many aggregate fields then it will become cumbersome to do and think about that you have to make change like this to more than 10 queries.
You should use GROUP BY clause to query which will help you to fix your mentioned issue but more than that it will help you to get aggregate values(field wise) based on group by clause. In your case you should use GROUP BY t1.name can work.
So your aggregate result set will be group by name.
If you do not use GROUP BY then think that you have name field which have 100 unique values but you'll only 1 row which all rows aggregated data which may be wrong implementation.
Here's some more details about aggregate functions and group by.

MySQL join two tables when one table is missing a row

How do I join these two tables together?
type count
NULL 117
2 1
type count
NULL 807
1 3
2 32
I've tried INNER JOIN, LEFT JOIN, and RIGHT JOIN and I can't quite figure it out. I would like the end result to look like
type count count
NULL 117 807
1 NULL 3
2 1 32
SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
Sourced from this question
You have a problem because NULLs do not match by default. But, you can make them match. Let me assume the first is t1 and the second t2
select t2.type, t1.count, t2.count
from t2 left join
t1
on t2.type = t1.type or (t2.type is null and t1.type is null);
Here is a SQL Fiddle that demonstrates that this correctly answers the OP's question.
The right type of join would be FULL OUTER JOIN, but it does not exist in MySQL.
We can emulate it in the following way:
SELECT t1.type, t1.count, t2.count # exact matches
FROM t1 join t2 on t1.type = t2.type
UNION
select t1.type, t1.count, NULL # t2 not present
FROM t1 left join t2 on t1.type = t2.type
WHERE t2.type IS NULL
UNION
select t2.type, NULL, t2.count # t1 not present
FROM t2 left join t1 on t2.type = t1.type
WHERE t1.type IS NULL
UNION
select NULL, t1.count, t2.count # NULL on both sides
FROM t1, t2
WHERE t1.type IS NULL and t2.type IS NULL;
I have a question for the question ;)
What would happen if the tables would look like follows? (just a small change)
type count
NULL 117
3 333
2 1
type count
NULL 807
1 3
2 32
Because in this case both tables contain records that do not match the other table, so probably joining from one direction is not enough and you need to join the tables from both directions, but then, you might have trouble using data for the 'type' from only one table...
So one solution might be something like:
select if (t1.type is null, t2.type, t1.type) as type, t1.count count1, t2.count count2
from t1
left join t2
on t1.type=t2.type or (t1.type is NULL and t2.type is NULL)
union
select if (t1.type is null, t2.type, t1.type) as type, t1.count count1, t2.count count2
from t1
right join t2
on t1.type=t2.type or (t1.type is NULL and t2.type is NULL);
In addition,
you may also use the coalesce() function instead of if (.. is null, ...) e.g. coalesce(t1.type, t2.type)
you may still need to be careful with union, perhaps you want to keep duplicated records (if there is any) and use union all
http://www.sqlfiddle.com/#!2/302e69/2

moving the subquery to the where or from clause, since I don't need it's values

Right now my sql query gives back a valid array of id's and distance pairs.
I want to change my query to just give back id's. Not sure how to rearrange the subquery in my query.
SELECT distinct t2.id, ( 5 * distance_given1 ) AS distance_calculated
FROM table1 t1
inner join table2 t2 on t1.id = t2.m_id
HAVING distance_calculated < distance_given2
ORDER BY distance_calculated LIMIT 0 , 20
Just move the expression into the WHERE clause.
SELECT DISTINCT t2.id
FROM table1 t1
INNER JION table2 t2 ON t1.id = t2.m_id
WHERE (5 * distance_given1) < distance_given2
ORDER BY (5 * distance_given1)
LIMIT 0, 20
If the expression is more complicated and you don't want to repeat it, you can use a subquery:
SELECT id
FROM (SELECT distinct t2.id, ( 5 * distance_given1 ) AS distance_calculated
FROM table1 t1
inner join table2 t2 on t1.id = t2.m_id
HAVING distance_calculated < distance_given2
ORDER BY distance_calculated
LIMIT 0 , 20) AS x

SQL Select When Something IS NOT IN a Joined Table

Ok. I have some data in one table, that references on multiple occasions some data in another table.
Table1 - main client table
Table2 - user defined fields
Say I have a query that shows a client id from Table1 and all attached / used "used defined fields" from Table2
SELECT t1.Id, t2.udf
FROM Table1 t1
JOIN Table2 t2 ON t1.Id = t2.Index
WHERE t1.EndDate IS NULL AND
t1.Id = '1234.9876' AND
I would get the following for a result...
ID UDF
1234.9876 100
1234.9876 110
1234.9876 118
1234.9876 124
1234.9876 198
1234.9876 256
Now, say I wanted to query this same thing, and get ONLY the ID of the Client, but ONLY IF a value for t2.udf equaling '194' did not exist. So, I would simply get
ID
1234.9876
...as a result.
Make the join a LEFT join and filer where t2.Index is null
SELECT t1.Id
FROM Table1 t1
LEFT JOIN Table2 t2 ON t1.Id = t2.Index
AND t2.UDF = 194 -- has to be before where clause
WHERE t2.Index IS NULL
AND t1.EndDate IS NULL
AND t1.Id = '1234.9876' -- not sure if you want this part
Another way by using NOT EXISTS
SELECT t1.Id
FROM Table1 t1
WHERE NOT EXISTS (SELECT 1 FROM Table2 t2 WHERE t1.Id = t2.INDEX
AND t2.UDF = 194)
AND t1.EndDate IS NULL
AND t1.Id = '1234.9876'
See also JOINS
You can add AND t2.udf not in (select udf from table2 where udf <> '194').
But #SQLMenace solution is better
This should do it.
SELECT DISTINCT t1.Id
FROM Table1 t1
LEFT JOIN Table2 t2 ON t1.Id = t2.Index
WHERE t2.UDF NOT IN (194)
AND t2.Index IS NULL
Select DISTINCT gives you unique entries that satisfy the other conditions, and the first where clause
t2.UDF NOT IN (194)
Normall would return all the rows for the t1 where the t2.UDF is not 194, but it is limited by the Select Distinct to give you only distinct id's
Try the following:
SELECT t1.Id
FROM Table1 t1
JOIN Table2 t2 ON t1.Id = t2.Index
WHERE t1.EndDate IS NULL AND
t1.Id = '1234.9876' AND
t2.udf <> '194'

JOIN - no matter the order

How can i join multiple tables not depending on LEFT or RIGHT join
example:
t1
id | date
-----------
NULL NULL
t2
id | value
------------
1 | bla
SELECT date,value
FROM t2 LEFT JOIN t1
ON t1.id = t2.id where t2.id = 1
-- select is ok
with right join same query return null values ...
SELECT `date`, `value` FROM t2 FULL OUTER JOIN t1 ON t1.id = t2.id
Edit: That will return all records, even if there is no match on those joining fields. Sorry, that will give you syntax error. See my edit down below.
If you only want matches, use inner join:
SELECT `date`, `value` FROM t2 INNER JOIN t1 ON t1.id = t2.id
Edit: There is no FULL OUTER JOIN in MySQL. You will have to simulate it with UNION and combine a LEFT and RIGHT JOIN:
SELECT `date`, `value` FROM t2 LEFT JOIN t1 ON t1.id = t2.id
UNION
SELECT `date`, `value` FROM t2 RIGHT JOIN t1 ON t1.id = t2.id
This will also return your NULL values, but they will not match, as NULL != NULL
You must use FULL OUTER JOIN synstax. In fact Left JOIN returns all results in Left column, whereas right join returns all results in right column. SO with full outer, both are returned.
SELECT date,value FROM t2 FULL OUTER JOIN t1 ON t1.id = t2.id where t2.id = 1