T-SQL: AGGREGATE AND/OR CONDITIONAL SUBQUERY ON WHERE CLAUSE - sql-server

Thanks in advance for your help on this. Here's the the scenario. I have two tables: Lots (stores lot info for food items) and Trans (stores inventory transactions on lot items).
I am trying to write a query that lists all the transactions that are older than 90 days based 3 conditions on the where clause somehow:
CurrentQty (on Lots table) > 0
If TransactionType = Shipment AND TransactionDate > 90 days (from current date) OR..
IF TransactionType = Receipt AND TransactionDate > 90 days (from current date)
Notes: for each lot, there could be many different transactions of the same type, as shown in the attached picture. There could be many shipments or receipts. I need to be able to select the MAX(TransactionDate) for a particular transaction type and check to see if it's over 90 days then show the record.
There will always be at least one of these two transaction types on the transaction table for every lot, either Shipment or Receipt. If there is no shipment transaction type on a particular lot, then I want to use the Max(TransactionDate) > 90 condition for the "receipt" transaction type.
I need to be able to evaluate all these conditions for each lot and it's particular transactions.
Below is the query I started to write, but then got stuck on how to structure the rest.
SELECT
LOTS.LOTNUMBER, TRANS.ITEMNUMBER, TRANS.DESCRIPTION,
TRANS.TRANSACTIONTYPE, TRANS.TRANSACTIONDATE, TRANS.WAREHOUSE,
TRANS.QUANTITY
FROM
LOTS
INNER JOIN
TRANS ON LOTS.LOTNUMBER = TRANS.LOTNUMBER
WHERE
LOTS.CURRENTQUANTITY > 0

SELECT
LOTS.LOTNUMBER, TRANS.ITEMNUMBER, TRANS.DESCRIPTION,
TRANS.TRANSACTIONTYPE, TRANS.TRANSACTIONDATE, TRANS.WAREHOUSE,
TRANS.QUANTITY
FROM
LOTS
INNER JOIN
(Select LotNumber, Max(TransactionDate) MaxTransDate From TRANS Group By LotNumber) Trans ON LOTS.LOTNUMBER = TRANS.LOTNUMBER
WHERE
LOTS.CURRENTQUANTITY > 0 And
DATEDIFF(d, TRANS.TRANSACTIONDATE, getdate()) > 90 And
(TRANS.TRANSACTIONTYPE = 'Reciept' Or
TRANS.TRANSACTIONTYPE = 'Shipment')

DECLARE #lots TABLE (
LotNumber NVARCHAR(10)
,ItemNum NVARCHAR(12)
,CurrentQty INT
)
DECLARE #trans TABLE (
TransNum INT
,ItemNum NVARCHAR(12)
,Description NVARCHAR(30)
,TransType NVARCHAR(10)
,TransDate DATE
,Warehouse NVARCHAR(5)
,Quantity INT
)
INSERT INTO #lots VALUES
('ABC','10-MAND-5879',925)
INSERT INTO #trans VALUES
(398741,'10-MAND-5879','10 Lb Mandarin Bag','Receipt','2016-01-01','IXCST',100)
,(973541,'10-MAND-5879','10 Lb Mandarin Bag','Shipment','2016-02-04','WTGS',365)
,(972547,'10-MAND-5879','10 Lb Mandarin Bag','Receipt','2016-02-29','GKWK',250)
,(632403,'10-MAND-5879','10 Lb Mandarin Bag','Shipment','2016-03-01','GWSJ',150)
,(692147,'10-MAND-5879','10 Lb Mandarin Bag','Shipment','2016-03-17','ABTK',25)
;WITH MaxShip
AS (
SELECT ItemNum, TransNum,
ROW_NUMBER () OVER (PARTITION BY ItemNum ORDER BY TransDate DESC) AS TransOrder
FROM #trans
WHERE TransType = 'Shipment'
),
MaxRcpt
AS (
SELECT ItemNum, TransNum,
ROW_NUMBER () OVER (PARTITION BY ItemNum ORDER BY TransDate DESC) AS TransOrder
FROM #trans
WHERE transtype = 'Receipt'
)
SELECT *
FROM #trans t
LEFT JOIN #lots l
ON t.ItemNum = l.ItemNum
JOIN MaxShip ms
ON ms.TransNum = t.TransNum
WHERE TransOrder = 1 AND CurrentQty > 0 AND DATEADD(dd,90,TransDate) < GETDATE()
UNION
SELECT *
FROM #trans t
LEFT JOIN #lots l
ON t.ItemNum = l.ItemNum
JOIN MaxRcpt mr
ON mr.TransNum = t.TransNum
WHERE TransOrder = 1 AND CurrentQty > 0 AND DATEADD(dd,90,TransDate) < GETDATE()

