retrieve only records that has join records - mysql

I have following sql query, which retrieves me joined records. However I want to get places which doesnt have joined records, see below:
SELECT `places`.* FROM `places` INNER JOIN `quotes` ON `places`.`id` = `quotes`.`place_id`
WHERE `quotes`.`user_id` = 1 AND
Here comes part I don't know how to write, but I want only to get places where count of quotes = 0 like:
"quotes.count == 0"
How to add another clause to this sql query, to match my requests?

You want an outer join, presumably a left outer join:
SELECT `places`.*
FROM `places` LEFT JOIN
`quotes`
ON `places`.`id` = `quotes`.`place_id` AND
`quotes`.`user_id` = 1
WHERE quotes.place_id is null;
Note that the condition in the where clause has been moved to the on clause. When there is no match, the columns from quotes are filled with NULLs -- which would cause a where condition to fail.

Related

WHERE followed by ON clause

What is the semantic difference between the following queries? To me both seemed similar until I executed them on presto. Is this something specific to presto or am I missing something in the SQL standard?
Form 1: All conditions specified in the ON clause.
SELECT
t1.colA,
t1.colB,
t1.colC,
t2.colD
FROM t1
LEFT OUTER JOIN t2
ON t1.colA = t2.colA
AND t1.colB = t2.colB
AND t1.colE = 1
AND t2.colF = 2;
Form 2: Some conditions specified in the WHERE clause instead.
SELECT
t1.colA,
t1.colB,
t1.colC,
t2.colD
FROM t1
LEFT OUTER JOIN t2
ON t1.colA = t2.colA
AND t1.colB = t2.colB
WHERE
t1.colE = 1
AND t2.colF = 2;
Form 1 results in some rows but form 2 doesn't but shouldn't they be equivalent?
There are two important differences.
First the condition t1.colE = 1. A LEFT JOIN keeps all rows in the first table, regardless of what the ON clause evaluates to. So, t1.colE does not change the number of rows in the result set. However, it does have the strange effect that any columns from t2 when this condition is not true are NULL.
Second the condition t2.colF = 2 has a different effect. This turns the LEFT JOIN into an INNER JOIN, because NULL values do not match the WHERE clause.
When you have a LEFT [OUTER] JOIN and WHERE clause that references the right_table, it is equivalent to an [INNER] JOIN. That is, the following two are equivalent:
-- [inner] join
select ...
from left_table
join right_table
on left_table.cola = right_table.cola
and right_table.colb = 2
-- left [outer] join + where
select ...
from left_table
left join right_table
on left_table.cola = right_table.cola
where right_table.colb = 2
Your 2nd example query has a left join and this condition t2.colF = 2 in the where clause, making it different from the first query.
where the the first includes all rows from t1, the second includes only rows where t1.colE = 1 and t2.colF = 2
The way to think about the difference between the WHERE clause and the ON clause is:
The WHERE clause determines which of the rows produced by the FROM clause should be considered in the query, so it's role is to filter rows before they get processed by a GROUP BY, WINDOW or the SELECT clause.
The ON clause determines whether a pair of rows from the left and right side of a JOIN should be considered for the join operation.
For an INNER JOIN, if the ON clause evaluates to false, the rows are not joined with each other and no result is emitted.
For a LEFT JOIN, if, for a given row on the left side the ON clause evaluates to false for all rows on the right side, a row containing the values from the left side and NULLs for the columns on the right side is emitted. The same logic applies for a RIGHT JOIN, but with the sides reversed.
A FULL JOIN is a combination of LEFT and RIGHT joins.
In your example, the second query, any row produced by the LEFT JOIN that contains NULLs in columns from the right side due to the ON clause evaluating to false will be filtered out by the t2.colF = 2 expression WHERE clause.

MySQL - How to get one of the repeated records given a condition in SQL?

