Extract records modified in different date - sql

I have this table called t1 with these fields: codLoan, codOp, codCau, action, and dateExec. Action field can be assumed three values: 'I' (Inserted), 'M' (Modified) or 'C' (Cancelled).
My records can be modified in different dates, so I can have two records with the same codLoan but with different value for dateExec.
I have to extract all the records that have the same codLoan and different Action (I or M) in different dateExec.
For instance:
codLoan=1
dateExec= '2018/08/08'
action='I'
codLoan=1
dateExec= '2018/08/08'
action='M'
codLoan=2
dateExec= '2018/08/07'
action='I'
codLoan=2
dateExec= '2018/08/08'
action='M'
Result: codLoan=2, dateExec= '2018/08/08'
I tried this query, but it extracts all the records with Action='I' and Action='M'.
select codLoan, dateExec
from t1
where Action in ('I','M');
How can I fix my code?

Perhaps you want :
select t.*
from table t
where exists (select 1
from table t1
where t1.codLoan = t.codLoan and
t1.dateExec <> t.dateExec and
t1.action <> t.action
);

Use a join:
select b.*
from mytable a
join mytable b on b.codLoan = a.codLoan
and b.dateExec > a.dateExec
and b.action != a.action
and b.action in ('I','M')
where a.action in ('I','M')
This returns the last action and date.

declare #t table
(
codLoan int,
dateExec date,
[action] char(1)
);
insert into #t values
(1, '2018-08-08', 'I'),
(1, '2018-08-08', 'M'),
(2, '2018-08-07', 'I'),
(2, '2018-08-08', 'M');
with diff as
(
select x.*
from
(
select
*,
cnt1 = count(*) over (partition by codLoan, dateExec
order by codLoan) ,
cnt2 = count(*) over (partition by codLoan, [action], dateExec
order by codLoan),
rnum = row_number() over (partition by codLoan
order by dateExec desc)
from #t
) x
where cnt1 = cnt2
)
select codLoan, dateExec
from diff
where rnum = 1
order by codLoan, dateExec;

Just another option
select codLoan
, dateExec = min(dateExec)
from t1
where Action in ('I','M')
Group By codLoan
Having min(dateExec)<>max(dateExec)

Related

Get 'most recent' grouped record (with order by)

I have a query like the below
SELECT
t1.Supplier,
t2.Product,
FROM
t1
INNER JOIN
t2 ON t1.ProductCode = t2.ProductCode
GROUP BY
t1.Supplier, t2.Product
On table t1, there are also columns called 'Timestamp' and 'Price' - I want to get the most recent price, i.e. SELECT Price ORDER BY Timestamp DESC. Can I do this with any aggregate functions, or would it have to be a subquery?
One standard way of doing this is to use ROW_NUMBER() to create an additional column in the source data, allowing you to identify which row is "first" within each "partition".
WITH
supplier_sorted AS
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY supplier, ProductCode
ORDER BY timestamp DESC
)
AS recency_id
FROM
supplier
)
SELECT
s.Supplier,
p.Product,
COUNT(*)
FROM
supplier_sorted AS s
INNER JOIN
product AS p
ON s.ProductCode = p.ProductCode
WHERE
s.recency_id = 1
GROUP BY
s.Supplier,
p.Product
You can use cross apply:
SELECT t2.*, t1.*
FROM t2 CROSS APPLY
(SELECT TOP (1) t1.*
FROM t1
WHERE t1.ProductCode = t2.ProductCode
ORDER BY t1.TimeStamp DESC
) t1;
So, GROUP BY is not necessary.
Can use the row_number() over the partiton of ProductCode and Supplier to by using Timestamp Order by desc to get the latest record by based on the partition. Then you can use in the same query without aggregation to get the desired result.
It is good to use Windows functions rather than Group by for these questions.
SELECT
A.Supplier
,A.Product
,A.Price
FROM
(
SELECT
t1.Supplier,
t2.Product,
T1.Price,
ROW_NUMBER () OVER ( PARTITION BY t1.Supplier,t2.Product ORDER BY T1.[Timestamp] DESC ) AS row_num
FROM t1
INNER JOIN t2
ON t1.ProductCode = t2.ProductCode
) AS A WHERE A.row_num = 1
Tested using below added data.
CREATE TABLE t1
( Supplier varchar(100)
,ProductCode int
, Price Decimal (10,2)
, [TimeStamp] datetime
)
CREATE TABLE t2
(
ProductCode int
,Product varchar(100)
)
insert into t1 values ('A', 1, 100.00, GetDate())
insert into t1 values ('A', 1, 80.00, GetDate())
insert into t1 values ('b', 2, 190.00, GetDate())
insert into t1 values ('b', 2, 500.00, GetDate())
insert into t2 values (1, 'Pro1')
insert into t2 values (2, 'Pro2')
insert into t2 values (3, 'Pro3')

