mysql - rewrite query with subqueries - mysql

Consider a table Users with columns Id, Name, Surname and a table Actions with columns Ip and Actor. I need to retrieve, for every Ip, the set of users who did as action using that Ip.
What I have now looks like:
SELECT a.ip, (
SELECT GROUP_CONCAT(t.id, '-', t.name, ' ', t.surname) FROM(
SELECT ud.id, ud.name, ud.surname
FROM users_data AS ud
JOIN actions AS a2 ON a2.actor = ud.id
WHERE a2.ip = a.ip
GROUP BY ud.id) AS t
)
FROM actions AS a
WHERE a.ip != '' AND a.ip != '0.0.0.0'
GROUP BY a.ip
It doesn't work because a.ip is unknown in the where clause in the inner subquery.
Do to performance issues, I need to avoid to use DISTINCT.
Any suggestion?

You can rewrite your query as
SELECT n.ip, GROUP_CONCAT( DISTINCT n.your_user SEPARATOR ' -- ') `users` FROM
(
SELECT a.ip AS ip, CONCAT(t.id, '-', t.name, ' ', t.surname) `your_user`
FROM users_data AS ud
JOIN actions AS a ON a.actor = ud.id
) `new_table` n
WHERE n.ip != '' AND n.ip != '0.0.0.0'
GROUP BY n.ip
Note Be aware of that the result is truncated to the maximum length
that is given by the group_concat_max_len system variable, which
has a default value of 1024

have you tried writing the condition a2.ip = a.ip outside the subquery.?
i.e. in the where clause of outer query!

