mysql query on case when or data between specified date - mysql

for each relationship manager display all the customer and their total orders,who ordered more than 5 times in the last week or more than 10 times in the last 14 days,there are two tables
1.orders[date,rel. manager],
2.customer[cid,cname].
I am trying like this, thanks.
select o.RelationshipManager,c.Name,count(*) total_orders
case
when o.OrderedDate >curdate() -interval 7 day then count(*)
else
o.OrderedDate >curdate() -interval 14 day then count(*)
end
from customer c
join orders o on c.customerid=o.customerid;

SELECT o.RelationshipManager, c.Name
, COUNT(*) total_orders
, COUNT(CASE WHEN o.OrderedDate > curdate() - INTERVAL 7 DAY THEN 1 ELSE NULL END) AS oneWeekCount
, COUNT(CASE WHEN o.OrderedDate > curdate() - INTERVAL 14 DAY THEN 1 ELSE NULL END) AS twoWeekCount
FROM customer AS c
JOIN orders AS o ON c.customerid=o.customerid
-- WHERE o.OrderedDate > curdate() - INTERVAL 14 DAY
GROUP BY o.RelationshipManager, c.Name
HAVING oneWeekCount > 5 OR twoWeekCount > 10
;
COUNT only counts non-null values, the WHERE is optional but changes the results (it should reduce the number of records inspected, but makes total_orders the same as twoWeekCount); the HAVING filters the results after the aggregation/counting has been performed.
In my experience, it is very rare for an aggregate function to be appropriate inside a conditional; I'm not even 100% sure there is an appropriate scenario for such use.

Related

Pulling sequential monthly lead counts as columns in SQL?

I'm trying to pull monthly lead counts for each company. I can do so for any individual month with these queries:
MONTH 1 LEAD COUNTS
select l.companyProfileID, count(l.id) as 'Month 1 LC'
from lead l
join companyProfile cp on cp.id = l.companyProfileID
where l.createTimestamp between cp.createTimestamp and date_sub(cp.createTimestamp, INTERVAL -1 month)
group by companyProfileID
MONTH 2 LEAD COUNTS
select l.companyProfileID, count(l.id) as 'Month 2 LC'
from lead l
join companyProfile cp on cp.id = l.companyProfileID
where l.createTimestamp between date_sub(cp.createTimestamp, INTERVAL -1 month) and date_sub(cp.createTimestamp, INTERVAL -2 month)
group by companyProfileID
But instead of running 12 different queries to get a year of lead counts, I'd like to produce a single table with columns: companyProfileID, Month 1 LC, Month 2 LC, etc.
I imagine this might require an embedded select function but I'm still learning SQL on the fly. How can I achieve this?
You can use "conditional aggregates" instead of running multiple queries. In effect you move your current where conditions INSIDE an aggregate function to form a case expression. Note that the count() function ignores NULLs
select
l.companyProfileID
, count(case when l.createTimestamp between cp.createTimestamp
and date_sub(cp.createTimestamp, INTERVAL -1 month) then 1 end) as 'Month 1 LC'
, count(case when l.createTimestamp between date_sub(cp.createTimestamp, INTERVAL -1 month)
and date_sub(cp.createTimestamp, INTERVAL -2 month) then 1 end) as 'Month 2 LC'
... more (similar to the above)
, count(case when l.createTimestamp between date_sub(cp.createTimestamp, INTERVAL -11 month)
and date_sub(cp.createTimestamp, INTERVAL -12 month) then 1 end) as 'Month 12 LC'
from lead l
join companyProfile cp on cp.id = l.companyProfileID
where l.createTimestamp between cp.createTimestamp and date_sub(cp.createTimestamp, INTERVAL -12 month)
group by companyProfileID
Please also note that "between" requires the first date be earlier than the second date e.g. the following would NOT return rows:
select * from t where datecol between 2018-01-01 and 2017-01-01
this would work however:
select * from t where datecol between 2017-01-01 and 2018-01-01

MySQL get results for different timespans and group

