collesce issue with mysql - mysql

I have a pretty flat table - tbl_values which has userids as well as netAmounts in a given row. In the example below, 2280 has no records in the past 30 days based on the timestamp.
I'd expect this to return 3 rows, with 2280 as "0" - but I'm only getting 2 back? Am I missing something obvious here?
SELECT userid, (COALESCE(SUM(netAmount),0)) as Sum FROM `tbl_values` where userid in (2280, 399, 2282) and date > (select DATE_SUB(NOW(), INTERVAL 30 day)) GROUP BY userid

Assuming you always want to return the user, regardless of rather they have a matching record in tbl_values, what you're looking for is an outer join:
SELECT u.userid, COALESCE(SUM(v.netAmount),0) as Sum
FROM (
SELECT 2280 userid UNION ALL
SELECT 399 UNION ALL
SELECT 2282
) u
LEFT JOIN `tbl_values` v ON u.userid = v.userid AND
v.date > DATE_SUB(NOW(), INTERVAL 30 day)
GROUP BY u.userid
If you perhaps have a Users table, then you can use it instead of the subquery.
SELECT u.userid, COALESCE(SUM(v.netAmount),0) as Sum
FROM users u
LEFT JOIN `tbl_values` v ON u.userid = v.userid AND
v.date > DATE_SUB(NOW(), INTERVAL 30 day)
WHERE u.userid in (2280, 399, 2282)
GROUP BY u.userid

This is your query:
SELECT userid, (COALESCE(SUM(netAmount),0)) as Sum
FROM `tbl_values`
where userid in (2280, 399, 2282) and
date > (select DATE_SUB(NOW(), INTERVAL 30 day))
GROUP BY userid;
The filter in the where clause finds no rows that match for user id 2280. Assuming that at least one row exists somewhere, you can get what you want by moving the date comparison to a conditional aggregation:
SELECT userid,
sum(case when date > DATE_SUB(NOW(), INTERVAL 30 day)
then netAmount else 0
end) as Sum
FROM `tbl_values`
WHERE userid in (2280, 399, 2282)
GROUP BY userid;
EDIT:
If you really want all three results, then use a left join:
SELECT u.userid,
coalesce(sum(netAmount), 0) as Sum
FROM (select 2280 as userid union all
select 399 union all
select 2282
) u left join
tbl_values t
on u.userid = t.userid and
t.date > DATE_SUB(NOW(), INTERVAL 30 day)
GROUP BY u.userid;

Related

How to calculate percent?

