MySQL Slow union query - mysql

I have a query which looks like this:
SELECT address, min(nm_time) as first, max(nm_time) as last
FROM (
(SELECT tx_from as address, nm_time FROM tbl_transactions)
UNION ALL
(SELECT tx_to as address, nm_time FROM tbl_transactions)
) a
GROUP by address
ORDER BY first LIMIT 0,10
This query takes about 5 seconds to complete (on a fast server)
The table tbl_transactions has around 200k records in it. What I'm trying to accomplish is create a list with all unique addresses and their first and last transaction timestamp. I am using pagination, hence the LIMIT 0,10.
Any tips how I could improve this?

I'm not sure if either of these will help, but here are two ideas.
One thing to try is doing aggregation in the subqueries:
SELECT address, min(minnt) as first, max(maxnt) as last
FROM ((SELECT tx_from as address, min(nm_time) as minnt, max(nm_time) as maxnt
FROM tbl_transactions
GROUP BY tx_from
)
UNION ALL
(SELECT tx_to as address, min(nm_time) as minnt, max(nm_time) as maxnt
FROM tbl_transactions
GROUP BY tx_to
)
) a
GROUP by address
ORDER BY first
LIMIT 0, 10;
One method that can be faster than aggregation is to start with a list of addresses and use correlated subqueries to get the information. For this to work, you need a table of addresses. The query would look like:
SELECT a.address,
least((select min(nm_time) from tbl_transactions t where t.tx_from = a.address),
(select min(nm_time) from tbl_transactions t where t.tx_to = a.address)
) as first,
least((select max(nm_time) from tbl_transactions t where t.tx_from = a.address),
(select max(nm_time) from tbl_transactions t where t.tx_to = a.address)
) as last
FROM addresses a
ORDER BY first
LIMIT 10 OFFSET 0;
This needs two indexes for performance: tbl_transactions(tx_from, nm_time) and tbl_transactions(tx_to, nm_time).

Related

How can I join SQL subqueries as they are?

I have 3 subqueries that when executed independently they all return 3 rows with the desired columns and values. Once I put them all in the from statement and select them all
SELECT
*,
ROUND(Verbrecher / Buerger * 100, 1) AS Sicherheitsgrad
FROM
(SELECT name AS Dorf
FROM dorf
GROUP BY dorfnr) AS Dorf,
(SELECT COUNT(*) AS Verbrecher
FROM bewohner
WHERE status LIKE 'boese'
GROUP BY dorfnr) AS Verbrecher,
(SELECT COUNT(*) AS Buerger
FROM bewohner
GROUP BY dorfnr) AS Buerger
This is the result of all three subqueries being respectively executed standalone
Standalone
This is the result
Snippet above being run
I expect them to be joined together and have three rows with the queries aligned horizontally.
That unfortunately is not the given result.
I hope this makes sense to a certain extent.
Maybe you need in this:
SELECT dorfnr, Dorf, Verbrecher, Buerger,
ROUND(Verbrecher / Buerger * 100, 1) AS Sicherheitsgrad
FROM ( SELECT dorfnr, name AS Dorf
FROM dorf
-- GROUP BY dorfnr
) AS Dorf
JOIN ( SELECT dorfnr, COUNT(*) AS Verbrecher
FROM bewohner
WHERE status LIKE 'boese'
GROUP BY dorfnr
) AS Verbrecher USING (dorfnr)
JOIN ( SELECT dorfnr, COUNT(*) AS Buerger
FROM bewohner
GROUP BY dorfnr
) AS Buerger USING (dorfnr)

how to enhance efficiency of my query

I have such a query:
SELECT
*,
(
SELECT COUNT(DISTINCT client_id)
FROM 1097_course_students_tbl
WHERE course_cycl_id=id
AND stts_id <> 8
AND client_id IN(SELECT id FROM 1097_clients_tbl WHERE is_removed=0)
) AS cnt
FROM 1097_course_cycle_tbl
WHERE (course_id IN (SELECT id FROM 1097_courses_tbl WHERE is_removed=2))
ORDER BY start_date DESC
I need to make it more efficient because it takes too long
any suggestions ?
thanks
Try the following
SELECT cc.*,IFNULL(q.cnt,0) cnt
FROM 1097_course_cycle_tbl cс
JOIN 1097_courses_tbl с ON c.id=cc.course_id AND c.is_removed=2
LEFT JOIN
(
SELECT cs.course_cycl_id,COUNT(DISTINCT cs.client_id) cnt
FROM 1097_course_students_tbl cs
JOIN 1097_clients_tbl c ON cs.client_id=c.id AND c.is_removed=0
WHERE cs.stts_id<>8
GROUP BY cs.course_cycl_id
) q
ON q.course_cycl_id=cс.id
ORDER BY cc.start_date DESC
I think id in 1097_courses_tbl and 1097_clients_tbl is primary key.
Therefore I replaced IN into JOIN.
And I converted the subquery from SELECT block wich executed for each rows into the subquery with GROUP BY which using in LEFT JOIN. Here it'll execute only one time and return all the necessary information.

