Find first buisness day of next month MySQL without function - mysql

We have a date_value column and another Boolean column which indicates whether the day is a business day or not.
We are trying to find the first business day of the next month( example, for September, 2015 I want it to return 2015-10-01)
We have tried a couple different methods involving last_day, intervals and subqueries but can't quite get it to work.
We also don't have the ability to create custom functions, which makes this a little more difficult.

I think you want something like this:
select min(date_value) fwd
from tablename
where isWorkDay = 1 and
extract(year from date_value)=extract(year from curdate()) and
extract(month from date_value)=extract(month from curdate()) + 1
For all months (v0.3) (please note that I can test this now, so it might have some error):
select t1.month_number, min(t2.date_value)
from tablename t1 join
tablename t2 on extract(year from t1.date_value) * 12 + t1.month_number = extract(year from t2.date_value) * 12 + t2.month_number - 1
where t2.isWorkDay = 1
group by t1.month_number

I was able to get it using the below
SELECT
d.year
,d.month_number
,first_business_period as next_month_first_period
,d2.previous_business_day
FROM lk_date d
JOIN (
SELECT
a.*
, MAX(CASE WHEN d2.business_period_in_days<>0 THEN d2.date_value ELSE NULL END) AS previous_business_day
FROM(
SELECT
d1.year
,d1.month_number
, MIN(CASE WHEN d1.business_period_in_days <> 0 THEN d1.date_value END) AS first_business_period
FROM lk_date d1
GROUP BY 1,2
) a
JOIN lk_date d2 ON d2.date_Value < a.first_business_period
GROUP BY 1,2,3) d2 on d2.previous_business_day = d.date_value

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...)

condition in SELECT in mysql