Related

SQL - Finding Gaps in Coverage

I am running this problem on SQL server
Here is my problem.
have something like this
Dataset A
FK_ID StartDate EndDate Type
1 10/1/2018 11/30/2018 M
1 12/1/2018 2/28/2019 N
1 3/1/2019 10/31/2019 M
I have a second data source I have no control over with data something like this:
Dataset B
FK_ID SpanStart SpanEnd Type
1 10/1/2018 10/15/2018 M
1 10/1/2018 10/25/2018 M
1 2/15/2019 4/30/2019 M
1 5/1/2019 10/31/2019 M
What I am trying to accomplish is to check to make sure every date within each TYPE M record in Dataset A has at least 1 record in Dataset B.
For example record 1 in Dataset A does NOT have coverage from 10/26/2018 through 11/30/2018. I really only care about when the coverage ends, in this case I want to return 10/26/2018 because it is the first date where the span has no coverage from Dataset B.
I've written a function that does this but it is pretty slow because it is cycling through each date within each M record and counting the number of records in Dataset B. It exits the loop when it finds the first one but I would really like to make this more efficient. I am sure I am not thinking about this properly so any suggestions anyone can offer would be helpful.
This is the section of code I'm currently running
else if #SpanType = 'M'
begin
set #CurrDate = #SpanStart
set #UncovDays = 0
while #CurrDate <= #SpanEnd
Begin
if (SELECT count(*)
FROM eligiblecoverage ec join eligibilityplan ep on ec.plandescription = ep.planname
WHERE ec.masterindividualid = #IndID
and ec.planbegindate <= #CurrDate and ec.planenddate >= #CurrDate
and ec.sourcecreateddate = #MaxDate
and ep.medicaidcoverage = 1) = 0
begin
SET #Result = concat('NON Starting ',format(#currdate, 'M/d/yyyy'))
BREAK
end
set #CurrDate = #CurrDate + 1
end
end
I am not married to having a function it just could not find a way to do this in queries that wasn't very very slow.
EDIT: Dataset B will never have any TYPEs except M so that is not a consideration
EDIT 2: The code offered by DonPablo does de-overlap the data but only in cases where there is an overlap at all. It reduces dataset B to:
FK_ID SpanStart SpanEnd Type
1 10/1/2018 10/25/2018 M
instead of
FK_ID SpanStart SpanEnd Type
1 10/1/2018 10/25/2018 M
1 2/15/2019 4/30/2019 M
1 5/1/2019 10/31/2019 M
I am still futzing around with it but it's a start.
I would approach this by focusing on B. My assumption is that any absent record would follow span_end in the table. So here is the idea:
Unpivot the dates in B (adding "1" to the end dates)
Add a flag if they are present with type "M".
Check to see if any not-present records are in the span for A.
Check the first and last dates as well.
So, this looks like:
with bdates as (
select v.dte,
(case when exists (select 1
from b b2
where v.dte between b2.spanstart and b2.spanend and
b2.type = 'M'
)
then 1 else 0
end) as in_b
from b cross apply
(values (spanstart), (dateadd(day, 1, spanend)
) v(dte)
where b.type = 'M' -- all we care about
group by v.dte -- no need for duplicates
)
select a.*,
(case when not exists (select 1
from b b2
where a.startdate between b2.spanstart and b2.spanend and
b2.type = 'M'
)
then 0
when not exists (select 1
from b b2
where a.enddate between b2.spanstart and b2.spanend and
b2.type = 'M'
)
when exists (select 1
from bdates bd
where bd.dte between a.startdate and a.enddate and
bd.in_b = 0
)
then 0
when exists (select 1
from b b2
where a.startdate between b2.spanstart and b2.spanend and
b2.type = 'M'
)
then 1
else 0
end)
from a;
What is this doing? Four validity checks:
Is the starttime valid?
Is the endtime valid?
Are any intermediate dates invalid?
Is there at least one valid record?
Start by framing the problem in smaller pieces, in a sequence of actions like I did in the comment.
See George Polya "How To Solve It" 1945
Then Google is your friend -- look at==> sql de-overlap date ranges into one record (over a million results)
UPDATED--I picked Merge overlapping dates in SQL Server
and updated it for our table and column names.
Also look at theory from 1983 Allen's Interval Algebra https://www.ics.uci.edu/~alspaugh/cls/shr/allen.html
Or from 2014 https://stewashton.wordpress.com/2014/03/11/sql-for-date-ranges-gaps-and-overlaps/
This is a primer on how to setup test data for this problem.
Finally determine what counts via Ranking the various pairs of A vs B --
bypass those totally Within, then work with earliest PartialOverlaps, lastly do the Precede/Follow items.
--from Merge overlapping dates in SQL Server
with SpanStarts as
(
select distinct FK_ID, SpanStart
from Coverage_B as t1
where not exists
(select * from Coverage_B as t2
where t2.FK_ID = t1.FK_ID
and t2.SpanStart < t1.SpanStart
and t2.SpanEnd >= t1.SpanStart)
),
SpanEnds as
(
select distinct FK_ID, SpanEnd
from Coverage_B as t1
where not exists
(select * from Coverage_B as t2
where t2.FK_ID = t1.FK_ID
and t2.SpanEnd > t1.SpanEnd
and t2.SpanStart <= t1.SpanEnd)
),
DeOverlapped_B as
(
Select FK_ID, SpanStart,
(select min(SpanEnd) from SpanEnds as e
where e.FK_ID = s.FK_ID
and SpanEnd >= SpanStart) as SpanEnd
from SpanStarts as s
)
Select * from DeOverlapped_B
Now we have something to feed into the next steps, and we can use the above as a CTE
======================================
with SpanStarts as
(
select distinct FK_ID, SpanStart
from Coverage_B as t1
where not exists
(select * from Coverage_B as t2
where t2.FK_ID = t1.FK_ID
and t2.SpanStart < t1.SpanStart
and t2.SpanEnd >= t1.SpanStart)
),
SpanEnds as
(
select distinct FK_ID, SpanEnd
from Coverage_B as t1
where not exists
(select * from Coverage_B as t2
where t2.FK_ID = t1.FK_ID
and t2.SpanEnd > t1.SpanEnd
and t2.SpanStart <= t1.SpanEnd)
),
DeOverlapped_B as
(
Select FK_ID, SpanStart,
(select min(SpanEnd) from SpanEnds as e
where e.FK_ID = s.FK_ID
and SpanEnd >= SpanStart) as SpanEnd
from SpanStarts as s
),
-- find A row's coverage
ACoverage as (
Select
a.*, b.SpanEnd, b.SpanStart,
Case
When SpanStart <= StartDate And StartDate <= SpanEnd
And SpanStart <= EndDate And EndDate <= SpanEnd
Then '1within' -- starts, equals, during, finishes
When EndDate < SpanStart
Or SpanEnd < StartDate
Then '3beforeAfter' -- preceeds, meets, preceeded, met
Else '2overlap' -- one or two ends hang over spanStart/End
End as relation
From Coverage_A a
Left Join DeOverlapped_B b
On a.FK_ID = b.FK_ID
Where a.Type = 'M'
)
Select
*
,Case
When relation1 = '2' And StartDate < SpanStart Then StartDate
When relation1 = '2' Then DateAdd(d, 1, SpanEnd)
When relation1 = '3' Then StartDate
End as UnCoveredBeginning
From (
Select
*
,SUBSTRING(relation,1,1) as relation1
,ROW_NUMBER() Over (Partition by A_ID Order by relation, SpanStart) as Rownum
from ACoverage
) aRNO
Where Rownum = 1
And relation1 <> '1'

SSRS:How to return count of events per day for Month

I have a table with the following information
ID,DateTime,EventType
1,6/5/2013 9:35:00,B
1,6/5/2013 9:35:24,A
2,6/5/2013 9:35:36,B
3,6/5/2013 9:36:11,D
2,6/5/2013 9:39:16,A
3,6/5/2013 9:40:48,B
4,7/5/2013 9:35:19,B
4,7/5/2013 9:35:33,A
5,7/5/2013 9:35:53,B
5,7/5/2013 9:36:06,D
6,7/5/2013 9:39:39,A
7,7/5/2013 9:40:28,B
8,8/5/2013 9:35:02,A
7,8/5/2013 9:35:08,A
8,8/5/2013 9:35:29,B
6,8/5/2013 9:36:39,B
I need to count how many times each day an event changed state as long as the time between states was less than 30 seconds over the time period.
Basically I am looking for the following result set
6/5/2013 | 1
7/5/2013 | 2
8/5/2013 | 1
I've tried several different types of queries, but nothing works. I am using SQL Server Reporting Services 2008.
declare #t table (ID int,[DateTime] datetime ,EventType varchar);
insert #t values
(1,'6/5/2013 9:35:00','B'),
(1,'6/5/2013 9:35:24','A'),
(2,'6/5/2013 9:35:36','B'),
(3,'6/5/2013 9:36:11','D'),
(2,'6/5/2013 9:39:16','A'),
(3,'6/5/2013 9:40:48','B'),
(4,'7/5/2013 9:35:19','B'),
(4,'7/5/2013 9:35:33','A'),
(5,'7/5/2013 9:35:53','B'),
(5,'7/5/2013 9:36:06','D'),
(6,'7/5/2013 9:39:39','A'),
(7,'7/5/2013 9:40:28','B'),
(8,'8/5/2013 9:35:02','A'),
(7,'8/5/2013 9:35:08','A'),
(8,'8/5/2013 9:35:29','B'),
(6,'8/5/2013 9:36:39','B');
--select * from #t order by ID, DateTime;
with cte as (
select *, cast([DateTime] as date) the_date, row_number() over (partition by ID order by DateTime) row_num
from #t
)
select c1.the_date, count(1)
from cte c1
join cte c2
on c2.ID = c1.ID
and c2.row_num = c1.row_num + 1
where datediff(S,c1.DateTime, c2.DateTime) < 30
group by c1.the_date
order by c1.the_date;
Try this:
select CONVERT(VARCHAR(10), a.DateTime, 103) [Date], count(a.ID) Count from Table a
inner join Table b on a.ID = b.ID
where DATEDIFF(second,a.DateTime,b.DateTime) between 1 and 29 and a.ID = b.ID
and Cast(a.DateTime as Date) = Cast(b.DateTime as date)
group by CONVERT(VARCHAR(10), a.DateTime, 103)

Get all parent rows that do not have a row for current date in child table?

SELECT
[dbo].[Mission].[MissionId]
FROM
[dbo].[Mission]
LEFT OUTER JOIN
[dbo].[Report] ON [dbo].[Mission].[MissionId] = [dbo].[Report].[MissionId]
WHERE
[dbo].[Report].ReportDate IS NULL
ORDER BY
[dbo].[Mission].[MissionId]
How can I change the above query such that it gives me all MissionId's from table [dbo].[Mission] that do not have a row in table [dbo].[Report] where [dbo].[Report].ReportDate is today?
MissionId is the primary key in table Mission and a foreign key in table Report. So I want to get all missions that do not have a row in table Report for the current date.
I've introduced some aliases to make the query easier to read, and added the needed condition. I've also changed the WHERE clause, not sure if that's required:
SELECT m.[MissionId]
FROM [dbo].[Mission] m LEFT OUTER JOIN [dbo].[Report] r
ON m.[MissionId] = r.[MissionId]
AND r.ReportDate = DATEADD(day,DATEDIFF(day,0,GETDATE()),0)
WHERE r.MissionId IS NULL
ORDER BY m.[MissionId]
This assumes that ReportDate contains dates with the time portions set to midnight. If that's not so, then a slightly more complex query is required:
SELECT m.[MissionId]
FROM [dbo].[Mission] m
WHERE NOT EXISTS(select * from dbo.Report r
where r.MissionID = m.MissionID and
r.ReportDate >= DATEADD(day,DATEDIFF(day,0,GETDATE()),0) and
r.ReportDate < DATEADD(day,DATEDIFF(day,0,GETDATE()),1)
)
ORDER BY m.[MissionId]
GETDATE() returns the current date and time. I'm using a couple of tricks with DATEADD and DATEDIFF to take that value and turn it into the current date at midnight, and (in the second query) tomorrow's date at midnight.
Second query as a fully runnable query:
declare #mission table (MissionID int not null);
insert into #mission (MissionID) select 1 union all select 2;
declare #report table (MissionID int not null,ReportDate datetime not null);
insert into #report (MissionID,ReportDate)
select 2,GETDATE() union all select 1,DATEADD(day,-1,GETDATE());
SELECT m.[MissionId]
FROM #mission m
WHERE NOT EXISTS(select * from #report r
where r.MissionID = m.MissionID and
r.ReportDate >= DATEADD(day,DATEDIFF(day,0,GETDATE()),0) and
r.ReportDate < DATEADD(day,DATEDIFF(day,0,GETDATE()),1)
)
ORDER BY m.[MissionId]
Result:
MissionId
-----------
1
select
m.MissionId
from Mission m
left join Report r
on m.MissionId = r.MissionId
and day(r.ReportDate) = day(getdate())
and month(r.ReportDate) = month(getdate())
and year(r.ReportDate) = year(getdate())
WHERE r.ReportDate is null
ORDER BY m.MissionId

SQL Server T-SQL: Get a records set based on a priority value, startdate and enddate

I am working on an time attendance system. I have to following tables:
Schedule: Contains a Name nvarchar field and a Start and End DateTime fields.
Policy: Contains Start and End DateTime fields too.
PolicySchedule (Cross Table): Contains a Priority int field beside the foreign keys.
End datetime fields are nullable which indicates open periods.
The schedule of the greatest priority will be applied and activated within its time and the policy time.
I need to get a list of the applied schedules within each policy and their activation periods start and end time knowing that shedules may be intersected. Policies are not related here ..
Example:
Schedule_____________Start_________________________End
Schedule1____________01/01/2011 00:00______________04/01/2011 00:00
Schedule2____________04/01/2011 00:00______________11/01/2011 14:00
Schedule3____________11/01/2011 14:00______________02/15/2012 00:00
Schedule2____________02/15/2012 00:00______________01/01/2013 00:00
What is the most efficient way to get the requested result ?
If you have lots of policies and schedules (separately) but few schedules per policy, the most straightforward way would be quite efficient:
WITH dates (policyId, changeDate) AS
(
SELECT policyId, changeDate,
ROW_NUMBER() OVER (PARTITION BY policyId ORDER BY changeDate) AS rn
FROM (
SELECT policyId, startDate AS changeDate
FROM policySchedule ps
JOIN schedule s
ON s.id = ps.scheduleId
UNION
SELECT policyId, endDate AS changeDate
FROM policySchedule ps
JOIN schedule s
ON s.id = ps.scheduleId
) q
),
ranges (startDate, endDate)AS
(
SELECT d1.policyId,
d1.changeDate,
d2.changeDate
FROM dates d1
JOIN dates d2
ON d2.policyId = d1.policyId
AND d2.rn = d1.rn + 1
)
SELECT *
FROM policy p
JOIN ranges r
ON r.policyId = p.id
CROSS APPLY
(
SELECT TOP 1 s.*
FROM policySchedules ps
JOIN schedule s
ON ps.policyId = p.id
AND s.id = ps.scheduleId
WHERE ps.startDate BETWEEN r.startDate AND r.endDate
AND ps.endDate BETWEEN r.startDate AND r.endDate
ORDER BY
ps.Priority DESC
)
Assuming you're using SQL Server 2005 or later, you can use an outer apply to look up the policy with the highest priority:
select *
from Schedule s
outer apply
(
select top 1 *
from PolicySchedule ps
join Policy p
on p.id = ps.policyid
where s.StartTime <= p.EndTime
and p.StartTime <= s.EndTime
order by
ps.priority desc
) pol
If there are time periods that have to overlap, you can add a where clause in the outer apply.

LASt 6 records of the output of the stored procedure

The following is the stored procedure and "I want to fetch the LATEST SIX
INVOICES FOR EACH CUSTOMER"
THERE COULD BE MORE INVOICES FOR EACH CUSTOMER BUT I HAVE TO FETCH ONLY
WHICH ARE LATEST 6 INVOICES.
ALTER PROCEDURE [dbo].[SCA_M_CUSTSOINV_REFRESH]
#COMP_CD NVARCHAR(20)='',
#USER_CD NVARCHAR(20)='',
#USER_TYPE NVARCHAR(1)=''
AS
SET NOCOUNT ON
DECLARE #SLSHIST_DATE NVARCHAR(10)
SELECT
#SLSHIST_DATE = CONVERT(NVARCHAR(10), DATEADD(MONTH,-SLSHIST_MTH,dbo.[GetCountryDate]()),120)
FROM SET_MASTER
WITH SUBQUERY AS
(SELECT
ROW_NUMBER() OVER (PARTITION BY A.CUST_CD ORDER BY C.INV_KEY DESC) "ROW_ID",
A.CUST_CD,C.SO_KEY "TXN_KEY",
C.INV_NO, C.INV_KEY, C.INV_DT, C.INV_STATUS, C.NET_TTL_TAX AS INV_AMT
FROM
(SELECT DIST_CD, SLSMAN_CD, CUST_CD FROM T_CA_SLSMANCUST
WHERE DIST_CD = #COMP_CD AND SLSMAN_CD = #USER_CD) A
INNER JOIN TXN_INVOICE C ON C.CUST_CD=A.CUST_CD
AND C.INV_DT >= #SLSHIST_DATE
)
SELECT
CUST_CD, TXN_KEY, INV_NO, INV_KEY, INV_DT, INV_STATUS, INV_AMT,
CASE ROW_ID WHEN 1 THEN 'Y' ELSE 'N' END "LAST_INV"
FROM SUBQUERY
ORDER BY CUST_CD,INV_KEY
You are getting ROW_ID by ROW_NUMBER() OVER (PARTITION BY A.CUST_CD ORDER BY C.INV_KEY DESC) and the last invoice is the one with ROW_ID = 1 so don't you just need to add WHERE ROW_ID <= 6 as below?
SELECT
CUST_CD, TXN_KEY, INV_NO, INV_KEY, INV_DT, INV_STATUS, INV_AMT,
CASE ROW_ID WHEN 1 THEN 'Y' ELSE 'N' END "LAST_INV"
FROM SUBQUERY
WHERE ROW_ID <= 6
ORDER BY CUST_CD,INV_KEY
SELECT TOP 6 * FROM invoices ORDER BY date DESC

Resources