update query problem - mysql

i have three tables on mysql database which are:
RECHARGE with these columns: rid, uid,res_id, agent_id, batch_id, rcard_order, serialno, email,
units, bankid, paydate, slipno, rpin, amtpd, bonus, description,
crSender, crSenderId,
transaction_ref,rechargeDate,
processed
SENT with these columns: sendid, uid, res_id, recipients, volume, ffdaily, message, sender, msgtype, flash, mob_field, wapurl,
date
BILL with these columns: bid, uid, email, unitBals, lastusedate
The question is these:i want a query that will subtract the sum of volume in SENT table from units in RECHARGE table and use the result to update the unitBals column on BILL table where the primary key joining the three tables is their uid.
i used this query but it is not giving me the same answer as when i sum(volume) and subtract it from sum(units) separately doing the calculation on my own
update bill set unitbals = (SELECT sum( recharge.units ) - sum( sent.volume )
FROM sent, recharge
WHERE sent.uid = recharge.uid)
where email = 'info#dunmininu.com'

There are two problems here. First, from the fact that you are using sum, I take it that there can be more than one Recharge record for a given Uid and more than one Sent record for a given Uid. If this is true, then when you do the join, you are not getting all the Recharges plus all the Sents, you are getting every combination of a Recharge and a Sent.
For example, suppose for a given Uid you have the following records:
Recharge:
Uid Units
42 2
42 3
42 4
Sent
Uid Volume
42 1
42 6
Then a query
select recharge.units, sent.volume
from recharge, sent
where recharge.uid=sent.uid
will give
Units Volume
2 1
2 6
3 1
3 6
4 1
4 6
So doing sum(units)-sum(volume) will give 18-21 = -3.
Also, you're doing nothing to connect the Uid of the Sent and Recharge to the Uid of the Bill. Thus, for any given Bill, you're processing records for ALL uids. The Uid of the Bill is never considered.
I think what you want is something more like:
update bill
set unitbals = (SELECT sum( recharge.units ) from recharge where recharge.uid=bill.uid)
- (select sum(sent.volume) from sent where sent.uid=bill.uid)
where email='info#dunmininu.com';
That is, take the sum of all the recharges for this uid, minus the sum of all the sents.
Note that this replaces the old value of Unitbals. It's also possible that you meant to say "unitbals=unitbals +" etc.

I think you need separate sum in the two tables:
update bill
set unitbals =
( SELECT sum( recharge.units )
FROM recharge
WHERE bill.id = recharge.uid
) -
( SELECT sum( sent.volume )
FROM sent
WHERE bill.id = sent.id
)
where email = 'info#dunmininu.com'

Related

How to get list of users that have used only one value in multiple rows - MySQL

I have a table for payments. It has a column named user_id, & payment_type. For every payment, a user can have multiple payment types.
I want to find the users that have used only one payment_type in their entire lifetime.
Let me make it clear through an example:
Let's say I have the following data:
user_id payment_type
1 UPI
1 NB
2 UPI
2 UPI
For the above, I only want user_id 2 as the output since for both the payments, it has used only 1 payment_type.
Can someone help?
A simple HAVING with COUNT should do the trick:
select user_id
from my_table
group by user_id
having count(distinct payment_type)=1;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=65f673a7df3ac0ee18c13105a2ec17ad
If you want to include payment_type in the result set , use:
select my.user_id,my.payment_type
from my_table my
inner join ( select user_id
from my_table
group by user_id
having count(distinct payment_type)=1
) as t1 on t1.user_id=my.user_id
group by my.user_id,my.payment_type ;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=cc4704c9e51d01e4e8fc087702edbe6e

SQL Query Sequential Month Logins

I have the following SQL table
username
Month
292
10
123
12
123
1
123
2
123
4
345
6
345
7
I want to query it, to get each username's login streak in Count of sequential Month. meaning the end result I am looking for looks like this :
username
Streak
292
1
123
3
345
2
How can I achieve it ? taking into note the Month 12 --> Month 1 issue;
Appreciate your help;
This would give you the result you want:
select username, count(*)
from (
select
username
, month_1
, coalesce(nullif(lead(month_1)
over (partition by username
order by coalesce(nullif(month_1,12),0))
- coalesce(nullif(month_1,12),0),-1),1) as MonthsTillNext
from login_tab
) Step1
where MonthsTillNext=1
group by username
By calculating the difference from the next row, where the next row is defined as the next month_no in ascending order, treating 12 as 0 (refer to the ambiguity I mentioned in my comment). It then just leaves the rows for consecutive months rows, and counts them.
Beware though, in addition to the anomaly around month:12, there is another case not considered: if the months for the user are 1,2,3 and 6,7,8 this would count as Streak:6; is it what you wanted?
One way would be with a recursive CTE, like
WITH RECURSIVE cte (username, month, cnt) AS
(
SELECT username, month, 1
FROM test
UNION ALL
SELECT test.username, test.month, cte.cnt+1
FROM cte INNER JOIN test
ON cte.username = test.username AND CASE WHEN cte.month = 12 THEN 1 ELSE cte.month + 1 END = test.month
)
SELECT username, MAX(cnt)
FROM cte
GROUP BY username
ORDER BY username
The idea is that the CTE (named cte in my example) recursively joins back to the table on a condition where the user is the same and the month is the next one. So for user 345, you have:
Username
Month
Cnt
345
6
1
345
7
1
345
7
2
The rows with cnt=1 are from the original table (with the extra cnt column hardcoded to 1), the row with cnt=2 is from the recursive part of the query (which found a match and used cnt+1 for its cnt). The query then selects the maximum for each user.
The join uses a CASE statement to handle 12 being followed by 1.
You can see it working with your sample data in this fiddle.
The one shared by #EdmCoff is quite elegant.
Another one without recursive and just using conditional logic -
with data_cte as
(
select username, month_1,
case when (count(month_1) over (partition by username) = 1) then 1
when (lead(month_1) over (partition by username order by username) - month_1) = 1 OR (month_1 - lag(month_1) over (partition by username order by username)) = 1 then 1
when (month_1 = 12 and min (month_1) over (partition by username) =1) then 1
end cnt
from login_tab
)
select username, count(cnt) from data_cte group by username
DB Fiddle here.

