Finding the next 3 occurences after an event is completed - SQL Server - sql-server

Need some help with this query please. We have events (drives) that take place at accounts (places) and once they happened, they are marked complete (status id = 2) in the database.
For this query, I'd like to be able to search for a particular account, find the last time an event was completed at this account, and then find the next three confirmed (status id = 1) events that will occur at this account.
I'm not exactly sure how to handle this. I'm guessing some sort of sub-query but I am having some trouble starting.
What I have so far is:
select
dm.driveid, dm.fromdatetime, dm.statusid, stat.statustext, acct.internalname
from
rpt_drivemaster dm
inner join
rpt_accounts acct on dm.accountid = acct.accountid
inner join
rpt_drivestatusdef stat on dm.statusid = stat.statusid
where
acct.accountid = '17708'
and dm.fromdatetime >= dateadd(month, -1, getdate())
and dm.statusid not in (3,5,6)
order by
dm.fromdatetime;
This will give me all drives for this account going back a month and looking forward. But what I would like it to actually do is only give me the next three drives (1/28/15, 2/4/15, 2/18/15).
What would be the best way to accomplish this?

Try this:
with completes as (
select dm.driveid, dm.fromdatetime, dm.statusid, stat.statustext, acct.internalname
from rpt_drivemaster dm
inner join rpt_accounts acct on dm.accountid=acct.accountid
inner join rpt_drivestatusdef stat on dm.statusid=stat.statusid
where acct.accountid='17708'
and dm.fromdatetime >= dateadd(month,-1,getdate())
and dm.statusid = 2
),
confirmed as (
select top(3) dm.driveid, dm.fromdatetime, dm.statusid, stat.statustext, acct.internalname
from rpt_drivemaster dm
inner join rpt_accounts acct on dm.accountid=acct.accountid
inner join rpt_drivestatusdef stat on dm.statusid=stat.statusid
inner join completes as c
on c.accountid = dm.accountid
and dm.fromdatetime > c.fromdatetime
where dm.statusid = 1
order by dm.fromdatetime
)
select * from completed
union
select * from confirmed
order by fromdatetime;

Related

How to get youngest matching record in SQL?

i am trying to create a table that tells me how long a product has aged until the date of the purchase. The code itself works, but I only want the youngest record to appear.
So far it matches the Order created with every historical receipt date of the product. However, I only want the youngest receipt date that is before Order created.
SELECT
OPF.Order_Number,
PD.[Product_Id],
PD.[Product_Title],
OPF.Order_Created,
MAX(R.[receipt_date]),
DATEDIFF(day, R.[receipt_date], OPF.Order_Created) AS 'Days Aged'
FROM [DataWarehouse].[dbo].[Product_D] PD (NOLOCK)
JOIN [DataWarehouse].[dbo].[Order_Product_F] OPF (NOLOCK)
ON PD.Product_Key = OPF.Product_Key
LEFT JOIN [Supply_Chain].[dbo].[Receipt_Product] RP (NOLOCK)
on PD.Product_Id = RP.product_id
JOIN [Supply_Chain].[dbo].[Receipt] R (NOLOCK)
ON RP.receipt_id = R.receipt_id
WHERE
OPF.Order_Number IN ('xxxxxxxx')
AND
R.[receipt_date] < OPF.Order_Created
GROUP BY
OPF.Order_Number,
PD.[Product_Id],
PD.[Product_Title],
OPF.Order_Created,
R.[receipt_date]
ORDER BY
DATEDIFF(day, R.[receipt_date], OPF.Order_Created) ASC
I think I will have to do a sub query, but I am not quite sure where to start.

Extract Specific Data After a aggregation (Or any other solution for the desired result)

