How to find a result with multiple different values on same column? - mysql

Imagine that we have a database with a logs table and types table. I want to do a query where I figure out if UserX has entries for certain types of logs. Let's say that UserX has logged type_1 and type_2, but not type_3. I want to write a simple query to see if this is true or false.
At first I tried something like:
SELECT * FROM logs AS l
INNER JOIN types AS t
ON t.id = l.type_id
WHERE t.name = "type_1"
AND t.name = "type_2"
AND t.name != "type_3";
But I quickly realised that it was not possible to do it like this, since t.name cannot have multiple values. I have tried a bunch of different approaches now, but cannot seem to find the one right for me. I'm sure the solution is fairly simple, I just don't see it at the moment.
Hope someone can point me in the right direction.
I have made a simple test database in this fiddle, to use for testing and example: https://www.db-fiddle.com/f/nA6iKgCcJwKnXKsxaNvsLt/0

One option with conditional aggregation.
SELECT l.userID
FROM logs AS l
JOIN types AS t ON t.id = l.type_id
GROUP BY l.userID
HAVING COUNT(DISTINCT CASE WHEN t.name IN ('type_1','type_2') THEN t.name END) = 2
AND COUNT(DISTINCT CASE WHEN t.name = 'type_3' THEN t.name END) = 0

You can do it like Vamsi, but if you prefer an easier to understand SQL then you can do it like this:
SELECT * FROM logs AS l
INNER JOIN types AS t
ON t.id = l.type_id
WHERE true
AND EXISTS (SELECT 1 FROM logs ll WHERE l.user_id = ll.user_id AND type_id = 1)
AND EXISTS (SELECT 1 FROM logs ll WHERE l.user_id = ll.user_id AND type_id = 2)
AND NOT EXISTS (SELECT 1 FROM logs ll WHERE l.user_id = ll.user_id AND type_id = 3)

I do not recommend using count(distinct) for this purpose. It can be expensive. I would simply do:
SELECT l.userId
FROM logs l INNER JOIN
types t
ON t.id = l.type_id
WHERE t.name IN ('type_1', 'type_2', 'type_3')
GROUP BY l.userId
HAVING SUM(t.name = 'type_1') > 0 AND -- at least one
SUM(t.name = 'type_2') > 0 AND -- at least one
SUM(t.name = 'type_3') = 0 ; -- none

Related

MYSQL return 0 for filtered rows

I am trying to filter with a "WHERE" but I want to display the filtered rows as "NULL" or 0 instead of hiding them.
Here is my code:
SELECT *, IFNULL(SUM(ROUND(TIMESTAMPDIFF(MINUTE,start,end)/60,1)),0) urlaub
FROM time_entries e
LEFT JOIN users u ON e.user_id = u.id
WHERE e.project_id = 10 AND YEAR(end) = YEAR(CURRENT_DATE)
GROUP BY e.user_id
Best,
Chris
I might be just being pedantic, but in case there is a genuine misunderstanding.... They are not "hidden", they are not there. The point of the WHERE is to choose which records to retrieve, so as to not retrieve the entire table contents when only a few records of data are relevant.
But if something like that is what you want to do, you should just be able to add another result field, like so:
(e.project_id = 10 AND YEAR(end) = YEAR(CURRENT_DATE)) AS relevantRecord
Got it working! This is the code that returns what I wanted to:
SELECT
*,
IFNULL(SUM(CASE WHEN e.project_id = 10 AND YEAR(end) = YEAR(CURRENT_DATE) THEN ROUND(TIMESTAMPDIFF(MINUTE, start, end)/60,1) ELSE 0 END),0) AS urlaub
FROM time_entries e
LEFT JOIN users u ON e.user_id = u.id
GROUP BY e.user_id
Thanks!

MySQL Subquery Select very slow