I have the next results from a query. I did this:
Where the user "Adriana Smith" with ID 6 is repeated because she has different contract dates, to do that I did a left join from table bo_users to bo_users_contracts (1:m One to Many Relation). The query is below:
SELECT bo_users.ID, bo_users.display_name, COALESCE (bo_users_contracts.contract_start_date,'-') AS contract_start_date, COALESCE (bo_users_contracts.contract_end_date, '-') AS contract_end_date, COALESCE (bo_users_contracts.current,'-') AS current
FROM bo_users
LEFT JOIN bo_users_contracts ON bo_users.ID = bo_users_contracts.bo_users_id
LEFT JOIN bo_usermeta ON bo_users.ID = bo_usermeta.user_id
WHERE (bo_usermeta.meta_key = 'role' AND bo_usermeta.meta_value = 'member')
But I want to get all users, but from user Adriana I just want to get the occurrence where "current" column = 1.
So the final result would be the 3 user's records:
Alejandro, Rhonda and Adriana (with "current" = 1)
Thank you!
Since you want to limit on a table being outer joined, the limit should be placed on the join itself so the all records from bo_users is retained. (as indicated desired by the outer join)
Essentially the limit is applied before the join so the unmatched records from BO_users to bo_users_contracts are kept. If applied after the join in a where clause the records from BO_user without a matching record would have a null value for current and thus be excluded when the current=1 filter is applied.
In this example the only values which should be in the where would be from table BO_USERS.
I'd even move the bo_usermeta filters to the join or you may lose bo_users; or the left join on the 3rd table should be an inner join.
SELECT bo_users.ID
, bo_users.display_name
, COALESCE (bo_users_contracts.contract_start_date,'-') AS contract_start_date
, COALESCE (bo_users_contracts.contract_end_date, '-') AS contract_end_date
, COALESCE (bo_users_contracts.current,'-') AS current
FROM bo_users
LEFT JOIN bo_users_contracts
ON bo_users.ID = bo_users_contracts.bo_users_id
and bo_users_contracts.current = 1
LEFT JOIN bo_usermeta --This is suspect
ON bo_users.ID = bo_usermeta.user_id
WHERE (bo_usermeta.meta_key = 'role' --this is suspect
AND bo_usermeta.meta_value = 'member') --this is suspect
The lines reading this is suspect are that way because you have a left join which means you want all users from bo_users.. However if a user doesn't have a meta_key or meta_value defined, they would be eliminated. Either change the join to an inner join or move the where clause limits to the join. I indicate this as you're query is "inconsistent" in it's definition leading to ambiguity when later maintained.

How to do INNER JOIN with 2 Column COUNTS equal

I am trying to perform the following query:
SELECT wwpqsr.statistic_ref_id,
wwpqsr.create_time,
wwpqm.name
FROM wp_wp_pro_quiz_statistic_ref AS wwpqsr
INNER JOIN wp_wp_pro_quiz_statistic AS wwpqs
ON ( wwpqs.statistic_ref_id = wwpqsr.statistic_ref_id
AND COUNT(wwpqs.correct_count) AS correct =
COUNT(wwpqs.incorrect_count) AS incorrect)
INNER JOIN wp_wp_pro_quiz_master AS wwpqm
ON (wwpqm.id = wwpqsr.quiz_id)
WHERE wwpqsr.user_id = 1;
I need to do a limit on the result here at the end, that is not being shown right now for functionality purposes, since I need to only get results returned from the p_wp_pro_quiz_statistic table where the count of correct_count equals the count of rows from the incorrect_count column. How can I do this within an INNER JOIN here? All within 1 query? Possible? The above code returns empty result, where it should not be an empty result. How should something like this be done?
As I said in comments, you can't use aggregate functions as a where clause unless it is a field from a subquery. For your case I think you are looking for:
SELECT wwpqsr.statistic_ref_id,
wwpqsr.create_time,
wwpqm.name
FROM wp_wp_pro_quiz_statistic_ref AS wwpqsr
INNER JOIN wp_wp_pro_quiz_statistic AS wwpqs
ON ( wwpqs.statistic_ref_id = wwpqsr.statistic_ref_id )
INNER JOIN wp_wp_pro_quiz_master AS wwpqm
ON (wwpqm.id = wwpqsr.quiz_id)
WHERE wwpqsr.user_id = 1
GROUP
BY wwpqsr.statistic_ref_id,
wwpqsr.create_time,
wwpqm.name
HAVING COUNT(wwpqs.correct_count) = COUNT(wwpqs.incorrect_count);

