I need to build an SQL query which does the following
SELECT pi.desc,
pa.nameAddress,
pi.ref,
pi.descItem,
pi.quantity,
pi.totalDF,
pi.code,
pi.codeBL,
cl.dateShip, po.dtValidated, po.supervisorDate, DATEDIFF(po.supervisorDate, po.dtValidated) AS 'diffValidSupervisor', DATEDIFF(cl.dtlivr, po.supervisorDate) AS 'diffExpeValid', year(cl.dtlivr), month(cl.dtlivr) FROM
new.proforma_item pi
INNER JOIN
old.cdestk_lig cl ON pi.codeCde = cl.codcde INNER JOIN new.proforma po ON po.idProforma = pi.idProforma Inner JOIN new.proforma_address pa ON po.idProforma = pa.idProforma GROUP BY pi.desc, pi.ref, pi.descItem, pi.code, pi.codeBL, cl.dateShip, po.dtValidated, po.supervisorDate, month(cl.dateShip), po.dateInvoice HAVING (po.dateInvoice between '2014-01-01' AND '2014-12-31')
But each year I have to review this request to change the year. I want to make this dynamic, because change this manually is just crazy in our architecture.
The best of all is:
Say we are the 15 June 2015. I would like a between clause that covers the period:
2015-01-01 to 2015-05-31
In finite I need a between which take the first day of the current year and last day of last month.
EDIT
When we are in Januray, we have to work on the full passed year, not the current month. (January will be treated next month)
You can use the DATE_FORMAT function to construct the first day of the year and LAST_DAY to get the last day of the previous month.
po.dateInvoice between DATE_FORMAT(NOW() - INTERVAL 1 MONTH, '%Y-01-01') AND LAST_DAY(NOW() - INTERVAL 1 MONTH)
SQL fiddle for the current date
SQL fiddle for January 2015 (Showing all of 2014)
You can easily get the first day of the current year using concat function and for getting the last day of the previous month is also straight forward something as
mysql> select
concat(year(curdate()),'-01-01') as fday,
last_day(curdate()-interval 1 month) as lday ;
+------------+------------+
| fday | lday |
+------------+------------+
| 2015-01-01 | 2015-05-31 |
+------------+------------+
1 row in set (0.00 sec)
So in the where clause you just need to replace the hardcoded part with the above date ranges.
HAVING
(
po.dateInvoice between
concat(year(curdate()),'-01-01') AND
last_day(curdate()-interval 1 month)
)
The above works but still there might be a logical issue, what if we are at January now, so as per the logic it will get the first day of the year and then last day of the previous month and its Dec 31st previous year and having clause will fail, so may be need an extra condition something while getting the last day as
case
when month(curdate()) = 1 then concat(year(curdate()),'-01-31')
else last_day(curdate()-interval 1 month)
end
So when you are in January it will get record from 1st Jan till 31st
UPDATE:
When we are in Januray, we have to work on the full passed year, not
the current month. (January will be treated next month)
The logic could be applied as
HAVING
(
po.dateInvoice between
case
when
month(curdate()) = 1 then concat(year(curdate())-1,'-01-01') else concat(year(curdate()),'-01-01')
end
AND
case
when
month(curdate()) = 1 then concat(year(curdate())-1,'-12-31') else last_day(curdate()-interval 1 month)
end
)
I think these SQL help to you.
SELECT pi.desc,
pa.nameAddress,
pi.ref,
pi.descItem,
pi.quantity,
pi.totalDF,
pi.code,
pi.codeBL,
cl.dateShip, po.dtValidated, po.supervisorDate, DATEDIFF(po.supervisorDate, po.dtValidated) AS 'diffValidSupervisor',
DATEDIFF(cl.dtlivr, po.supervisorDate) AS 'diffExpeValid', year(cl.dtlivr), month(cl.dtlivr) FROM
new.proforma_item pi
INNER JOIN
old.cdestk_lig cl ON pi.codeCde = cl.codcde INNER JOIN new.proforma po ON po.idProforma = pi.idProforma
Inner JOIN new.proforma_address pa ON po.idProforma = pa.idProforma GROUP BY pi.desc, pi.ref, pi.descItem, pi.code, pi.codeBL, cl.dateShip,
po.dtValidated, po.supervisorDate, month(cl.dateShip), po.dateInvoice
HAVING (po.dateInvoice between CONCAT(YEAR(CURDATE()),'-01-01') AND last_day(curdate()-interval 1 month))
Thank you.
Related
I have a table of users and a table of orders. Table data is linked using a key
user_id. The user has a date of birth. It is necessary to compose a query to display one random user from the users table, over 30 years old, who has made at least 3 orders in the last six months.
I was able to make a query to sample by age:
SELECT Name from users WHERE(DATEDIFF(SYSDATE(), birthday_at)/365)>30;
but I don’t know how to solve the problem to the end
I like the additional effort LukStorms has shown by including details of the date calculations but one important point was missed. It may seem like a subtle difference but it is amazing how often it goes unnoticed until the dataset gets significantly larger. In the WHERE clause for the users age -
WHERE TIMESTAMPDIFF(YEAR, usr.birthday_at, CURDATE()) > 30
the result of the function call (age calculation) is being compared to a static integer. This will result in every user record having its age calculated unnecessarily and will also mean that any applicable index on the birthday_at column cannot be used. By moving the date calculation to the other side of the comparison available indices can be used -
WHERE u.birthday_at <= DATE_SUB(CURDATE(), INTERVAL 30 YEAR)
This may be insignificant for your use case but it is still a good habit to get into as it will almost certainly catch you out one day.
Furthermore, if you are retrieving the random user as part of some kind of reward scheme, I would suggest applying a random order of some kind as the single row returned will be predictable and repeatable.
SELECT u.id, u.Name
FROM users AS u
JOIN orders AS o
ON u.id = o.user_id
AND o.order_date >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
WHERE u.birthday_at <= DATE_SUB(CURDATE(), INTERVAL 30 YEAR)
GROUP BY u.id
HAVING COUNT(o.id) >= 3
ORDER BY RAND()
LIMIT 1
Join to orders
Get only those over 30 years old and with orders from last 6 months
Group by the user
Filter on the count with a having
Limit to 1 without sorting (since random)
SELECT usr.Name AS UserName
FROM users AS usr
JOIN orders AS ord
ON ord.user_id = usr.user_id
WHERE TIMESTAMPDIFF(YEAR, usr.birthday_at, CURDATE()) > 30
AND ord.order_date BETWEEN DATE_ADD(LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 6+1 MONTH)), INTERVAL 1 DAY)
AND LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 1 MONTH))
GROUP BY usr.Name
HAVING COUNT(ord.order_id) >= 3
LIMIT 1
Test code for the date calculations
-- previous month, last day
select LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 1 MONTH))
| LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 1 MONTH)) |
| :---------------------------------------------- |
| 2021-10-31 |
-- 6 months ago, first day
select DATE_ADD(LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 6+1 MONTH)), INTERVAL 1 DAY)
| DATE_ADD(LAST_DAY(DATE_SUB(CURDATE(), INTERVAL 6+1 MONTH)), INTERVAL 1 DAY) |
| :-------------------------------------------------------------------------- |
| 2021-05-01 |
-- someone's current age
select TIMESTAMPDIFF(YEAR, '2005-11-28', CURDATE())
| TIMESTAMPDIFF(YEAR, '2005-11-28', CURDATE()) |
| -------------------------------------------: |
| 15 |
db<>fiddle here
I have tried looking at some similar examples like group by date range and weekdays etc but I couldnt fix it on my query.
as per my sample data screenshot, I need to only return
sum(salesamount)/sum(salescount) for week 1
and
sum(salesamount)/sum(salescount) for week 2.
Each of the week contain 5 days (in this example is wednesday - sunday).
My Attempt:
select salesstartdate, date_add(salesstartdate, interval 5 day) as gdate,
salesamount, salescount, sum(salesamount)/sum(salescount) as ATV
from testing
group by gdate;
My desired output is:
Week 1 15.34173913
Week 2 15.80365088
Calculation to get week 1 is (3507.1+3639.97+5258.77+8417.04+5994.48)/(285+273+344+478+368)
Calculation to get week 2 is the same as above except the date would now be from 8 to 12 of June.
You can do it with a subquery. In order to first group your result set properly and then execute aggregation on it:
SELECT
concat('WEEK', ' ', weekno) as `Week #`,
MIN(salesstartdate) as startDate,
MAX(salesstartdate) as endDate,
sum(salesamount)/sum(salescount) as ATV
FROM
(
SELECT
salesstartdate,
salesamount,
salescount,
WEEKOFYEAR(salesstartdate) as weekno -- get the week number of the current year
FROM
weekno
WHERE
WEEKDAY(salesstartdate) BETWEEN 2 AND 6 -- get index of week day
) as weeks
GROUP BY
weekno
I have used 2 MySQL functions here:
WEEKOFYEAR()
WEEKDAY()
Output:
WEEK 23 | 2016-06-08 | 2016-06-12 | 15.8040
WEEK 24 | 2016-06-16 | 2016-06-19 | 15.9323
and without subquery as well:
SELECT
concat('WEEK', ' ', WEEKOFYEAR(salesstartdate)) as `Week #`,
MIN(salesstartdate) as startDate,
MAX(salesstartdate) as endDate,
sum(salesamount)/sum(salescount) as ATV
FROM
weekno
WHERE
WEEKDAY(salesstartdate) BETWEEN 2 AND 6 -- get index of week day
GROUP BY
WEEKOFYEAR(salesstartdate)
You can do this way
select SUBDATE(salesstartdate, WEEKDAY(salesstartdate)) as week_range
, sum(salesamount)/sum(salescount)
from testing
where salesstartdate between SUBDATE(salesstartdate, WEEKDAY(salesstartdate))
and date_add(SUBDATE(salesstartdate, WEEKDAY(salesstartdate)), interval 5 day))
Group by week_range
I have a table like this:
id | created_on
1 2013-09-03 20:05:09
2 2013-09-05 17:03:13
...
How do I write a query to return a result of record counts that was created from Date X to Date Y in 7-day intervals?
So the result would look like this:
count | created_on
4 2013-09-17 00:00:00
2 2013-09-24 00:00:00
1 2013-09-31 00:00:00
10 2013-10-07 00:00:00
...
You can go to the beginning of the week by subtracting the day of the week. Here is one way to do that:
select date(created_on - interval dayofweek(created_on) day), count(*)
from t
group by date(created_on - interval dayofweek(created_on) day);
If this is not the day you want the week to start, then you can add an offset day.
Group by the date field, floored to the week:
SELECT
count(*),
YEARWEEK(created_on) as week
FROM
yourtable
GROUP BY week
This assumes that created_on is a type that can be interpreted as a date:
http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_yearweek
This will get you weekly groupings, but you may want to then convert that field (which will look like YYYYWW) back to something more readable.
You can try this
SELECT created_on, count( id ) AS count
FROM `test_table`
WHERE created_on
BETWEEN '2013-09-01'
AND '2013-10-10'
GROUP BY WEEK( created_on )
I have an app that inserts a Unix timestamp on registration. What I'd like to do, is calculate usage details for the month since the last monthly anniversary day. So I would need a unix timestamp of what the most recent anniversary day would be.
For example, if a registration is submitted on January 5, the customer's anniversary day is the 5th. So to check usage on February 15th, I need to retrieve all entries from the logs since Feb 5.
Getting the day of registration is easy:
SELECT DATE_FORMAT(FROM_UNIXTIME(created), '%d') FROM accounts
however, I'm lost finding the unix timestamp of the last anniversary date based on the registration day. How would I do that? To clarify, I'm looking to return all action_id created on or after the most recent anniversary date.
Tables:
accounts
+------------+------------+
| account_id | created |
+------------+------------+
| 1 | 1321838910 |
+------------+------------+
....
logs
+------------+------------+------------+
| account_id | action_id | created |
+------------+------------+------------+
| 1 | 233 | 1348249244 |
+------------+------------+------------+
| 1 | 263 | 1348257653 |
+------------+------------+------------+
....
Note: to keep things simple, I'm going to forgo figuring out what happens if an anniversary day is the 31st for example - that is, unless someone has a super ninja statement that takes those occurrences into account.
Not tested. See what you think. Logic is to:
Get the last day of the current month.
Add the account created day number of days to #1 result.
If current day is greater than created day, subtract 1 month from #2 result. Else subtract 2 months.
SELECT l.*
FROM accounts a
LEFT JOIN logs l
ON a.account_id = l.account_id
AND l.created >= UNIX_TIMESTAMP(
DATE_SUB(DATE_ADD(LAST_DAY(NOW()), INTERVAL DAY(FROM_UNIXTIME(a.created)) DAY),
INTERVAL IF(DAY(NOW()) > DAY(FROM_UNIXTIME(a.created)), 1, 2) MONTH));
Edit
I gave this some more thought and perhaps the query below will work regardless of when the anniversary date is. Assumption I made that if the anniversary day is not in a particular month then last day of the month should be taken. It's ugly but I put in some variables to make it more concise, there must be a nicer way. Anyway, I haven't tested but logic as follows.
If current day > anniversay day then just subtract the difference in days to get date.
else if the last day of the previous month is less than anniversary day then use the last day of previous month.
else subtract the day difference between anniversary day and last day of previous month from last date of previous month.
SELECT l.*
FROM accounts a
JOIN logs l
ON a.account_id = l.account_id
AND l.created >= UNIX_TIMESTAMP(
IF(#dNow := DAY(NOW()) >= #dCreated := DAY(FROM_UNIXTIME(a.created)),
DATE_SUB(NOW(), INTERVAL #dNow - #dCreated DAY),
IF(DAY(#endLastMonth := LAST_DAY(DATE_SUB(NOW(), INTERVAL 1 MONTH))) <= #dCreated,
#endLastMonth,
DATE_SUB(#endLastMonth, INTERVAL DAY(#endLastMonth) - #dCreated DAY))));
Perhaps this using order by desc to get most recent created date?
SELECT DATE_FORMAT(FROM_UNIXTIME(X.created), '%d')
FROM (
SELECT CREATED
FROM mytable
WHERE ACCOUNT_ID = ? -- customer id
AND DATE_DIFF(DATE_FORMAT(FROM_UNIXTIME(CREATED),'%Y-%m-%d'), NOW()) MOD 30 = 0
AND DATE_DIFF(DATE_FORMAT(FROM_UNIXTIME(CREATED),'%Y-%m-%d'), NOW()) / 30 = 1
ORDER BY CREATED DESC LIMIT 1)X;
Consider a table with id,date datetime,value double, I have data in the table every minute.
I'm trying to use mysql to identify "events" where value > 10 continuously for more than 3 hours.
At the time I am using the query:
select date from table where value > 10;
Then I manually read where the dates are continuously.
Example of "event":
Date - value
2000/01/01 00:00 - 5
2000/01/01 01:00 - 5
2000/01/01 02:00 - 5
2000/01/01 03:00 - 11
2000/01/01 04:00 - 11
2000/01/01 05:00 - 11
2000/01/01 06:00 - 5
2000/01/01 07:00 - 5
2000/01/01 08:00 - 5
2000/01/01 09:00 - 11
2000/01/01 10:00 - 11
2000/01/01 11:00 - 5
In this case there is one "event" between 03:00 and 05:00.
In MySQL, you can assign variables in a SELECT statement while retrieving data. This functionality helps in solving many problems where one would "normally" use windowing functions (which MySQL doesn't have). It can also help in yours. Here's a solution I ended up with:
SET #startdate = CAST(NULL AS datetime);
SET #granularity = 60; /* minutes */
SET #minduration = 180; /* minutes */
SET #minvalue = 10;
SELECT
t.Date,
t.Value
FROM (
SELECT
StartDate,
MAX(Date) AS EndDate
FROM (
SELECT
Date,
Value,
CASE
WHEN Value > #minvalue OR #startdate IS NOT NULL
THEN IFNULL(#startdate, Date)
END AS StartDate,
#startdate := CASE
WHEN Value > #minvalue
THEN IFNULL(#startdate, Date)
END AS s
FROM (
SELECT Date, Value FROM YourTable
UNION ALL
SELECT MAX(Date) + INTERVAL #granularity MINUTE, #minvalue FROM YourTable
) s
ORDER BY Date
) s
WHERE StartDate IS NOT NULL
GROUP BY StartDate
) s
INNER JOIN YourTable t ON t.Date >= s.StartDate AND t.Date < s.EndDate
WHERE s.EndDate >= s.StartDate + INTERVAL #minduration MINUTE
;
Three of the four variables used here are merely script arguments, and only one, #startdate, actually gets both assigned and checked in the query.
Basically, the query iterates over the rows, marking those where the value is greater than a specific minimum (#minvalue), eventually producing a list of time ranges during which values matched the condition. Actually, in order to calculate the ending bounds correctly, non-matching rows that immediately follow groups of the matching ones are also included in the respective groups. Because of that, an extra row is being added to the original dataset, where Date is calculated off the latest Date plus the specified #granularity of timestamps in your table and Value is just #minvalue.
Once obtained, the list of ranges is joined back to the original table to retrieve the detail rows that fall in between the ranges' bounds, the ranges that are not long enough (as specified by #minduration) being filtered out along the way.
If you run this solution on SQL Fiddle, you will see the following output:
DATE VALUE
------------------------------ -----
January, 01 2000 03:00:00-0800 11
January, 01 2000 04:00:00-0800 11
January, 01 2000 05:00:00-0800 11
which, I understand, is what you would expect.
select count(*) from table where DATE_SUB(CURDATE(),INTERVAL 3 HOUR) < `date`
select count(*) from table where DATE_SUB(CURDATE(),INTERVAL 3 HOUR) < `date` AND `value` > 10
Then compare the result, if not same, then is not continuously.
Wild guess:
select * from
(select event, MAX(date) as date from table where value > 10 group by event) maxs
inner join
(select event, MIN(date) as date from table where value > 10 group by event) mins
on maxs.event = mins.event
where (time_to_sec(timediff(maxes.date, mins.date)) / 3600) > 3