Getting last element from Group By - mysql

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

Related

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.

JOIN on multiple tables giving duplicate records - MySql

Here is list of my tables and necessary columns
users u .
screen_name,
country,
status
twitter_users_relationship tf. This table have multiple target_screen_name for each screen_name.
screen_name,
target_screen_name,
target_country,
follow_status
user_twitter_action_map ta
screen_name,
action_name,
action_status
user_targeted_countries utc .This table have multiple countries for each screen_name
screen_name,
country_name
I want to get all target_screen_name from twitter_users_relationship that have matched target_country with u.country or utc.country_name
My query so far
SELECT u.screen_name,
u.country,
tf.target_screen_name,
tf.target_country,
ta.action_name,
ta.action_status,
utc.country_name
FROM users u
LEFT JOIN twitter_users_relationship tf
ON u.screen_name=tf.screen_name
LEFT JOIN user_twitter_action_map ta
ON u.screen_name=ta.screen_name
AND ta.action_name='follow'
AND ta.action_status='active'
LEFT JOIN user_targeted_countries utc
ON u.screen_name= utc.screen_name
WHERE u.status = 'active'
AND tf.follow_status = 'pending'
AND tf.target_country != ''
AND tf.target_country IS NOT NULL
AND ( utc.country_name=tf.target_country OR u.country=tf.target_country)
AND u.screen_name = 'my_screen_name';
But this query giving me duplicate record for each entry of countries in user_targeted_countries. If there are 3 counties in user_targeted_countries the it will return 3 duplicate records.
Please let me know what JOIN I need to use with user_targeted_countries to get desired results.
u.country can be different than countries in utc.country_name
UPDATE -
If I removes OR u.country=tf.target_country from the WHERE clause then I get all the matched target_screen_name without duplicate. But I am not sure how to get all those records also that matches with u.country=tf.target_country ?
Depends on the business logic required ..
First, regardless to the question, your query is wrong(Either the LEFT JOIN or the conditions) . When using LEFT JOIN , conditions on the right table should only be specified in the ON clause, which means you need to move all the conditions on tf. and utc. to the ON clause.
Secondly, you can use a GROUP BY clause and choose one of the utc.country_name (different answers will be if you want a specific one, if it doesn't matter, use MAX() on this column).

Returning rows with 0 count

Attempting to return the number of staff that has been booked to work a specific event by using a count function on tblEmployeeBooking ; This value will be used as a comparison against the number of staff required from tblEvent.
Currently, the below statement doesn't return any tblEvent records which have a zero count within tblEmployeeBooking
SELECT e.eventID
, e.staffQuantity
, Count(b.eventID) AS CountOfeventID
FROM tblEmployeeBooking b
LEFT
JOIN tblEvent e
ON b.eventID = e.eventID
GROUP
BY e.eventID
, e.staffQuantity
, b.cancelled
HAVING (((b.cancelled)=0));
You either need to use RIGHT JOIN or switch the order of the tables in your LEFT JOIN. When you use LEFT JOIN, it selects all matching rows from the left table, it's the right table that's allowed to have non-matches in the ON condition.
Also, you need to move any conditions on the table that could have missing rows into the ON clause. Testing them in WHERE or HAVING will filter out those rows because columns are all NULL when there's no match, and that doesn't = 0.
And you shouldn't use any columns from the child table in GROUP BY, because when there's no match those columns will all be NULL, and they'll group together. In this case, there's no point in grouping by tblEmployeeBooking.cancelled because you're only selecting rows where cancelled = 0, so they'll all be in the same group anyway.
So it should be:
SELECT tblEvent.eventID, tblEvent.staffQuantity, Count(tblEmployeeBooking.eventID) AS CountOfeventID
FROM tblEvent
LEFT JOIN tblEmployeeBooking ON tblEmployeeBooking.eventID = tblEvent.eventID AND tblEmployeeBooking.cancelled = 0
GROUP BY tblEvent.eventID, tblEvent.staffQuantity

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.

MySQL include zero rows when using COUNT with GROUP BY

I am trying to perform a query which groups a set of data by an attribute called type_id.
SELECT
vt.id AS voucher_type,
COALESCE(COUNT(v.id), 0) AS vouchers_remaining
FROM
vouchers v
INNER JOIN voucher_types vt
ON vt.id = v.type_id
WHERE
v.sold = 0
GROUP BY vt.id
What I want in the result is the type_id and the number of unsold products remaining for each type. This is working OK provided that there is at least one left, however if there is a zero count row, it is not returned in the result set.
How can I set up a dummy row for those types which do not have any corresponding rows to count?
Any advice would be greatly appreciated.
Thanks
You'll have to use a LEFT JOIN instead of an INNER JOIN. You start by selecting all voucher_types and then left join to find the count.
SELECT
voucher_types.id AS voucher_type,
IFNULL(vouchers_count.vouchers_remaining, 0) AS vouchers_remaining
FROM
voucher_types
LEFT JOIN
(
SELECT
v.type_id AS voucher_type,
COUNT(v.id) AS vouchers_remaining
FROM
vouchers v
WHERE
v.sold = 0
GROUP BY v.type_id
) AS vouchers_count
ON vouchers_count.voucher_type = voucher_types.id
You want an OUTER JOIN (or LEFT JOIN, same difference) instead of an INNER JOIN. That should already do the trick.
Because you're doing an INNER JOIN you automatically exclude types with no corresponding vouchers. You need a RIGHT OUTER JOIN.
Also, as far as I can remember, COUNT will always give you an integer, so there is no need for the COALESCE.
Good luck,
Alin