Joining Summed data that has nulls - SQL Server - sql-server-2008

How do I retain the Acct_Name field where appropriate when summing the data below by the Amount column and grouping by the Line_Num field? The "Null" values in Line_Num column cause a problem in the grouping terms when the account name is added. Accounts C and D both have Null values in Line_Num. If I add Acct_Name to the group by clause, I lose the ability to sum the values only by the Line_Num field.
I am attempting to sum lines of accounting and group based on the line number. The null data isn't my doing, unfortunately it's just the data set I was handed.
Original data:
Acct_Name ID Line_Num Amount
Acct A 1 1_01 100.0000
Acct A 1 1_01 -50.0000
Acct A 1 1_02 75.0000
Acct A 1 _02 125.0000
Acct B 2 2_01 200.0000
Acct B 2 2_01 50.0000
Acct B 2 2_02 25.0000
Acct C 3 3_01 75.0000
Acct C 3 3_02 50.0000
Acct C 3 3_03 -25.0000
Acct C 3 Null 65.0000
Acct D 4 Null 300.0000
Acct D 4 _02 100.0000
Acct D 4 Null -50.0000
Acct D 4 Null 75.0000
If the Line_Num value is null, that line is allowed to be aggregated with the other null values. It will show up in reports as being unaccounted for and it can be dealt with appropriately.
Ideal processed data set:
Amount Line_Num Acct_Name
390.00 Null Null
225.00 _02 Null
50.00 1_01 Acct A
75.00 1_02 Acct A
250.00 2_01 Acct B
25.00 2_02 Acct B
75.00 3_01 Acct C
50.00 3_02 Acct C
-25.00 3_03 Acct C
Here are the following queries I have used:
Select SUM(Amount), Line_Num
FROM dbo.tblRawData
Group By Line_Num
This query works just fine, but it does not include the account name in any of the aggregated fields. I need the account name in the fields that did not contain null values.
Select SUM(Amount), Line_Num, Acct_Name
FROM dbo.tblRawData
Group By Line_Num, Acct_Name
This query includes the account name, but it ends up grouping based on Account Name and not just Line_Num.
Select *
From dbo.tblRawData a
Inner Join dbo.tblRawData b On (a.Line_Num = b.Line_Num)
(SELECT SUM(CAST(Amount as money)) as Amount, Line_Num
FROM dbo.tblRawData
GROUP BY Line_Num)
This inner join is intended to join only those lines that are equivalent on the Line Num, but I am receiving a cartesian result set. Clearly I have not written this join correctly or I am using the incorrect command.
Here is the query that can be used to build the same schema that I am using:
CREATE TABLE [dbo].[tblRawData](
[Acct_Name] [nvarchar](50) NULL,
[ID] [nvarchar](50) NULL,
[Line_Num] [nvarchar] (50),
[Amount] [money]
) ON [PRIMARY]
GO
insert into dbo.tblRawData values ('Acct A', '1', '1_01', '100')
insert into dbo.tblRawData values ('Acct A', '1', '1_01', '-50')
insert into dbo.tblRawData values ('Acct A', '1', '1_02', '75')
insert into dbo.tblRawData values ('Acct A', '1', '_02', '125')
insert into dbo.tblRawData values ('Acct B', '2', '2_01', '200')
insert into dbo.tblRawData values ('Acct B', '2', '2_01', '50')
insert into dbo.tblRawData values ('Acct B', '2', '2_02', '25')
insert into dbo.tblRawData values ('Acct C', '3', '3_01', '75')
insert into dbo.tblRawData values ('Acct C', '3', '3_02', '50')
insert into dbo.tblRawData values ('Acct C', '3', '3_03', '-25')
insert into dbo.tblRawData values ('Acct C', '3', '', '65')
insert into dbo.tblRawData values ('Acct D', '4', '', '300')
insert into dbo.tblRawData values ('Acct D', '4', '_02', '100')
insert into dbo.tblRawData values ('Acct D', '4', '', '-50')
insert into dbo.tblRawData values ('Acct D', '4', '', '75')
P.S. SQL Fiddle appears to be inaccessible at the moment (might be on my end, don't know)
Edit
Take a look at the following code and holler if it seems that there are blatant flaws in trying to accomplish my goal. I'd prefer for Acct_Name to remain null if Line_Item doesn't match up, but perhaps I can sort that out.
IF (SELECT object_id('TempDB..#temp4')) IS NOT NULL
BEGIN
DROP TABLE #temp4
END
SELECT SUM(CAST(Amount as money)) as Amount, Line_Num INTO #temp4
FROM dbo.tblRawData
GROUP BY Line_Num
Select * from #temp4
Select MAX(a.Acct_Name) as Acct_Name, MAX(b.Line_Num) as Line_Num, MAX(b.Amount) as Amount
From dbo.tblRawData a
Inner Join #temp4 b On (a.Line_Num = b.Line_Num)
Group By b.Line_Num
Results:
Acct_Name Line_Num Amount
Acct D Null 390.00
Acct D _02 225.00
Acct A 1_01 50.00
Acct A 1_02 75.00
Acct B 2_01 250.00
Acct B 2_02 25.00
Acct C 3_01 75.00
Acct C 3_02 50.00
Acct C 3_03 -25.00

Here you go:
;WITH CTE AS
(
SELECT Line_Num,
SUM(Amount) Amount,
MIN(Acct_Name) MinAcct_Name,
MAX(Acct_Name) MaxAcct_Name
FROM tblRawData
GROUP BY Line_Num
)
SELECT Amount,
Line_Num,
CASE WHEN Line_Num IS NULL
OR MinAcct_Name <> MaxAcct_Name THEN NULL
ELSE MinAcct_Name END Acct_Name
FROM CTE

Related

Select the oldest record of a certain group until it changes pattern, in SQL

I am trying to get the oldest record for every status update/change in the following table.
Table (status_updates) :
id
entity_id
status
date
7
2
Approved
2022-02-10
6
2
Approved
2022-02-05
5
2
Approved
2022-02-04
4
2
OnHold
2022-02-04
3
2
OnHold
2022-02-03
2
2
Approved
2022-02-02
1
2
Approved
2022-02-01
Result Needed :
id
entity_id
status
date
5
2
Approved
2022-02-04
3
2
OnHold
2022-02-03
1
2
Approved
2022-02-01
Tried :
select
`status`,
`created_at`
from
`status_updates`
left join
(select
`id`,
row_number() over (partition by status_updates.entity_id, status_updates.status order by status_updates.created_at asc) as sequence
from
`status_updates`)
as `oldest_history`
on
`oldest_history`.`id` = `shipper_credit_histories`.`id`
where `sequence` = 1
Result Achived :
id
entity_id
status
date
3
2
OnHold
2022-02-03
1
2
Approved
2022-02-01
Just using lag:
select s.*
from (
select id, status<>coalesce(lag(status) over (partition by entity_id order by id),'') status_change
from status_updates
) ids
join status_updates s using (id)
where status_change
here are the queries:
create table status_updates
(entity_id integer,
status varchar(32),
date date
);
insert into status_updates values (2, 'Approved', '2022-02-05');
insert into status_updates values (2, 'Approved', '2022-02-04');
insert into status_updates values (2, 'On Hold', '2022-02-04');
insert into status_updates values (2, 'On Hold', '2022-02-03');
insert into status_updates values (2, 'Approved', '2022-02-02');
insert into status_updates values (2, 'Approved', '2022-02-01');
select b.*
from status_updates a
right join status_updates b
on a.status=b.status and a.date=(b.date - interval 1 day)
where a.entity_id is null;
or this query(if you prefer left join)
select a.*
from status_updates a
left join status_updates b
on a.status=b.status and a.date=(b.date + interval 1 day)
where b.entity_id is null;
in both you will see the expected result
the second solution is almost the same, but join by id instead of date
create table status_updates
(id integer,
entity_id integer,
status varchar(32),
date date
);
insert into status_updates values (7, 2, 'Approved', '2022-02-10');
insert into status_updates values (6, 2, 'Approved', '2022-02-05');
insert into status_updates values (5, 2, 'Approved', '2022-02-04');
insert into status_updates values (4, 2, 'On Hold', '2022-02-04');
insert into status_updates values (3, 2, 'On Hold', '2022-02-03');
insert into status_updates values (2, 2, 'Approved', '2022-02-02');
insert into status_updates values (1, 2, 'Approved', '2022-02-01');
select a.*
from status_updates a
left join status_updates b
on a.status=b.status and a.id=b.id + 1
where b.entity_id is null;
result is the same what you expected

Find DISTINCT LAST record with SQL LEFT JOIN

I'm running MySQL 5.6.
I have two related tables:
CREATE TABLE Cars (
id INT NOT NULL AUTO_INCREMENT,
plate VARCHAR(16) NOT NULL,
flag TINYINT,
PRIMARY KEY(id)
)
and:
CREATE TABLE Rents (
id INT NOT NULL AUTO_INCREMENT,
out_date DATE NOT NULL,
in_date DATE,
car_id INT,
FOREIGN KEY (car_id) REFERENCES Cars(id),
PRIMARY KEY(id)
)
I can have multiple rents for each car (0 to many).
I need to select all vehicles in table Cars (with flag = 1) along with their status i.e. I need to know if each car is currently unavailable (only out_date is filled) or availabe (out_date and in_date filled) of course also vehicles without any rents are to be considered available.
The result set need to include out_date and in_date values [Update 17/07/2022].
I tought to use something like:
SELECT
*,
IF(Rents.in_date IS NOT NULL AND Rents.out_date IS NOT NULL, 1, IF(Rents.id IS NULL, 1, 0)) AS status
FROM Cars
LEFT JOIN Rents ON Cars.id = Rent.Car_id WHERE Cars.Flag = 1
but this of course will just return all the rows with positive flag match and a status evaluation (0 unavailable, 1 available):
id | plate | flag | id | out_date | in_date | car_id | status
---------------------------------------------------------------------
'1', 'FA787MX', '1', '1', '2022-07-14', '2022-07-15', '1', '1'
'1', 'FA787MX', '1', '2', '2022-07-16', NULL, '1', '0'
'3', 'AB124DF', '1', '4', '2022-07-13', '2022-07-14', '3', '1'
'4', 'CC666VC', '1', NULL, NULL, NULL, NULL, '1'
'5', 'GG435ED', '1', '5', '2022-07-16', NULL, '5', '0'
While I need to have this (edited 17/07/2022):
'1', 'FA787MX', '1', '2', '2022-07-16', NULL, '1', '0'
'3', 'AB124DF', '1', '4', '2022-07-13', '2022-07-14', '3', '1'
'4', 'CC666VC', '1', NULL, NULL, NULL, NULL, '1'
'5', 'GG435ED', '1', '5', '2022-07-16', NULL, '5', '0'
i.e. only the second row of FA787MX car should be mantained since it's the most recent out_date value (no matter if it's id is higher or lower).
For the sake of completeness: There is no guarantee that rental ids will be kept consistent with their rental history. In other words you cannot be sure that for a given car the rental where in_date = NULL is the correct one but you should compare them by out_date value.
Data sample:
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (1, 'FA787MX', 1);
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (2, 'EX431YY', 0);
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (3, 'AB124DF', 1);
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (4, 'CC666VC', 1);
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (5, 'GG435ED', 1);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (1, '2022-07-14', '2022-07-15', 1);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (2, '2022-07-16', NULL, 1);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (3, '2022-07-16', NULL, 2);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (4, '2022-07-13', '2022-07-14', 3);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (5, '2022-07-16', NULL, 5);
One option is to join to find only those rentals that are still outstanding (in_date IS NULL). That will drop the old rentals having in_date not null.
Based on the updated requirements, there are a few ways to do it. One is a simple outer join to find the most recent rental per car to obtain the corresponding in_date as well...
MySQL 5.6 fiddle
SELECT Cars.*
, Rents.out_date
, Rents.in_date
, Rents.id IS NULL OR Rents.in_date IS NOT NULL AS status_final
FROM Cars
LEFT JOIN Rents
ON Cars.id = Rents.Car_id
LEFT JOIN Rents AS r2
ON Rents.out_date < r2.out_date
AND Rents.Car_id = r2.Car_id
WHERE Cars.Flag = 1
AND r2.Car_id IS NULL
ORDER BY Cars.id
;
The result:
id
plate
flag
out_date
in_date
status_final
1
FA787MX
1
2022-07-16
0
3
AB124DF
1
2022-07-13
2022-07-14
1
4
CC666VC
1
1
5
GG435ED
1
2022-07-16
0
Based on the original requirements: Try this (fiddle):
SELECT Cars.*
, Rents.in_date
, CASE WHEN in_date IS NOT NULL OR Rents.id IS NULL THEN 1 ELSE 0 END AS status_final
FROM Cars
LEFT JOIN Rents
ON Cars.id = Rents.Car_id
AND in_date IS NULL
WHERE Cars.Flag = 1
;
and if the results contain only those with in_date IS NULL, this reduces to:
SELECT Cars.*
, out_date
, Rents.in_date
, Rents.id IS NULL AS status_final
FROM Cars
LEFT JOIN Rents
ON Cars.id = Rents.Car_id
AND in_date IS NULL
WHERE Cars.Flag = 1
;
Result:
id
plate
flag
out_date
in_date
status_final
1
FA787MX
1
2022-07-16
0
3
AB124DF
1
1
4
CC666VC
1
1
5
GG435ED
1
2022-07-16
0
If your version of MySql is 8.0+ use ROW_NUMBER() window function to pick the latest row for each car in Rents:
SELECT c.*, r.*,
r.out_date IS NULL OR r.in_date IS NOT NULL status
FROM Cars c
LEFT JOIN (
SELECT *, ROW_NUMBER() OVER (PARTITION BY car_id ORDER BY out_date DESC) rn
FROM Rents
) r ON r.car_id = c.id AND r.rn = 1
WHERE c.flag = 1;
For previous versions use NOT EXISTS:
SELECT c.*, r.*,
r.out_date IS NULL OR r.in_date IS NOT NULL status
FROM Cars c
LEFT JOIN (
SELECT r1.*
FROM Rents r1
WHERE NOT EXISTS (
SELECT *
FROM Rents r2
WHERE r2.car_id = r1.car_id AND r2.out_date > r1.out_date
)
) r ON r.car_id = c.id
WHERE c.flag = 1;
See the demo.
If you imagine the result of your query as a table, you can easily write a query that would give you what you need (the subquery is just yours with the select spelled out to give a unique column name to the second id column, as it seemed useful - the only way to uniquely identify a row):
SELECT MAX(rent_id) FROM (
SELECT
Cars.id as id,
plate,
flag,
Rents.id as rent_id,
out_date,
in_date,
car_id,
IF(Rents.in_date IS NOT NULL AND Rents.out_date IS NOT NULL, 1, IF(Rents.id IS NULL, 1, 0)) AS status
FROM Cars
LEFT JOIN Rents ON Cars.id = Rents.car_id WHERE Cars.Flag = 1
) as rental_status
WHERE status = 0
GROUP BY car_id;
Which tells you which rows are interesting:
+--------------+
| MAX(rent_id) |
+--------------+
| 2 |
| 5 |
+--------------+
Now you can use a join to return the results of your initial query only for the interesting rows. To avoid having to spell out that query all over again, MySQL 8 has a way to stash the results of your core query and use it like a table:
WITH
status_data AS (
SELECT
Cars.id as id,
plate,
flag,
Rents.id as rent_id,
out_date,
in_date,
car_id,
IF(Rents.in_date IS NOT NULL AND Rents.out_date IS NOT NULL, 1, IF(Rents.id IS NULL, 1, 0)) AS status
FROM Cars
LEFT JOIN Rents ON Cars.id = Rents.car_id WHERE Cars.Flag = 1
)
SELECT * from status_data
JOIN (
SELECT MAX(rent_id) as rent_id FROM status_data
WHERE status = 0
GROUP BY car_id
) as ids using(rent_id);
Giving the result:
+---------+----+---------+------+------------+---------+--------+--------+
| rent_id | id | plate | flag | out_date | in_date | car_id | status |
+---------+----+---------+------+------------+---------+--------+--------+
| 2 | 1 | FA787MX | 1 | 2022-07-16 | NULL | 1 | 0 |
| 5 | 5 | GG435ED | 1 | 2022-07-16 | NULL | 5 | 0 |
+---------+----+---------+------+------------+---------+--------+--------+