MySql Select data from SubQuery when master query has groupby clause

I have two tables in MySql db.
1) networks (NetworkId, NetworkType)
2) users (Id, NetworkId, IpAddress)
Using [NetworkId and IpAddress] defines unique users.
Now, I want to use group by clause on NetworkType and at the same time want to list count of all users as like below:
SELECT (SELECT Count(distinct IpAddress) FROM users
WHERE NetworkId in nr.NetworkId ) as UsersCount
FROM networks as nr
GROUP BY NetworkType;
But due to any reason I always gets zero users.
When I run following queries
SELECT GROUP_CONCATE(nr.NetworkId)
FROM networks as nr
GROUP BY NetworkType;
Then I am getting valid values with ',' separated.
Thanks in advance.
Updated per your new information about what you needed with the group by.
How about an upvote for my efforts at least.
Latest SQLFIDDLE
select
mysub.count,
nr.`networkid`
from
`networks` as nr,
(select
count(`ipaddress`) as count,
`networkid`
from
`users`
where
`networkid`
in (
select
`networkid`
from
`networks`
)
group by
`networkid`) as mysub
where nr.`networkid` = mysub.`networkid`
group by nr.`networkid`
Something like this perhaps:
select n.networktype, count(distinct u.ipaddress)
from networks n
join users u on n.networkid = u.networkid
group by n.networktype

How to use actual row count (COUNT(*)) in WHERE clause without writing the same query as subquery?

I have something like this:
SELECT id, fruit, pip
FROM plant
WHERE COUNT(*) = 2;
This weird query is self explanatory I guess. COUNT(*) here means the number of rows in plant table. My requirement is that I need to retrieve values from specified fields only if total number of rows in table = 2. This doesn't work but: invalid use of aggregate function COUNT.
I cannot do this:
SELECT COUNT(*) as cnt, id, fruit, pip
FROM plant
WHERE cnt = 2;
for one, it limits the number of rows outputted to 1, and two, it gives the same error: invalid use of aggregate function.
What I can do is instead:
SELECT id, fruit, pip
FROM plant
WHERE (
SELECT COUNT(*)
FROM plant
) = 2;
But then that subquery is the main query re-run. I'm presenting here a small example of the larger part of the problem, though I know an additional COUNT(*) subquery in the given example isn't that big an overhead.
Edit: I do not know why the question is downvoted. The COUNT(*) I'm trying to get is from a view (a temporary table) in the query which is a large query with 5 to 6 joins and additional where clauses. To re-run the query as a subquery to get the count is inefficient, and I can see the bottleneck as well.
Here is the actual query:
SELECT U.UserName, E.Title, AE.Mode, AE.AttemptNo,
IF(AE.Completed = 1, 'Completed', 'Incomplete'),
(
SELECT COUNT(DISTINCT(FK_QId))
FROM attempt_question AS AQ
WHERE FK_ExcAttemptId = #excAttemptId
) AS Inst_Count,
(
SELECT COUNT(DISTINCT(AQ.FK_QId))
FROM attempt_question AS AQ
JOIN `question` AS Q
ON Q.PK_Id = AQ.FK_QId
LEFT JOIN actions AS A
ON A.FK_QId = AQ.FK_QId
WHERE AQ.FK_ExcAttemptId = #excAttemptId
AND (
Q.Type = #descQtn
OR Q.Type = #actQtn
AND A.type = 'CTVI.NotImplemented'
AND A.IsDelete = #status
AND (
SELECT COUNT(*)
FROM actions
WHERE FK_QId = A.FK_QId
AND type != 'CTVI.NotImplemented'
AND IsDelete = #status
) = 0
)
) AS NotEvalInst_Count,
(
SELECT COUNT(DISTINCT(FK_QId))
FROM attempt_question AS AQ
WHERE FK_ExcAttemptId = #excAttemptId
AND Mark = #mark
) AS CorrectAns_Count,
E.AllottedTime, AE.TimeTaken
FROM attempt_exercise AS AE
JOIN ctvi_exercise_tblexercise AS E
ON AE.FK_EId = E.PK_EId
JOIN ctvi_user_table AS U
ON AE.FK_UId = U.PK_Id
JOIN ctvi_grade AS G
ON AE.FK_GId = G.PK_GId
WHERE AE.PK_Id = #excAttemptId
-- AND COUNT(AE.*) = #number --the portion in contention.
Kindly ignore the above query and guide me to right direction from the small example query I posted, thanks.
In MySQL, you can only do what you tried:
SELECT id, fruit, pip
FROM plant
WHERE (
SELECT COUNT(*)
FROM plant
) = 2;
or this variation:
SELECT id, fruit, pip
FROM plant
JOIN
(
SELECT COUNT(*) AS cnt
FROM plant
) AS c
ON c.cnt = 2;
Whether the 1st or the 2nd is more efficient, depends on the version of MySQL (and the optimizer). I would bet on the 2nd one, on most versions.
In other DBMSs, that have window functions, you can also do the first query that #Andomar suggests.
Here is a suggestion to avoid the bottleneck of calculating the derived table twice, once to get the rows and once more to get the count. If the derived table is expensive to be calculated, and its rows are thousands or millions, calculating them twice only to throw them away, is a problem, indeed. This may improve efficiency as it will limit the intermediately (twice) calculated rows to 3:
SELECT p.*
FROM
( SELECT id, fruit, pip
FROM plant
LIMIT 3
) AS p
JOIN
( SELECT COUNT(*) AS cnt
FROM
( SELECT 1
FROM plant
LIMIT 3
) AS tmp
) AS c
ON c.cnt = 2 ;
After re-reading your question, you're trying to return rows only if there are 2 rows in the entire table. In that case I think your own example query is already the best.
On another DBMS, you could use a Windowing function:
select *
from (
select *
, count(*) over () as cnt
from plant
) as SubQueryAlias
where cnt = 2
But the over clause is not supported on MySQL.
old wrong anser below
The where clause works before grouping. It works on single rows, not groups of rows, so you can't use aggregates like count or max in the where clause.
To set filters that work on groups of rows, use the having clause. It works after grouping and can be used to filter with aggregates:
SELECT id, fruit, pip
FROM plant
GROUP BY
id, fruit, pip
HAVING COUNT(*) = 2;
The other answers do not fulfill the original question which was to filter the results "without using a subquery".
You can actually do this by using a variable in 2 consecutive MySQL statements:
SET #count=0;
SELECT * FROM
(
SELECT id, fruit, pip, #count:=#count+1 AS count
FROM plant
WHERE
) tmp
WHERE #count = 2;