SQL: Finding the "new users" to "returning users" ratio

I have a table as below:
answer_id
user_id
date
1
22
2020-01
2
33
2020-02
3
78
2020-03
4
33
3030-04
what I need to do is to find the ratio of the new to returning users. By new, I mean the user_ids that were appeared only once, such as 22 and 78. And by returning users, I mean the user_ids that appeared more than once, such as 33.For example, the output should be something like this ----> 2:1
I need to do it with SQL and I though maybe I should create two temporary tables like the following:
with table_a as(
select user_id , count(answer_id)
from 1
group by 1
having count(answer_id)>1
)
table_b as (
select user_id , count(answer_id)
from b
group by 1
having count(answer_id)<2
)
the first table returns the user_ids that were repeated more than once, and the second table returns the number of ids that were unique. However, I am just lost what to do next. first time I tried it, I wanted to join them somehow, but I cannot join them as there aren't any matching field. Can any one help me on how I can find the ratio?
You just want to find out how many have a frequency of one and how many have a frequency higher than one. You can manipulate the output however you'd like later.
SELECT sum(vol=1) as newUsers,
sum(vol>1) as returning,
count(*) as total
FROM (
SELECT user_id, count(*) as vol
FROM table GROUP BY user_id
) z

Swap Values from database after performing count