I have a big table with user "session" data (login/logout time and the use_time). Now I would like to create a top list, with:
total time
time last 24 hours (day)
week time
month time
The table with the data looks like this:
id | user_id | login_time (datetime) | logout_time (datetime) | online_time (int in seconds)
Unfortunately I haven't found a way to get this done with one query.
SELECT sum(s.online_time) as ontime, u.name as name
FROM session_user s
INNER JOIN users u
ON u.id=s.user_id
WHERE login_time > NOW() - INTERVAL 1 DAY
GROUP BY u.id
This query does give me the daily time of each active user, but I also want the other 3 (total time, week time, month time).
Is it possible to get that done with one query and if possible within a reasonable time?
You can use conditional aggregation to get this information:
SELECT u.name, sum(s.online_time) as oneime,
sum(case when login_time > NOW() - INTERVAL 1 DAY then s.online_time end) as ontime_1day,
sum(case when login_time > NOW() - INTERVAL 7 DAY then s.online_time end) as ontime_1week,
sum(case when login_time > NOW() - INTERVAL 1 MONTH then s.online_time end) as ontime_1month
FROM session_user s INNER JOIN
users u
ON u.id=s.user_id
GROUP BY u.id;

Return a zero for a day with no results

I have a query which returns the total of users who registered for each day. Problem is if a day had no one register it doesn't return any value, it just skips it. I would rather it returned zero
this is my query so far
SELECT count(*) total FROM users WHERE created_at < NOW() AND created_at >
DATE_SUB(NOW(), INTERVAL 7 DAY) AND owner_id = ? GROUP BY DAY(created_at)
ORDER BY created_at DESC
Edit
i grouped the data so i would get a count for each day- As for the date range, i wanted the total users registered for the previous seven days
A variation on the theme "build your on 7 day calendar inline":
SELECT D, count(created_at) AS total FROM
(SELECT DATE_SUB(NOW(), INTERVAL D DAY) AS D
FROM
(SELECT 0 as D
UNION SELECT 1
UNION SELECT 2
UNION SELECT 3
UNION SELECT 4
UNION SELECT 5
UNION SELECT 6
) AS D
) AS D
LEFT JOIN users ON date(created_at) = date(D)
WHERE owner_id = ? or owner_id is null
GROUP BY D
ORDER BY D DESC
I don't have your table structure at hand, so that would need adjustment probably. In the same order of idea, you will see I use NOW() as a reference date. But that's easily adjustable. Anyway that's the spirit...
See for a live demo http://sqlfiddle.com/#!2/ab5cf/11
If you had a table that held all of your days you could do a left join from there to your users table.
SELECT SUM(CASE WHEN U.Id IS NOT NULL THEN 1 ELSE 0 END)
FROM DimDate D
LEFT JOIN Users U ON CONVERT(DATE,U.Created_at) = D.DateValue
WHERE YourCriteria
GROUP BY YourGroupBy
The tricky bit is that you group by the date field in your data, which might have 'holes' in it, and thus miss records for that date.
A way to solve it is by filling a table with all dates for the past 10 and next 100 years or so, and to (outer)join that to your data. Then you will have one record for each day (or week or whatever) for sure.
I had to do this only for MS SqlServer, so how to fill a date table (or perhaps you can do it dynamically) is for someone else to answer.
A bit long winded, but I think this will work...
SELECT count(users.created_at) total FROM
(SELECT DATE_SUB(CURDATE(),INTERVAL 6 DAY) as cdate UNION ALL
SELECT DATE_SUB(CURDATE(),INTERVAL 5 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(),INTERVAL 4 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(),INTERVAL 3 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(),INTERVAL 2 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(),INTERVAL 1 DAY) UNION ALL
SELECT CURDATE()) t1 left join users
ON date(created_at)=t1.cdate
WHERE owner_id = ? or owner_id is null
GROUP BY t1.cdate
ORDER BY t1.cdate DESC
It differs from your query slightly in that it works on dates rather than date times which your query is doing. From your description I have assumed you mean to use whole days and therefore have used dates.

Non repeating monthly SQL

I have a query in which I would like to return the number of users who have logged in for the month without repeating the record in the next month.
If a user has logged in April and May, it only shows one record for April. This is what I have so far.
SELECT DISTINCT (a.userid), EXTRACT(MONTH FROM a.loginTime) as month
FROM login_audit a LEFT JOIN user u on u.userid = a.userid
WHERE a.loginTime <= '2012-12-31 11:59:59'
AND a.loginTime >= '2012-01-01 00:00:00'
GROUP BY month
So far the records are returning
userid month
1 1
2 1
1 2
3 2
In this scenario, user 1 is coming up for both January and Februray. I would like it to ommit that record. Either that or have it accumulated. Like so:
Either
userid month
1 1
2 1
3 2
Or
userid month
1 1
2 1
1 2
2 2
3 2
I hope this made sense. Please ask me anything if you'd like any further clarifications. Thanks a lot!
Don't see where you need table user...
For first "wanted scenario" :
SELECT
a.userid,
MIN(EXTRACT(MONTH FROM a.loginTime)) as month
FROM login_audit a
WHERE a.loginTime <= '2012-12-31 11:59:59' AND a.loginTime >= '2012-01-01 00:00:00'
GROUP BY a.userid
I would use this approach.
SELECT DISTINCT (a.userid), EXTRACT(MONTH FROM a.loginTime) as month
FROM login_audit a
WHERE a.loginTime <= '2012-12-31 11:59:59'
AND a.loginTime >= '2012-01-01 00:00:00'
and not exists
(select userid
from login_audit
where login_audit.user_id = a.user_id
and carry on with date range for the following month
)

