SQL select last before by date and group by - mysql

Using MySQL, I want to select all submissions (rows) which last submission is NULL and previous one is not, grouped by user_id.
If I consider the following example table, then the answer would be rows: 2, 5 and 7.
| ID | submission | date_submission | user_id |
|----|------------|-----------------|---------|
| 1 | lorem | 2019-01-01 | 1 |
| 2 | ipsum | 2019-01-02 | 1 |
| 3 | NULL | 2019-01-03 | 1 |
| 4 | amet | 2019-01-05 | 2 |
| 5 | NULL | 2019-01-06 | 2 |
| 6 | sit | 2019-01-04 | 1 |
| 7 | sed | 2019-01-08 | 3 |
| 8 | elit | 2019-01-07 | 4 |
| 9 | NULL | 2019-01-09 | 3 |
MRE:
CREATE TABLE submissions (
id int NOT NULL,
submission varchar(45) NULL,
date_submitted date NOT NULL,
user_id int DEFAULT NULL
);
insert into submissions (1, "lorem", 2019-01-01, 1);
insert into submissions (2, "ipsum", 2019-01-02, 1);
insert into submissions (3, NULL, 2019-01-03, 1);
insert into submissions (4, "amet", 2019-01-05, 2);
insert into submissions (5, NULL, 2019-01-06, 2);
insert into submissions (6, "sit", 2019-01-04, 1);
insert into submissions (7, "sed", 2019-01-08, 3);
insert into submissions (8, "elit", 2019-01-07, 4);
insert into submissions (9, NULL, 2019-01-09, 3);

First get the last date with null submission for each user and then join to the table to get the rows of the previous dates.
By using ROW_NUMBER() get the last of these previous dates if it is not null:
select t.id, t.submission, t.date_submitted, t.user_id
from (
select s.*,
row_number() over (partition by s.user_id order by s.date_submitted desc) rn
from submissions s inner join (
select user_id,
max(case when submission is null then date_submitted end) maxnulldate
from submissions
group by user_id
) g on g.user_id = s.user_id and g.maxnulldate > s.date_submitted
) t
where t.rn = 1 and t.date_submitted is not null
See the demo.
Results:
| id | submission | date_submitted | user_id |
| --- | ---------- | -------------- | ------- |
| 2 | ipsum | 2019-01-02 | 1 |
| 4 | amet | 2019-01-05 | 2 |
| 7 | sed | 2019-01-08 | 3 |
I guess you meant row number 4 and not 5 in your expected results, right?

You can use lag() for this:
select s.*
from (select s.*,
lag(submission) over (partition by user_id order by date_submitted) as prev_submission
from submissions s
) s
where prev_submission is not null and submission is null;
Here is a db<>fiddle.
EDIT:
It occurs to me that "last submission" really is the last submission for each user. In that case, the above can be tweaked:
select s.*
from (select s.*,
row_number() over (partition by user_id order by date_submitted desc) as seqnum,
lag(submission) over (partition by user_id order by date_submitted) as prev_submission
from submissions s
) s
where prev_submission is not null and submission is null and seqnum = 1;

Related

How to get calculated data from one column in database

