I appear to be having a little spazzy moment today and can't seem to come up with a better way of doing sub select counts on a table.
Basically what I need to do is for each distinct supplier I then need 2 counts (from the same table) one for the total records assigned to that supplier and one for disputed records for that supplier. At the moment I have the query below which is technically correct but slow as hell on a table with 1 mill + records. I'm sure there's a better way of doing it but I can't for the life of me work it out this morning.
SELECT
DISTINCT tblsuppliers.SupplierName,
(SELECT COUNT(*) FROM tblmovements a WHERE m.Supplier=a.Supplier AND a.TicketStatus IN(0,1) AND a.DateRequired>='2013-09-01 00:00:00' AND a.DateRequired<='2013-11-30 23:59:59') as 'Total Tickets',
(SELECT COUNT(*) FROM tblmovements b WHERE m.Supplier=b.Supplier AND b.TicketStatus IN(0,1) AND b.DateRequired>='2013-09-01 00:00:00' AND b.DateRequired<='2013-11-30 23:59:59' AND (b.SuppIsDisputed=1 OR b.SuppDisputeClearedBy>0)) as 'Total Disputed'
FROM tblmovements m
INNER JOIN tblsuppliers ON m.Supplier=tblsuppliers.ID
ORDER BY tblsuppliers.SupplierName ASC
The joined table is just to give me an supplier name as opposed to the supplier ID which is stored in the movements table. Any suggestions are greatly appreciated.
Try this:
SELECT s.SupplierName,
SUM(CASE WHEN m.TicketStatus IN(0,1) AND m.DateRequired>='2013-09-01 00:00:00' AND
m.DateRequired<='2013-11-30 23:59:59' THEN 1 ELSE 0
END) AS 'Total Tickets',
SUM(CASE WHEN m.TicketStatus IN(0,1) AND m.DateRequired>='2013-09-01 00:00:00' AND
m.DateRequired<='2013-11-30 23:59:59' AND
(m.SuppIsDisputed=1 OR m.SuppDisputeClearedBy>0) THEN 1 ELSE 0
END) AS 'Total Disputed'
FROM tblmovements m
INNER JOIN tblsuppliers s ON m.Supplier=s.ID
GROUP BY s.ID
ORDER BY s.SupplierName ASC
SELECT
tblsuppliers.SupplierName,
SUM(IF(TicketStatus IN(0,1) AND DateRequired>='2013-09-01 00:00:00' AND DateRequired<='2013-11-30 23:59:59', 1, 0)) as 'Total Tickets',
SUM(IF(TicketStatus IN(0,1) AND DateRequired>='2013-09-01 00:00:00' AND DateRequired<='2013-11-30 23:59:59' AND (SuppIsDisputed=1 OR SuppDisputeClearedBy>0), 1, 0)) as 'Total Disputed'
FROM tblmovements m
INNER JOIN tblsuppliers ON m.Supplier=tblsuppliers.ID
GROUP BY tblsuppliers.SupplierName
Related
I have a sales table with a sales column TCV, customerKey column which holds customerKey stored in customer table. Each row has an order_date and there are other columns irrelevant to this query.
I have to find sales for current period and another period grouped by customers for comparison. So I have this below query.
SELECT
*,
SUM(TCV) AS sales,
current_sales - SUM(TCV) AS difference
FROM
`sales`
LEFT JOIN
(SELECT
customerKey AS customerID,
SUM(TCV) AS current_sales
FROM
`sales`
WHERE `order_date` BETWEEN '2020-08-01'
AND '2020-08-31'
GROUP BY `sales`.`customerKey`) AS `current_sales`
ON `customerKey` = `customerID`
WHERE `order_date` BETWEEN '2020-09-01'
AND '2020-09-31'
GROUP BY `sales`.`customerKey`
I have this query and it runs very slowly, takes about 30 secs, but if I run the query without the join the result comes back in a second.
What could be the problem, is it structured wrong?
Rewrite it without join, it will perform better:
select s.*
, current_sales - sales as difference
from
( SELECT customerKey
, sum(CASE WHEN order_date BETWEEN '2020-08-01' AND '2020-08-31' then TCV else 0 end) current_sales
, sum(CASE WHEN order_date BETWEEN '2020-09-01' AND '2020-09-30' then TCV else 0 end) sales
FROM sales
WHERE order_date BETWEEN '2020-08-01' AND '2020-09-30'
GROUP
BY sales.customerKey
) s
I have a table "products" and a table "links". every product can have multiple links, each link can have many sales, clicks and impressions, but doesn't necessarily have all of them. I want to get a list of links of a certain product matching some criteria for them. I want to get this data grouped per day and campaign and link banner size.
The following query works correctly, but it's much slower than it could be. The problem is that the subqueries get the data for all link ids and it's just filtered in the end. The overall query would become much faster if the sub queries included something like
where link_id IN (...) but I only know the link_ids from the main query, not before
if I try to add
where link_id = l.id
it's obviously an unknown column, because the sub query doesn't have access to the main queries results.
how can I let the sub queries only look up data for the link_Ids that the main query found? I could split it up to 2 complete separate queries, but is this possible in one query?
select p.id, p.name, l.id, l.banner_size,
coalesce(sum(case when t1.col = 'sales' then ct else 0 end), 0) as total_sales,
coalesce(sum(case when t1.col = 'clicks' then ct else 0 end), 0) as total_clicks,
coalesce(sum(case when t1.col = 'impressions' then ct else 0 end), 0) as total_impressions,
t1.dt
from links l
inner join products p
on p.id = l.product_id
left join
(
select count(1) as ct, link_id, date(clicked) dt, 'sales' as col
from sales
where clicked >= '2020-01-01 00:00:00' and clicked <= '2020-01-31 00:00:00'
group by date(clicked), link_id
union all
select count(1) as ct, link_id, date(created) dt, 'clicks'
from clicks_source1
where created >= '2020-01-01 00:00:00' and created <= '2020-01-31 00:00:00'
group by date(created), link_id
union all
select count(1) as ct, link_id, date(time) dt, 'clicks'
from clicks_source2
where time >= '2020-01-01 00:00:00' and time <= '2020-01-31 00:00:00'
group by date(time), link_id
union all
select count(1) as ct, link_id, date(created) dt, 'impressions'
from impression_source1
where created > '2020-01-01 00:00:00' and created <= '2020-01-31 00:00:00'
group by date(created), link_id
union all
select count(1) as ct, link_id, date(time) dt, 'impressions'
from impression_source2
where time > '2020-01-01 00:00:00' and time <= '2020-01-31 00:00:00'
group by date(time), link_id
) t1 on t1.link_id = l.id
where l.agent_id = 300
and p.id = 3454
and l.banner_size = 48
and p.company NOT IN (13592, 28189)
group by c.id, l.banner_size, t1.dt
having (total_sales + total_clicks + total_impressions) > 0
order by dt DESC, p.id ASC, l.banner_size ASC
you can just add inner joins with links to all the subqueries
select count(1) as ct, s.link_id, date(s.clicked) dt, 'sales' as col
from sales s
join links l1 on l1.id = s.link_id
where s.clicked >= '2020-01-01 00:00:00' and s.clicked <= '2020-01-31 00:00:00'
group by date(s.clicked), s.link_id
union all etc.
Then you'll only get the rows with a match and the query should run faster
Needs help with my SQL query
Query :
Select customer_id,
if (call_details ='International' , 'International Calls', 'National Calls'),
sum(minutes)
from call_minutes
where date between '$from' and '$to'
group by call_details
My Result is displaying as below
Please let me know why National Calls is not getting grouped. I wanted to find the sum of national calls and international calls
use below SQL:
select customer_id,
call_details,
sum(minutes) as minutes
from(
Select customer_id,
if (call_details ='International' , 'International Calls', 'National Calls') as call_details,
minutes
from call_minutes
where date between '$from' and '$to') x
group by customer_id,call_details
You don't need a subquery for this, because MySQL allows you to use column aliases in the group by (not all databases do). So:
Select customer_id,
(case when call_details = 'International'
then 'International Calls'
else 'National Calls'
end) as call_group,
sum(minutes)
from call_minutes
where date between '$from' and '$to'
group by customer_id, call_group;
Note: this assumes that you want separate rows for each customer. If not, remove customer_id from both the select and the group by:
Select (case when call_details = 'International'
then 'International Calls'
else 'National Calls'
end) as call_group,
sum(minutes)
from call_minutes
where date between '$from' and '$to'
group by call_group;
If you want a list of customer ids in each group, you can always add group_concat(customer_id).
This query will return a list of engineer names with test results for what they have tested in the last hour, what is faulty, what's is working and the total for each engineer.
I want to be able to add a row at the bottom which will total these amounts but am struggling, any one have any suggestions?
select distinct qcheck.checkby,
ifnull(fully,0) as fully,
ifnull(faulty,0) as faulty,
ifnull(lasthour,0) as lasthour,
ifnull(total,0) as total
from qcheck
left join (
select count(*) AS fully,
checkby,
qcheck.id
from qcheck
where result = 'fully tested & working'
and date(finishdate) = CURDATE()
group by checkby) AS fw
on fw.checkby=qcheck.checkby
left join (
select count(*) AS faulty,
checkby,
qcheck.id
from qcheck
where result = 'faulty'
and date(finishdate) = CURDATE()
group by checkby) AS ff
on ff.checkby=qcheck.checkby
left join (
select count(*) AS Lasthour,
checkby,
qcheck.id from qcheck
where finishdate >= now() - interval 1 hour
group by checkby) AS lh
on lh.checkby=qcheck.checkby
left join (
select count(*) AS total,
checkby,
qcheck.id from qcheck
where date(finishdate) = CURDATE()
group by checkby) AS total
on total.checkby=qcheck.checkby
where date(finishdate) = CURDATE()
and qcheck.checkby not like 'michael'
and qcheck.checkby not like 'chaz'
group by qcheck.checkby
order by total desc
First of all, you don't need the sub queries, you can instead do a count on a condition.
The with rollup modifier can be added to the group by clause to include the grand total. The order by cannot be used in the same query then, but can be applied in an outer query.
Furthermore, with the use of coalesce you can replace the null value for that total row with the label of your choice.
Finally, to still sort the total row at the end, you could add an is null expression in the order by clause, which will evaluate to false or true. The latter is ordered last.
select coalesce(checkby, 'Total') as checkby_or_total,
fully,
faulty,
lasthour,
total
from (
select qcheck.checkby,
count(case result when 'fully tested & working' then 1 end) as fully,
count(case result when 'faulty' then 1 end) as faulty,
count(case when finishdate >= now()-interval 1 hour then 1 end) as lasthour,
count(*) as total
from qcheck
where date(finishdate) = CURDATE()
and qcheck.checkby not like 'michael'
and qcheck.checkby not like 'chaz'
group by qcheck.checkby with rollup
) as main
order by checkby is null,
total desc
first one:
SELECT MONTH(timestamp) AS d, COUNT(*) AS c
FROM table
WHERE YEAR(timestamp)=2012 AND Status = 1
GROUP BY MONTH(timestamp)
one of the issues I'm facing for this one is that I have to run multiple queries that use different values for Status. Is there a way to combine them into one? Like in one column it would have all the counts for when Status=1 and another column for when Status=2, etc.
second one:
SELECT COUNT(*) c , MONTH(timestamp) t FROM
(
SELECT t.adminid, timestamp
FROM table1 t
LEFT JOIN admins a ON a.adminID=t.adminID
WHERE YEAR(timestamp)=2012
GROUP BY t.adminID, DATE(Timestamp)
ORDER BY timestamp DESC
) AS a
GROUP BY MONTH(timestamp)
ORDER BY MONTH(timestamp) ASC;
a nested query, not sure if I can improve on this. I'm running this one on 2 tables, one has ~35k rows and one has ~300k rows. It takes about half a second for the first table and 4-5 seconds for the second.
These might help:
First one:
SELECT MONTH(timestamp) AS d,
sum(case when Status=1 then 1 else 0 end) as Status1Count,
sum(case when Status=2 then 1 else 0 end) as Status2Count,
sum(case when Status=3 then 1 else 0 end) as Status3Count
FROM `table`
WHERE timestamp between '2012-01-01 00:00:00' and '2012-12-31 23:59:59'
AND Status in (1,2,3)
GROUP BY MONTH(timestamp);
Second one:
Make sure that there is an index on the timestamp column and then make sure that you do not run any conversion functions e.g. MONTH(timestamp) on the indexed column. Somthing like:
SELECT COUNT(*) c , a.m as t FROM
(
SELECT t.adminid, timestamp, MONTH(timestamp) as m
FROM table1 t
LEFT JOIN admins a ON a.adminID=t.adminID
WHERE timestamp between '2012-01-01 00:00:00' and '2012-12-31 23:59:59'
GROUP BY t.adminID, DATE(Timestamp)
ORDER BY timestamp DESC
) AS a
GROUP BY a.m
ORDER BY a.m ASC;
Second one is a bit tricky since I do not have the data in front of me so I can't see the DB access path!