showing previous and current month data in table using mysql

I am trying to show three different figures of the same column In a mysql query, I would like to keep one month static: April, so it would be a case like this I want to show The current month, the previous month and the static month of the year I'm working with, in this case let us stick with 2012
Example
Tablename:payment
id , pay_date, amount
1 2012-02-12 1000
2 2012-03-11 780
3 2012-04-15 890
4 2012-05-12 1200
5 2012-06-12 1890
6 2012-07-12 1350
7 2012-08-12 1450
So what I want to do is show the column amount for the month of April as I said I want to keep that row static: 890, the current month lets say the current month is August:1450 and the previous month amount which would be July:1350: so the final result would be something like this:
april_amount current_month_amount previous_month_amount
890 1450 1350
However I'm stuck here:
select amount as april_amount
from payment
where monthname(pay_date) LIKE 'April'
and year(pay_date) LIKE 2012
I hope the question is written clear enough, and thanks alot for the help much appreciated.
If the results can be rows instead of columns:
SELECT MONTHNAME(pay_date), amount FROM payment
WHERE pay_date BETWEEN '2012-04-01'
AND '2012-04-30'
OR pay_date BETWEEN CURRENT_DATE
- INTERVAL DAYOFMONTH(CURRENT_DATE) - 1 DAY
AND LAST_DAY(CURRENT_DATE)
OR pay_date BETWEEN CURRENT_DATE
- INTERVAL DAYOFMONTH(CURRENT_DATE) - 1 DAY
- INTERVAL 1 MONTH
AND LAST_DAY(CURRENT_DATE - INTERVAL 1 MONTH)
See it on sqlfiddle.
I might be way off here. But try:
select top 1
p.amount, c.amount, n.amount
from payment c
inner join payment p ON p.pay_date < c.pay_date
inner join payment n ON n.pay_date > c.pay_date
where monthname(c.paydate) LIKE 'April'
and year(c.pay_date) LIKE 2012
order by p.pay_date DESC, n.pay_date ASC
EDIT, I didnt read your question properly. I was going for previous, current, and next month. 1 minute and I'll try again.
select top 1
p.amount AS april_amount, c.amount AS current_month_amount, n.amount AS previous_month_amount
from payment c
inner join payment p ON monthname(p.pay_date) = 'April' AND year(p.pay_date) = 2012
inner join payment n ON n.pay_date > c.pay_date
where monthname(c.paydate) = monthname(curdate())
and year(c.pay_date) = year(curdate())
order by n.pay_date ASC
This assumes there is only 1 entry per month.
Ok, so i haven't written in mysql for a while. here is what worked for your example data:
select
p.amount AS april_amount, c.amount AS current_month_amount, n.amount AS previous_month_amount
from payment AS c
inner join payment AS p ON monthname(p.pay_date) LIKE 'April' AND year(p.pay_date) LIKE 2012
inner join payment AS n ON n.pay_date < c.pay_date
where monthname(c.pay_date) LIKE monthname(curdate())
and year(c.pay_date) LIKE year(curdate())
order by n.pay_date DESC
limit 1
the previous month table joined is counterintuitively named n, but this works. I verified it in a WAMP install.
To handle aggregates per month you can use subselects. Performance may suffer on very large tables (millions of rows or more).
SELECT SUM( a.amount ) AS april_amount,
(
SELECT SUM( c.amount )
FROM payment c
WHERE MONTH( c.pay_date ) = MONTH( CURDATE( ) )
) AS current_month_amount,
(
SELECT SUM( p.amount )
FROM payment p
WHERE MONTH( p.pay_date ) = MONTH( CURDATE( ) - INTERVAL 1
MONTH )
) AS previous_month_amount
FROM payment a
WHERE MONTHNAME( a.pay_date ) = 'April'
AND YEAR( a.pay_date ) =2012