SQL performing day difference by matching value - sql

My goal is to get the duration when the 1st OLD or 1st NEW status reaches to the 1st END. For example: Table1
ID Day STATUS
111 1 NEW
111 2 NEW
111 3 OLD
111 4 END
111 5 END
112 1 OLD
112 2 OLD
112 3 NEW
112 4 NEW
112 5 END
113 1 NEW
113 2 NEW
The desired outcome would be:
STATUS Count
NEW 2 (1 for ID 111-New on day 1 to End on day 4,and 1 for 112-new on day 3 to End on day 5)
OLD 2 (1 for ID 111-Old on day 3 to End on day 4, and 1 for 112-OLD on day 1 to End on day 5)

The following is T-SQL (SQL Server) and NOT available in MySQL. The choice of dbms is vital in a question because there are so many dbms specific choices to make. The query below requires using a "window function" row_number() over() and a common table expression neither of which exist yet in MySQL (but will one day). This solution also uses cross apply which (to date) is SQL Server specific but there are alternatives in Postgres and Oracle 12 using lateral joins.
SQL Fiddle
MS SQL Server 2014 Schema Setup:
CREATE TABLE Table1
(id int, day int, status varchar(3))
;
INSERT INTO Table1
(id, day, status)
VALUES
(111, 1, 'NEW'),
(111, 2, 'NEW'),
(111, 3, 'OLD'),
(111, 4, 'END'),
(111, 5, 'END'),
(112, 1, 'OLD'),
(112, 2, 'OLD'),
(112, 3, 'NEW'),
(112, 4, 'NEW'),
(112, 5, 'END'),
(113, 1, 'NEW'),
(113, 2, 'NEW')
;
Query 1:
with cte as (
select
*
from (
select t.*
, row_number() over(partition by id, status order by day) rn
from table1 t
) d
where rn = 1
)
select
t.id, t.day, ca.nxtDay, t.Status, ca.nxtStatus
from cte t
outer apply (
select top(1) Status, day
from cte nxt
where t.id = nxt.id
and t.status = 'NEW' and nxt.status = 'END'
order by day
) ca (nxtStatus, nxtDay)
where nxtStatus IS NOT NULL or Status = 'OLD'
order by id, day
Results:
| id | day | nxtDay | Status | nxtStatus |
|-----|-----|--------|--------|-----------|
| 111 | 1 | 4 | NEW | END |
| 111 | 3 | (null) | OLD | (null) |
| 112 | 1 | (null) | OLD | (null) |
| 112 | 3 | 5 | NEW | END |
As you can see, counting that Status column would result in NEW = 2 and OLD = 2

Related

How to create a field which shows a custom order based on two fields?

I am trying to create a field which shows an order based on two columns. I have one column with a code in and one with a date. There are many dates for each code, but I am trying to pick out the latest date for each code. The table below shows the two columns I have and the order column that I need to create.
code date order column
1 10/04/22 3
1 11/04/22 2
1 14/05/22 1
2 10/04/22 2
2 15/04/22 1
3 11/04/22 1
4 12/04/22 2
4 16/04/22 1
5 15/04/22 2
5 17/04/22 1
As Larnu and Sean have already stated, Row_number is your friend here.
Start with the data:
CREATE TABLE #Table (code int, date date)
INSERT INTO #table
VALUES
(1, '04/10/22')
,(1, '04/11/22')
,(1, '05/14/22')
,(2, '04/10/22')
,(2, '04/15/22')
,(3, '04/11/22')
,(4, '04/12/22')
,(4, '04/16/22')
,(5, '04/15/22')
,(5, '04/17/22');
Then we write the query with the row numbers. The magic here is in the partition by/order by. That partitions your data based on the code, so it takes the three 1s and puts the dates in descending order. It then numbers them 1, 2, 3 with the latest date being number 1. Then it does code 2...
SELECT code
, date
, ROW_NUMBER() OVER (PARTITION BY code ORDER BY date desc) rn
FROM #table
GROUP BY code, date
ORDER BY code asc, date asc;
And that gets us the result you asked for:
|code | date | rn |
|:----|:---------|:----|
| 1 |2022-04-10| 3 |
| 1 |2022-04-11| 2 |
| 1 |2022-05-14| 1 |
| 2 |2022-04-10| 2 |
| 2 |2022-04-15| 1 |
| 3 |2022-04-11| 1 |
| 4 |2022-04-12| 2 |
| 4 |2022-04-16| 1 |
| 5 |2022-04-15| 2 |
| 5 |2022-04-17| 1 |
And then if you only want the max date for each code... keep only the ones where row number equals 1.
WITH CTE AS
(SELECT code
, date
, ROW_NUMBER() OVER (PARTITION BY code ORDER BY date desc) rn
FROM #table
)
SELECT code
, date
FROM CTE
WHERE rn = 1
ORDER BY code
| code | date |
|:-----|:---------|
| 1 |2022-05-14|
| 2 |2022-04-15|
| 3 |2022-04-11|
| 4 |2022-04-16|
| 5 |2022-04-17|