How to fix a count() in a query with a "group by" clause?

I have a function that gets a SQL code and inserts a count field in it and executes the query to return the number of rows in it. The objective is to have a dynamic SQL code and be able to get its record count no matter what code it has, because I use it in a registry filter window and I never know what code may be generated, because the user can add as many filters as he/she wants.
But as I use the group by clause, the result is wrong because it is counting the number of times a main registry appears because of the use on many join connections.
The result of that code above should only one row with a columns with 10 as result, but I get a new table with the first columns with a 2 in the first row and a 1 on the other rows.
If I take off the group by clause I will receive a 11 as a count result, but the first row will be counted twice.
What should I do to get a single row and the correct number?
SELECT
COUNT(*) QUERYRECORDCOUNT, // this line appears only in the Count() function
ARTISTA.*,
CATEGORIA.NOME AS CATEGORIA,
ATIVIDADE.NOME AS ATIVIDADE,
LOCALIDADE.NOME AS CIDADE,
MATRICULA.NUMERO AS MAP
FROM
ARTISTA
LEFT JOIN PERFIL ON PERFIL.REGISTRO = ARTISTA.ARTISTA_ID
LEFT JOIN CATEGORIA ON CATEGORIA.CATEGORIA_ID = PERFIL.CATEGORIA
LEFT JOIN ATIVIDADE ON ATIVIDADE.ATIVIDADE_ID = PERFIL.ATIVIDADE
LEFT JOIN LOCALIDADE ON LOCALIDADE.LOCALIDADE_ID = ARTISTA.LOCAL_ATIV_CIDADE
LEFT JOIN MATRICULA ON MATRICULA.REGISTRO = ARTISTA.ARTISTA_ID
WHERE
((ARTISTA.SIT_PERFIL <> 'NORMAL') AND (ARTISTA.SIT_PERFIL <> 'PRIVADO'))
GROUP BY
ARTISTA.ARTISTA_ID
ORDER BY
ARTISTA.ARTISTA_ID;
This always gives you the number of rows for any query you have:
Select count(*) as rowcount from
(
Paste your query here
) as countquery
Since your are GROUPING BY ARTISTA.ARTISTA_ID, COUNT(*) QUERYRECORDCOUNT will return records count for each ARTISTA.ARTISTA_ID value.
If you want GLOBAL count, then you need to use a nested query:
SELECT COUNT(*) AS QUERYRECORDCOUNT
FROM (SELECT
ARTISTA.*,
CATEGORIA.NOME AS CATEGORIA,
ATIVIDADE.NOME AS ATIVIDADE,
LOCALIDADE.NOME AS CIDADE,
MATRICULA.NUMERO AS MAP
FROM
ARTISTA
LEFT JOIN PERFIL ON PERFIL.REGISTRO = ARTISTA.ARTISTA_ID
LEFT JOIN CATEGORIA ON CATEGORIA.CATEGORIA_ID = PERFIL.CATEGORIA
LEFT JOIN ATIVIDADE ON ATIVIDADE.ATIVIDADE_ID = PERFIL.ATIVIDADE
LEFT JOIN LOCALIDADE ON LOCALIDADE.LOCALIDADE_ID = ARTISTA.LOCAL_ATIV_CIDADE
LEFT JOIN MATRICULA ON MATRICULA.REGISTRO = ARTISTA.ARTISTA_ID
WHERE
((ARTISTA.SIT_PERFIL <> 'NORMAL') AND (ARTISTA.SIT_PERFIL <> 'PRIVADO'))
GROUP BY
ARTISTA.ARTISTA_ID
ORDER BY
ARTISTA.ARTISTA_ID);
In this case, you may not need to select those many columns.
If you need to retrieve the all records count with details, then better to use two separate queries.

Getting last element from Group By