Query for any person has any account of type x

Imagine I have two tables, Person and Account, a person can have accounts (type 1 and/or 2).
I'd like to get a list of people who have at least one type 1 account, and also get a list of people who don't have a type 1 account. I'm using Query #1 and #2 for this respectively but I think I'm doing something is wrong because the results do not match.
Schema (MySQL v5.7)
CREATE TABLE Person (
`PersonId` INTEGER,
`Name` VARCHAR(5)
);
INSERT INTO Person
(`PersonId`, `Name`)
VALUES
('1', 'Leo'),
('2', 'Natan'),
('3', 'Vera'),
('4', 'Julio'),
('5', 'Mary');
CREATE TABLE Accounts (
`AccountId` INTEGER,
`PersonId` INTEGER,
`Type` INTEGER
);
INSERT INTO Accounts
(`AccountId`, `PersonId`, `Type`)
VALUES
('1', '1', '0'),
('2', '1', '1'),
('3', '2', '0'),
('4', '2', '0'),
('5', '3', '1'),
('6', '4', '0'),
('7', '1', '0'),
('8', '2', '0');
Query #1
SELECT * FROM Person AS PD
LEFT JOIN Accounts AS AC ON AC.PersonId = PD.PersonId
WHERE AC.Type = 1;
PersonId
Name
AccountId
PersonId
Type
1
Leo
2
1
1
3
Vera
5
3
1
Query #2
SELECT * FROM Person AS PD
LEFT JOIN Accounts AS AC ON AC.PersonId = PD.PersonId
WHERE AC.Type = 0;
PersonId
Name
AccountId
PersonId
Type
1
Leo
1
1
0
1
Leo
7
1
0
2
Natan
3
2
0
2
Natan
4
2
0
2
Natan
8
2
0
4
Julio
6
4
0
View on DB Fiddle
EXISTS and NOT EXISTS are the more suitable solutions for this requirement:
-- Account type = 1
SELECT p.* FROM Person AS p
WHERE EXISTS (
SELECT *
FROM Accounts AS a
WHERE a.PersonId = p.PersonId AND a.Type = 1
);
-- No type 1 account
SELECT p.* FROM Person AS p
WHERE NOT EXISTS (
SELECT *
FROM Accounts AS a
WHERE a.PersonId = p.PersonId AND a.Type = 1
);
See the demo.