I solved it using this query (still quite slow, so there's still space for improvements...):
SELECT SQL_NO_CACHE t.ip, COUNT(t.id) AS c, GROUP_CONCAT(t.id, '-', t.name, ' ', t.surname, '-', t.designerAt > 0) FROM (
SELECT a.ip, ud.id, ud.name, ud.surname, u.designerAt
FROM actions AS a
JOIN users_data AS ud ON ud.id = a.actor
JOIN users AS u ON u.id = a.actor
WHERE a.ip != ''
AND a.ip != '0.0.0.0'
AND a.actor !=0
GROUP BY a.ip, a.actor
) AS t
GROUP BY t.ip

Related

MySQL IF distinct problem - why are my records not unique?

I have a sql query like this :
SELECT DISTINCT Users.UserID,Users.UserInfo,UserType.UserTypeName,
IF(Users.UserID IN (SELECT DISTINCT Aproved.UserID FROM Aproved WHERE DATE(Aproved.Date)= '2020-07-12'),'İzinli',
IF(Users.UserID IN (SELECT DISTINCT CheckInLog.UserID FROM CheckInLog WHERE DATE(CheckInLog.CheckTime)= '2020-07-12' AND CheckInLog.CheckType=1) ,'Başladı','Giriş Yapmadı') ) AS 'Status' ,
IF(Users.UserID IN (SELECT DISTINCT CheckInLog.UserID FROM CheckInLog WHERE DATE(CheckInLog.CheckTime)= '2020-07-12' AND CheckInLog.CheckType=1),
(SELECT DISTINCT CheckInLog.CheckTime FROM CheckInLog WHERE DATE(CheckInLog.CheckTime)= '2020-07-12' AND CheckInLog.UserID=Users.UserID AND CheckInLog.CheckType=1),' - ') AS 'Start' ,
IF(Users.UserID IN (SELECT DISTINCT CheckInLog.UserID FROM CheckInLog WHERE DATE(CheckInLog.CheckTime)= '2020-07-12' AND CheckInLog.CheckType=2),
(SELECT DISTINCT CheckInLog.CheckTime FROM CheckInLog WHERE DATE(CheckInLog.CheckTime)= '2020-07-12' AND CheckInLog.UserID=Users.UserID AND CheckInLog.CheckType=2),' - ') AS 'End'
FROM Users
JOIN UserType ON Users.UserType=UserType.UserTypeID
Where Users.UserType != 1 and Users.UserType != 2
This give me data like:
But I want data to be like
My check-in log table is like:
CheckType =1 -> Start
CheckType =2 -> End
Please help me guys. Thank you.
Users should be duplicated in the users table, so it is not clear what is going on. However, you can simplify your query considerably:
SELECT u.UserID, u.UserInfo, ut.UserTypeName,
(CASE WHEN s.UserID IN (SELECT a.UserID FROM Aproved a WHERE DATE(Aproved.Date)= '2020-07-12')
THEN 'İzinli'
WHEN u.UserID IN (SELECT cil.UserID FROM CheckInLog cil WHERE DATE(cil.CheckTime)= '2020-07-12' AND cil.CheckType = 1)
THEN 'Başladı'
ELSE 'Giriş Yapmadı'
END) AS Status,
(CASE WHEN u.UserID IN (SELECT cil.UserID FROM CheckInLog cil WHERE DATE(cil.CheckTime) = '2020-07-12' AND cil.CheckType = 1),
THEN (SELECT cil.CheckTime FROM CheckInLog cil WHERE DATE(cil.CheckTime) = '2020-07-12' AND cil.UserID = u.UserID AND cil.CheckType = 1),
ELSE ' - '
END) AS Start ,
(CASE WHEN u.UserID IN (SELECT cil.UserID FROM CheckInLog cil WHERE DATE(c.cil) = '2020-07-12' AND cil.CheckType = 2)
THEN (SELECT cil.CheckTime FROM CheckInLog cil WHERE DATE(cil.CheckTime) = '2020-07-12' AND cil.UserID = u.UserID AND cil.CheckType = 2),
ELSE ' - '
END) AS `End`
FROM Users u JOIN
UserType ut
ON u.UserType = ut.UserTypeID
WHERE u.UserType NOT IN (1, 2);
I'm not sure if this will fix your particular problem. But note the following:
NOT IN is simpler than multiple comparisons.
Single quotes should only be used for string and date constants, not column names.
Table aliases make the query easier to write and read.
SELECT DISTINCT is not appropriate for an IN subquery.
CASE is the standard form for conditional logic in SQL and is more powerful because it allows multiple clauses.

SQL query not displaying the desired result

I have this query which works fine. No errors or what soever. But it doesn't seem to work with last part and doesn't populate the last three fields from contact_data cd.
select c.*,cd.* FROM
(select * from
(select con.id_contact as v,substr(concat(ifnull(con.firstname,''),ifnull(con.lastname,'')),1,4) as s0, null as s1, null as s2
from contact con where id_user=123 and firstname != '' and firstname != ' ' and firstname is not null) as tbl235768 where 1=1 AND v IN
(select v from
(select * from
(select con.id_contact as v,substr(concat(ifnull(con.firstname,''),ifnull(con.lastname,'')),1,4) as s0, null as s1, null as s2
from contact con where id_user=123 and email = '') as tbl235770 where 1=1) as tblAnd) AND v IN
(select v from
(select * from (select con.id_contact as v,substr(concat(ifnull(con.firstname,''),ifnull(con.lastname,'')),1,4) as s0, null as s1, null as s2
from contact con where id_user=123 and mobile != '' and mobile != ' ' and mobile is not null) as tbl235772 where 1=1) as tblAnd)) as tblBucket
left join contact c on c.id_contact=v left join
(select id_contact, group_concat(name) as custom_names,group_concat(value) as custom_values FROM contact_data where
id_user=20 group by id_contact) as cd on cd.id_contact=c.id_contact group by (c.id_contact)
But, if i run the last part only which is the following
select id_contact, group_concat(name) as custom_names,group_concat(value) as custom_values
FROM contact_data where id_user=798 group by id_contact
It gives me desired result. what is wrong with my query? Any help would be greatly apprecited, Thanks.
Edit : i am editing my question after getting a few answers.
I have removed all the nested parts but still no luck.
select c.*,cd.* FROM (select * from (select con.id_contact as v,substr(concat(ifnull(con.firstname,''),ifnull(con.lastname,'')),1,4) as s0, null as s1, null as s2 from contact con where id_user=10879) as tbl235785
where 1=1) as tblBucket left join contact c on c.id_contact=v left join (select id_contact, group_concat(name) as custom_names,group_concat(value) as custom_values
FROM contact_data where id_user=798 group by id_contact) as cd on cd.id_contact=c.id_contact group by (c.id_contact)
Here is your sql made simpler:
All of the 1=1 and SELECT * FROM (sub-query) can go away -- they don't do anything. Then the logic becomes clear -- you just need some additional clauses on your where statement instead of the sub-queries. I think when it is cleaned up like this you can see - in the first select you have id_user=123 and in the second you have id_user=20.
Probably want those to be the same? You don't even need the need the where clause in the 2nd joined select. Since it is joined you should just join this field the value in the outer query.
select c.*,cd.*
FROM (select con.id_contact as v,substr(concat(ifnull(con.firstname,''),ifnull(con.lastname,'')),1,4) as s0, null as s1, null as s2
from contact con
where id_user=123 and
firstname != '' and firstname != ' ' and firstname is not null
and
email = ''
OR
(
mobile != '' and mobile != ' ' and mobile is not null
)
) as tbl235768
left join contact c on c.id_contact=v
left join (select id_contact, group_concat(name) as custom_names,group_concat(value) as custom_values
FROM contact_data
where id_user=20
group by id_contact
) as cd on cd.id_contact=c.id_contact
group by (c.id_contact)
In one place you are matching in id_user=10879 and then in different sub query you are matching on id_user=798, then you join the two and since the id_user fields are both looking at different numbers, you get no matches on your join.

MySQL - Way to set a default return value when NULL is found?

Is there a way in SQL to set a default return value when NULL is returned for part of the results?
Here is my SQL:
SELECT p.id, p.title, concat( u1.meta_value, ' ', u2.meta_value ) as fullname, concat( r.name, ', ', c.name ) as location
FROM modules_profiles p
LEFT JOIN moonlight_usermeta u1 ON p.user_id = u1.user_id AND u1.meta_key = 'first_name'
LEFT JOIN moonlight_usermeta u2 ON p.user_id = u2.user_id AND u2.meta_key = 'last_name'
LEFT JOIN modules_regions r ON r.id = p.region_id
LEFT JOIN modules_countries c ON c.id = p.country_id
WHERE p.certification IN ( 'certified' ) AND p.country_id IN ( 2 )
ORDER BY p.user_id ASC
There are times when there is no region_id set for a given profile; therefore, NULL is returned for location for that respective user_id, even though we do have a country's name (c.name).
Is there a way in this case to just return the c.name only?
Use COALESCE() function like below, it will return the first non NULL value provided in list
COALESCE(col_name, 'default_value')
For your case, do
COALESCE(region_id, c.name)
I think, you are specifically talking about the part
concat( r.name, ', ', c.name ) as location
You can modify this using CASE expression as well
case when r.name is not null and c.name is not null
then concat( r.name, ', ', c.name ) else c.name end as location
You want to use MySQL's IFNULL(value, default) function.
Coalesce could help you
COALESCE(region_id, 'default value')

MySQL Optimizing Query With IN

SELECT `listener` ,
SEC_TO_TIME( SUM( TIME_TO_SEC( `call_time` ) ) ) AS total_time,
COUNT( `listener` ) AS number
FROM calls
WHERE listened_date = '2013-05-09'
AND type in ('column1','column2')
AND id
IN ( SELECT id
FROM calls
GROUP BY CONCAT( name, ' ', when ) )
GROUP BY `listener`
This query is working so slow and making other queries not working in same time. How can i make this lighter?
I think IN make it slower. What is alternative in this case?
Maybe ?:
SELECT c.`listener` ,
SEC_TO_TIME(SUM(TIME_TO_SEC(c.`call_time`))) AS total_time,
COUNT(c.`listener`) AS number
FROM calls c
WHERE c.listened_date = '2013-05-09'
AND c.TYPE IN ('column1',
'column2')
AND EXISTS (SELECT 0
FROM calls c2
WHERE c2.id = c.id)
GROUP BY c.`listener`

Slow MySQL query with subquery from table

I am trying to bring back a string based on an IF statement but it is extremely slow.
It has something to do with the first subquery but I am unsure of how to rearrange this as to bring back the same results but faster.
Here is my SQL:
SELECT IF
(
(
SELECT COUNT(*)
FROM
(
SELECT DISTINCT enquiryId, type
FROM parts_enquiries, parts_service_types AS pst
WHERE parts_enquiries.serviceTypeId = pst.id
) AS parts
WHERE parts.enquiryId = enquiries.id
) > 1, 'Mixed',
(
SELECT DISTINCT type
FROM parts_enquiries, parts_service_types AS pst
WHERE parts_enquiries.serviceTypeId = pst.id AND enquiryId = enquiries.id
)
) AS partTypes
FROM enquiries,
entities
WHERE enquiries.entityId = entities.id
How can I make it faster?
I have modified my original query below, but I am getting the error that subquery returns more than one row:
SELECT
(SELECT
CASE WHEN COUNT(DISTINCT type) > 1 THEN 'Mixed' ELSE `type` END AS type
FROM parts_enquiries
INNER JOIN parts_service_types AS pst ON parts_enquiries.serviceTypeId = pst.id
INNER JOIN enquiries ON parts_enquiries.enquiryId = enquiries.id
INNER JOIN entities ON enquiries.entityId = entities.id
GROUP BY enquiryId) AS partTypes
FROM enquiries,
entities
WHERE enquiries.entityId = entities.id
Please have a look if this query yields the same results:
SELECT
enquiryId,
CASE WHEN COUNT(DISTINCT type) > 1 THEN 'Mixed' ELSE `type` END AS type
FROM parts_enquiries
INNER JOIN parts_service_types AS pst ON parts_enquiries.serviceTypeId = pst.id
INNER JOIN enquiries ON parts_enquiries.enquiryId = enquiries.id
INNER JOIN entities ON enquiries.entityId = entities.id
GROUP BY enquiryId
But N.B.'s comment is still valid. To see if and index is used and other information we need to see the EXPLAIN and the table definitions.
This should get you what you want.
I would first pre-query your parts enquiries and parts service types looking for both the count and MINIMUM of the part 'type', grouped by the enquiry ID.
then, run your IF() against that result. If the distinct count is > 0, then 'Mixed'. If only one, since I did the MIN(), it would only have the description of that one value that you desire anyhow.
SELECT
E.ID
IF ( PreQuery.DistTypes > 1, 'Mixed', PreQuery.FirstType ) as PartType
from
Enquiries E
JOIN ( SELECT
PE.EnquiryID,
COUNT( DISTINCT PE.ServiceTypeID ) as DistTypes,
MIN( PST.Type ) as FirstType
from
Parts_Enquiries PE
JOIN Parts_Service_Types PST
ON PE.ServiceTypeID = PST.ID
group by
PE.EnquiryID ) as PreQuery
ON E.ID = PreQuery.EnquiryID