I know I can do a count from mysql by doing this:
Select customer, Count (customer) Numbers from TblOrders group by customer
I want to count how many times each customer appears in the Order table.
After that I want to update Order table by swapping the customer id of the customer with the highest number of orders with another customer with id = 1.
Customer | Numbers
1 | 5
2 | 18
3 | 0
so here the highest was C2 so it'll swap C1 with C2 now after performing another count, I should get the table below.
Customer | Numbers
1 | 18
2 | 5
3 | 0
I also know the swapping can be done this way from a little research I've done.
Update TblOrders Set Customer = Case when 1 then 2
when 2 then 1
end
where TblOrders in (1, 2)
the problem with this statement is that it assumes I already know the two ID's that I'm swapping. but in case I don't know but I want the second one to be the ID with the highest number of orders...how do I go about it?
You want to issue an UPDATE request which affects all rows that belong to either one of the two customers in question. So the basic idea is this:
UPDATE TblOrders
SET customer = IF(customer == 'C1', #MaxCust, 'C1')
WHERE customer IN ('C1', #MaxCust)
If your customer IDs were integers, you could abbreviate this to
UPDATE TblOrders
SET customer = 1 + #MaxCust - customer
WHERE customer IN (1, #MaxCust)
This works because a + b - a = b and a + b - b = a, so a and b are exchanged.
My notation above was using a user variable #MaxCust to store the ID of the customer to be swapped. Depending on your environment, you might also use application code to enter the correct value into the query. Using a user variable, you could set it like this:
SELECT customer
FROM TblOrders GROUP BY customer
ORDER BY COUNT(*) DESC
LIMIT 1
INTO #MaxCust
after taking a look at # MvG's solutions, I came up with my own solution which is below.
SELECT customer FROM TblOrders GROUP BY customer ORDER BY COUNT(*) DESC LIMIT 1 INTO #MaxCust;
Update TblOrders Set Customer = Case
when 1 then #MaxCust
when #MaxCust then 1
end
where TblOrders in (1, #MaxCust );

mysql query to generate a commision report based on referred members

A person gets a 10% commision for purchases made by his referred friends.
There are two tables :
Reference table
Transaction table
Reference Table
Person_id Referrer_id
3 1
4 1
5 1
6 2
Transaction Table
Person_id Amount Action Date
3 100 Purchase 10-20-2011
4 200 Purchase 10-21-2011
6 400 Purchase 12-15-2011
3 200 Purchase 12-30-2011
1 50 Commision 01-01-2012
1 10 Cm_Bonus 01-01-2012
2 20 Commision 01-01-2012
How to get the following Resultset for Referrer_Person_id=1
Month Ref_Pur Earn_Comm Todate_Earn_Comm BonusRecvd Paid Due
10-2011 300 30 30 0 0 30
11-2011 0 0 30 0 0 30
12-2011 200 20 50 0 0 50
01-2012 0 0 50 10 50 0
Labels used above are:
Ref_Pur = Total Referred Friend's Purchase for that month
Earn_Comm = 10% Commision earned for that month
Todate_Earn_Comm = Total Running Commision earned upto that month
MYSQL CODE that i wrote
SELECT dx1.month,
dx1.ref_pur,
dx1.earn_comm,
( #cum_earn := #cum_earn + dx1.earn_comm ) as todate_earn_comm
FROM
(
select date_format(`date`,'%Y-%m') as month,
sum(amount) as ref_pur ,
(sum(amount)*0.1) as earn_comm
from transaction tr, reference rf
where tr.person_id=rf.person_id and
tr.action='Purchase' and
rf.referrer_id=1
group by date_format(`date`,'%Y-%m')
order by date_format(`date`,'%Y-%m')
)as dx1
JOIN (select #cum_earn:=0)e;
How to join the query to also include BonusRecvd,Paid and Due trnsactions, which is not dependent on reference table?
and also generate row for the month '11-2011', even though no trnx occured on that month
If you want to include commission payments and bonuses into the results, you'll probably need to include corresponding rows (Action IN ('Commision', 'Cm_Bonus')) into the initial dataset you are using to calculate the results on. Or, at least, that's what I would do, and it might be like this:
SELECT t.Amount, t.Action, t.Date
FROM Transaction t LEFT JOIN Reference r ON t.Person_id = r.Person_id
WHERE r.Referrer_id = 1 AND t.Action = 'Purchase'
OR t.Person_id = 1 AND t.Action IN ('Commision', 'Cm_Bonus')
And when calculating monthly SUMs, you can use CASE expressions to distinguish among Amounts related to differnt types of Action. This is how the corresponding part of the query might look like:
…
IFNULL(SUM(CASE Action WHEN 'Purchase' THEN Amount END) , 0) AS Ref_Pur,
IFNULL(SUM(CASE Action WHEN 'Purchase' THEN Amount END) * 0.1, 0) AS Earn_Comm,
IFNULL(SUM(CASE Action WHEN 'Cm_Bonus' THEN Amount END) , 0) AS BonusRecvd,
IFNULL(SUM(CASE Action WHEN 'Commision' THEN Amount END) , 0) AS Paid
…
When calculating the Due values, you can initialise another variable and use it quite similarly to #cum_earn, except you'll also need to subtract Paid, something like this:
(#cum_due := #cum_due + Earn_Comm - Paid) AS Due
One last problem seems to be missing months. To address it, I would do the following:
Get the first and the last date from the subset to be processed (as obtained by the query at the beginning of this post).
Get the corresponding month for each of the dates (i.e. another date which is merely the first of the same month).
Using a numbers table, generate a list of months covering the two calculated in the previous step.
Filter out the months that are present in the subset to be processed and use the remaining ones to add dummy transactions to the subset.
As you can see, the "subset to be processed" needs to be touched twice when performing these steps. So, for effeciency, I would insert that subset into a temporary table and use that table, instead of executing the same (sub)query several times.
A numbers table mentioned in Step #3 is a tool that I would recommend keep always handy. You would only need to initialise it once, and its uses for you may turn out numerous, if you pardon the pun. Here's but one way to populate a numbers table:
CREATE TABLE numbers (n int);
INSERT INTO numbers (n) SELECT 0;
INSERT INTO numbers (n) SELECT cnt + n FROM numbers, (SELECT COUNT(*) AS cnt FROM numbers) s;
INSERT INTO numbers (n) SELECT cnt + n FROM numbers, (SELECT COUNT(*) AS cnt FROM numbers) s;
INSERT INTO numbers (n) SELECT cnt + n FROM numbers, (SELECT COUNT(*) AS cnt FROM numbers) s;
INSERT INTO numbers (n) SELECT cnt + n FROM numbers, (SELECT COUNT(*) AS cnt FROM numbers) s;
INSERT INTO numbers (n) SELECT cnt + n FROM numbers, (SELECT COUNT(*) AS cnt FROM numbers) s;
INSERT INTO numbers (n) SELECT cnt + n FROM numbers, (SELECT COUNT(*) AS cnt FROM numbers) s;
INSERT INTO numbers (n) SELECT cnt + n FROM numbers, (SELECT COUNT(*) AS cnt FROM numbers) s;
INSERT INTO numbers (n) SELECT cnt + n FROM numbers, (SELECT COUNT(*) AS cnt FROM numbers) s;
/* repeat as necessary; every repeated line doubles the number of rows */
And that seems to be it. I will not post a complete solution here to spare you the chance to try to use the above suggestions in your own way, in case you are keen to. But if you are struggling or just want to verify that they can be applied to the required effect, you can try this SQL Fiddle page for a complete solution "in action".