Creating dummy variables using condition - mysql

I am a student and beginner in SQL
I have a table that has columns: ID_USER, DATE and PAYMENT_DUMMY, which contains information about user payments in each month (1 - paid, 0 - did not pay).
I need to create another dummy variable that will identify users who did not pay in the first or first and second months, but paid in the remaining (in the screenshot, these are users are 1225964 and 1249528).
Can anyone help me on this?

Perhaps this where the inner query allocates a row number and the outer query works out how many of the first 2 month have not been paid
drop table if exists t;
create table t(id int, dt date, dummy int);
insert into t values
(1,'2018-01-31',1),(1,'2018-02-28',1),(1,'2018-03-31',1),
(2,'2018-01-31',0),(2,'2018-02-28',0),(2,'2018-03-31',1),
(3,'2018-01-31',1),(3,'2018-02-28',0),(3,'2018-03-31',1),
(4,'2018-01-31',0),(4,'2018-02-28',0),(4,'2018-03-31',0);
select s.id,sum(dummy) cnt
from
(
select t.*,
if (t.id <> #p,#r:=1,#r:=#r+1) r,
#p:=t.id p
from t
cross join (select #r:=0,#p:=0) rn
order by t.id,t.dt
) s
where s.r <= 2
group by s.id having cnt = 0;
+------+------+
| id | cnt |
+------+------+
| 2 | 0 |
| 4 | 0 |
+------+------+
2 rows in set (0.00 sec)

Related

getting data from multiple tables and applying arithmatic operation on the result

I want to fetch data from two table and apply arithmetic operation on the column.
This is wha I tried :
String sql = "SELECT SUM(S.san_recover-C.amount) as total
FROM sanction S
LEFT JOIN collection C ON S.client_id = C.client_id
WHERE S.client_id=?";
This code is working only when there is value in both tables, but if there is no value in one of two tables there is no result.
SELECT SUM(S.san_recover - C.amount) as total
FROM sanction S
LEFT JOIN collection C ON S.client_id = C.client_id
WHERE S.client_id = ?
The problem with your query lies in the SUM() function. When the left join does not bring back records, then c.amount is NULL. When substracting NULL from something, you get a NULL result, which then propagates across the computation, and you end up with a NULL result for the SUM().
You probably want COALESCE(), like so:
SELECT SUM(S.san_recover - COALESCE(C.amount, 0)) as total
FROM sanction S
LEFT JOIN collection C ON S.client_id = C.client_id
WHERE S.client_id = ?
Where there is a possibility that a client may exist in one table but no another a full join would be appropriate but since mysql does not have such a thing then a union in a sub query will do
drop table if exists sanctions,collections;
create table sanctions(client_id int, amount int);
create table collections(client_id int, amount int);
insert into sanctions values
(1,10),(1,10),(2,10);
insert into collections values
(1,5),(3,10);
Select sum(Samount - camount)
From
(Select sum(amount) Samount, 0 as camount from sanctions where client_id =3
Union all
Select 0,sum(amount) as camount from collections where client_id =3
) s
;
+------------------------+
| sum(Samount - camount) |
+------------------------+
| -10 |
+------------------------+
1 row in set (0.00 sec)
If you want to do this for all clients
Select client_id,sum(Samount - camount) net
From
(Select client_id,sum(amount) Samount, 0 as camount from sanctions group by client_id
Union all
Select client_id,0,sum(amount) as camount from collections group by client_id
) s
group by client_id
;
+-----------+------+
| client_id | net |
+-----------+------+
| 1 | 15 |
| 2 | 10 |
| 3 | -10 |
+-----------+------+
3 rows in set (0.00 sec)

Update MySQL Table from Subquery/Joined Same Table

I've seen many questions along this issue, but can't get this to work.
I want to UPDATE multiple columns in a table (but will start with one) based upon a calculated value from the same table.
It is a list of transactions per customer, per month.
TransID | Cust | Month | Value | PastValue | FutureValue
1 | 1 | 2018-01-01 | 45 |
2 | 1 | 2018-02-01 | 0 |
3 | 1 | 2018-03-01 | 35 |
4 | 1 | 2018-04-01 | 80 |
.
UPDATE tbl_transaction a
SET PrevMnthValue =
(SELECT COUNT(TransactionID) FROM tbl_transaction b WHERE b.Cust=a.Cust AND b.Month<a.Month)
But we get the dreaded 'Can't update a table using a where with a subquery of the same table).
I've tried to nest the subquery as this has been touted as a workaround:
UPDATE tbl_transactions a
SET
PastValue =
(
SELECT CNT FROM
(
SELECT
COUNT(TransactionID) AS CNT
FROM tbl_transactions b
WHERE
b.CustomerRef=a.CustomerRef AND b.Month<a.Month
) x
),
FutureValue =
(
SELECT CNT FROM
(
SELECT
COUNT(TransactionID) AS CNT
FROM tbl_transactions b
WHERE
b.CustomerRef=a.CustomerRef AND b.Month>a.Month
) x
)
But I get an UNKNOWN a.CustomerRef in WHERE clause. Where am I going wrong?
You can't update and read from one table at the same time.
MySQL documentation tell about it
You cannot update a table and select from the same table in a subquery.
At first you must select necessary data and save them to somewhere, for example to temporary table
CREATE TEMPORARY TABLE IF NOT EXISTS `temp` AS (
SELECT
COUNT(`TransactionID`) AS CNT,
`CustomerRef`,
`Month`
FROM `tbl_transactions`
GROUP BY `Custom,erRef`, `Month`
);
After it, you can use JOIN statement for update table
UPDATE `tbl_transactions` RIGTH
JOIN `temp` ON `temp`.`CustomerRef` = `tbl_transactions`.`CustomerRef`
AND `temp`.`Month` < `tbl_transactions`.`Month`
SET `tbl_transactions`.`PastValue` = `temp`.`cnt`
UPDATED: if you want to update several columns by different condition you can combine temporary table, UPDATE + RIGHT JOIN and CASE statement. For example:
UPDATE `tbl_transactions`
RIGTH JOIN `temp` ON `temp`.`CustomerRef` = `tbl_transactions`.`CustomerRef`
SET `tbl_transactions`.`PastValue` = CASE
WHEN `temp`.`Month` < `tbl_transactions`.`Month` THEN `temp`.`cnt`
ELSE `tbl_transactions`.`PastValue`
END,
`tbl_transactions`.`FutureValue` = CASE
WHEN `temp`.`Month` > `tbl_transactions`.`Month` THEN `temp`.`cnt`
ELSE `tbl_transactions`.`FutureValue`
END
You can try below
UPDATE tbl_transactions a
Join
( SELECT CustomerRef,COUNT(TransactionID) AS CNT FROM tbl_transactions b
group by CustomerRef)x
SET PastValue = CNT
WHERE x.CustomerRef=a.CustomerRef AND x.Month<a.Month

SQL query relating how many of each collection set a given consumer has

Say I have customers who can be awarded certain prizes:
SELECT gs.claimed_by AS consumer_id, p.prize_id AS prize_id FROM
awarded_prizes
And right now, customer 1 has three prizes and customer 2 has a single prize
+-------------+----------+
| consumer_id | prize_id |
+-------------+----------+
| 1 | 45 |
| 1 | 46 |
| 1 | 47 |
| 2 | 66 |
+-------------+----------+
Say we also have collections, and if you collect all the members to that collectible, you now have a collectable set:
SELECT set_id, member_prize_id AS prize_id FROM collectable_set_members;
+--------+----------+
| set_id | prize_id |
+--------+----------+
| 1 | 45 |
| 1 | 46 |
| 1 | 47 |
| 2 | 65 |
| 2 | 66 |
+--------+----------+
With the above table and the previous query, we can see customer 1 has completed set 1 once (they have 45, 46, 47) and customer 2 has completed nothing.
There are cases where a customer can complete a set multiple times (customer could have 45, 46, 47, 45, 46, 47 in the awarded_prize table.
I've been looking at the pantry problem and its variations (like the bartender problem), have been playing with cross joins and groupings and can't seem to find what I want.
I'm trying to get a result for a given customer, showing all the set_ids they own and the number of sets they've completed:
+-------------+---------------+--------+
| consumer_id | completed_set | count |
+-------------+---------------+--------+
| 1 | 1 | 1 |
+-------------+---------------+--------+
I'm on mariadb:5.5
See here SqlFiddle
My tables have different names than yours, but it proves the point:
select sets_x_consumers.consumer_id, sets_x_consumers.set_id,
set_summary.items_in_set = consumer_summary.items_per_set_per_consumer as set_is_complete
from (
-- build a cross-product of sets and consumers
select distinct set_id, consumer_id
from sets join consumers -- no join condition -> cross product
) sets_x_consumers
inner join
( -- the total number of items in each set per set_id
select set_id, count(*) items_in_set
from sets
group by set_id
) set_summary on sets_x_consumers.set_id = set_summary.set_id
inner join
( -- the total number of items per set and customer
select set_id, consumer_id, count(*) items_per_set_per_consumer
from sets
inner join consumers on sets.prize_id = consumers.prize_id
group by consumer_id, set_id
) consumer_summary on sets_x_consumers.set_id = consumer_summary.set_id and sets_x_consumers.consumer_id = consumer_summary.set_id
My basic idea is to sum up the number of items in each set and the number of items per set each consumer has claimed. As long as there are no duplicate entries for the pair of consumer and prize, this should work (if duplicates were allowed, I would use count distinct(prize_id) for the consumer_summary).
The output of the query above is:
| consumer_id | set_id | set_is_complete |
|-------------|--------|-----------------|
| 1 | 1 | 1 |
| 2 | 2 | 0 |
This lists each pair of consumers and set for which a consumer has at least one prize. (to change this to list every consumer-set combination, use outer join)
Listing only complete sets or summarizing the number of complete sets should be easy on this basis ;-)
Can't really figure out what your last column 'count' is supposed to mean,
but here is a solution that lists users and their sets completed.
demo Link
The whole idea is to count the number of prizes required for each set, and count the collected prizes per customer per set, and thus you can join the two.
I know it's mssql, but I did not manage to make mysql ctes work in sqfiddle.
CTE-s is nothing more than a subquery basically. If your server does not support CTE-s you could use normal subqueries or temp tables instead.
For what it's worth I came up with a nice routine for this in Sql Server. This works even if there are overlapping prize_id values in each set (will default to higher setid if ambiguous). Assume all temp tables are original data:
declare #awarded_prize table (rowid int identity, consumer_id int, prize_id int )
insert #awarded_prize
select * from #awarded_prizes
declare #collections table ( set_id int, prize_id int, rownumber int , filled int)
insert #collections
select *, row_number() over(partition by set_id order by set_id, prize_id) , null
from #collections
declare #todelete table (rowid int)
declare #scorecard table (consumer_id int, set_id int)
declare #iterator int=1
declare #prize_id int
declare #set_id int = (Select min(set_id) from #collections)
declare #consumer_id int = (Select min(consumer_id) from #awarded_prize)
while #consumer_id<=(select max(consumer_id) from #awarded_prize)
begin
while #set_id<=(select max(set_id) from #collections)
begin
while 1=1
begin
select #prize_id=prize_id
from #collections
where set_id=#set_id and rownumber=#iterator
if (select max(rowid) from #awarded_prize where prize_id=#prize_id and consumer_id=#consumer_id and rowid not in (select rowid from #todelete)) is null break
insert #todelete
select max(rowid) from #awarded_prize where prize_id=#prize_id and consumer_id=#consumer_id and rowid not in (select rowid from #todelete)
update #collections set filled=1
where rownumber=#iterator and set_id=#set_id
if not exists(select 1 from #collections where set_id=#set_id and filled is null)
begin
insert #scorecard
select #consumer_id, #set_id
delete #awarded_prize where rowid in (Select rowid from #todelete)
delete #todelete
update #collections set filled=null where filled=1
end
set #iterator=case when #iterator=(Select max(rownumber) from #collections where set_id=#set_id) then
(select min(rownumber) from #collections where set_id=#set_id) else #iterator+1 end
end
delete #todelete
set #iterator=1
set #set_id=#set_id+1
end
set #iterator=1
select #set_id=min(set_id) from #collections
select #consumer_id=min(consumer_id) from #awarded_prize where consumer_id>#consumer_id
end
select consumer_id, set_id, count(*) complete_sets
from #scorecard
group by consumer_id, set_id
order by consumer_id, set_id

how to get last row from table 2 using left join mysql

I have tow tables tbl_product_checkout and tbl_product_checkout_status in which I want to get the last row from tbl_product_checkout_status
//tbl_product_checkout
product_checkout_id user_id product_checkout_order_no
-----------------------------------------------------------
1 1 ORD123456
//tbl_product_checkout_status
checkout_status_id product_checkout_id checkout_status_check
------------------------------------------------------------------
1 1 Dispatched
2 1 Delivered
I have tried using the following query
SELECT *
FROM tbl_product_checkout pc
LEFT
JOIN tbl_product_checkout_status cs
ON cs.product_checkout_id = pc.product_checkout_id
WHERE pc.user_id = 1
GROUP
BY pc.product_checkout_id
ORDER
BY cs.checkout_status_id DESC
but the output for above query is,
user_id product_checkout_order_no checkout_status_check
-------------------------------------------------------------
1 ORD123456 Dispatched
but I want the result as,
user_id product_checkout_order_no checkout_status_check
-------------------------------------------------------------
1 ORD123456 Delivered
Add a where = max sub query eg
DROP TABLE IF EXISTS tbl_product_checkout,tbl_product_checkout_status;
CREATE TABLE tbl_product_checkout(product_checkout_id INT, user_id INT, product_checkout_order_no VARCHAR(20));
INSERT INTO tbl_product_checkout VALUES
( 1 , 1 , 'ORD123456');
CREATE TABLE tbl_product_checkout_status(checkout_status_id INT, product_checkout_id INT, checkout_status_check VARCHAR(20));
INSERT INTO tbl_product_checkout_status VALUES
( 1 , 1 , 'Dispatched'),
( 2 , 1 , 'Delivered');
SELECT * FROM
tbl_product_checkout T1
LEFT JOIN tbl_product_checkout_status T2 ON T1.PRODUCT_CHECKOUT_ID = T2.PRODUCT_CHECKOUT_ID
WHERE T2.CHECKOUT_STATUS_ID = (
SELECT MAX(T3.CHECKOUT_STATUS_ID)
FROM tbl_product_checkout_status T3
WHERE T3.PRODUCT_CHECKOUT_ID = T2.PRODUCT_CHECKOUT_ID
)
;
Result
+---------------------+---------+---------------------------+--------------------+---------------------+-----------------------+
| product_checkout_id | user_id | product_checkout_order_no | checkout_status_id | product_checkout_id | checkout_status_check |
+---------------------+---------+---------------------------+--------------------+---------------------+-----------------------+
| 1 | 1 | ORD123456 | 2 | 1 | Delivered |
+---------------------+---------+---------------------------+--------------------+---------------------+-----------------------+
1 row in set (0.00 sec)
I think your group by mess up your desired outcome. I worked on your given database schema and cretaed a fiddle and managed to get your desired outcome. So your sql should be something like this:
SELECT * FROM tbl_product_checkout as pc
LEFT JOIN tbl_product_checkout_status as cs ON
cs.product_checkout_id = pc.product_checkout_id
WHERE pc.user_id = 1 ORDER BY cs.checkout_status_id DESC limit 1
By using limit 1, you will get last row as we ordered by DESC.
Keep in mind that i removed date part since there was no date on your example code.
Check Out Fiddle

Query to Segment Results Based on Equal Sets of Column Value

I'd like to construct a single query (or as few as possible) to group a data set. So given a number of buckets, I'd like to return results based on a specific column.
So given a column called score which is a double which contains:
90.00
91.00
94.00
96.00
98.00
99.00
I'd like to be able to use a GROUP BY clause with a function like:
SELECT MIN(score), MAX(score), SUM(score) FROM table GROUP BY BUCKETS(score, 3)
Ideally this would return 3 rows (grouping the results into 3 buckets with as close to equal count in each group as is possible):
90.00, 91.00, 181.00
94.00, 96.00, 190.00
98.00, 99.00, 197.00
Is there some function that would do this? I'd like to avoid returning all the rows and figuring out the bucket segments myself.
Dave
create table test (
id int not null auto_increment primary key,
val decimal(4,2)
) engine = myisam;
insert into test (val) values
(90.00),
(91.00),
(94.00),
(96.00),
(98.00),
(99.00);
select min(val) as lower,max(val) as higher,sum(val) as total from (
select id,val,#row:=#row+1 as row
from test,(select #row:=0) as r order by id
) as t
group by ceil(row/2)
+-------+--------+--------+
| lower | higher | total |
+-------+--------+--------+
| 90.00 | 91.00 | 181.00 |
| 94.00 | 96.00 | 190.00 |
| 98.00 | 99.00 | 197.00 |
+-------+--------+--------+
3 rows in set (0.00 sec)
Unluckily mysql doesn't have analytical function like rownum(), so you have to use some variable to emulate it. Once you do it, you can simply use ceil() function in order to group every tot rows as you like. Hope that it helps despite my english.
set #r = (select count(*) from test);
select min(val) as lower,max(val) as higher,sum(val) as total from (
select id,val,#row:=#row+1 as row
from test,(select #row:=0) as r order by id
) as t
group by ceil(row/ceil(#r/3))
or, with a single query
select min(val) as lower,max(val) as higher,sum(val) as total from (
select id,val,#row:=#row+1 as row,tot
from test,(select count(*) as tot from test) as t2,(select #row:=0) as r order by id
) as t
group by ceil(row/ceil(tot/3))