How to get distinct users with daily transactions - mysql

I have two table User and transactions where transaction table has user id
UserID | UserName
TransactionID | UserID | TransactioDate
I need to get number of distinct users that transact daily within a timeframe, i.e. users with txns everyday in a certain timeframe.
I can get distinct user for each day,
SELECT t.TransactioDate, COUNT(DISTINCT u.UserID)
FROM user u
INNER JOIN transaction t ON u.UserID = t.UserID AND t.TransactioDate BETWEEN '2018-01-13' AND '2018-02-12'
GROUP BY CAST(t.TransactioDate AS DATE)
ORDER BY t.TransactioDate
but i was wondering how to get users with txns everyday in a certain timeframe.

Join it with a subquery that counts the number of distinct days that each user has transactions, and returns the ones where this count is the same as the number of days in the time period.
SELECT DATE(t.TransactioDate) AS date, COUNT(DISTINCT u.UserID)
FROM user u
INNER JOIN transaction t ON u.UserID = t.UserID
INNER JOIN (
SELECT userID
FROM transaction
WHERE TransactioDate BETWEEN '2018-01-13' AND '2018-02-12'
GROUP BY userID
HAVING COUNT(DISTINCT(DATE(transactioDate))) = DATEDIFF('2018-02-12', '2018-01-13') + 1
) AS t1 ON t1.userID = u.userID
WHERE t.TransactioDate BETWEEN '2018-01-13' AND '2018-02-12'
GROUP BY date
ORDER BY t.TransactioDate

Related

MySQL query to get last 4 records of a related table

I have a database with two tables: users and payments.
Each user has many payments and each payment can be successful or failed.
I need to write a query to get all the users who failed the last 4 payments.
This is what I tried so far:
select *
from users u
where u.id in(
select p.user_id
from payments
where p.status = 'failed'
group by p.user_id
having count(p.id) = 4
);
But as you can see this is not only checking for the last 4 payments, but all of them. So, it is returning the users that have failed 4 payments (in global, not only the last 4).
I don't know if it is important but the fields on the tables are:
users:
id | name | email | password
payment:
id | date | status | user_id
| | (can be success or failed) | (FK)
Update:
This sqlfiddle will help to understand what I need.
The query is returning all users with 4 failed payments. But I only need the users whose 4 most recent payments failed. In this case it will be only user with id 5
This works
SELECT x.user_id, count(*) as cnt
FROM (
SELECT a.user_id, a.date, a.status FROM payment AS a WHERE
(SELECT COUNT(*) FROM payment AS b
WHERE b.user_id = a.user_id AND b.date >= a.date) <= 4
ORDER BY a.user_id ASC, a.date DESC) AS x
WHERE x.status = 'failed'
GROUP BY x.user_id
HAVING cnt >=4;
If you want the users, whose last 4 transactions were failed (only last 4, not total 4) then following query should get the job done:
select u.* from users u
where
id in
(select p.user_id from payment p
where (select count(*) from payment p1
where p.user_id = p1.user_id
and p.date <= p1.date
order by p1.user_id asc,p1.date desc
) <= 4
and p.status <> 'success'
group by p.user_id
having count(*)>=4);
check the sqlfiddle
Hope it helps!
You want to use the LIMIT keyword, and specify an ORDER.
Try this
select *
from users u
where u.id in(
select p.user_id
from payments
where p.status = 'failed'
group by p.user_id
having count(p.id) = 4
) ORDER BY p.id DESC LIMIT 4;
Not entirely sure what you are trying to do inside the WHERE statement, but ORDER BY p.id DESC LIMIT 4 will retrieve the four most recent rows.
I think you can use a query like this:
select users.id, users.name, users.email, users.password
from users
left join (
select p1.id, p1.date, p1.status, p1.user_id,
count(p2.id) seq -- this count() creates a sequence number for each user ordered by date
from payment p1
left join payment p2
on p1.user_id = p2.user_id -- here I set sequence for each user
and p1.date <= p2.date -- here I set sequence is ordered by data
group by p1.id, p1.date, p1.status, p1.user_id
) t
on users.id = t.user_id
where t.seq < 5 -- Now filter last 4 sequences of each user's payments
and t.status = 'failed'
group by users.id, users.name, users.email, users.password
having count(*) = 4; -- At last filter those have 4 failed in last 4 sequences
[ SQL Fiddle Demo ]

search by date mysql

I have a table named messages:
date (format DATETIME)
id
text
user_id(FK)
and a table named users:
id
name
how to get count of messages with given date (message date = xxxx-xx-xx) for each user?
like:
user message_count
user1_name 5
user2_name 2
and how get users with 3 or more message today?
SELECT users.name
FROM users
WHERE Users.id IN ( SELECT Messages.user_id
FROM chat.Messages
GROUP BY Messages.user_id
HAVING COUNT(*) >=3
)
It returns users who have 3 or more messages, how to get users, who have 3 or more message today? where date = now()
How to get count of messages with given date (message date = xxxx-xx-xx) for each user?
Query #1:
SELECT
users.name,
COUNT(*) message_count
FROM messages
INNER JOIN users ON messages.user_id = users.id
WHERE messages.date = PUT_YOUR_DATE_HERE
GROUP BY messages.user_id;
How to get count of messages for those users who have 3 or more messages today?
Query #2:
SELECT
users.name,
COUNT(*) message_count
FROM messages
INNER JOIN users ON messages.user_id = users.id
WHERE messages.date = CURDATE()
GROUP BY messages.user_id
HAVING COUNT(*) >= 3;
select b.name,count(*) from messages a,users b where date ='2016-05-28' group by b.name

