Sql query too slow - looking for alternative way to write it - mysql

I have this mysql query that does what it needs to but it takes a really long time to load the content where as my other queries run perfectly fine and quick. Is there a better way to do it? I just want it to load faster. Here is my query:
SELECT
DISTINCT( # Without this I get too many results
CONCAT_WS(' ',
MONTHNAME(a.DateTimeViewed),
YEAR(a.DateTimeViewed)
)
) AS ViewedDate, # Displays as "January 2017" (example)
(
SELECT COUNT(b.ViewID)
FROM views b
WHERE
MONTH(b.DateTimeViewed) = MONTH(a.DateTimeViewed) AND
YEAR(b.DateTimeViewed) = YEAR(a.DateTimeViewed)
) as TotalViews
FROM views a
WHERE a.DateTimeViewed >= date_sub(now(), interval 6 month)
ORDER BY YEAR(a.DateTimeViewed) ASC, MONTH(a.DateTimeViewed) ASC

You are working too hard.
SELECT CONCAT_WS(' ', MONTHNAME(a.DateTimeViewed), YEAR(a.DateTimeViewed)
) AS ViewedDate,
COUNT(*) as TotalViews
FROM views a
WHERE a.DateTimeViewed >= date_sub(now(), interval 6 month)
ORDER BY YEAR(a.DateTimeViewed) ASC, MONTH(a.DateTimeViewed) ASC
Since you are backing up 6 months from this instant, you are getting the count for only part of the first month. Perhaps you want to change one line:
WHERE a.DateTimeViewed >= CONCAT(LEFT(CURDATE() - INTERVAL 6 MONTH, 7), '-01')
COUNT(*) is the usual way to count rows. COUNT(x) does the same, but adds on the effort to filter out rows with x IS NULL.

Related

Add months in db date using interval in mysql