Could you help me to calculate percent of users, which made payments?
I've got two tables:
activity
user_id login_time
201 01.01.2017
202 01.01.2017
255 04.01.2017
255 05.01.2017
256 05.01.2017
260 15.03.2017
2
payments
user_id payment_date
200 01.01.2017
202 01.01.2017
255 05.01.2017
I try to use this query, but it calculates wrong percent:
SELECT activity.login_time, (select COUNT(distinct payments.user_id)
from payments where payments.payment_time between '2017-01-01' and
'2017-01-05') / COUNT(distinct activity.user_id) * 100
AS percent
FROM payments INNER JOIN activity ON
activity.user_id = payments.user_id and activity.login_time between
'2017-01-01' and '2017-01-05'
GROUP BY activity.login_time;
I need a result
01.01.2017 100 %
02.01.2017 0%
03.01.2017 0%
04.01.2017 0%
05.01.2017 - 50%
If you want the ratio of users who have made payments to those with activity, just summarize each table individually:
select p.cnt / a.cnt
from (select count(distinct user_id) as cnt from activity a) a cross join
(select count(distinct user_id) as cnt from payment) p;
EDIT:
You need a table with all dates in the range. That is the biggest problem.
Then I would recommend:
SELECT d.dte,
( ( SELECT COUNT(DISTINCT p.user_id)
FROM payments p
WHERE p.payment_date >= d.dte and p.payment_date < d.dte + INTERVAL 1 DAY
) /
NULLIF( (SELECT COUNT(DISTINCT a.user_id)
FROM activity a
WHERE a.login_time >= d.dte and p.login_time < d.dte + INTERVAL 1 DAY
), 0
) as ratio
FROM (SELECT date('2017-01-01') dte UNION ALL
SELECT date('2017-01-02') dte UNION ALL
SELECT date('2017-01-03') dte UNION ALL
SELECT date('2017-01-04') dte UNION ALL
SELECT date('2017-01-05') dte
) d;
Notes:
This returns NULL on days where there is no activity. That makes more sense to me than 0.
This uses logic on the dates that works for both dates and date/time values.
The logic for dates can make use of an index, which can be important for this type of query.
I don't recommend using LEFT JOINs. That will multiply the data which can make the query expensive.
First you need a table with all days in the range. Since the range is small you can build an ad hoc derived table using UNION ALL. Then left join the payments and activities. Group by the day and calculate the percentage using the count()s.
SELECT x.day,
concat(CASE count(DISTINCT a.user_id)
WHEN 0 THEN
1
ELSE
count(DISTINCT p.user_id)
/
count(DISTINCT a.user_id)
END
*
100,
'%')
FROM (SELECT cast('2017-01-01' AS date) day
UNION ALL
SELECT cast('2017-01-02' AS date) day
UNION ALL
SELECT cast('2017-01-03' AS date) day
UNION ALL
SELECT cast('2017-01-04' AS date) day
UNION ALL
SELECT cast('2017-01-05' AS date) day) x
LEFT JOIN payments p
ON p.payment_date = x.day
LEFT JOIN activity a
ON a.login_time = x.day
GROUP BY x.day;

MySQL listing all entries within x days of first entry

I have a table orders with the columns id, user_id, created_on and paid_amount. I'm trying to find the entries for each user_id within the first 7 days of their first order. Here's what I have so far:
SELECT user_id, created_on, paid_amount FROM orders WHERE created_on BETWEEN min(created_on) AND DATE_ADD(MIN(created_on), INTERVAL 7 DAY) GROUP BY user_id
I'm guessing that the problem lies in the face that the BETWEEN-command is assigned to a single value instead of the whole table? How could I fix this?
My ultimate goal is to find out the average amount spent by all users within their first 7 days, but I think I can figure out the rest of the steps myself.
This will give you first 7 day records, for each user_id
SELECT orders.* FROM orders
INNER JOIN (
select user_id, min(created_on) as mindt from orders group by user_id
) t
ON orders.user_id = t.user_id AND orders.created_on <= DATE_ADD(t.mindt, INTERVAL 7 DAY)
ORDER BY user_id, created_on
For average paid_amount for each user, in first 7 day, use this:
SELECT orders.user_id, avg(paid_amount) FROM orders
INNER JOIN (
select user_id, min(created_on) as mindt from orders group by user_id
) t
ON orders.user_id = t.user_id AND orders.created_on <= DATE_ADD(t.mindt, INTERVAL 7 DAY)
group by orders.user_id

how to join tables and get sum of first table if second has multiple occurences

I have two tables "temp_user_batches" and "user_activities" i am trying to find sum of user_activities for users present in temp_user_batches table.
problem is sum of user_activities is getting multiplied by number of times in ratio of occurences of user in temp_user_batches table.
Below is temp_user_batches table
This is user_activities table
it is supposed to give sum of time_spent column 649 + 364 = 1013 but instead its giving 2016
my query is:
SELECT temp_user_batches.user_id as user_id,
temp_user_batches.activity_goal as goal,
DATE_SUB(CURDATE(), INTERVAL 7 day) as min_activity_date,
CURDATE() as max_activity_date,
(sum(user_activities.time_spent)/60) as total_time_spent
FROM temp_user_batches
INNER JOIN user_activities
ON temp_user_batches.user_id = user_activities.user_id
WHERE activity_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 7 day) AND CURDATE()
group by user_id, goal, max_activity_date, min_activity_date
You can use a derived table that contains the DISTINCT pairs of user_id, activity_goal from table temp_user_batches:
SELECT t1.user_id as user_id,
t2.activity_goal as goal,
DATE_SUB(CURDATE(), INTERVAL 7 day) as min_activity_date,
CURDATE() as max_activity_date,
(sum(t2.time_spent)/60) as total_time_spent
FROM (
SELECT DISTINCT user_id, activity_goal
FROM temp_user_batches) AS t1
INNER JOIN user_activities AS t2 ON t1.user_id = t2.user_id
WHERE activity_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 7 day) AND CURDATE()
group by user_id, goal, max_activity_date, min_activity_date
From my understanding, you should try to GROUP_BY the temp_user_batches on user_id, last_activity before joining it with user_activities. This is because now you join user_activities on 2 rows instead of 1 row the way you want (from what I understand).
Something like:
SELECT
temp_user_batches.user_id AS user_id,
temp_user_batches.activity_goal AS goal,
DATE_SUB(CURDATE(), INTERVAL 7 DAY) AS min_activity_date,
CURDATE() AS max_activity_date,
(SUM(user_activities.time_spent) / 60) AS total_time_spent
FROM
(SELECT
*
FROM
temp_user_batches
GROUP BY user_id , last_activity)
INNER JOIN
user_activities ON temp_user_batches.user_id = user_activities.user_id
WHERE
activity_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 7 DAY) AND CURDATE()
GROUP BY user_id , goal , max_activity_date , min_activity_date

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.

MySQL query with join or subquery

I have such a schema and queries:
http://sqlfiddle.com/#!2/7b032/3
Seperately I have these queries:
SELECT COUNT(*) AS 'times', userid, name
FROM main
WHERE comedate <= DATE_SUB(CURDATE(),
INTERVAL 5 DAY)
GROUP BY userid ORDER BY times DESC LIMIT 0,2;
SELECT * FROM details WHERE 1;
By comparing userid columns of both table I need to join them.
I need an output having these columns:
"times, userid, name, age, location"
Also order, group and limits should be considered.
I would be happy if you can write one query with JOIN and one query with subquery.
I have a 60k table and I will compare the performances.
How about this:
select x.times,
x.userid,
x.name,
d.age,
d.location
from
(
SELECT COUNT(*) AS 'times', userid, name
FROM main
WHERE comedate <= DATE_SUB(CURDATE(),
INTERVAL 5 DAY)
GROUP BY userid
) x
left join details d
on x.userid = d.userid
see SQL Fiddle with Demo
edit:
select x.times,
x.userid,
x.name,
d.age,
d.location
from
(
SELECT COUNT(*) AS 'times', userid, name
FROM main
WHERE comedate <= DATE_SUB(CURDATE(),
INTERVAL 5 DAY)
GROUP BY userid
ORDER BY times DESC
LIMIT 0,2
) x
left join details d
on x.userid = d.userid
see SQL Fiddle with demo