Reputation: 4132
I have three tables. All follow the CRUD-pattern for inserts and updates. I need to make a query qhich selects all latest updates in one table, based on unique values of another table, twice removed through joins: (I have left out the CRUD structure on two of the tables in the example)
table COPY_HISTORY
COPY_ID | DATA_ID | STATUS | TIME |
1 | A | open | 10:34 |
1 | A | locked | 10:37 |
2 | A | open | 10:38 |
3 | B | open | 11:29 |
4 | C | open | 10:37 |
5 | D | locked | 09:34 |
table DATA_SET
DATA_ID | LOCATION |
A | 88 |
B | 77 |
C | 88 |
D | 99 |
E | 88 |
F | 88 |
table COPY_RULES
LOCATION_FROM | LOCATION_TO
55 | 110
66 | 120
77 | 120
88 | 130
99 | 130
What I need to do, is get the status for each LOCATION_TO from the copy rules table. If a copy is executed, it will be registered in copy history, and a copy will always overwrite all data at a location (110,120,130).
In this case, location 110 has never had a copy happen, and 110 should not be included in the returned data at all. Even though it exists in the rules, that is not relevant. 120 will receive copies from data sets on 66 and 77. However, we have no data set on 66 yet, so only 77 is relevant. B is located at 77, and data set B had a copy history registration inserted at 11:29, with the status open. Since this is the only status belonging to 120, status is open for 120, and overwrite will be possible for location 120.
Location 130 however, receive copies from both 88 and 99. This means that data sets A, C, D, E and F all will copy into 130, as they are all located in 88 or 99. We have two copy history logs for A, and one each for C and D. Meaning I want the status of only the newest registration, in this case, 10:38.
Now, I have tried to select the maximum value from COPY_HISTORY, based on the LOCATION_TO, but I am at an impasse getting the LOCATION_TO to be unique
I try:
SELECT cr.LOCATION_TO, ch.STATUS, ch.TIME FROM COPY_HISTORY ch
JOIN DATA_SET ds ON ch.DATA_ID = ds.DATA_ID
JOIN COPY_RULES cr ON cr.LOCATION_FROM = ds.LOCATION
WHERE ch.TIME = (SELECT MAX(TIME) FROM COPY_HISTORY
WHERE COPY_ID = ch.COPY_ID
AND ch.DATA_ID = ds.DATA_ID
AND ds.LOCATION = cr.LOCATION_FROM)
That select max time statement is incomplete. The two AND-lines does nothing at all. The only thing I can accomplish, is getting rid of the first line in COPY_HISTORY, meaning I can get all max times for each COPY_ID, but I can not figure out how to filter them to get only the maximum for each LOCATION_TO instead.
I managed to get the max value from one single LOCATION_TO, by doing this:
SELECT cr.LOCATION_TO, ch.STATUS, ch.TIME FROM COPY_HISTORY ch
JOIN DATA_SET ds ON ch.DATA_ID = ds.DATA_ID
JOIN COPY_RULES cr ON cr.LOCATION_FROM = ds.LOCATION
WHERE ch.TIME = (SELECT MAX(TIME) FROM COPY_HISTORY ch2, DATA_SET ds2, COPY_RULES cs2
WHERE ch2.DATA_ID = ds2.DATA_ID
AND ds2.LOCATION = cr2.LOCATION_FROM
AND cr2.LOCATION_TO = 2180)
However, this does not solve my issue of getting the list of statuses for all LOCATION_TO.
DESIRED OUTPUT:
LOCATION_TO | STATUS | TIME |
120 | open | 11:29 |
130 | open | 10:38 |
Valid rows in COPY_HISTORY would be line 3 and line 4
This is an attempt at an extreme simplification of the actual database structure, so there are risks of typos in this question.
DDL
create table #COPY_HISTORY (COPY_ID int, DATA_ID char(1), [STATUS] varchar(16), [TIME] time)
create table #DATA_SET (DATA_ID char(1), [LOCATION] int)
create table #COPY_RULES (LOCATION_FROM int, LOCATION_TO int)
insert into #COPY_HISTORY
values
(1,'A','open','10:34'),
(1,'A','locked','10:37'),
(2,'A','open','10:38'),
(3,'B','open','11:29'),
(4,'C','open','10:37'),
(5,'D','locked','09:34')
insert into #DATA_SET
values
('A',88),
('B',77),
('C',88),
('D',99),
('E',88),
('F',88)
insert into #COPY_RULES
values
(55,110),
(66,120),
(77,120),
(88,130),
(99,130)
Upvotes: 0
Views: 53
Reputation: 190
I think this query will work for you. As Ryan mentioned in the comments, you can use the ROW_NUMBER analytical function to rank your location records (PARTITION BY) by the time (ORDER BY), then only return the first row for each location (RowOrder = 1).
SELECT * FROM (
SELECT cr.LOCATION_TO, ch.STATUS, ch.TIME, ROW_NUMBER() OVER (PARTITION BY cr.LOCATION_TO ORDER BY ch.TIME desc) AS RowOrder
FROM #COPY_HISTORY ch
JOIN #DATA_SET ds ON ch.DATA_ID = ds.DATA_ID
JOIN #COPY_RULES cr ON cr.LOCATION_FROM = ds.LOCATION
) ordered_set
WHERE RowOrder = 1
Upvotes: 4
Reputation: 361
If I'm getting the logic correctly:
DECLARE @COPY_HISTORY TABLE
(
COPY_ID INT NOT NULL,
DATA_ID CHAR(1) NOT NULL,
[STATUS] VARCHAR(50) NOT NULL,
[TIME] TIME NOT NULL
);
DECLARE @DATA_SET TABLE
(
DATA_ID CHAR(1) NOT NULL,
LOCATION INT NOT NULL
);
DECLARE @COPY_RULES TABLE
(
LOCATION_FROM INT NOT NULL,
LOCATION_TO INT NOT NULL
);
INSERT INTO @COPY_HISTORY
VALUES (1,'A','open','10:34'),
(1,'A','locked','10:37'),
(2,'A','open','10:38'),
(3,'B','open','11:29'),
(4,'C','open','10:37'),
(5,'D','locked','09:34');
INSERT INTO @DATA_SET
VALUES ('A',88),
('B',77),
('C',88),
('D',99),
('E',88),
('F',88);
INSERT INTO @COPY_RULES
VALUES (55,110),
(66,120),
(77,120),
(88,130),
(99,130);
WITH CTE
AS
(
SELECT CR.LOCATION_TO,
CH.STATUS,
CH.TIME,
ROW_NUMBER() OVER(PARTITION BY CR.LOCATION_TO ORDER BY CASE WHEN CH.Status = 'OPEN' THEN 1 ELSE 0 END DESC, CH.TIME DESC) AS RN
FROM @COPY_HISTORY AS CH
INNER
JOIN @DATA_SET AS DS
ON CH.DATA_ID = DS.DATA_ID
INNER
JOIN @COPY_RULES AS CR
ON CR.LOCATION_FROM = DS.LOCATION
)
SELECT C.LOCATION_TO,
C.STATUS,
C.TIME
FROM CTE AS C
WHERE RN = 1;
Upvotes: 2