How to get most recent balance from many users balances?

I have two table users and transactions. transactions table has relation with users two table format like below
users
id name email created
1 a a#mail.com 12-03-01
2 b b#mail.com 11-03-01
Transactions
id user_id balance
1 1 250
2 1 550
3 2 50
4 2 1000
I need last inserted users balance from transactions table with all users information. I am new in sql.
So I have tried below code
select * from transactions
where id in (select max(id) from transactions group by user_id)
INNER JOIN users on transactions.user_id=users.id
It's giving me syntax error near inner join.Have I made any mistake in inner join ? or I am in wrong direction ?
If you only want the balance, then a correlated subquery might be faster:
select u.*,
(select t.balance
from transactions t
where t.user_id = u.id
order by t.id desc
limit 1
) as MostRecentBalance
from users u;
For maximum performance, you want an index on transactions(user_id, id desc, balance).
The reason this is faster is because it avoids the aggregation on the entire transactions table. This is even more important if you are only selecting a subset of users.
EDIT:
I originally read this question as one row per user. However, if you only want one row returned -- for the last insert into transactions -- then a simpler method is:
select u.*, t.balance
from users u join
transactions t
on u.id = t.user_id
order by t.id desc
limit 1;
The JOIN should be part of the FROM statement so it should look more like the code below.
select *
from transactions ts
INNER JOIN users
ON (transactions.user_id=users.id)
where ts.id in
(
select max(transactions.id)
from transactions
group by user_id
);
edited to clarify which id is in use as per Gordons suggestion
2 simple methods.
A sub query to get the lastest transaction, and from that all the transaction details and then the user
SELECT users.*
FROM users
INNER JOIN transactions
ON users.id = transactions.user_id
INNER JOIN
(
SELECT MAX(id) AS max_id
FROM transactions
) sub0
ON transactions.id = sub0.max_id
Or you could try ordering by the id descending with a limit of 1:-
SELECT users.*
FROM users
INNER JOIN transactions
ON users.id = transactions.user_id
ORDER BY transactions.id DESC
LIMIT 1
EDIT
To get the last transaction for all users then you could use the following:-
SELECT *
FROM users
INNER JOIN transactions
ON users.id = transactions.user_id
INNER JOIN
(
SELECT user_id, MAX(id) AS max_id
FROM transactions
GROUP BY user_id
) sub0
ON transactions.id = sub0.max_id
ON transactions.user_id = sub0.user_id

Query on two tables with belongs_to/has_many relation

One table is Users with id and email columns.
Another table is Payments with id, created_at, user_id and foo columns.
User has many Payments.
I need a query that returns each user's email, his last payment date and this last payment's foo value. How do I do that? What I have now is:
SELECT users.email, MAX(payments.created_at), payments.foo
FROM users
JOIN payments ON payments.user_id = users.id
GROUP BY users.id
This is wrong, because foo value does not necessarily belong to user's most recent payment.
Try this :
select users.email,foo,create_at
from users
left join(
select a.* from payments a
inner join (
select id,user_id,max(create_at)
from payments
group by id,user_id
)b on a.id = b.id
) payments on users.id = payments.user_id
If users has no payment yet, then foo and create_at would return NULL. if you want to exclude users who has no payment, then use INNER JOIN.
One approach would be to use a MySQL version of rank over partition and then select only those rows with rank = 1:
select tt.email,tt.created_at,tt.foo from (
select t.*,
case when #cur_id = t.id then #r:=#r+1 else #r:=1 end as rank,
#cur_id := t.id
from (
SELECT users.id,users.email, payments.created_at, payments.foo
FROM users
JOIN payments ON payments.user_id = users.id
order by users.id asc,payments.created_at desc
) t
JOIN (select #cur_id:=-1,#r:=0) r
) tt
where tt.rank =1;
This would save hitting the payments table twice. Could be slower though. Depends on your data!

Left outer join + SUM with group by

I have a table called Purchases which belongs to a Users (that is a Purchase has a foreign key to User).
The Purchases table has a column called quantity and a state_id column, both are integers.
I want to be able to order the Users by their completed purchases (state_id = 10) where the SUM of their quantities is bigger than > 100. That is, all the users which have a total of completed purchases > 100, should appear first, and the rest right after.
This is what I have tried:
SELECT users.id as user_id,
SUM(CASE WHEN purchases.state_id = 5
THEN purchases.quantity
ELSE 0
END) as quantity
FROM users
LEFT OUTER JOIN purchases ON purchases.user_id = users.id
GROUP BY purchases.user_id
But this is just returning me one User, not all of them. What am I missing?
You can JOIN only completed purchases using JOIN condition. And you should group by users.id not purchases.user_id as soon you can have users in a table without purchases at all:
SELECT users.id as user_id,
SUM(purchases.quantity) as quantity
FROM users
LEFT JOIN purchases ON (users.id=purchases.user_id)
AND (purchases.state_id = 10)
GROUP BY users.id
ORDER BY quantity DESC