I have a query that looks like this
SELECT customer, totalvolume
FROM orders
WHERE deliverydate BETWEEN '2020-01-01' AND CURDATE()
Is there any way to select totalvolume for specific date range and make it a separate column?
So for example, I already have totalvolume. I'd like to also add totalvolume for the previous month as a separate column (totalvolume where deliverydate BETWEEN '2020-08-01' AND '2020-08-31'). Is there a function for that?
Simply use 2 table copies:
SELECT t1.customer, t1.totalvolume, t2.totalvolume previousvolume
FROM orders t1
LEFT JOIN orders t2 ON t1.customer = t2.customer
AND t1.deliverydate = t2.deliverydate + INTERVAL 1 MONTH
WHERE t1.deliverydate BETWEEN '2020-08-01' AND '2020-08-31';
You can do it with case/when construct in your columns and just expand your WHERE clause. Sometimes I would do it by having a secondary #variables to simplify my clauses. Something like
SELECT
o.customer,
sum( case when o.deliveryDate < #beginOfMonth
then o.TotalVolume else 0 end ) PriorMonthVolume,
sum( case when o.deliveryDate >= #beginOfMonth
then o.TotalVolume else 0 end ) ThisMonthVolume,
sum( o.totalvolume ) TwoMonthsVolume
FROM
( select #myToday := date(curdate()),
#beginOfMonth := date_sub( #myToday, interval dayOfMonth( #myToday ) -1 day ),
#beginLastMonth := date_sub( #beginOfMonth, interval 1 month ) ) SqlVars,
orders o
WHERE
o.deliverydate >= #beginLastMonth
group by
o.customer
To start, the "from" clause of the query alias "SqlVars" will dynamically create 3 variables and return a single row for that set. With no JOIN condition, is always a 1:1 ratio for everything in the orders table. Nice thing, you don't have to pre-declare variables and the #variables are available for the query.
By querying for all records on or after the beginning of the LAST month, you get all records for both months in question. The sum( case/when ) can now use those variables as the demarcation point for the respective volume totals.
I know you mentioned this was a simplified query, but masking that might not be a perfect answer to what you need, but may help you look at it from a different querying perspective.

SQL finding number of users in consecutive months in a table

I have an table with the following columns
email ---- created at
abc#gmail.com 2019-12-12 16:03:34
rp#gamil.com 2019-11-12 16:03:34
abc#gmail.com 2020-1-12 16:03:34
er#gmail.com 2020-1-12 16:03:34
I want to design a query that return the back number of emails that registered in consecutive 2 months. I am no novice with queries and have been struggling to come up with a query for this.
For the above the query abc#gmail.com was registered twice in consecutive months
By doing a self-join for Month + 1 and email (and also taking December-to-January transitions into account) this should work:
SELECT
*
FROM
(
SELECT
email,
YEAR( created ) AS createdYear,
MONTH( created ) AS createdMonth
FROM
table
) AS t
INNER JOIN
(
SELECT
email,
YEAR( created ) AS createdYear,
MONTH( created ) AS createdMonth
FROM
table
) AS monthPlus1 ON
t.email = monthPlus1.email
AND
(
(
t.createdMonth = monthPlus1.createdMonth + 1
AND
t.createdYear = monthPlus1.createdYear
)
OR
(
t.createdMonth = 12
AND
monthPlus1.createdMonth = 1
AND
t.createdYear + 1 = monthPlus1.createdYear
)
)
The date logic in this query is a bit gnarly - it can probably be improved by representing the month as a single date value or integer months-since-epoc rather than a year + month tuple.
You can use an EXISTS query to check if an email exists that also had a registration in the previous month:
SELECT DISTINCT email
FROM yourtable t1
WHERE EXISTS (SELECT *
FROM yourtable t2
WHERE t2.email = t1.email
AND DATE_FORMAT(t2.createdat, '%Y%m') = DATE_FORMAT(t1.createdat - INTERVAL 1 MONTH, '%Y%m'))
Output for your sample data
abc#gmail.com
Demo on dbfiddle
We use DISTINCT so we don't get multiple copies of the same email if an email address is registered in more than one consecutive month.
You can use lag(). If this occurs, then lag() will be in two adjacent months.
select t.email
from (select t.*
lag(created_at) over (partition by t.email order by created_at) as prev_created_at
from t
) t
where extract(year_month from created_at) = extract(year_month from (prev_created_at + interval 1 month));
You may need select distinct, if this can occur multiple times.

Combining database queries

How can these SQL-queries to extract statistics from my database be combined for better performance?
$total= mysql_query("SELECT COUNT(*) as number, SUM(order_total) as sum FROM history");
$month = mysql_query("SELECT COUNT(*) as number, SUM(order_total) as sum FROM history WHERE date >= UNIX_TIMESTAMP(DATE_ADD(CURDATE(),INTERVAL -30 DAY))");
$day = mysql_query("SELECT COUNT(*) as number, SUM(order_total) as sum FROM history WHERE date >= UNIX_TIMESTAMP(CURDATE())");
If you want to all the data in a single query, you have two choices:
Use a UNION query (as sugested by bishop in his answer)
Tweak a query to get what you need in a single row
I'll show option 2 (option 1 has been already covered).
Note: I'm using user variables (that stuff in the init subquery) to avoid writing the expressions again and again. Also, to filter the aggregate data, I'm using case ... end expressions.
select
-- Your first query:
count(*) as number, sum(order_total) as `sum`
-- Your second query:
, sum(case when `date` <= #prev_date then 1 else 0 end) as number_prev
, sum(case when `date` <= #prev_date then order_total else 0 end) as sum_prev
-- Your third query:
, sum(case when `date` <= #cur_date then 1 else 0 end) as number_cur
, sum(case when `date` <= #cur_date then order_total else 0 end) as sum_cur
from (
select #cur_date := unix_timestamp(curdate())
, #prev_date := unix_timestamp(date_add(curdate(), interval -30 day))
) as init
, history;
Hope this helps
Since the queries have the same column structure, you can ask MySQL to combine them with the UNION operation:
(SELECT 'total' AS kind, COUNT(*) as number, SUM(order_total) as sum FROM history~
UNION
(SELECT 'by-month' AS kind, COUNT(*) as number, SUM(order_total) as sum FROM history WHERE date <= UNIX_TIMESTAMP(DATE_ADD(CURDATE(),INTERVAL -30 DAY)))
UNION
(SELECT 'by-day' AS kind, COUNT(*) as number, SUM(order_total) as sum FROM history WHERE date <= UNIX_TIMESTAMP(CURDATE()))

MYSQL return 0 on hourly count

I am at a loss of how to accomplish this but have seen online ISNULL() and COALESCE() used to return a zero if the query is null. I am unsure though how to use it properly though I am intuitively thinking i need to have in a subquery then have ISNULL or COALESCE around that subquery?
The query goes:
SELECT HOUR( dateAdded ) AS HOUR , COUNT( DISTINCT remoteAddr, xForwardedFor) AS cnt
FROM Track
WHERE accessMask = '1iczo'
AND destination = 'lp_include.php'
AND dateAdded
BETWEEN '2014-05-01'
AND '2014-05-02'
GROUP BY HOUR
ORDER BY HOUR
Some help in the right direction would be greatly appreciated!
UPDATE
I used what #Barmar had suggested but it wasn't returning accurate results. I used what he provided and also another topic with a similar situation, Group by should return 0 when grouping by hours. How to do this? . I actually didn't find this topic till after posting this one, :( unfortunately. Here is the final code that appears to return accurate results, distinct across two columns with empty data being returned as 0.
SELECT a.hour, COALESCE(cnt, 0) AS cnt
FROM (SELECT 0 AS hour
UNION ALL
SELECT 1
UNION ALL
SELECT 2 .....
UNION ALL
SELECT 23) a
LEFT JOIN
(SELECT COUNT(DISTINCT remoteAddr, xForwardedFor) AS cnt, HOUR(dateAdded) AS hour
FROM Track
WHERE accessMask = '1iczo'
AND destination = 'lp_include.php'
AND dateAdded
BETWEEN '2014-05-01 00:00:00' AND '2014-05-01 23:59:59') AS totals
ON a.hour = totals.hour
Fiddle for better reference: http://sqlfiddle.com/#!2/9ab660/7
Thanks again to #Barmar, he really put me in the right direction to get to the solution!
You have to join with a table that contains all the hours. This must be a LEFT JOIN so that the results will include hours that have no matches in Track table.
SELECT allHours.hour, IFNULL(cnt, 0) AS cnt
FROM (SELECT 0 AS hour
UNION
SELECT 1
UNION
SELECT 2
UNION
SELECT 3
...
UNION
SELECT 23) AS allHours
LEFT JOIN
(SELECT HOUR(dateAdded) AS hour, COUNT(DISTINCT remoteAddr, xForwardedFor) AS cnt
FROM Track
WHERE accessMask = '1iczo'
AND destination = 'lp_include.php'
AND dateAdded
BETWEEN '2014-05-01' AND '2014-05-02') AS totals
ON allHours.hour = totals.hour
If you assume that you have some data for every hour, you can move the conditional part into the select:
SELECT HOUR(dateAdded) AS HOUR ,
COUNT(DISTINCT CASE WHEN accessMask = '1iczo' AND destination = 'lp_include.php'
THEN CONCAT(remoteAddr, ',', xForwardedFor)
END) AS cnt
FROM Track
WHERE dateAdded BETWEEN '2014-05-01' AND '2014-05-02'
GROUP BY HOUR
ORDER BY HOUR;