I have this query...
$sQuery = "
SELECT SQL_CALC_FOUND_ROWS ".str_replace(" , ", " ", implode(", ", $aColumns))."
FROM dominios left join datas on dominios.id_dominio=datas.id_dominio
left join dnss on dominios.id_dominio=dnss.id_dominio
left join entidades_gestoras on dominios.id_dominio=entidades_gestoras.id_dominio
left join estados on dominios.id_dominio=estados.id_dominio
left join ips on dominios.id_dominio=ips.id_dominio
left join quantidade_dnss on dominios.id_dominio=quantidade_dnss.id_dominio
left join responsaveis_tecnicos on dominios.id_dominio=responsaveis_tecnicos.id_dominio
left join titulares on dominios.id_dominio=titulares.id_dominio
WHERE dominios.estado not like 2 and dominios.estado not like 0 AND data_expiracao > '".date("Ymd")."' $sWhere $where
GROUP BY dominio
$sOrder
$sLimit
";
It returns me the results I 'need'...
But the Group By, it show me the first result that appear on the database, and I needed the last...
How can I do this? :s
Edited
This is the final query, without those variables
SELECT SQL_CALC_FOUND_ROWS `datas`.`data_insercao`, `datas`.`data_expiracao`, `datas`.`data_registo`,
`dominios`.`dominio`,
`titulares`.`nome`, `titulares`.`morada`, `titulares`.`email`, `titulares`.`localidade`, `titulares`.`cod_postal`,
`entidades_gestoras`.`nome`, `entidades_gestoras`.`email`,
`responsaveis_tecnicos`.`nome`, `responsaveis_tecnicos`.`email`,
`ips`.`ip`, `dominios`.`id_dominio` FROM dominios left join datas on dominios.id_dominio=datas.id_dominio
left join dnss on dominios.id_dominio=dnss.id_dominio
left join entidades_gestoras on dominios.id_dominio=entidades_gestoras.id_dominio
left join estados on dominios.id_dominio=estados.id_dominio
left join ips on dominios.id_dominio=ips.id_dominio
left join quantidade_dnss on dominios.id_dominio=quantidade_dnss.id_dominio
left join responsaveis_tecnicos on dominios.id_dominio=responsaveis_tecnicos.id_dominio left join titulares on dominios.id_dominio=titulares.id_dominio WHERE dominios.estado not like 2 and dominios.estado not like 0 AND data_expiracao > '20120730' GROUP BY dominio ORDER BY `datas`.`data_insercao` asc LIMIT 0, 10
General considerations
I'm not sure what columsn you have in aColumns, or what table that dominio column comes from. When you group a number of rows using GROUP BY, then the columns you select for your result should either have the same value for all rows of the group (i.e. be functionally dependent), or should be some aggregate function combining the values of all the rows in the group.
Some SQL dialects enforce this. MySQL doesn't, but if you select an unaggregated column which has different values within the group, there are no guarantees as to what value will actually be returned to you. It might come from any row within the group. So there is no way to get the “last” of these rows, as there isn't any inherent order. In simple cases you can use MIN or MAX to select the value you need. In more complicated cases, you'll most likely have to use subqueries to do the selection from within the groups.
For example, this answer computes for every Name (which corresponds to your dominio grouping column) the last value of Action based on an ordering by ascending Time. Or rather the first value using a descending ordering, which is the same.
Your application
As your comment below indicates that you want the maximal id_dominio for each dominio in dominios, I suggest the following:
SELECT …
FROM (SELECT MAX(id_dominio) AS id_dominio
FROM dominios
GROUP BY dominio
WHERE estado <> 2
AND estado <> 0
) domIds
LEFT JOIN datas ON domIds.id_dominio=datas.id_dominio
…
So there will be one subquery to compute the maximal id_dominio for each dominio group, and all subsequent joins can use the IDs from that subquery instead of the full dominio table. If you need other columns from the dominio table as well, you might have to include that in the join again, so that you can get all the values from those row3s whose IDs you selected in the subquery.
By default MySQL sorts records in ascending order, to get last records first you need to sort the records in DESCNDING ORDER:
$sOrder DESC