I have difficulty with syntax...
This is my query:
SELECT t.diapason,
Count(*) AS 'number_of_users'
FROM (SELECT CASE
WHEN amount < 200 THEN '0-200'
WHEN amount >= 200 THEN '200 +'
end AS diapason
FROM (SELECT Sum(amount) AS amount
FROM payments) p) t
GROUP BY t.diapason
ORDER BY number_of_users DESC;
But now I need to select only users which had activity.login_time between '2018-01-01' and'2018-01-12'.
So, I think I should use INNER JOIN and set period of time. Bu how?
My tables:
activity
user_id login_time
1 01.01.2018
2 01.01.2018
3 03.01.2018
4 30.02.2018
payments
user_id amount payment_time
1 50 10.12.2017
1 200 09.12.2017
2 40 08.08.2017
what should I change in my query to add activity.login_time?
Output for period 01.01.2018-12.01.2018
diapason number_of_users
0-200 2
200+ 1
I understand your question as this. You had 3 users (user_id=1,2,3) login in the period 01.01.2018-12.01.2018. Of those users, user_id 1 made 2 payments totalling 250, user_id 2 made 1 payment of 40, and user_id 3 made 0 payments so their total is 0. Hence there are 2 values in the range 0-200, and 1 in the range 200 +. If that is the correct understanding, this query will give you the desired results:
SELECT CASE
WHEN amount < 200 THEN '0-200'
WHEN amount >= 200 THEN '200 +'
END AS diapason,
COUNT(*) AS number_of_users
FROM (SELECT a.user_id, COALESCE(SUM(p.amount), 0) AS amount
FROM activity a
LEFT JOIN payments p ON p.user_id = a.user_id
WHERE a.login_time BETWEEN '01.01.2018' AND '12.01.2018'
GROUP BY a.user_id) p
GROUP BY diapason;
Output:
diapason number_of_users
0-200 2
200 + 1
SQLFiddle demo
Update
To add another row with the total number_of_users, just add WITH ROLLUP to the GROUP BY clause:
SELECT CASE
WHEN amount < 200 THEN '0-200'
WHEN amount >= 200 THEN '200 +'
END AS diapason,
COUNT(*) AS number_of_users
FROM (SELECT a.user_id, COALESCE(SUM(p.amount), 0) AS amount
FROM activity a
LEFT JOIN payments p ON p.user_id = a.user_id
WHERE a.login_time BETWEEN '01.01.2018' AND '12.01.2018'
GROUP BY a.user_id) p
GROUP BY diapason WITH ROLLUP
Output:
diapason number_of_users
0-200 2
200 + 1
(null) 3
In your application framework you can use the fact that the diapason value is NULL to output something like Total instead.
Updated SQLFiddle
You can also do the same in MySQL (see this SQLFiddle) by wrapping this query up as a subquery and using a COALESCE on the diapason column. In that case the output would be:
diapason number_of_users
0-200 2
200 + 1
Total 3
You add WHERE clause to filter.
SELECT t.diapason,
COUNT(*) AS 'number_of_users'
FROM (
SELECT
CASE
WHEN amount < 200 THEN '0-200'
WHEN amount >= 200 THEN '200 +'
END AS diapason
FROM (
SELECT payments.user_id, SUM(amount) AS amount
FROM payments
INNER JOIN activity ON payments.user_id = activity.user_idAND activity.login_time = payments.payment_time
WHERE activity.login_time BETWEEN '2018-01-10' AND '2018-01-12'
GROUP BY payments.user_id
) p
) t
GROUP BY t.diapason
ORDER BY number_of_users DESC;
Related
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;
There is a table "payments"
user_id payment_time amount sale_type
1 2018-04-01 10 cash
1 2018-04-01 10 cash
1 2018-04-01 10 cash
1 2018-04-01 20 bank
2 2018-04-01 10 cash
2 2018-04-01 10 cash
Need the sum of cash.
I don't understand why this query gives wrong results:
select SUM(CASE WHEN p1.sale_type='cash' THEN p1.amount ELSE 0 END)
as cash
FROM
(SELECT distinct user_id, SUM(amount) AS amount, sale_type FROM payments where
payment_time = '2018-04-01' group by user_id) p1
You need to add sale_type column to GROUP BY statement for the inner query and that should be group by user_id, sale_type for the correct results for your query style.
P.S. actually, I don't think you need a subquery.
The above query gives result as 60, while
select SUM(CASE WHEN p1.sale_type='cash' THEN p1.amount ELSE 0 END) as cash
from
(select distinct user_id, SUM(amount) AS amount, sale_type
from payments
where payment_time = date'2018-04-01'
group by user_id, sale_type) p1;
or
select SUM(CASE WHEN sale_type='cash' THEN amount ELSE 0 END) as cash
from payments
where payment_time = date'2018-04-01'
gives 40 for resulting cash column
SQL Fiddle Demo
Why don't you use 'Having' clause which is made for this purpose.
SELECT SUM(amount) AS cash FROM payments
WHERE payment_time = '2018-04-01'
GROUP BY sale_type
HAVING sale_type= 'cash'
I think you may not need the distinct in your sub-query or the entire sub-query at all.
select p.user_id as id, sum(case when p.sale_type = 'cash' then p.amount else 0 end) as amount
from payments p
where p.payment_time = '2018-04-01'
group by p.user_id
or without case
select p.user_id, sum(p.amount)
from payments p
where p.sale_type = 'cash' and p.payment_time = '2018-04-01'
group by p.user_id
Good day! Could you help me with query?
I have a table "payments":
payments
user_id amount payment_time sale_type
1 20 31.01.2011 card
1 10 02.01.2012 cash
3 10 03.01.2012 card
4 15 05.02.2012 cash
...and so on
The task is to select total amount of payments for 01.01.2012 - 30.01.2012 and divide this sum on groups due to the amount user ever payed.
The groups are "0-10" - if sum is 0 -10 $
"10 and more" - if sum > 10 $.
My code:
SELECT * from (select IFnull(t.diapason,'total') as diapason, total_amount
FROM
(SELECT p.user_id, p.amount as total_amount, CASE
when amount<=10 then '0-10'
when amount>10 then '10 and more' END AS diapason
FROM (SELECT distinct payments.user_id, SUM(amount) AS amount
FROM payments inner JOIN (SELECT DISTINCT user_id
FROM payments where payment_time between '2012-01-01'
and '2012-01-30') a ON payments.user_id = a.user_id
GROUP BY payments.user_id) p) t GROUP BY diapason WITH ROLLUP) as
t1 ORDER BY total_amount desc;
What is wrong here?
Expected output
diapason total_amount
0-10 10 - here is user with id 3
10 and more 10 - here is user with id 1 (because he ever payed 30)
total
Try this query -
select case when p2.amount <=10 then '0-10'
else '10 and more' end diapason
,p1.amount "total amount"
,p1.payment_by_card
,p1.cash
from (select user_id, sum(amount) amount, payment_by_card, cash
from payments
where payment_time between '2012-01-01' and '2012-01-30'
group by user_id, payment_by_card, cash) p1
join (select user_id, sum(amount) amount
from payments
group by user_id) p2
on p1.user_id = p2.user_id
Here is the fiddle - http://www.sqlfiddle.com/#!9/22caaa/8
I currently have a query that provides the result set below, I now need to add to this query to provide a total at the bottom of all the sales. I am not sure how to do this.
Current query:
SELECT
product,
COUNT(OrderNumber) AS CountOf
FROM
orders
WHERE
STATUS = 'booking' AND
Date(OrderDate) <= CURDATE() AND
Date(OrderDate) > DATE_SUB(CURDATE(),INTERVAL 30 DAY)
GROUP BY
product
ORDER BY CountOf DESC
Current Resultset:
product| count
-----------------------
pd1 | 3
pd4 | 1
pd2 | 1
desired result set =
product| count
-----------------------
pd1 | 3
pd4 | 1
pd2 | 1
Total | 5
Maybe you can add a UNION, and a SELECT with total amount. Something like this:
SELECT
product,
COUNT(OrderNumber) AS CountOf
FROM
orders
WHERE
STATUS = 'booking' AND
Date(OrderDate) <= CURDATE() AND
Date(OrderDate) > DATE_SUB(CURDATE(),INTERVAL 30 DAY)
GROUP BY
product
UNION
SELECT 'Total', count(OrderNumber) AS CountOf
FROM orders
WHERE
STATUS = 'booking' AND
Date(OrderDate) <= CURDATE() AND
Date(OrderDate) > DATE_SUB(CURDATE(),INTERVAL 30 DAY)
ORDER BY CountOf DESC;
Try using an Inner join on the same table, the union did not work due to there being the incorrect amount of columns on each side.
The Initial select had 2 set columns, where the second select (after the union) did not.
I have a table emails
id date sent_to
1 2013-01-01 345
2 2013-01-05 990
3 2013-02-05 1000
table2 is responses
email_id email response
1 xyz#email.com xxxx
1 xyzw#email.com yyyy
.
.
.
I want a result with the following format:
Month total_number_of_subscribers_sent total_responded
2013-01 1335 2
.
.
this is my query:
SELECT
DATE_FORMAT(e.date, '%Y-%m')AS `Month`,
count(*) AS total_responded,
SUM(e.sent_to) AS total_sent
FROM
responses r
LEFT JOIN emails e ON e.id = r.email_id
WHERE
e.date > '2012-12-31' AND e.date < '2013-10-01'
GROUP BY
DATE_FORMAT(e.date, '%Y %m')
it works ok with total_responded, but the total_sent goes crazy in millions, obviously because the resultant join table has the redundant values.
So basically can I do a SUM and COUNT in the same query on separate tables ?
If you want to count duplicates in each table, then the query is a little complicated.
You need to aggregate the sends and responses separately, before joining them together. The join is on the date, which necessarily comes from the "sent" information:
select r.`Month`, coalesce(total_sent, 0) as total_sent, coalesce(total_emails, 0) as total_emails,
coalesce(total_responses, 0) as total_responses,
coalesce(total_email_responses, 0) as total_email_responses
from (select DATE_FORMAT(e.date, '%Y-%m') as `Month`,
count(*) as total_sent, count(distinct email) as total_emails
from emails e
where e.date > '2012-12-31' AND e.date < '2013-10-01'
group by DATE_FORMAT(r.date, '%Y-%m')
) e left outer join
(select DATE_FORMAT(e.date, '%Y-%m') as `Month`,
count(*) as total_responses, count(distinct r.email) as total_email_responses
from emails e join
responses r
on e.email = r.email
where e.date > '2012-12-31' AND e.date < '2013-10-01'
) r
on e.`Month` = r.`Month`;
The apparent fact that your responses have no link to the "sent" information -- not even the date -- suggests a real problem with your operations and data.