One of our websites has a table with about 60'000 records in it. Recently we noticed the page was timing out and could only be resolved by setting the memory limit to -1. This allowed the page to load but it was very slow. Furthermore I did not believe this was the correct way to resolve the problem, as obviously it indicates something is not quite right.
I managed to output the query that the page was running:
SELECT u.*,
(SELECT COUNT(*) FROM enquiry e WHERE e.user_id = u.id AND e.deleted = 0 AND e.time_started != 0) AS opened_count,
(SELECT COUNT(*) FROM enquiry e WHERE e.user_id = u.id AND e.deleted = 0 AND e.confirmed = 1 AND e.time_started != 0) AS confirmed_count,
(SELECT COUNT(*) FROM enquiry e WHERE e.user_id = u.id AND e.deleted = 0 AND e.call_back = 1 AND e.time_started != 0) AS call_back_count
FROM user u
WHERE u.active = 1 AND u.deleted = 0
ORDER BY u.username
I ran this query in phpMyAdmin and it takes over 30 seconds to return the results.
I feel the query needs optimising in some way but I'm struggling to work out how. I'm guessing I need to use a JOIN of some sort?
You're really running >180,000 queries, since each of those 3 subqueries will be run once for every row in the user table.
You could try simplifying into a standard join with some groups, e.g.
SELECT user.*,
COUNT(enq.id) AS opened_count
SUM(e.confirmed = 1) AS confirmed_count
SUM(e.call_back = 1) AS call_back_count
FROM user
LEFT JOIN enquiry ON enquiry.user_id = user.id
WHERE user.active = 1 and user.deleted AND enquiry.deleted = 0
GROUP BY user.id

Count number of occurrences based on user id

