Date calculation ranking - Assign a value for event age - mysql

I'm trying to assign a value for events based on it's age using this:
SELECT u.iduser, timeIsImportant
FROM user AS u
LEFT OUTER JOIN(
SELECT action, user_id, action_time,
(IF(bb.action_time < DATE_SUB(CURDATE(), INTERVAL 7 DAY),
(CASE
WHEN bb.action_time BETWEEN DATE_SUB(CURDATE(), INTERVAL 3 MONTH) AND CURDATE() THEN 0.1
WHEN bb.action_time BETWEEN DATE_SUB(CURDATE(), INTERVAL 6 MONTH) AND CURDATE() THEN 0.2
WHEN bb.action_time BETWEEN DATE_SUB(CURDATE(), INTERVAL 12 MONTH) AND CURDATE() THEN 0.4
WHEN bb.action_time BETWEEN DATE_SUB(CURDATE(), INTERVAL 18 MONTH) AND CURDATE() THEN 0.7
WHEN bb.action_time BETWEEN DATE_SUB(CURDATE(), INTERVAL 24 MONTH) AND CURDATE() THEN 1.0
END), 0))
AS timeIsImportant
FROM bigbrother AS bb
ORDER BY bigbrother.uid DESC LIMIT 1)
AS bbb
ON
bbb.user_id = u.iduser AND bbb.action = "C"
WHERE u.iduser = 2;
The idea is that older 'events' on the bigbrother table need to subtract different values from a ranking query calculation. The timeIsImportant value from the query above, would be the agePoints on the following example.
sample data:
row1 row2
------------- -------------
rank: 4.7 rank 4.9
agePoints: 0.1 agePoints 0.4
timedRank: (rank-AgePoints) timedRank: (rank-AgePoints)
-------------------------------------------------------------
SQL: ORDER BY timedRank DESC
row1, row2
SQL: ORDER BY timedRank ASC
row2, row1
I wonder if there's another way to assign the values based on events age, since I'm doing this calculation on every page load in order to rank search results and found that this piece of code slows the overall performance when nested within the search query.

Related

Calculate momentum as weighted average by recency