im new in sql. I cannot get data with format what i want in one step. Now i'm using more sql commands. I want to get all data in one command because i cant to connect them in subquery with group by. Somebodys can help me?
example of Table i have:
id
order_id
order_status
1
1
0
2
1
0
3
1
0
4
1
1
5
1
1
6
2
0
7
2
0
8
2
1
Table i want to have after sql query:
order_id
count
of
progress(%)
1
2
5
40
2
1
3
33
queries i use:
SELECT order_id, COUNT(status) as count
FROM `orders`
WHERE status = 1
GROUP by order_id;
SELECT order_id, COUNT(status) as of
FROM `orders`
GROUP by order_id;
SELECT order_id,
CAST((SELECT COUNT(status) FROM `orders` WHERE status = 1) /
(SELECT COUNT(status) FROM `orders`) *100 as int) AS progress FROM orders
group by order_id;
but last working properly only if i use where to single order id.
I want to make this data in one sql query to format i showed up.
Thanks a lot guys!
You don't need subqueries to do this, SQL's ordinary aggregate functions already work as you want with your group by clause:
SELECT order_id,
SUM(order_status) AS `count`,
COUNT(*) AS `of`,
SUM(order_status) / COUNT(order_status) * 100 as `progress`
FROM orders
group by order_id;
See example at http://sqlfiddle.com/#!9/d1799db/4/0
you need to use multiple subqueries
here's a query that I used and worked on your example on the onecompiler.com website
-- create
CREATE TABLE EMPLOYEE (
order_id INTEGER,
order_status INTEGER
);
-- insert
INSERT INTO EMPLOYEE VALUES (1,0 );
INSERT INTO EMPLOYEE VALUES (1, 0);
INSERT INTO EMPLOYEE VALUES (1, 0);
INSERT INTO EMPLOYEE VALUES (1, 1);
INSERT INTO EMPLOYEE VALUES (1,1 );
INSERT INTO EMPLOYEE VALUES (2, 0);
INSERT INTO EMPLOYEE VALUES (2, 0);
INSERT INTO EMPLOYEE VALUES (2, 1);
select *
from EMPLOYEE;
SELECT order_id, count, off , count/off
from(
select distinct order_id as order_id,
(select count(order_id) from EMPLOYEE C WHERE A.order_id=C.order_id AND order_status =1) as 'count',
(select count(order_id) from EMPLOYEE B WHERE A.order_id=B.order_id ) as 'off'
FROM EMPLOYEE A
) AA
;
You need to use sum and count with group by.
create table orders(
id int,
order_id int,
order_status int);
insert into orders values
(1,1,0),
(2,1,0),
(3,1,0),
(4,1,1),
(5,1,1),
(6,2,0),
(7,2,0),
(8,2,1);
select
order_id,
sum(order_status) count,
count(order_id) "of",
(100 * sum(order_status))
/ count(order_id) progress
from orders
group by order_id
order by order_id;
order_id | count | of | progress
-------: | ----: | -: | -------:
1 | 2 | 5 | 40.0000
2 | 1 | 3 | 33.3333
db<>fiddle here
i was described my problem without some details, w i want to join with other table but i see only record with status
oders_details
| id | order_describe | order_date |
|:----:|:--------------:|:----------:|
| 1 | sample 1 | 2022-02-28 |
| 2 | sample 2 | 2022-02-28 |
| 3 | sample 3 | 2022-03-01 |
| 4 | sample 4 | 2022-03-02 |
orders_status
| id | order_id |order_status|
|:---:|:---------------:|:----------:|
| 1 | 1 | 0 |
| 2 | 1 | 0 |
| 3 | 1 | 0 |
| 4 | 1 | 1 |
| 5 | 1 | 1 |
| 6 | 2 | 0 |
| 7 | 2 | 0 |
| 8 | 2 | 1 |
table i want after query
orders_view
| id |order_id|order_describe| order_date | count | of | progress |
|-----|--------|--------------|------------|-------|----|:--------:|
| 1 | 1 | sample 1 | 2022-02-28| 2 | 5 | 40 |
| 2 | 2 | sample 2 | 2022-02-28| 1 | 3 | 33 |
| 3 | 3 | sample 3 | 2022-03-01| null |null| null |
| 4 | 4 | sample 4 | 2022-03-02| null |null| null |
i want to get some hint what i have todo, to get finally table or view, not complete solution, to better understand sql lang

how to track score gains in mysql