I'm very new to SQL/MySQL and Stackoverflow for that matter, and I'm trying to create a query through iReport (though I don't have to use iReport) for SugarCRM CE. What I need is to create a report that displays the number of "Referrals", "Voicemails", "Emails", and "Call_ins" that are linked to a specific "user" (employee). The query I currently have set up works; however it is running through the data multiple times generating a report that is 200+ pages. This is the code that I am currently using:
SELECT
( SELECT COUNT(*) FROM `leads` INNER JOIN `leads_cstm` ON `leads`.`id` = `leads_cstm`.`id_c` WHERE (leadtype_c = 'Referral' AND users.`id` = leads.`assigned_user_id`) ),
( SELECT COUNT(*) FROM `leads` INNER JOIN `leads_cstm` ON `leads`.`id` = `leads_cstm`.`id_c` WHERE (leadtype_c = 'VM' AND users.`id` = leads.`assigned_user_id`) ),
( SELECT COUNT(*) FROM `leads` INNER JOIN `leads_cstm` ON `leads`.`id` = `leads_cstm`.`id_c` WHERE (leadtype_c = 'Email' AND users.`id` = leads.`assigned_user_id`) ),
users.`first_name`,users.`last_name`
FROM
`users` users,
`leads` leads
I would appreciate any guidance!
You want to use conditional summation. The following uses MySQL syntax:
SELECT sum(leadtype_c = 'Referral') as Referrals,
sum(leadtype_c = 'VM') as VMs,
sum(leadtype_c = 'Email') as Emails,
users.`first_name`, users.`last_name`
FROM users join
`leads`
on users.`id` = leads.`assigned_user_id` INNER JOIN
`leads_cstm`
ON `leads`.`id` = `leads_cstm`.`id_c`
group by users.id;
You can use COUNT with CASE for this:
SELECT u.first_name,
u.last_name,
count(case when leadtype_c = 'Referral' then 1 end),
count(case when leadtype_c = 'VM' then 1 end),
count(case when leadtype_c = 'Email' then 1 end)
FROM users u
JOIN leads l ON u.id = l.assigned_user_id
JOIN leads_cstm lc ON l.id = lc.id_c
GROUP BY u.id
To match your exact results, you should probably use an OUTER JOIN instead, but this gives you the idea.
A Visual Explanation of SQL Joins

How to get LIMIT on LEFT JOIN

cI'm seeking some help with my left join with a limit.
What i'm trying to do is to loop through my users and check in another table if there's problems connected to the user.
But currently I'm getting all kinds of weird results and it does not LIMIT the result for each user and it also lists column status_link_missing = 0 even though i have told the sub query to only list status_link_missing = 1
So I'm stuck for now, help is much appreciated!
SELECT
a.user_id AS settings_userid
, a.contact_interval
, b.user_id
, b.notify_user
, b.status_host_down
, b.status_link_missing
, b.status_relnofollow
FROM `link_exchange_settings` a
LEFT JOIN link_exchange_links b
ON b.id
= ( SELECT c.id
FROM link_exchange_links AS c
WHERE
b.user_id = a.user_id
AND c.notify_user = 1
AND c.status_link_missing = 1
LIMIT 1
)
WHERE a.allow_contact = 1
LIMIT 10
Edit
I switched SELECT b.id to c.id now and LIMIT works but now it only works for the first user
Try this change (no reference to b in the subquery):
SELECT
a.user_id AS settings_userid
, a.contact_interval
, b.user_id
, b.notify_user
, b.status_host_down
, b.status_link_missing
, b.status_relnofollow
FROM `link_exchange_settings` a
LEFT JOIN link_exchange_links b
ON b.id
= ( SELECT c.id -- changed
FROM link_exchange_links AS c
WHERE
c.user_id = a.user_id -- changed
AND c.notify_user = 1
AND c.status_link_missing = 1
-- ORDER BY c.something -- optional, recommended addition
LIMIT 1
)
WHERE a.allow_contact = 1
-- ORDER BY a.something_else -- optional, recommended addition
LIMIT 10 ;
It's also good to have ORDER BY when you use LIMIT. Unless you want indeterminate results.
You are using "c.status_link_missing = 1" in sub-query but if you will use it in WHERE clause with "AND" you will get your desired results.
In fact you have to decide and use appropriate condition in WHERE clause of main query instead of LEFT JOIN sub-query.

How to optimize this mysql query? Runs slow

I'm using a database that, imho, wasn't designed well, but maybe it's just me not understanding it. Anyways, I have a query that pulls the correct information, but it is really slowing down my php script. I was hoping someone could take a look at this and let me know if nesting queries to this depth is bad, and whether or not there is a way to simplify the query from the relationships depicted in the sql statement below.
SELECT name
FROM groups
WHERE id = (SELECT DISTINCT immediateparentid
FROM cachedgroupmembers
WHERE groupid = (SELECT g.id AS AdminCc
FROM Tickets t, groups g
WHERE t.Id = 124 AND t.id = g.instance AND g.type = 'AdminCc')
AND immediateparentid <> (SELECT g.id AS AdminCc
FROM Tickets t, groups g
WHERE t.Id = 124 AND t.id = g.instance AND g.type = 'AdminCc'))
Please help
Update:
Here is the output from using Explain
You may need to right click and select "View Image" for the text to be clear.
From what I can tell, you can eliminate one sub-select.
SELECT name
FROM groups
WHERE id = (
SELECT DISTINCT immediateparentid
FROM cachedgroupmembers
WHERE groupid = (
SELECT g.id
FROM Tickets t, groups g
WHERE t.Id = 124 AND t.id = g.instance AND g.type = 'AdminCc'
) AND immediateparentid != groupid
)
I'm much more used to PL/SQL on Oracle but I'll give it a try.
Get rid of aliases, you don't need them here.
Make sure columns used in the where clause are indexed (t.Id and g.type).
Don't know if MySQL indexes foreign keys by default but worth the check.
You can shorten your SQL code like that:
SELECT name
FROM groups
WHERE id = (
SELECT DISTINCT immediateparentid
FROM cachedgroupmembers
WHERE groupid = (
SELECT g.id
FROM Tickets t, groups g
WHERE t.Id = 124 AND t.id = g.instance AND g.type = 'AdminCc'
) AND immediateparentid != groupid
)
or:
SELECT name
FROM groups
WHERE id = (
SELECT DISTINCT immediateparentid
FROM cachedgroupmembers
WHERE groupid = (
SELECT g.id
FROM Tickets t inner join groups g on t.id = g.instance
WHERE t.Id = 124 AND g.type = 'AdminCc'
) AND immediateparentid != groupid
)
if your tickets table is big you may consider a temp table instead of querying it twice