I have a subscriptions table with an associated feed_id and creation timestamp. A feed has N subscriptions.
It's easy enough to show the most popular feeds using a group query to count the number of records with each feed_id. But I want to calculate momentum to show most trending feeds.
A simplified algorithm would be:
momentum of feed_id =
10 * (count of subscriptions with created_at in past day)
+ 5 * (count of subscriptions with created_at from 2-7 days ago)
+ 1 * (count of subscriptions with created_at from 7-28 days ago)
How can something like this be done in a single (My)SQL query instead of doing it with 3 queries and programmatically summing the results?
You can use conditional aggregation for this. MySQL treats booleans as integers, with true being "1", so you can just sum the expression for time.
I am guessing it looks something like this:
select feedid,
(10 * sum(createdat >= date_sub(now(), interval 1 day)) +
5 * sum(createdat >= date_sub(now(), interval 7 day) and
createdat < date_sub(now(), interval 1 day)) +
1 * sum(createdat >= date_sub(now(), interval 28 day) and
createdat < date_sub(now(), interval 7 day))
) as momentum
from subscriptions
group by feedid
SELECT 10*COUNT(IF(created_at >= CURDATE(), 1, 0)) +
5*COUNT(IF(created_at BETWEEN DATE_ADD(CURDATE(), - INTERVAL 7 days) AND DATE_ADD(CURDATE(), - INTERVAL 1 day), 1, 0) +
1*COUNT(IF(created_at BETWEEN DATE_ADD(CURDATE(), - INTERVAL 28 days) AND DATE_ADD(CURDATE(), - INTERVAL 8 day), 1, 0)
FROM ...
I'm not 100% sure I've caught the edge conditions (yesterday or 8 days ago) to get exactly the right count. You'll want to test that.
If you're interested in 24-hour periods then just substitute NOW() for CURDATE() and everything will go to DATETIME.

Percentage change from previous row when multiple rows have the same date?

I have a query that calculates ratios and returns them for each hour and server on a given day:
SELECT a.day,
a.hour,
Sum(a.gemspurchased),
Sum(b.gems),
Sum(b.shadowgems),
( Sum(b.gems) / Sum(a.gemspurchased) ) AS GemRatio,
( Sum(b.shadowgems) / Sum(a.gemspurchased) ) AS ShadowGemRatio
FROM (SELECT Date(Date_sub(createddate, INTERVAL 7 hour)) AS day,
Hour(Date_sub(createddate, INTERVAL 7 hour)) AS hour,
serverid,
Sum(gems) AS GemsPurchased
FROM dollartransactions
WHERE Date(Date_sub(createddate, INTERVAL 7 hour)) BETWEEN
Curdate() - INTERVAL 14 day AND Curdate()
GROUP BY 1,
2,
3) a,
/*Gems recorded from DollarTransactions Table after purchasing gem package*/
(SELECT Date(Date_sub(createddate, INTERVAL 7 hour)) AS day,
Hour(Date_sub(createddate, INTERVAL 7 hour)) AS hour,
serverid,
Sum(acceptedamount) AS Gems,
Sum(acceptedshadowamount) AS ShadowGems
FROM gemtransactions
WHERE Date(Date_sub(createddate, INTERVAL 7 hour)) BETWEEN
Curdate() - INTERVAL 14 day AND Curdate()
AND transactiontype IN ( 990, 2 )
AND fullfilled = 1
AND gemtransactionid >= 130000000
GROUP BY 1,
2,
3) b
/*Gems & Shadow Gems spent, recorded from GemTransactions Table */
WHERE a.day = b.day
AND a.serverid = b.serverid
GROUP BY 1,
2
This code returns the component parts of the ratios, as well as the ratios themselves (which are sometimes null):
day hour sum(a.GemsPurchased) sum(b.Gems) sum(b.ShadowGems) GemRatio ShadowGemRatio
9/5/2014 0 472875 465499 60766 0.9844 0.1285
9/5/2014 1 350960 371092 45408 1.0574 0.1294
9/5/2014 2 472985 509618 58329 1.0775 0.1233
9/5/2014 3 1023905 629310 71017 0.6146 0.0694
9/5/2014 4 1273170 628697 74896 0.4938 0.0588
9/5/2014 5 998920 637709 64145 0.6384 0.0642
9/5/2014 6 876470 651451 68977 0.7433 0.0787
9/5/2014 7 669100 667217 81599 0.9972 0.122
What I'd like to do is create an 8th and 9th column which calculate the % change from previous row for both GemRatio and ShadowGemRatio. I've seen other threads here on how to do this for specific queries, but I couldn't get it to work for my particular MySQL query...
Ok first create a view for that query. Let's call it v1:
CREATE VIEW v1 AS SELECT YOUR QUERY HERE;
Now here is the query to have the ratios. I assumed a day has 24 hours. The first row ratio change will be zero.
select now.*,
CASE
WHEN yesterday.gemRatio is null THEN 0
ELSE 100*(now.gemRatio-yesterday.gemRatio)/yesterday.gemRatio
END as gemChange,
CASE
WHEN yesterday.ShadowGemRatio is null THEN 0
ELSE 100*(now.ShadowGemRatio-yesterday.ShadowGemRatio)/yesterday.ShadowGemRatio
END as shadowGemChange
from v1 now left outer join v1 yesterday on
((now.day = yesterday.day && now.hour = yesterday.hour+1) ||
(DATEDIFF(now.day,yesterday.day) = 1 && now.hour = 0 && yesterday.hour=23))

MySQL: search orders made since yesterday 3pm till today 4pm

I have table ORDERS where is stored data about orders with their status and the date of order. I would like to search all orders with specified status and which was made yesterday after 3pm untill today 4pm. The query will run in different times (10am, 3pm, 5 pm... regardless).
So on example: if I run the query today (13.05.2014) I would like to get all orders made from 2014-12-05 15:00:00 untill 13-05-2015 16:00:00
The date is stored in format: YYYY-MM-DD HH:MM:SS
What I got is:
select *
from orders
where status = 'new'
and (
(
date_add(created_at, INTERVAL 1 day) = CURRENT_DATE()
and hour(created_at) >= 15
) /*1*/
or (
date(created_at) = CURRENT_DATE()
and hour(created_at) <= 16
) /*2*/
)
And I get only orders made today - like only the 2nd condition was taken into account.
I prefer not to use created >= '2014-05-12 16:00:00' (I will not use this query, someone else will).
When you add an interval of 1 day to the date/time, you still keep the time component. Use date() for the first condition:
where status = 'new' and
((date(date_add(created_at, INTERVAL 1 day)) = CURRENT_DATE() and
hour(created_at) >= 15
) /*1*/ or
(date(created_at) = CURRENT_DATE() and
hour(created_at) <= 16
) /*2*/
)
And alternative method is:
where status = 'new' and
(created_at >= date_add(CURRENT_DATE(), interval 15-24 hour) and
created_at <= date_add(CURRENT_DATE(), interval 16 hour)
)
The advantage of this approach is that all functions are moved to CURRENT_DATE(). This would allow MYSQL to take advantage of an index on created_at.

Linear equation function with MySQL?

I want to compute scores for some data I have in a MySQL database. The score will be computed as follows:
score = COUNT(purchases MADE BETWEEN NOW() AND (NOW() - 1 WEEK))
+ 0.7 * COUNT(purchases MADE BETWEEN (NOW() - 1 WEEK) AND (NOW() - 2 WEEKS))
+ 0.4 * COUNT(purchases OLDER THAN (NOW() - 2 WEEKS))
I have purchses in a table with a purchase_time column.
Is it possible to do this in MySQL and get output similar to the following?
ORDER_ID SCORE
3 8
4 3
5 15
Thanks
--- EDIT ---
The table structure is:
tblOrder - table
id - primary key
created - time stamp
SELECT orderId,
SUM
(
CASE
WHEN purchase_date > NOW() - INTERVAL 1 WEEK AND purchase_date <= NOW() THEN
1
WHEN purchase_date > NOW() - INTERVAL 2 WEEK AND purchase_date <= NOW() - INTERVAL 1 WEEK THEN
0.7
ELSE
0.3
END
)
FROM mytable
GROUP BY
orderId
Your between and older need to be converted into CASE.
CASE case_value
WHEN when_value THEN statement_list
[WHEN when_value THEN statement_list] ...
[ELSE statement_list]
END CASE
At the same time you can rewrite the expression so that specific cases are the factors such as
SELECT SUM(
CASE DATEDIFF(now(),purchase_datetime) DIV 7
WHEN 0 THEN 1
WHEN 1 THEN 0.7
ELSE 0.4
END
)
FROM table
WHERE purchase_datetime < now()
GROUP BY ORDER_ID
SELECT ORDER ID, COUNT(purchases MADE BETWEEN NOW() AND (NOW() - 1 WEEK))
+ 0.7 * COUNT(purchases MADE BETWEEN (NOW() - 1 WEEK) AND (NOW() - 2 WEEKS))
+ 0.4 * COUNT(purchases OLDER THAN (NOW() - 2 WEEKS)) AS SCORE
FROM TABLE
should do the trick, I don't currently have mysql installed so I can't test it.
also, use datediff to find if a date is between the range of dates
SELECT order_id, sum(score) FROM
(
(SELECT Order_id, COUNT(id) AS Score FROM purchases
WHERE purchase_time BETWEEN CURDATE() AND DATE_SUB(CURDATE(),INTERVAL 1 WEEK))
GROUP BY order_id
UNION ALL
(SELECT Order_id, (COUNT(id) * 0.7) AS score FROM purchases
WHERE purchase_time BETWEEN DATE_SUB(CURDATE(),INTERVAL 1 WEEK)
AND DATE_SUB(CURDATE(),INTERVAL 2 WEEK))
GROUP BY order_id
UNION ALL
(SELECT Order_id, (COUNT(id) * 0.4) AS score FROM purchases
WHERE purchase_time < DATE_SUB(CURDATE(),INTERVAL 2 WEEK))
GROUP BY order_id
) s
GROUP BY order_id;

Query to get all rows from previous month

I need to select all rows in my database that were created last month.
For example, if the current month is January, then I want to return all rows that were created in December, if the month is February, then I want to return all rows that were created in January. I have a date_created column in my database that lists the date created in this format: 2007-06-05 14:50:17.
SELECT * FROM table
WHERE YEAR(date_created) = YEAR(CURRENT_DATE - INTERVAL 1 MONTH)
AND MONTH(date_created) = MONTH(CURRENT_DATE - INTERVAL 1 MONTH)
Here's another alternative. Assuming you have an indexed DATE or DATETIME type field, this should use the index as the formatted dates will be type converted before the index is used. You should then see a range query rather than an index query when viewed with EXPLAIN.
SELECT
*
FROM
table
WHERE
date_created >= DATE_FORMAT( CURRENT_DATE - INTERVAL 1 MONTH, '%Y/%m/01' )
AND
date_created < DATE_FORMAT( CURRENT_DATE, '%Y/%m/01' )
If there are no future dates ...
SELECT *
FROM table_name
WHERE date_created > (NOW() - INTERVAL 1 MONTH);
Tested.
Alternatively to hobodave's answer
SELECT * FROM table
WHERE YEAR(date_created) = YEAR(CURRENT_DATE - INTERVAL 1 MONTH)
AND MONTH(date_created) = MONTH(CURRENT_DATE - INTERVAL 1 MONTH)
You could achieve the same with EXTRACT, using YEAR_MONTH as unit, thus you wouldn't need the AND, like so:
SELECT * FROM table
WHERE EXTRACT(YEAR_MONTH FROM date_created) = EXTRACT(YEAR_MONTH FROM CURDATE() - INTERVAL
1 MONTH)
SELECT *
FROM yourtable
where DATE_FORMAT(date_created, '%Y-%m') = date_format(DATE_SUB(curdate(), INTERVAL 1 month),'%Y-%m')
This should return all the records from the previous calendar month, as opposed to the records for the last 30 or 31 days.
Even though the answer for this question has been selected already, however, I believe the simplest query will be
SELECT *
FROM table
WHERE
date_created BETWEEN (CURRENT_DATE() - INTERVAL 1 MONTH) AND CURRENT_DATE();
WHERE created_date >= DATE_ADD(LAST_DAY(DATE_SUB(NOW(), INTERVAL 2 MONTH)), INTERVAL 1 DAY)
AND created_date <= DATE_ADD(LAST_DAY(DATE_SUB(NOW(), INTERVAL 1 MONTH)), INTERVAL 0 DAY)
This worked for me (Selects all records created from last month, regardless of the day you run the query this month)
Alternative with single condition
SELECT * FROM table
WHERE YEAR(date_created) * 12 + MONTH(date_created)
= YEAR(CURRENT_DATE) * 12 + MONTH(CURRENT_DATE) - 1
select fields FROM table
WHERE date_created LIKE concat(LEFT(DATE_SUB(NOW(), interval 1 month),7),'%');
this one will be able to take advantage of an index if your date_created is indexed, because it doesn't apply any transformation function to the field value.
Here is the query to get the records of the last month:
SELECT *
FROM `tablename`
WHERE `datefiled`
BETWEEN DATE_SUB( DATE( NOW( ) ) , INTERVAL 1
MONTH )
AND
LAST_DAY( DATE_SUB( DATE( NOW( ) ) , INTERVAL 1
MONTH ) )
Regards
- saqib
if you want to get orders from last month, you can try using
WHERE MONTH(order_date) = MONTH(CURRENT_DATE()) -1
One more way to do this in:
MYSQL
select * from <table_name> where date_created >= DATE_ADD(NOW(), INTERVAL -30 DAY);
SELECT * FROM table
WHERE YEAR(date_created) = YEAR(CURRENT_DATE - INTERVAL 1 MONTH)
AND MONTH(date_created) = MONTH(CURRENT_DATE - INTERVAL 1 MONTH)