How to get difference or delta of counts entries of each days with window functions?

I have a table with few fields like id, country, ip, created_at. Then I am trying to get the deltas between total entry of one day and total entry of the next day.
CREATE TABLE session (
id int NOT NULL AUTO_INCREMENT,
country varchar(50) NOT NULL,
ip varchar(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
INSERT INTO `session` (`id`, `country`, `ip`, `created_at`) VALUES
('1', 'IN', '10.100.102.11', '2021-04-05 20:26:02'),
('2', 'IN', '10.100.102.11', '2021-04-05 19:26:02'),
('3', 'US', '10.120.102.11', '2021-04-17 10:26:02'),
('4', 'US', '10.100.112.11', '2021-04-16 12:26:02'),
('5', 'AU', '10.100.102.122', '2021-04-12 19:36:02'),
('6', 'AU', '10.100.102.122', '2021-04-12 18:20:02'),
('7', 'AU', '10.100.102.122', '2021-04-12 23:26:02'),
('8', 'US', '10.100.102.2', '2021-04-16 21:33:01'),
('9', 'AU', '10.100.102.122', '2021-04-18 20:46:02'),
('10', 'AU', '10.100.102.111', '2021-04-04 13:19:12'),
('11', 'US', '10.100.112.11', '2021-04-16 12:26:02'),
('12', 'IN', '10.100.102.11', '2021-04-05 15:26:02'),
('13', 'IN', '10.100.102.11', '2021-04-05 19:26:02');
Now I have written this query to get the delta
SELECT T1.date1 as date, IFNULL(T1.cnt1-T2.cnt2, T1.cnt1) as delta from (
select TA.dateA as date1, MAX(TA.countA) as cnt1 from (
select DATE(created_at) AS dateA, COUNT(*) AS countA
FROM session
GROUP BY DATE(created_at)
UNION
select DISTINCT DATE(DATE(created_at)+1) AS dateA, 0 AS countA
FROM session
) as TA
group by TA.dateA
) as T1
LEFT OUTER JOIN (
select DATE(DATE(created_at)+1) AS date2,
COUNT(*) AS cnt2
FROM session
GROUP BY DATE(created_at)
) as T2
ON T1.date1=T2.date2
ORDER BY date;
http://sqlfiddle.com/#!9/4f5fd26/60
Then I am getting the results as
date delta
2021-04-04 1
2021-04-05 3
2021-04-06 -4
2021-04-12 3
2021-04-13 -3
2021-04-16 3
2021-04-17 -2
2021-04-18 0
2021-04-19 -1
Now, is there any place of improvements/optimizes on it with/or window functions? (I am zero with SQL, still playing around).
Try a shorter version
with grp as (
SELECT t.dateA, SUM(t.cnt) AS countA
FROM session,
LATERAL (
select DATE(created_at) AS dateA, 1 as cnt
union all
select DATE(DATE(created_at)+1), 0 as cnt
) t
GROUP BY dateA
)
select t1.dateA as date, IFNULL(t1.countA-t2.countA, t1.countA) as delta
from grp t1
left join grp t2 on DATE(t2.dateA + 1) = t1.dateA
order by t1.dateA
db<>fiddle

inner join with subquery results differs for these data sets

CREATE TABLE IF NOT EXISTS `wcd` (
`id` int(6) unsigned NOT NULL,
`wid` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `wcd` (`id`, `wid`) VALUES
('168', '5685'),
('167', '5685'),
('166', '5685'),
('165', '5685'),
('164', '5685'),
('163', '5685'),
('162', '5684'),
('161', '5684');
CREATE TABLE IF NOT EXISTS `cases` (
`id` int(6) unsigned NOT NULL,
`wcd_id` int(11) unsigned NOT NULL,
`reason_id` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `cases` (`id`, `wcd_id`, `reason_id`) VALUES
('20', '168', '4'),
('19', '168', '1'),
('18', '167', '6'),
('17', '167', '5'),
('16', '166', '4'),
('15', '166', '1'),
('14', '165', '4'),
('13', '165', '1'),
('12', '164', '1'),
('11', '163', '4'),
('10', '162', '1'),
('9', '162', '4'),
('8', '162', '5'),
('7', '161', '5'),
('6', '161', '6');
the above two table has foreignkey relation with wcd.id = cases.wcd_id,
Lets consider the records related to wcd.wid 5865. The result should be grouped by reason_id with the condition max(cases.id)
I used the query below to achieve this and got the result as expected.
SELECT d.id, d.wid, c.* FROM wcd d
LEFT JOIN cases c ON c.wcd_id = d.id
inner JOIN (SELECT MAX(id) AS max_id FROM cases GROUP BY reason_id) c2
ON c2.max_id = c.id
WHERE d.wid = 5685;
Result:
id wid id wcd_id reason_id
168 5685 19 168 1
168 5685 20 168 4
167 5685 17 167 5
167 5685 18 167 6
with the same query for 5684, the query returns 0 rows though there is data available for it. but I'm expecting the rows below.
id wid id wcd_id reason_id
162 5684 10 162 1
162 5684 9 162 4
162 5684 8 162 5
161 5684 6 161 6
What the issue with the query and what needs to be changed to get the result above for 5684.?
here is the sqlfiddle link
You need to look back at the wcd table to propery correlate, since you need the id of the row that has the "latest" reason per wid - and that column is not available in cases.
In MySQL 8.0, we would just use row_number()... but you tagged your question MySQL 5.6. I find that the simplest way to express this is with a correlated subquery:
SELECT d.id, d.wid, c.*
FROM wcd d
INNER JOIN cases c ON c.wcd_id = d.id
WHERE c.id = (
SELECT max(c2.id)
FROM wcd d2
INNER JOIN cases c2 ON c2.wcd_id = d2.id
WHERE d2.wid = d.wid AND c2.reason_id = c.reason_id
)
AND d.wid = 5685;
Then you must use MIN and get rid of the Where Clause.because
('162', '5684')
('161', '5684')
because
SELECT
d.id
, d.wid
,
c.*
FROM
wcd d
LEFT JOIN
cases c
ON c.wcd_id = d.id
inner JOIN (SELECT MIN(id) AS min_id FROM cases GROUP BY reason_id) c2
ON c2.min_id = c.id
see http://sqlfiddle.com/#!9/fb4569/26