Group by with gap in date sequence ("gaps and islands")

I am trying to solve a "gaps and islands" by date issue I'm facing (kudos to Gordon Linoff helping me identify this issue). I want to group the below table by person, office and job while respecting order by person,from_date. consider the table below:
declare #temp table(person varchar(25),office varchar(25),job varchar(25),from_date date,to_date date)
insert into #temp values ('jon','ny','programmer','1/1/2020','1/3/2020');
insert into #temp values ('jon','ny','programmer','1/4/2020','1/5/2020');
insert into #temp values ('jon','dc','programmer','1/6/2020','1/7/2020');
insert into #temp values ('jon','ny','programmer','1/8/2020','1/9/2020');
insert into #temp values ('lou','ny','programmer','1/1/2020','1/3/2020');
insert into #temp values ('lou','ny','programmer','1/4/2020','1/5/2020');
insert into #temp values ('lou','dc','programmer','1/6/2020','1/7/2020');
insert into #temp values ('lou','ny','programmer','1/8/2020','1/9/2020');
the intended output is
This is a type of gaps-and-islands problem. If there are no gaps in the dates, the simplest solution is the difference of row numbers:
select person, office, job, min(from_date), max(to_date)
from (select t.*,
row_number() over (partition by person, office, job order by from_date) as seqnum,
row_number() over (partition by person, office order by from_date) as seqnum_2
from t
) t
group by person, office, job, (seqnum - seqnum_2)
This is a general solution:
WITH preceders_and_followers AS (
SELECT
b.person,
b.office,
b.job,
b.from_date,
b.to_date,
CASE
WHEN EXISTS (
SELECT
c.*
FROM
ora$ptt_tmp c
WHERE
b.person = c.person
AND b.office = c.office
AND b.job = c.job
AND ( b.from_date - 1 BETWEEN c.from_date AND c.to_date )
) THEN
1
END AS has_preceder,
CASE
WHEN EXISTS (
SELECT
c.*
FROM
ora$ptt_tmp c
WHERE
b.person = c.person
AND b.office = c.office
AND b.job = c.job
AND ( b.to_date + 1 BETWEEN c.from_date AND c.to_date )
) THEN
1
END AS has_follower
FROM
ora$ptt_tmp b
ORDER BY
1,
2,
3
)
SELECT DISTINCT
pf1.person,
pf1.office,
pf1.job,
pf1.from_date,
(
SELECT
MIN(pf2.to_date)
FROM
preceders_and_followers pf2
WHERE
pf1.person = pf2.person
AND pf1.office = pf2.office
AND pf1.job = pf2.job
AND pf2.to_date >= pf1.from_date
AND has_follower IS NULL
) to_date
FROM
preceders_and_followers pf1
WHERE
pf1.has_preceder IS NULL
ORDER BY
1,
4,
2,
3;

Latest entry in a group SQL Server