I want to select the Total "sales" of a specific "main_category" for the year 2016
(main categories that don't have sales in that year should appear as zero)
I have managed to select the "sales" of a specific "main category" with all the other "main_categories" (that doesn't have any sales) appearing as zero using below query:
SELECT
mc.name,
ISNULL(SUM(s.no_of_units * b.unit_price),0) AS tCatSales
FROM Sales s
INNER JOIN Invoice i ON i.invoice_ID = s.invoice_id
INNER JOIN Inventory inv ON inv.inventory_ID = s.inventory_ID
INNER JOIN Batch b ON b.batch_ID = inv.batch_ID
INNER JOIN Products p ON p.product_id = b.product_ID
INNER JOIN Category c ON c.category_ID = p.category_id
RIGHT JOIN Main_Category mc ON mc.cat_id = c.main_category
--WHERE YEAR(i.trans_date) = 2016
GROUP BY mc.name
--HAVING YEAR(i.trans_date)=2016
but when I try to further segregate it for year 2016 ONLY either by WHERE clause or HAVING clause, it stops showing "main_category" names that have zero sales in the year.
One thing that I can think of is to give the query invoices only from 2016
which I tried to did by doing something like,
Replacing the line:
INNER JOIN Invoice i ON i.invoice_ID = s.invoice_id
with:
INNER JOIN Invoice i ON i.invoice_ID IN (SELECT invoice_id FROM Invoice in2 WHERE Year(in2.trans_date)=2016)
which did display the categories with zero values but with increased the calculated Sales Amount (from 2069 to something 203151022.75).
I understand this addition is somewhat illogical and disrupts the whole Inner Joins but so far these are the closest thing I can think of or find on the web.
I REPEAT the desired result is: main categories that don't have sales in that year should appear as zero with the year given year/month/date
As Sean and Eli mentioned, RIGHT JOIN is not recommended, you may change it to LEFT JOIN, OR use subquery like this:
SELECT
mc.name,
tCatSales = ISNULL(
(
SELECT
SUM(s.no_of_units * b.unit_price) AS tCatSales
FROM Sales s
INNER JOIN Invoice i ON i.invoice_ID = s.invoice_id
INNER JOIN Inventory inv ON inv.inventory_ID = s.inventory_ID
INNER JOIN Batch b ON b.batch_ID = inv.batch_ID
INNER JOIN Products p ON p.product_id = b.product_ID
INNER JOIN Category c ON c.category_ID = p.category_id
WHERE mc.cat_id = c.main_category
AND YEAR(i.trans_date) = 2016
) , 0)
FROM Main_Category mc
try this:
WHERE ISNULL(YEAR(i.trans_date), 1) = 2016
if you put simple equals conditions on outer join it will eliminate nulls, which give zero-valued rows you desire.
Also note that something like:
WHERE YEAR(i.trans_date) = 2016
is not sargable, see here

Find the Record - Then Average the "X" Number of Previous Occurrences

What I hope to accomplish here to to create T-SQL that will find a particular record and display the appropriate projections for that particular record. And also display the average of the three previous times the record occurred.
In the query below, the event was to occur on 10/7/2015 and had two projections of 23 and 23.
SELECT
CONVERT(DATE, DM.FromDateTime) AS 'DriveDate',
Acct.InternalName,
DM.LocationID,
DPaCT.ProcedureProjection,
DPaCT.ProductProjection
FROM
dbo.DriveMaster DM
INNER JOIN dbo.Accounts Acct ON DM.AccountID = Acct.AccountID
INNER JOIN dbo.DriveProjectionAndCollectedTotals DPaCT ON DM.DriveID = DPaCT.DriveID
INNER JOIN dbo.DriveStatusDef Stat ON DM.StatusID = Stat.StatusID
WHERE
Acct.AccountID = 17708
AND DM.FromDateTime = '2015-10-07'
Here are the results - which are as expected and appear correct:
In the second query, I am displaying the three previous times the same event occurred at this location and was marked in a complete status.
SELECT TOP 3
CONVERT(DATE, DM.FromDateTime) AS 'DriveDate',
Acct.InternalName,
DM.LocationID,
DPaCT.ProcedureProjection,
DPaCT.ProductProjection
FROM
Hemasphere.dbo.DriveMaster DM
INNER JOIN dbo.Accounts Acct ON DM.AccountID = Acct.AccountID
INNER JOIN dbo.DriveProjectionAndCollectedTotals DPaCT ON DM.DriveID = DPaCT.DriveID
INNER JOIN dbo.DriveStatusDef Stat ON DM.StatusID = Stat.StatusID
WHERE
Acct.AccountID = 17708
AND DM.FromDateTime < '2015-10-07'
AND DM.StatusID = 2
ORDER BY
DM.FromDateTime DESC;
This is not what I am looking for. Instead of three separate drives, I'd like to have it displayed on in line with the average of the past three drives.
If possible, what I would like to happen have this occur within one statement so that is displays the "upcoming" drive that was to occur on 10/7/2015 and then in calculated columns - average the three previous projections from the event location.
Hopefully this makes sense.
If I'm understanding you correctly, how about making your second query as a subquery in your first, and averaging its results there. Something like this:
-- This is your original query
SELECT
CONVERT(DATE, DM.FromDateTime) AS 'DriveDate',
Acct.InternalName,
DM.LocationID,
DPaCT.ProcedureProjection,
DPaCT.ProductProjection,
--This is the sub-query to get the prior projection average
[PriorProcedureProjectionAverage] = (SELECT AVG(ProcedureProjection) FROM (
SELECT TOP 3 DPaCT2.ProcedureProjection
FROM
Hemasphere.dbo.DriveMaster DM2
INNER JOIN dbo.Accounts Acct2 ON DM2.AccountID = Acct2.AccountID
INNER JOIN dbo.DriveProjectionAndCollectedTotals DPaCT2 ON DM2.DriveID = DPaCT2.DriveID
INNER JOIN dbo.DriveStatusDef Stat22 ON DM2.StatusID = Stat2.StatusID
WHERE
Acct2.AccountID = 17708
AND DM2.FromDateTime < '2015-10-07'
AND DM2.StatusID = 2
ORDER BY
DM2.FromDateTime DESC;
) As SUB)
FROM
dbo.DriveMaster DM
INNER JOIN dbo.Accounts Acct ON DM.AccountID = Acct.AccountID
INNER JOIN dbo.DriveProjectionAndCollectedTotals DPaCT ON DM.DriveID = DPaCT.DriveID
INNER JOIN dbo.DriveStatusDef Stat ON DM.StatusID = Stat.StatusID
WHERE
Acct.AccountID = 17708
AND DM.FromDateTime = '2015-10-07'
That's a little messy, and you'd have to add a second subquery for ProductProjection if you want that too. But hopefully that gives you an idea.

Identify Inter-Account Transfers in SQL

I have a bunch of bank transactions in a table in SQL.
Example: http://sqlfiddle.com/#!6/6b2c8/1/0
I need to identify the transactions that are made between these 2 linked accounts. The Accounts table (not shown) links these 2 accounts to the one source (user).
For example:
I have an everyday account, and a savings account. From time to time, I may transfer money from my everyday account, to my savings account (or vice-versa).
The transaction descriptions are usually similar (Transfer to xxx/transfer from xxx), usually on the same day, and obviously, the same dollar amount.
EDIT: I now have the following query (dumbed down), which works for some scenarios
Basically, I created 2 temp tables with all withdrawals and deposits that met certain criteria. I then join them together, based on a few requirements (same transaction amount, different account # etc). Then using the ROW_NUMBER function, I have ordered which ones are more likely to be inter-account transactions.
I now have an issue where if, for example:
$100 transferred from Account A to Account B
$100 Transferred from Account B to Account C
My query will match the transfer between Account A and C, then there is only one transaction for account B, and it will not be matched. So essentially, instead of receiving 2 rows back (2 deposits, lined up with 2 withdrawals), I only get 1 row (1 deposit, 1 withdrawal), for a transfer from A to B :(
INSERT INTO #Deposits
SELECT t.*
FROM dbo.Customer c
INNER JOIN dbo.Source src ON src.AppID = app.AppID
INNER JOIN dbo.Account acc ON acc.SourceID = src.SourceID
INNER JOIN dbo.Tran t ON t.AccountID = acc.AccountID
WHERE c.CustomerID = 123
AND t.Template = 'DEPOSIT'
INSERT INTO #Withdrawals
SELECT t.*
FROM dbo.Customer c
INNER JOIN dbo.Source src ON src.AppID = app.AppID
INNER JOIN dbo.Account acc ON acc.SourceID = src.SourceID
INNER JOIN dbo.Tran t ON t.AccountID = acc.AccountID
WHERE c.CustomerID = 123
AND t.Template = 'WITHDRAWAL'
;WITH cte
AS ( SELECT [...] ,
ROW_NUMBER() OVER ( PARTITION BY d.TranID ORDER BY SUM( CASE WHEN d.TranDate = d.TranDate THEN 2 ELSE 1 END), w.TranID ) AS DepRN,
ROW_NUMBER() OVER ( PARTITION BY w.TranID ORDER BY SUM( CASE WHEN d.TranDate = d.TranDate THEN 2 ELSE 1 END ), d.TranID ) AS WdlRN
FROM #Withdrawal w
INNER JOIN d ON w.TranAmount = d.TranAmount -- Same transaction amount
AND w.AccountID <> d.AccountID -- Different accounts, same customer
AND w.TranDate BETWEEN d.TranDate AND DATEADD(DAY, 3, d.TranDate) -- Same day, or within 3 days
GROUP BY [...]
)
SELECT *
FROM cte
WHERE cte.DepRN = cte.WdlRN
Maybe this is a start? I don't think we have enough info to say whether this would be reliable or would cause a lot of "false positives".
select t1.TransactionID, t2.TransactionID
from dbo.Transactions as t1 inner join dbo.Transactions as t2
on t2.AccountID = t2.AccountID
and t2.TransactionDate = t1.TransactionDate
and t2.TransactionAmount = t1.TransactionAmount
and t2.TransactionID - t1.TransactionID between 1 and 20 -- maybe??
and t1.TransactionDesc like 'Transfer from%'
and t2.TransactionDesc like 'Transfer to%'
and t2.TransactionID > t1.TransactionID

Optimising this query. Relevant for DBA's working on a social network/community type website

I suppose this is quite a common SP present in socialnetworks and community type websites.
I have this SP that returns all of a user's friends on their 'friends' page order by those currently online, then alphabetically. It's taking quite a while to load and I am looking to speed it up.
I remember reading somewhere on SO that breaking up multiple joins into smaller result sets might speed it up. I haven't tried this yet but I am curious to see what other recommendations SO might have on this procedure.
DECLARE #userID INT -- This variable is parsed in
DECLARE #lastActivityMinutes INT
SET #lastActivitytMinutes = '15'
SELECT
Active = CASE WHEN DATEDIFF("n", b.LastActivityDate ,GETDATE()) < #lastActivityMinutes THEN 1 ELSE 0 END,
a.DisplayName, a.ImageFile, a.UserId, b.LastActivityDate
FROM
Profile AS a
INNER JOIN aspnet_Users as b on b.userId = a.UserId
LEFT JOIN Friend AS x ON x.UserID = a.UserID
LEFT JOIN Friend AS z ON z.FriendID = a.UserID
WHERE ((x.FriendId = #userID AND x.status = 1) -- Status = 1 means friendship accepted
OR (z.UserID = #userID AND z.Status = 1))
GROUP BY a.userID, a.DisplayName, a.ImageFile, a.UserId, b.LastActivityDate
ORDER BY Active DESC, DisplayName ASC
I am not sure how to clip in my execution plan but the main bottle neck seems to be occurring on a MERGE JOIN (Right Outer Join) that's costing me 29%. At various stages, Parallelism is also costing 9%, 6%, 5% and 9% for a total of 29% as well.
My initial thoughts are to first return the JOINED results from the Profile and aspnet tables with a CTE and then do LEFT JOINS to the Friends table.
You are joining Friend twice, using a LEFT JOIN, then you are removing the NULL's returned by the LEFT JOIN by WHERE condition, then using GROUP BY to get rid on distincts.
This is not the best query possible.
Why don't you just use this:
SELECT Active = CASE WHEN DATEDIFF("n", b.LastActivityDate ,GETDATE()) < #lastActivityMinutes THEN 1 ELSE 0 END,
a.DisplayName, a.ImageFile, a.UserId, b.LastActivityDate
FROM (
SELECT FriendID
FROM Friends
WHERE UserID = #UserId
AND status = 1
UNION
SELECT UserID
FROM Friends
WHERE FriendID = #UserId
AND status = 1
) x
INNER JOIN
Profile AS a
ON a.UserID = x.FriendID
INNER JOIN
aspnet_Users as b
ON b.userId = a.UserId
ORDER BY
Active DESC, DisplayName ASC

Resources