I would like to display a players current score as well as how many points they have gained within a selected time frame.
I have 2 tables
skills table
+----+---------+---------------------+
| id | name | created_at |
+----+---------+---------------------+
| 1 | skill 1 | 2020-06-05 00:00:00 |
| 2 | skill 2 | 2020-06-05 00:00:00 |
| 3 | skill 3 | 2020-06-05 00:00:00 |
+----+---------+---------------------+
scores table
+----+-----------+----------+-------+---------------------+
| id | player_id | skill_id | score | created_at |
+----+-----------+----------+-------+---------------------+
| 1 | 1 | 1 | 5 | 2020-06-06 00:00:00 |
| 2 | 1 | 1 | 10 | 2020-07-06 00:00:00 |
| 3 | 1 | 2 | 1 | 2020-07-06 00:00:00 |
| 4 | 2 | 1 | 11 | 2020-07-06 00:00:00 |
| 5 | 1 | 1 | 13 | 2020-07-07 00:00:00 |
| 6 | 1 | 2 | 10 | 2020-07-07 00:00:00 |
| 7 | 2 | 1 | 12 | 2020-07-07 00:00:00 |
| 8 | 1 | 1 | 20 | 2020-07-08 00:00:00 |
| 9 | 1 | 2 | 15 | 2020-07-08 00:00:00 |
| 10 | 2 | 1 | 17 | 2020-07-08 00:00:00 |
+----+-----------+----------+-------+---------------------+
my expected results are:-
24 hour query
+-----------+---------+-------+------+
| player_id | name | score | gain |
+-----------+---------+-------+------+
| 1 | skill 1 | 20 | 7 |
| 1 | skill 2 | 15 | 5 |
+-----------+---------+-------+------+
7 day query
+-----------+---------+-------+------+
| player_id | name | score | gain |
+-----------+---------+-------+------+
| 1 | skill 1 | 20 | 10 |
| 1 | skill 2 | 15 | 14 |
+-----------+---------+-------+------+
31 day query
+-----------+---------+-------+------+
| player_id | name | score | gain |
+-----------+---------+-------+------+
| 1 | skill 1 | 20 | 15 |
| 1 | skill 2 | 15 | 14 |
+-----------+---------+-------+------+
so far I have the following, but all this does is return the last 2 records for each skill, I am struggling to calculate the gains and the different time frames
SELECT player_id, skill_id, name, score
FROM (SELECT player_id, skill_id, name, score,
#skill_count := IF(#current_skill = skill_id, #skill_count + 1, 1) AS skill_count,
#current_skill := skill_id
FROM skill_scores
INNER JOIN skills
ON skill_id = skills.id
WHERE player_id = 1
ORDER BY skill_id, score DESC
) counted
WHERE skill_count <= 2
I would like some help figuring out the query I need to build to get the desired results, or is it best to do this with php instead of in the db?
EDIT:-
MYSQL 8.0.20 dummy data id's are primary_key auto increment but I didnt ad that for simplicity:-
CREATE TABLE skills
(
id bigint,
name VARCHAR(80)
);
CREATE TABLE skill_scores
(
id bigint,
player_id bigint,
skill_id bigint,
score bigint,
created_at timestamp
);
INSERT INTO skills VALUES (1, 'skill 1');
INSERT INTO skills VALUES (2, 'skill 2');
INSERT INTO skills VALUES (3, 'skill 3');
INSERT INTO skill_scores VALUES (1, 1, 1 , 5, '2020-06-06 00:00:00');
INSERT INTO skill_scores VALUES (2, 1, 1 , 10, '2020-07-06 00:00:00');
INSERT INTO skill_scores VALUES (3, 1, 2 , 1, '2020-07-06 00:00:00');
INSERT INTO skill_scores VALUES (4, 2, 1 , 11, '2020-07-06 00:00:00');
INSERT INTO skill_scores VALUES (5, 1, 1 , 13, '2020-07-07 00:00:00');
INSERT INTO skill_scores VALUES (6, 1, 2 , 10, '2020-07-07 00:00:00');
INSERT INTO skill_scores VALUES (7, 2, 1 , 12, '2020-07-07 00:00:00');
INSERT INTO skill_scores VALUES (8, 1, 1 , 20, '2020-07-08 00:00:00');
INSERT INTO skill_scores VALUES (9, 1, 2 , 15, '2020-07-08 00:00:00');
INSERT INTO skill_scores VALUES (10, 2, 1 , 17, '2020-07-08 00:00:00');
WITH cte AS (
SELECT id, player_id, skill_id,
FIRST_VALUE(score) OVER (PARTITION BY player_id, skill_id ORDER BY created_at DESC) score,
FIRST_VALUE(score) OVER (PARTITION BY player_id, skill_id ORDER BY created_at DESC) - FIRST_VALUE(score) OVER (PARTITION BY player_id, skill_id ORDER BY created_at ASC) gain,
ROW_NUMBER() OVER (PARTITION BY player_id, skill_id ORDER BY created_at DESC) rn
FROM skill_scores
WHERE created_at BETWEEN #current_date - INTERVAL #interval DAY AND #current_date
)
SELECT cte.player_id, skills.name, cte.score, cte.gain
FROM cte
JOIN skills ON skills.id = cte.skill_id
WHERE rn = 1
ORDER BY player_id, name;
fiddle
Ps. I don't understand where gain=15 is taken for 31-day period - the difference between '2020-07-08 00:00:00' and '2020-06-06 00:00:00' is 32 days.
Well i think you need a (temporary) table for this. I will call it "player_skill_gains". Its basically the players skills ordered by created_at and with an auto_incremented id:
CREATE TABLE player_skill_gains
(`id` int PRIMARY KEY AUTO_INCREMENT NOT NULL
, `player_id` int
, skill_id int
, score int
, created_at date)
;
INSERT INTO player_skill_gains(player_id, skill_id, score, created_at)
SELECT player_skills.player_id AS player_id
, player_skills.skill_id
, SUM(player_skills.score) AS score
, player_skills.created_at
FROM player_skills
GROUP BY player_skills.id, player_skills.skill_id, player_skills.created_at
ORDER BY player_skills.player_id, player_skills.skill_id, player_skills.created_at ASC;
Using this table we can relatively easily select the last skill for each row (id-1). Using this we can calculate the gains:
SELECT player_skill_gains.player_id, skills.name, player_skill_gains.score
, player_skill_gains.score - IFNULL(bef.score,0) AS gain
, player_skill_gains.created_at
FROM player_skill_gains
INNER JOIN skills ON player_skill_gains.skill_id = skills.id
LEFT JOIN player_skill_gains AS bef ON (player_skill_gains.id - 1) = bef.id
AND player_skill_gains.player_id = bef.player_id
AND player_skill_gains.skill_id = bef.skill_id
For the different queries you want to have (24 hours, 7 days, etc.) you just have to specify the needed where-part for the query.
You can see all this in action here: http://sqlfiddle.com/#!9/1571a8/11/0