Given the sample data below, I need a list of the ids whose latest entry is Rejected. Thus, I need to see id 2 because its latest is 6/4/2020 and that is Rejected. I do not want to see id 1 as its latest entry is Requested.
CREATE TABLE #temp
(
id int,
mydate datetime,
status VARCHAR(20)
)
INSERT INTO #temp VALUES (1, '6/1/2020', 'Rejected')
INSERT INTO #temp VALUES (1, '6/2/2020', 'Requested')
INSERT INTO #temp VALUES (1, '6/3/2020', 'Rejected')
INSERT INTO #temp VALUES (1, '6/4/2020', 'Requested')
INSERT INTO #temp VALUES (2, '6/1/2020', 'Requested')
INSERT INTO #temp VALUES (2, '6/2/2020', 'Requested')
INSERT INTO #temp VALUES (2, '6/3/2020', 'Requested')
INSERT INTO #temp VALUES (2, '6/4/2020', 'Rejected')
SELECT * FROM #temp
SELECT id, MAX(mydate)
FROM #temp
WHERE status = 'Rejected'
GROUP BY id
This is my pathetic attempt so far
SELECT id, MAX(mydate)
FROM #temp
WHERE status = 'Rejected'
GROUP BY id
But this will only bring back the latest date in each group. I need a list where the latest entry was Rejected. I expect the answer to be embarrassingly simple but I'm having a heck of a time with this.
Thanks
Carl
You can get this using row_number() function as shown below.
;WITH cte
AS (
SELECT Id
,mydate
,STATUS
,ROW_NUMBER() OVER (
PARTITION BY Id, status ORDER BY mydate desc
) row_num
FROM #temp
)
SELECT *
FROM cte
WHERE row_num = 1
AND STATUS = 'Rejected'
Here is the live db<>fiddle demo.
One method uses aggregation and having:
select id
from #temp
group by id
having max(case when status = 'Rejected' then mydate end) = max(mydate);
This is almost a direct translation of your question: the latest date for 'Rejected' is the latest date for a given id.
More traditional methods use a correlated subquery:
select t.*
from #temp t
where t.mydate = (select max(t2.mydate)
from #temp t2
where t2.id = t.id
) and
t.status = 'Rejected';
Or window functions:
select t.*
from (select t.*,
row_number() over (partition by id order by mydate desc) as seqnum
from #temp t
) t
where t.seqnum = 1 and t.status = 'Rejected';

How can I transform my N little queries into one query?

I have a query that gives me the first available value for a given date and pair.
SELECT
TOP 1 value
FROM
my_table
WHERE
date >= 'myinputdate'
AND key = 'myinpukey'
ORDER BY date
I have N pairs of key and dates, and I try to find out how not to query each pair one by one. The table is rather big, and N as well, so it's currently heavy and slow.
How can I query all the pairs in one query ?
A solution is to use APPLY like a "function" created on the fly with one or many columns from another set:
DECLARE #inputs TABLE (
myinputdate DATE,
myinputkey INT)
INSERT INTO #inputs(
myinputdate,
myinputkey)
VALUES
('2019-06-05', 1),
('2019-06-01', 2)
SELECT
I.myinputdate,
I.myinputkey,
R.value
FROM
#inputs AS I
CROSS APPLY (
SELECT TOP 1
T.value
FROM
my_table AS T
WHERE
T.date >= I.myinputdate AND
T.key = I.myinputkey
ORDER BY
T.date ) AS R
You can use OUTER APPLY if you want NULL result values to be shown also. This supports fetching multiple columns and using ORDER BY with TOP to control amount of rows.
This solution is without variables. You control your N by setting the right value to the row_num predicate.
There are plenty of ways how to do you what you want and it all depends on your specific needs. As it answered already, that you can use temp/variable table to store these conditions and then join it on the same conditions you use predicates. You can also create user defined data type and use it as param to the function/procedure. You might use CROSS APPLY + VALUES clause to get that list and then join it.
DROP TABLE IF EXISTS #temp;
CREATE TABLE #temp ( d DATE, k VARCHAR(100) );
GO
INSERT INTO #temp
VALUES ( '20180101', 'a' ),
( '20180102', 'b' ),
( '20180103', 'c' ),
( '20180104', 'd' ),
( '20190101', 'a' ),
( '20190102', 'b' ),
( '20180402', 'c' ),
( '20190103', 'c' ),
( '20190104', 'd' );
SELECT a.d ,
a.k
FROM ( SELECT d ,
k ,
ROW_NUMBER() OVER ( PARTITION BY k ORDER BY d DESC ) row_num
FROM #temp
WHERE (d >= '20180401'
AND k = 'a')
OR (d > '20180401'
AND k = 'b')
OR (d > '20180401'
AND k = 'c')
) a
WHERE a.row_num <= 1;
-- VALUES way
SELECT a.d ,
a.k
FROM ( SELECT t.d ,
t.k ,
ROW_NUMBER() OVER ( PARTITION BY t.k ORDER BY t.d DESC ) row_num
FROM #temp t
CROSS APPLY (VALUES('20180401','a'), ('20180401', 'b'), ('20180401', 'c')) f(d,k)
WHERE t.d >= f.d AND f.k = t.k
) a
WHERE a.row_num <= 1;
If all the keys are using the same date, then use window functions:
SELECT key, value
FROM (SELECT t.*, ROW_NUMBER() OVER (PARTITION BY key ORDER BY date) as seqnum
FROM my_table t
WHERE date >= #input_date AND
key IN ( . . . )
) t
WHERE seqnum = 1;
SELECT key, date,value
FROM (SELECT ROW_NUMBER() OVER (PARTITION BY key,date ORDER BY date) as rownum,key,date,value
FROM my_table
WHERE
date >= 'myinputdate'
) as d
WHERE d.rownum = 1;