I want to add month in transaction date using mysql interval function by join plan table and transaction table,however this method not working but If I add months in static way to transaction date it is working.
plan table:
plan_id plan
1 6 month
2 12 month
3 3 month
transaction table:
id user_id subscribed_on plan_id
1 2 2020-04-04 1
2 4 2019-02-22 2
Mysql query (not working):
SELECT t.* FROM transaction t inner join plan p on p.plan_id=t.plan_id
where t.user_id=2 and DATE_ADD(date(t.subscribed_on), INTERVAL p.plan) >= CURDATE()
order by t.id desc
If I add month in static way than it is working fine:
SELECT t.* FROM transaction t inner join plan p on p.plan_id=t.plan_id
where t.user_id=2 and DATE_ADD(date(t.subscribed_on),
INTERVAL 6 month) >= CURDATE()
order by t.id desc
MySQL does not support using interval that way. Unlike in other databaes (such as Postgres for example), the unit argument is a keyword, not a literal string.
I would suspect that your table may store other intervals than just months (say, years, days, and so on). If so, you can use string functions and a case expression to accommodate the different possible values, like:
select t.*
from transaction t
inner join plan p on p.plan_id = t.plan_id
where
t.user_id = 2
and date(t.subscribed_on) + case substring_index(p.plan, ' ', -1)
when 'year' then interval substring_index(p.plan, ' ', 1) year
when 'month' then interval substring_index(p.plan, ' ', 1) month
when 'day' then interval substring_index(p.plan, ' ', 1) day
end
>= current_date
order by t.id desc
The logic here is to split the stored interval string into two parts: the number, and the unit; the case expression processes the unit and generate the proper literal interval accordingly.
Unfortunately a string in the data is not equivalent to an interval. One method is:
date(t.subscribed_on) + interval substring_index(plan, ' ') + 0 month
Note here that month is a keyword, not a string.
Try to force the plan column in the plan table to be an integer. Does not seem to be possible to cast a string to an interval.
I tried like so:
WITH
plan( plan_id,plan) AS (
SELECT 1,'6 month'
UNION ALL SELECT 2,'12 month'
UNION ALL SELECT 3,'3 month'
)
,
transaction(id,user_id,subscribed_on,plan_id) AS (
SELECT 1,2,DATE '2020-09-04',1
UNION ALL SELECT 2,4,DATE '2019-02-22',2
)
SELECT t.*
FROM transaction t
INNER JOIN plan p ON p.plan_id = t.plan_id
WHERE t.user_id = 2
AND DATE_ADD(
DATE(t.subscribed_on)
, INTERVAL CAST(REPLACE(plan,' month','') AS SIGNED) MONTH
) >= CURDATE()
ORDER BY t.id DESC
(returns no results, as you don't have any dates high enough in your example data...)

date_trunc PostgreSQL function equal for mySQL

Im trying to retrieve data to make statistics, im using mySQL and i cant get the following function to work - the postgreSQL is working.
I want to retrieve the request for the last month and count the amount of new requests for each day.
postgreSQL
SELECT count(*), date_trunc('day', created_at) as date FROM requests
WHERE(created_at > '2014-08-13 00:00:00') GROUP BY 2 ORDER BY 2 ASC;
*mySQL - my code *
SELECT count(EXTRACT(DAY FROM created_at)), EXTRACT(DAY FROM created_at) as date
FROM `requests`
WHERE EXTRACT(DAY FROM NOW() - INTERVAL 1 MONTH)
GROUP BY date
Final code
SELECT count( * ) , date( created_at ) AS date
FROM `requests`
WHERE DATE( created_at ) > DATE( DATE_SUB( NOW( ) , INTERVAL 1 MONTH ) )
GROUP BY date
The equivalent for your case is date():
select date(created_at), count(*)
from requests
. . .
This isn't a general replacement, but it works to remove the time portion of a date.
EDIT:
Perhaps the better solution for these two databases is:
select cast(created_at as date)
This is ANSI standard and works in both these databases (as well as SQL Server). I personally don't use this in general, lest I accidentally use it in Oracle, causing difficult to find errors. (dates in Oracle have a time component, alas.)

Order by ignoring second criteria

I'm having problems with my query, basically what I'm trying to do here is to order first by
item_info.content_time desc
and then by
item_views.views desc
My intention is to get the most recent items (order by item_info.content_time desc), with the most views (item_views.views desc). In essence something like the stackoverflow main page, where you have the most recent with the most views (I really don't know if that's how they are doing it). The following query either orders the items by one criteria or the other (if I reverse the order by criteria). Here's my code:
SELECT item_info.item_id, item_info.profile_id, item_info.tittle, item_covers.reference, usuarios.username, item_views.views
FROM item_info
LEFT JOIN item_covers ON item_covers.cover_id = item_info.book_id
LEFT JOIN usuarios ON item_info.profile_id = usuarios.id
LEFT JOIN item_views ON item_views.id = item_info.book_id
WHERE item_info.content_time
BETWEEN UNIX_TIMESTAMP( CURDATE( ) + INTERVAL -1
DAY )
AND UNIX_TIMESTAMP( CURDATE( ) + INTERVAL 1
DAY )
order by item_info.content_time desc, item_views.views desc
Roughly something like...
Expected output:
content_time | views
17:00 500
13:00 300
11:00 100
10:00 50
Actual output:
content_time | views
17:00 500
16:00 10
15:00 30
14:00 50
If you want a combination of the two you should create a column consisting of the combined (possibly weighed by an extra factor) sum of both like DATEDIFF(content_time,NOW())+views. By using DATEDIFF you will make sure that content_time is a numeric type and can be added to views. By using DATEDIFF()with the arguments in the shown order you will be getting a negative number for any content_times in the past which will reduce the combined value with views accordingly.
So your query should end in something like this
..
...
ORDER BY DATEDIFF(viewsitem_info.content_time,NOW())+viewsitem_info.views desc
DATEDIFF() gives the difference in days. Maybe you want to be a bit more precise. In that case you might want to use TIMESTAMPDIFF(MINUTE,viewsitem_info.content_time,NOW()) instead.
#Aaron: You will not have to change your data structure at all. If necessary you should introduce some kind of weighing factor like
ORDER BY
(0.123* TIMESTAMPDIFF(MINUTE,viewsitem_info.content_time,NOW())
+ viewsitem_info.view) DESC
write Your query like below
SELECT item_info.item_id, item_info.profile_id, item_info.tittle, item_covers.reference,
usuarios.username, item_views.views FROM item_info
LEFT JOIN item_covers ON item_covers.cover_id = item_info.book_id
LEFT JOIN usuarios ON item_info.profile_id = usuarios.id
LEFT JOIN item_views ON item_views.id = item_info.book_id
WHERE item_info.content_time
BETWEEN UNIX_TIMESTAMP( CURDATE( ) + INTERVAL -1DAY )
AND UNIX_TIMESTAMP( CURDATE( ) + INTERVAL 1
DAY )
order by item_info.content_time,item_views.views desc
I think it'll work.

Calculating Average over different intervals

In mysql, I am calculating averages of the same metric over different intervals (3 Day, 7 Day, 30 Day, 60 Day, etc...), and I need the results to be in a single line per id.
Currently, I am using a Join per each interval. Given that I have to compute this for many different stores, and over several different intervals, is there a cleaner and/or more efficient way of accomplishing this?
Below is the code I am currently using.
Thanks in advance for the help
SELECT T1.id, T1.DailySales_3DayAvg, T2.DailySales_7DayAvg
FROM(
SELECT id, avg(DailySales) as 'DailySales_3DayAvg'
FROM `SalesTable`
WHERE `Store`=2
AND `Date` >= DATE_SUB('2012-07-28', INTERVAL 3 DAY)
AND `Date` < '2012-07-28'
) AS T1
JOIN(
SELECT id, avg(DailySales) as 'DailySales_7DayAvg'
FROM `SalesTable`
WHERE `Store`=2
AND `Date` >= DATE_SUB('2012-07-28', INTERVAL 7 DAY)
AND `Date` < '2012-07-28'
) AS T2
ON T1.ArtistId = T2.ArtistId
Where the results are:
id DailySales_3DayAvg DailySales_7DayAvg
3752 1234.56 1114.78
...
You can use a query like this -
SELECT
id,
SUM(IF(date >= '2012-07-28' - INTERVAL 3 DAY, DailySales, 0)) /
COUNT(IF(date >= '2012-07-28' - INTERVAL 3 DAY, 1, NULL)) 'DailySales_3DayAvg',
SUM(IF(date >= '2012-07-28' - INTERVAL 7 DAY, DailySales, 0)) /
COUNT(IF(date >= '2012-07-28' - INTERVAL 7 DAY, 1, NULL)) 'DailySales_7DayAvg'
FROM
SalesTable
WHERE
Store = 2 AND Date < '2012-07-28'
GROUP BY
id
I don't think you can do this in any other way if you want to pull real-time data. However, if you can afford displaying slightly outdated data, you could pre-calculate these average (like once or twice a day) for each item.
You may want to look into the Event Scheduler, which allows you to keep everything inside MySQL.

Count timestamps with offset between two databases in a view

I am trying to count time stamps between two databases but one has overlapping time stamps, due to not my design flaw.
SELECT date(time + INTERVAL 8 HOUR) as day, COUNT(DISTINCT comment)
FROM news.data
GROUP BY day
UNION ALL
SELECT date(time + INTERVAL 8 HOUR) as day, COUNT(DISTINCT comment)
FROM`news-backup`.`data`
GROUP BY day
ORDER BY year(day) desc, day(day) DESC
LIMIT 20
What seems to happen, there are some timestamps in range of both databases so they produce separate counts for certain dates. So it would give count for TODAY from news and news-backup
EX:
date count
2013-1-15 10
2013-1-15 13
2013-1-14 8
2013-1-13 15
What I want is
EX:
date count
2013-1-15 23
2013-1-14 8
2013-1-13 15
Here is a kicker, I need it in a view, so there are some limitations with that (no subqueries allowed). Thoughts? And no I cannot change the data dump sequence that happens between to DBs
You can't put a subquery in a view, but you can put a view in a view.
So:
create view1 as
SELECT date(time + INTERVAL 8 HOUR) as day, 'current' as which, COUNT(DISTINCT comment) as cnt
FROM news.data
GROUP BY day
UNION ALL
SELECT date(time + INTERVAL 8 HOUR) as day, 'backup' as which, COUNT(DISTINCT comment) as cnt
FROM`news-backup`.`data`
GROUP BY day, which
I'm not sure what you logic for combining them is:
create view2 as
select day, max(cnt) -- sum(cnt)? prefer current or backup?
from view1
group by day
ORDER BY day desc
The documentation that bans subqueries is here. Be sure to search for "The SELECT statement cannot contain".
If you have a table of all the dates, the following "absurd" SQL might work:
select c.date,
coalesce( (select count(distinct comment) from news.data where date(time + INTERVAL 8 HOUR) = c.date),
(select count(distinct comment) from news_backup.data where date(time + INTERVAL 8 HOUR) = c.date)
) as NumComments
from calendar c
This version is assuming you want the "new" first, then the backup. If you want the sum, then you would add them.