GROUP BY same record but different TIMESTAMP/DATETIME

I'm trying to GROUP BY same records but different timestamp or datetime.
The difference of time is only about 3 minutes from the first entry.
example:
This is what the database table looks like.
*-------------------------------------------*
| id | name | time |
| 1 | Lei | 2018-02-21 12:00:10 |
| 2 | Lei | 2018-02-21 12:01:11 |
| 3 | Lei | 2018-02-21 12:01:15 |
| 4 | Lei | 2018-02-21 12:01:16 |
| 5 | Anna | 2018-02-21 12:03:11 |
| 6 | Anna | 2018-02-21 12:03:13 |
| 7 | Bell | 2018-02-21 12:05:01 |
| 8 | Lei | 2018-02-21 12:10:00 |
*-------------------------------------------*
I want to get Lei's entry from 12:00:10 up to 3 minutes from her first timestamp or datetime record.
so the output would be like this.
*-------------------------------------------*
| id | name | time |
| 1 | Lei | 2018-02-21 12:00:10 |
| 5 | Anna | 2018-02-21 12:03:11 |
| 7 | Bell | 2018-02-21 12:05:01 |
| 8 | Lei | 2018-02-21 12:10:00 |
*-------------------------------------------*
I'll be gladly appreciate your help, mysql or php it is.
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE Table1
(`id` int, `name` varchar(4), `time` datetime)
;
INSERT INTO Table1
(`id`, `name`, `time`)
VALUES
(1, 'Lei', '2018-02-21 12:00:10'),
(2, 'Lei', '2018-02-21 12:01:11'),
(3, 'Lei', '2018-02-21 12:01:15'),
(4, 'Lei', '2018-02-21 12:01:16'),
(5, 'Anna', '2018-02-21 12:03:11'),
(6, 'Anna', '2018-02-21 12:03:13'),
(7, 'Bell', '2018-02-21 12:05:01')
;
Query 1:
select id, name, min(time) as time
from Table1
group by name
order by time
Results:
| id | name | time |
|----|------|----------------------|
| 1 | Lei | 2018-02-21T12:00:10Z |
| 5 | Anna | 2018-02-21T12:03:11Z |
| 7 | Bell | 2018-02-21T12:05:01Z |
OR if you want to group by interval 3 minute you can do it like this
select id, name, min(time) as time
from Table1
group by name, UNIX_TIMESTAMP(time) DIV 180
order by time
;
With your sample data, you don't need to consider the timestamp at all:
select (#rn := #rn + 1) as id, name, min(time) as time
from t cross join
(select #rn := 0) params
group by id, name;
Grouping things by three minute intervals, from the first record in the interval is much harder. This requires either variables or recursive CTEs.
Looks like you need something like this:
select *
from mytable t
where not exists (
select *
from mytable t1
where t1.name = t.name
and t1.id <> t.id
and t1.time >= t.time - interval 3 minute
and t1.time < t.time
);
Demo: http://sqlfiddle.com/#!9/03cf16/1
It will select rows only if no row with the same name exists within a three munutes interval.

MySQL UNION ALL (Full Join) with conditional aggregation

I have three tables:
CREATE TABLE `Agreement` (
`AID` bigint(20) NOT NULL AUTO_INCREMENT,
`FLAGS` bigint(20) NOT NULL DEFAULT '0',
PRIMARY KEY (`AID`)
);
CREATE TABLE `Assessment` (
`ASMID` bigint(20) NOT NULL AUTO_INCREMENT,
`AID` bigint(20) NOT NULL DEFAULT '0',
`Amount` decimal(19,4) NOT NULL DEFAULT '0.0000',
`Description` text,
PRIMARY KEY (`ASMID`)
);
CREATE TABLE `Payment` (
`RID` bigint(20) NOT NULL AUTO_INCREMENT,
`AID` bigint(20) NOT NULL DEFAULT '0',
`ASMID` bigint(20) NOT NULL DEFAULT '0',
`Amount` decimal(19,4) NOT NULL DEFAULT '0.0000',
`Description` text,
PRIMARY KEY (`RID`)
);
And I'm inserting one Agreement, three Assessment, five Payment rows mentioned as below:
INSERT INTO Agreement(FLAGS) VALUES(0);
INSERT INTO Assessment(AID, Amount, Description) VALUES (1, 1200, "Rent");
INSERT INTO Assessment(AID, Amount, Description) VALUES (1, 20, "Damage - car break");
INSERT INTO Assessment(AID, Amount, Description) VALUES (1, 500, "Damage - vehicle");
INSERT INTO Payment(AID, ASMID, Amount, Description) VALUES(1, 1, 500, "Rent Fee");
INSERT INTO Payment(AID, ASMID, Amount, Description) VALUES(1, 1, 600, "Rent Fee");
INSERT INTO Payment(AID, ASMID, Amount, Description) VALUES(1, 2, 20, "Damage Fee");
INSERT INTO Payment(AID, Amount, Description) VALUES(1, 600, "Deposit Fee");
INSERT INTO Payment(AID, Amount, Description) VALUES(1, 50, "Application Fee");
and When I see the data, so it should look like this:
mysql> SELECT * FROM Agreement;
+-----+-------+
| AID | FLAGS |
+-----+-------+
| 1 | 0 |
+-----+-------+
1 row in set (0.00 sec)
mysql> SELECT * FROM Assessment;
+-------+-----+-----------+--------------------+
| ASMID | AID | Amount | Description |
+-------+-----+-----------+--------------------+
| 1 | 1 | 1200.0000 | Rent |
| 2 | 1 | 20.0000 | Damage - car break |
| 3 | 1 | 500.0000 | Damage - vehicle |
+-------+-----+-----------+--------------------+
3 rows in set (0.00 sec)
mysql> SELECT * FROM Payment;
+-----+-----+-------+----------+-----------------+
| RID | AID | ASMID | Amount | Description |
+-----+-----+-------+----------+-----------------+
| 1 | 1 | 1 | 500.0000 | Rent Fee |
| 2 | 1 | 1 | 600.0000 | Rent Fee |
| 3 | 1 | 2 | 20.0000 | Damage Fee |
| 4 | 1 | 0 | 600.0000 | Deposit Fee |
| 5 | 1 | 0 | 50.0000 | Application Fee |
+-----+-----+-------+----------+-----------------+
5 rows in set (0.00 sec)
So, any Agreement has multiple Assessments all need to be paid in near future. It may have multiple Payments which might be associated with Assessment (i.e., Rent Fee) or might be not (i.e., Application Fee).
Now, in reality, there are multiple Agreements which have multiple Assessments and multiple Payments.
Now I want the result that covers all of the rows from both tables Assessment and Payment associated with Agreement GROUPED BY first Agreement, second Assessment. Additionally, I need to aggregate AMOUNT as PaymentsApplied from the table Payment table for each Assessment so that we can compare it with Amount from the table Assessment as AmountDue. Plus if any Payment is not associated with any Assessment then don't do aggregation. The result would look like this:
+-----+-------+-----------+-----------------+--------------------+-----------------+
| AID | ASMID | AmountDue | PaymentsApplied | ASM-Descr | PMT-Description |
+-----+-------+-----------+-----------------+--------------------+-----------------+
| 1 | 1 | 1200.0000 | 1100.0000 | Rent | Rent Fee |
| 1 | 2 | 20.0000 | 20.0000 | Damage - car break | Damange Fee |
| 1 | 3 | 500.0000 | NULL | Damage - vehicle | NULL |
| 1 | 0 | NULL | 600.0000 | NULL | Deposit Fee |
| 1 | 0 | NULL | 50.0000 | NULL | Application Fee |
+-----+-------+-----------+-----------------+--------------------+-----------------+
5 Rows
I tried my best to explain the situation. Actually, in my application query has joins over 10 tables like Agreement!
Any Help would be most welcome!!
UPDATE 1
I've started from this query,
(SELECT DISTINCT
Payment.RID, Payment.Amount as PaymentsApplied, Payment.ASMID as PMT_ASMID, null as AmountDue, null AS ASMID
FROM Payment
LEFT JOIN Assessment ON Assessment.ASMID=Payment.ASMID)
UNION
(SELECT DISTINCT
null, null, null, Assessment.Amount, Assessment.ASMID
FROM Assessment
LEFT JOIN Payment ON Payment.ASMID=Assessment.ASMID)
ORDER BY ASMID, PMT_ASMID;
which gives me result,
+------+-----------------+-----------+-----------+-------+
| RID | PaymentsApplied | PMT_ASMID | AmountDue | ASMID |
+------+-----------------+-----------+-----------+-------+
| NULL | NULL | NULL | 1200.0000 | 1 |
| NULL | NULL | NULL | 20.0000 | 2 |
| NULL | NULL | NULL | 500.0000 | 3 |
| 1 | 500.0000 | 1 | NULL | NULL |
| 2 | 600.0000 | 1 | NULL | NULL |
| 3 | 20.0000 | 2 | NULL | NULL |
| 4 | 600.0000 | 0 | NULL | NULL |
| 5 | 50.0000 | 0 | NULL | NULL |
+------+-----------------+-----------+-----------+-------+
8 rows in set (0.01 sec)
Now, from this point, IDK how to aggregate Payment rows by Assessment ID (ASMID) and do join with Agreement table also?
UPDATE 2
I've made sqlfiddle link just in case someone wants to try it.
I've added conditional aggregation in my query,
(SELECT DISTINCT
null as AmountDue,
null AS ASMID,
null as ASM_Descr,
Payment.Description as PMT_Descr,
(CASE WHEN Payment.ASMID > 0 THEN SUM(Payment.Amount) ELSE Payment.Amount END) as PaymentsApplied,
(CASE WHEN Payment.ASMID > 0 THEN GROUP_CONCAT(Payment.RID) ELSE Payment.RID END) as PaymentList,
Payment.ASMID as PMT_ASMID
FROM Payment
LEFT JOIN Assessment ON Assessment.ASMID=Payment.ASMID
GROUP BY Assessment.ASMID)
UNION ALL
(SELECT DISTINCT
Assessment.Amount,
Assessment.ASMID,
Assessment.Description,
null,
null,
null,
null
FROM Assessment
LEFT JOIN Payment ON Payment.ASMID=Assessment.ASMID
GROUP BY Assessment.ASMID)
ORDER BY ASMID, PMT_ASMID;
which gives me,
+-----------+-------+--------------------+-------------+-----------------+-------------+-----------+
| AmountDue | ASMID | ASM_Descr | PMT_Descr | PaymentsApplied | PaymentList | PMT_ASMID |
+-----------+-------+--------------------+-------------+-----------------+-------------+-----------+
| NULL | NULL | NULL | Deposit Fee | 600.0000 | 4 | 0 |
| NULL | NULL | NULL | Rent Fee | 1100.0000 | 1,2 | 1 |
| NULL | NULL | NULL | Damage Fee | 20.0000 | 3 | 2 |
| 1200.0000 | 1 | Rent | NULL | NULL | NULL | NULL |
| 20.0000 | 2 | Damage - car break | NULL | NULL | NULL | NULL |
| 500.0000 | 3 | Damage - vehicle | NULL | NULL | NULL | NULL |
+-----------+-------+--------------------+-------------+-----------------+-------------+-----------+
But, this one is still missing one row from Payment row (RID: 5) and I'm not getting the expected result.
I'd first collect all assessments, left join them to the payments and then union all payments without assessments:
# Assessments with payments
SELECT asm.AID,
asm.ASMID,
min(asm.Amount) AS AmountDue,
SUM(pam.Amount) AS PaymentsApplied,
asm.Description AS `ASM-Descr`,
pam.Description AS `PMT-Descr`,
agr.FLAGS
FROM Assessment asm
LEFT JOIN Payment pam ON pam.ASMID = asm.ASMID
JOIN Agreement agr ON agr.AID = asm.AID
GROUP BY asm.AID,
asm.ASMID
UNION # Payments without assessments
SELECT pam.AID,
pam.ASMID,
NULL AS AmountDue,
SUM(pam.Amount) AS PaymentsApplied,
NULL AS `ASM-Descr`,
pam.Description AS `PMT-Descr`,
agr.FLAGS
FROM Payment pam
LEFT JOIN Assessment asm ON pam.ASMID = asm.ASMID
JOIN Agreement agr ON agr.AID = pam.AID
WHERE asm.ASMID IS NULL
GROUP BY pam.AID, pam.RID;
If you want to add more information you could wrap this result, give it a name and join more tables to the temporary result:
SELECT payment_overview.*,
p.name
FROM
( # Assessments with payments
SELECT asm.AID,
asm.ASMID,
min(asm.Amount) AS AmountDue,
SUM(pam.Amount) AS PaymentsApplied,
asm.Description AS `ASM-Descr`,
pam.Description AS `PMT-Descr`,
agr.FLAGS
FROM Assessment asm
LEFT JOIN Payment pam ON pam.ASMID = asm.ASMID
JOIN Agreement agr ON agr.AID = asm.AID
GROUP BY asm.AID,
asm.ASMID
UNION # Payments without assessments
SELECT pam.AID,
pam.ASMID,
NULL AS AmountDue,
SUM(pam.Amount) AS PaymentsApplied,
NULL AS `ASM-Descr`,
pam.Description AS `PMT-Descr`,
agr.FLAGS
FROM Payment pam
LEFT JOIN Assessment asm ON pam.ASMID = asm.ASMID
JOIN Agreement agr ON agr.AID = pam.AID
WHERE asm.ASMID IS NULL
GROUP BY pam.AID,
pam.RID ) AS payment_overview
JOIN Payor p ON p.AID = payment_overview.AID ;
It seems you want a result row per AID + ASMID + PMT-Description. So:
Select from payments and aggregate.
Select from assessments.
Full outer join the two, as there can be payments without accessments and accessments without payments.
MySQL lacks FULL OUTER JOIN. So write the same query twice, once with LEFT OUTER JOIN, once with RIGHT OUTER JOIN, and use UNION on the two result sets.
select
p.aid,
p.asmid,
a.amount as amount_due,
p.payments_applied,
a.description as asm_description,
p.description as pmt_description
from
(
select aid, asmid, description, sum(amount) as payments_applied
from payment
group by aid, asmid, description
) p
left join assessment a on a.aid = p.aid and a.asmid = p.asmid
union
select
p.aid,
p.asmid,
a.amount as amount_due,
p.payments_applied,
a.description as asm_description,
p.description as pmt_description
from
(
select aid, asmid, description, sum(amount) as payments_applied
from payment
group by aid, asmid, description
) p
right join assessment a on a.aid = p.aid and a.asmid = p.asmid
order by aid, asmid, pmt_description;
Once MySQL features FULL OUTER JOIN you can cut this query in half.

MySQL count changes

I would like to count number of changes in column Value grouped by Id using MySQL.
Source Table:
create table sequence
(
`Id` int,
`Date` date,
`Value` int not null,
PRIMARY KEY (`Id`,`Date`)
);
insert into sequence
( `Id`,`Date`, `Value` )
values
(1, '2016-01-01' , 0 ),
(1, '2016-01-02' , 10 ),
(1, '2016-01-03' , 0 ),
(1, '2016-01-05' , 0 ),
(1, '2016-01-06' , 10 ),
(1, '2016-01-07' , 15 ),
(2, '2016-01-08' , 15 );
Visualization:
+------------+-------+-------+
| Date | ID | Value |
+------------+-------+-------+
| 2016-01-01 | 1 | 0 |
| 2016-01-02 | 1 | 10 | (change)
| 2016-01-03 | 1 | 0 | (change)
| 2016-01-05 | 1 | 0 |
| 2016-01-06 | 1 | 10 | (change)
| 2016-01-07 | 1 | 15 | (change)
| 2016-01-08 | 2 | 15 |
+------------+-------+-------+
Expected output:
+-------+-------+
| ID | Value |
+-------+-------+
| 1 | 4 |
| 2 | 0 |
+-------+-------+
I would like to ask if there is a way how to do this in SQL.
This is not the very efficient or elegant solution,
but just to show some goals that you can achieve using mysql :-)
http://sqlfiddle.com/#!9/1db14/6
SELECT t1.id, MAX(t1.changes)
FROM (SELECT t.*,
IF (#i IS NULL,#i:=0,IF(#lastId <> id,#i:=0,IF (#lastV <> `value`, #i:=#i+1, #i:=#i))) as changes,
#lastV := `value`,
#lastId := `id`
FROM (
SELECT *
FROM sequence
ORDER BY id, date) t
) t1
GROUP BY t1.id