How do I get more than one column from a SELECT subquery?

Here is my problem :
I have 3 tables : account, account_event and account_subscription
account contains details like : company_name, email, phone, ...
account_event contains following events : incoming calls, outgoing calls, visit, mail
I use account_subscription in this query to retrieve the "prospects" accounts. If the account does not have a subscription, it is a prospect.
What I am using right now is the following query, which is working fine :
SELECT `account`.*,
(SELECT event_date
FROM clients.account_event cae
WHERE cae.account_id = account.id
AND cae.event_type = 'visit'
AND cae.event_done = 'Y'
ORDER BY event_date DESC
LIMIT 1) last_visit_date
FROM (`clients`.`account`)
WHERE (SELECT count(*)
FROM clients.account_subscription cas
WHERE cas.account_id = account.id) = 0
ORDER BY `last_visit_date` DESC
You can see that it returns the last_visit_date.
I would like to modify my query to return the last event details (last contact). I need the event_date AND the event_type.
So I tried the following query which is NOT working because apparently I can't get more than one column from my select subquery.
SELECT `account`.*,
(SELECT event_date last_contact_date, event_type last_contact_type
FROM clients.account_event cae
WHERE cae.account_id = account.id
AND cae.event_done = 'Y'
ORDER BY event_date DESC
LIMIT 1)
FROM (`clients`.`account`)
WHERE (SELECT count(*)
FROM clients.account_subscription cas
WHERE cas.account_id = account.id) = 0
ORDER BY `last_visit_date` DESC
I tried a lot of solutions around joins but my problem is that I need to get the last event for each account.
Any ideas?
Thank you in advance.
Jerome
Get a PRIMARY KEY in a subquery and join the actual table on it:
SELECT a.*, ae.*
FROM account a
JOIN account_event ae
ON ae.id =
(
SELECT id
FROM account_event aei
WHERE aei.account_id = a.id
AND aei.event_done = 'Y'
ORDER BY
event_date DESC
LIMIT 1
)
WHERE a.id NOT IN
(
SELECT account_id
FROM account_subscription
)
ORDER BY
last_visit_date DESC
Try moving the subquery to from part and alias it; it will look as just another table and you'll be able to extract more than one column from it.