Selecting only top parent table row with all of it's children table rows

So I have two tables:
#ProjectHealthReports
Id | From | SubmittedOn
1 | 2020-01-01 |
2 | 2020-02-01 | 2020-10-23
3 | 2020-03-01 |
4 | 2020-04-01 | 2020-10-23
5 | 2020-05-01 | 2020-10-23
#ProjectHealthReportItems
Id | Note | ProjectHealthReportId
1 | First for 2020-01-01 | 1
2 | Second for 2020-01-01 | 1
3 | First for 2020-02-01 | 2
4 | Second for 2020-02-01 | 2
5 | First for 2020-03-01 | 3
6 | Second for 2020-03-01 | 3
7 | First for 2020-04-01 | 4
8 | Second for 2020-04-01 | 4
9 | (We want this one) First for 2020-05-01 | 5
10 | (We want this one) Second for 2020-05-01 | 5
How can I get all #ProjectHealthReportItems and #ProjectHealthReport details for the last From date which has value for SubmittedOn (so in this case it would be ProjectHealthReport 5 and ProjectHealthReportItems 9, 10).
Basically, I need something like this just, obviously without top 1 as it only returns one row and I need, in this case, to return 2 rows :)
select top 1 phr.Id, phr.[From], phr.SubmittedOn, phri.Note from #ProjectHealthReports phr
inner join #ProjectHealthReportItems phri on phr.Id = phri.ProjectHealthReportId
where phr.SubmittedOn is not null
order by phr.[From] desc
Here is the SQL for creating and seeding the tables
create table #ProjectHealthReports(
Id int primary key,
[From] date not null ,
SubmittedOn date null
)
go
create table #ProjectHealthReportItems(
Id int primary key,
Note nvarchar(max),
ProjectHealthReportId int constraint FK_PHR references #ProjectHealthReports
)
go
insert into #ProjectHealthReports(Id, [From], SubmittedOn)
values (1, '2020-01-01', null),
(2, '2020-02-01', getutcdate()),
(3, '2020-03-01', null),
(4, '2020-04-01', getutcdate()),
(5, '2020-05-01', getutcdate())
go
insert into #ProjectHealthReportItems(Id, Note, ProjectHealthReportId)
values (1, 'First for 2020-01-01', 1),
(2, 'Second for 2020-01-01', 1),
(3, 'First for 2020-02-01', 2),
(4, 'Second for 2020-02-01', 2),
(5, 'First for 2020-03-01', 3),
(6, 'Second for 2020-03-01', 3),
(7, 'First for 2020-04-01', 4),
(8, 'Second for 2020-04-01', 4),
(9, '(We want this one) First for 2020-05-01', 5),
(10, '(We want this one) Second for 2020-05-01', 5)
go
First select top then join
select t.*, phri.Note
from (select top(1) phr.Id phrid, phr.[From], phr.SubmittedOn
from #ProjectHealthReports phr
where phr.SubmittedOn is not null
order by phr.[From] desc) t
inner join #ProjectHealthReportItems phri on t.phrId = phri.ProjectHealthReportId
I would suggest window functions:
select phr.*, phri.*
from #ProjectHealthReports phr left join
(select phri.*,
row_number() over (partition by ProjectHealthReportId order by id desc) as seqnum
from #ProjectHealthReportItems phri
) phri
on phr.Id = phri.ProjectHealthReportId and seqnum = 1
order by phr.[From] desc;
You can also do this using filtering in the where, such as correlated subquery:
select phr.*, phri.*
from #ProjectHealthReports phr join
#ProjectHealthReportItems phri
on phr.Id = phri.ProjectHealthReportId and seqnum = 1
where phri.id = (select max(phri2.id)
from #ProjectHealthReportItems phri2
where phri2.ProjectHealthReportId = phri.ProjectHealthReportId
)
order by phr.[From] desc
An efficient way to do this without a LEFT JOIN would be assign a row number, using the ROW_NUMBER() windowing function, to the #ProjectHealthReports table. Something like this
with lv_cte as (
select *, row_number() over (order by [From] desc) rn
from #ProjectHealthReports)
select l.*, phri.*
from lv_cte l
join #ProjectHealthReportItems phri on l.id=phri.ProjectHealthReportId
where l.rn=1;
Output
Id From SubmittedOn rn Id Note ProjectHealthReportId
5 2020-05-01 2020-10-23 1 9 (We want this one) First for 2020-05-01 5
5 2020-05-01 2020-10-23 1 10 (We want this one) Second for 2020-05-01 5

Oracle Sql: Obtain a Sum of a Group, if Subgroup condition met

I have a dataset upon which I am trying to obain a summed value for each group, if a subgroup within each group meets a certain condition. I am not sure if this is possible, or if I am approaching this problem incorrectly.
My data is structured as following:
+----+-------------+---------+-------+
| ID | Transaction | Product | Value |
+----+-------------+---------+-------+
| 1 | A | 0 | 10 |
| 1 | A | 1 | 15 |
| 1 | A | 2 | 20 |
| 1 | B | 1 | 5 |
| 1 | B | 2 | 10 |
+----+-------------+---------+-------+
In this example I want to obtain the sum of values by the ID column, if a transaction does not contain any products labeled 0. In the above described scenario, all values related to Transaction A would be excluded because Product 0 was purchased. With the outcome being:
+----+-------------+
| ID | Sum of Value|
+----+-------------+
| 1 | 15 |
+----+-------------+
This process would repeat for multiple IDs with each ID only containing the sum of values if the transaction does not contain product 0.
Hmmm . . . one method is to use not exists for the filtering:
select id, sum(value)
from t
where not exists (select 1
from t t2
where t2.id = t.id and t2.transaction = t.transaction and
t2.product = 0
)
group by id;
Do not need to use correlated subquery with not exists.
Just use group by.
with s (id, transaction, product, value) as (
select 1, 'A', 0, 10 from dual union all
select 1, 'A', 1, 15 from dual union all
select 1, 'A', 2, 20 from dual union all
select 1, 'B', 1, 5 from dual union all
select 1, 'B', 2, 10 from dual)
select id, sum(sum_value) as sum_value
from
(select id, transaction,
sum(value) as sum_value
from s
group by id, transaction
having count(decode(product, 0, 1)) = 0
)
group by id;
ID SUM_VALUE
---------- ----------
1 15

Select rows with highest value in one column in SQL

in MySQL, I am trying to select one row for each "foreign_id". It must be the row with the highest value in column "time" (which is of type DATETIME). Can you help me how the SQL SELECT statement must look like? Thank you!
This would be really great! I am already trying for hours to find a solution :(
This is my table:
primary_id | foreign_id | name | time
----------------------------------------------------
1 | 3 | a | 2017-05-18 01:02:03
2 | 3 | b | 2017-05-19 01:02:03
3 | 3 | c | 2017-05-20 01:02:03
4 | 5 | d | 2017-07-18 01:02:03
5 | 5 | e | 2017-07-20 01:02:03
6 | 5 | f | 2017-07-18 01:02:03
And this is what the result should look like:
primary_id | foreign_id | name | time
----------------------------------------------------
3 | 3 | c | 2017-05-20 01:02:03
5 | 5 | e | 2017-07-20 01:02:03
I tried to order the intermediate result by time (descending) and then to select only the first row by using LIMIT 1. But like this I cannot get one row for each foreign_id.
Another try was to first order the intermediate result by time (descending) and then to GROUP BY foreign_id. But the GROUP BY statement seems to be executed before the ORDER BY statement (I received the rows with primary_id 1 and 4 as a result, not 3 and 5).
Try this
SELECT DISTINCT *
From my_table A
INNER JOIN (SELECT foreign_id, Max(time) AS time FROM my_table GROUP BY foreign_id) B
ON A.foreign_id = B.foreign_id AND A.time = B.time
Just add some data sample to analyze special case
CREATE TABLE Table1
(`primary_id` int, `foreign_id` int, `name` varchar(1), `time` datetime)
;
INSERT INTO Table1
(`primary_id`, `foreign_id`, `name`, `time`)
VALUES
(1, 3, 'a', '2017-05-18 01:02:03'),
(2, 3, 'b', '2017-05-19 01:02:03'),
(3, 3, 'c', '2017-05-20 01:02:03'),
(7, 3, 'H', '2017-05-20 01:02:03'),
(4, 5, 'd', '2017-07-18 01:02:03'),
(5, 5, 'e', '2017-07-20 01:02:03'),
(6, 5, 'f', '2017-07-18 01:02:03')
;
http://sqlfiddle.com/#!9/38947b/6
select d.primary_id, d.foreign_id, c.name, d.time
from table1 c inner join (
select max(b.primary_id) primary_id, a.foreign_id, a.time
from table1 b inner join
( select foreign_id, max(time) time
from table1
group by foreign_id) a
on a.foreign_id = b.foreign_id and a.time=b.time
group by a.foreign_id, a.time ) d
on c.primary_id=d.primary_id
In days gone by you would code this as a correlated subquery:
SELECT *
FROM Table1 o
WHERE primary_id = (
SELECT min (m.primary_id) FROM Table1 m
WHERE m.time= (
SELECT max (i.time) FROM Table1 i
WHERE o.foreign_id=i.foreign_id
)
)
The extra subquery handles the case of duplicate foreign_id & time values. If you were sure that time was unique for each foreign_id you could omit the middle subquery.

Filter SQL Query?

The sample data is:
l16seqno | l16lcode | carrno | ecarrno | l16qty | reasoncode
32001 | 12 | 207620 | 370036873034035916 | 32 | 0
32269 | 12 | 207620 | 370036873034035916 | -32 | 800
39075 | 12 | 207620 | 370036873034035916 | 32 | 0
39074 | 12 | 207622 | 370036873034035923 | 32 | 0
32268 | 12 | 207622 | 370036873034035923 | -32 | 800
31999 | 12 | 207622 | 370036873034035923 | 32 | 0
32271 | 12 | 207624 | 370036873034035930 | -32 | 800
32005 | 12 | 207624 | 370036873034035930 | 32 | 0
39077 | 12 | 207624 | 370036873034035930 | 32 | 0
I have logging of all the events in table Z02T1. Whenever I have l16lcode=12 – I am blocking or unblocking a pallet. When I block a pallet l16lqty feild is negative, and when I unblock – it is positive.
Reason codes can be found in Z02T2 table (can be connected to Z02T1 by l16seqno – a unique sequence number of each log record).
Z14T1 table contains info about pallets – pallet numbers.
My aim is to find two lines for each pallet i.e.
when blocked with code 800 ... and ... when unblocked with code 0
For this I have to find the nearest next record of l16lcode=12 for the same pallet with reason code 0 (after there was a record for this pallet with reason code 800).
The initial query I have made is:
select Z02T1.datreg, Z02T1.l16seqno, Z02T1.l16lcode, Z02T1.divcode, Z02T1.carrno,
Z14T1.ecarrno, Z02T1.l16qty, Z02T2.reascode from Z02T2
inner join Z02T1 on Z02T1.l16seqno=Z02T2.l16seqno
left outer join Z14T1 ON Z14T1.carrno=Z02T1.carrno
where Z02T1.l16lcode=12
and (Z02T2.reascode=800 or Z02T2.reascode=0 )
order by Z14T1.ecarrno
How I can change this query to get one record with reasoncode 800 and then very next record with reasoncode 0 for same ecarrno feild ?
Here is some sample code that you could use to modify your existing query.
Be aware that this example filters on the first occurance of reasoncode=800 and then subfilters on the first occurance of reasoncode=0 that has a l16seqno greater than the reasoncode=800 record.
CREATE TABLE reasons (
l16seqno int NOT NULL,
carrno int NOT NULL,
reasoncode int NOT NULL
);
INSERT INTO reasons
(l16seqno, carrno, reasoncode)
VALUES
(1, 1, 0),
(2, 1, 800),
(3, 1, 0),
(10, 300, 0),
(11, 300, 800),
(12, 300, 0),
(13, 300, 800),
(14, 300, 0),
(1003, 1212, 0),
(1004, 1212, 800),
(1005, 1212, 0),
(1006, 1212, 0);
WITH cte1 (l16seqno, carrno, reasoncode, rownumber)
AS
(
SELECT l16seqno, carrno, reasoncode, ROW_NUMBER() OVER (PARTITION BY carrno, reasoncode ORDER BY l16seqno)
FROM reasons
WHERE reasoncode = 800
),
cte2 (l16seqno, carrno, reasoncode, rownumber)
AS
(
SELECT r.l16seqno, r.carrno, r.reasoncode, ROW_NUMBER() OVER (PARTITION BY r.carrno, r.reasoncode ORDER BY r.l16seqno)
FROM reasons AS r
INNER JOIN cte1 AS c ON r.carrno = c.carrno
WHERE r.reasoncode = 0 AND r.l16seqno > c.l16seqno
)
SELECT r.l16seqno, r.carrno, r.reasoncode
FROM reasons AS r
LEFT OUTER JOIN cte1 AS c1 ON c1.l16seqno = r.l16seqno
LEFT OUTER JOIN cte2 AS c2 ON c2.l16seqno = r.l16seqno
WHERE c1.rownumber = 1
OR c2.rownumber = 1
ORDER BY r.carrno, r.l16seqno;
Here is the SQL Fiddle demo of the sample code listed above.
I hope this helps.
Here you go:
;with cte as
(
Select l16seqno
,l16lcode
,carrno
,ecarrno
,l16qty
,reasoncode
,ROW_NUMBER() Over(Partition By ecarrno, reasoncode Order By l16seqno) rn
From MyTable
)
Select l16seqno
,l16lcode
,carrno
,ecarrno
,l16qty
,reasoncode
From cte
Where rn = 1
Order By ecarrno asc, reasoncode desc