Get the maximum values of column B per each distinct value of column A sql

I have this table:
I am trying to pull all records from this table for the max value in the DIST_NO column for every distinct ID in the left most column, but I still want to pull every record for each ID in which there are different Product_ID's as well.
I tried partitioning and using row_number, but I am having trouble at the moment.
Here are my desired results:
This is what my code looks like currently:
select *
from
(SELECT *,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY DIST_NO DESC) RN
FROM Table) V
WHERE RN<=3
you want the max(DIST_NO) for each ID, product_ID?
If so, you can:
SELECT
ID, product_ID, max(DIST_NO)
from table
group by ID, product_ID
If you want the detail rows related to the max row, you just need to join it back to your table:
Select
t.ID, max_dist_no, TRANSaction_ID , LINE_NO , PRODUCT_ID
from
table t inner join
(SELECT
ID, max(DIST_NO) as max_dist_no
from table
group by ID) mx on
t.ID = mx.ID and
t.DIST_NO = max_DIST_NO
Try
SELECT MT.ID
, MT.DIST_NO
, MT.TRANS_ID
, MT.LINE_NO
, MT.PRODUCT_ID
FROM MYTABLE MT
INNER JOIN (
SELECT T.ID, MAX(T.DIST_NO) as DIST_NO FROM MYTABLE T
GROUP BY T.ID
) MAX_MT ON MT.Id = MAX_MT.ID AND MT.DIST_NO = MAX_MT.DIST_NO
The sub query returns each combination of ID and Max value of DIST_NO:
SELECT T.ID, MAX(T.DIST_NO) as DIST_NO FROM MYTABLE T
GROUP BY T.ID
Joining this back to your original table will basically filter your original data-set by only these combinations of values.
Tested on PostgreSQL:
WITH t1 AS (
SELECT id, product_id, MAX(dist_no) AS dist_no
FROM test
GROUP BY 1,2)
SELECT t1.id, t1.dist_no, t2.trans_id, t2.line_no, t1.product_id
FROM test t2, t1
WHERE t1.id=t2.id AND t1.product_id=t2.product_id AND t1.dist_no=t2.dist_no
Use rank() or dense_rank():
select t.*
from (SELECT t.*
RANK() OVER (PARTITION BY ID ORDER BY DIST_NO DESC) as seqnum
FROM Table t
) t
WHERE seqnum = 1;
This is almost a literal translation of your request:
I am trying to pull all records from this table for the max value in
the DIST_NO column for every distinct ID in the left most column.
you can try something like this one :). (But is your result correct? I think there is little mistake in TRANS_ID...)
DECLARE #ExampleTable TABLE
(ID INT,
DIST_NO INT,
TRANS_ID INT,
LINE_NO INT,
PRODUCT_ID INT)
INSERT INTO #ExampleTable
( ID, DIST_NO, TRANS_ID,LINE_NO, PRODUCT_ID )
VALUES ( 102657, 1, 1105365, 1, 109119 ),
( 102657, 1, 1105366, 2, 109114 ),
( 102657, 2, 1105365, 1, 109119 ),
( 102657, 2, 1105366, 2, 109114 ),
( 104371, 1, 1190538, 1, 110981 ),
( 104371, 2, 1190538, 1, 110981 )
;WITH CTE AS ( SELECT DISTINCT ID, LINE_NO
FROM #ExampleTable)
SELECT a.ID,
x.DIST_NO,
x.TRANS_ID,
x.LINE_NO,
x.PRODUCT_ID
FROM CTE a
CROSS APPLY (SELECT TOP 1 *
FROM #ExampleTable f
WHERE a.ID = f.ID AND
a.LINE_NO = f. LINE_NO
ORDER BY DIST_NO DESC) x