SQL Show All Rows - sql

I am getting 331 result rows when I want to get all 595 from the inner most query. The reason it eliminates 264 rows (595-331=264) is that those 264 rows do not meet all of the crieria in STEP #2. The 331 rows that do pass the criteria get a '>>>' in the OK column. So, I want to show the most recent date for the 331 rows, plus I want to show the 'cid' and NULL values for the other 264 that do not pass the criteria in STEP #2.
As a C# programmer, I can think of many ways to do this. But, what is the best way to do this in SQL?
/* STEP #4: ORDER RESULTS*/ /* SEE LINE 43 FOR ALL EVENTS */
SELECT cid
, edate, OK
, (SELECT CASE WHEN OK = '>>>'
THEN DATEDIFF(day, edate, ChartResp.TxPlanDueDate(t2.cid))
ELSE NULL
END
) AS 'DaysBtwnDueDateAndLDOSPrimClin'
, eser, eatt, erecip, Age, ccm, estaff
FROM (
/* STEP #3: SELECT MOST RECENT EVENT FROM STEP 2 FOR EACH CLIENT*/
SELECT *
FROM (
/* STEP #2: SELECT EVENTS THAT PASS FILTER CRITERIA FOR THOSE CLIENTS*/
SELECT --cid, edate, eser, eatt, erecip, DATEDIFF (Year, cbd, GetDate()) AS 'Age', ccm, estaff,
(SELECT CASE WHEN
(eatt IN (1,2)
AND edate > DATEADD(month, -6, getdate())
AND eser NOT IN (100,115,142)
AND erecip NOT IN ('2','7')
AND (( (erecip = '3') AND (DATEDIFF(Year, cbd, GetDate())<10) ) OR (erecip <> '3') )
AND ccm = estaff)
THEN '>>>'
ELSE ''
END
) AS 'OK'
,cid, edate, eser, eatt, erecip, DATEDIFF(Year, cbd, GetDate()) AS 'Age', ccm, estaff
,rownumber() OVER (PARTITION BY cid ORDER BY edate DESC) rn
FROM events INNER JOIN client ON ecaseno = cid
LEFT OUTER JOIN doc ON doc.docdbid = client.cid
WHERE client.cid IN (
/* STEP #1: SELECT CLIENTS THAT ARE IN ORIGINAL OVERDUE TX PLAN REPORT */ SELECT client.cid
FROM client LEFT OUTER JOIN admission ON client.cid = admission.cid
WHERE ((client.ctype = 'AC') AND (admission.alapdt IS NULL))
GROUP BY client.cid
HAVING ((ChartResp.TxPlanDueDate(client.cid) < DATEADD(day, - 1, GETDATE()))
AND (dbo.FFT(client.cid) IS NULL)
AND (dbo.IsHousingOnly(client.cid) IS NULL)
AND (DATEDIFF(day, ChartResp.TxPlanDueDate(client.cid),DATEADD(day, - 1, GETDATE())) > 0))
/* STEP #1 END */
)
AND eser BETWEEN 11 AND 1000
AND ccm = estaff
AND eatt IN (1,2)
AND edate > DATEADD(month, -6, getdate())
AND eser NOT IN (100,115,142)
AND erecip NOT IN ('2','7')
AND (( (erecip = '3') AND (DATEDIFF(Year, cbd, GetDate())<10) ) OR (erecip <> '3') )
GROUP BY cid, edate, eser, eatt, erecip, cbd, ccm, estaff
/* STEP #2 END */
) t1
WHERE rn = 1-- COMMENT THIS OUT TO SEE ALL EVENTS
/* STEP #3 END */
) t2
ORDER BY cid, edate DESC

You can also use a CTE to prepare your required data set and then use a query to join back to your CTE to get the required outpu.

Related

SQL Server : remove response with almost same date

I have a query in SQL Server that looks like this.
SELECT
[ActionId], [CreationDate], [Description]
FROM
[Action]
INNER JOIN
People ON Action.personid = People.personid
WHERE
datediff(mm, Action.creationdate, getdate()) = 1
AND People.typeofpersonid = 8
But now I would like to remove any responses that have a creationtime within a minute of another one.
So if response is currently
ActionId CreationDate Description
---------------------------------------------------------
510467 2015-04-07 11:21:02.030 Registered errand.
510468 2015-04-07 11:21:25.840 Email sent to:....
510477 2015-04-07 11:50:22.830 Registered errand.
I would like for the second row to not be returned.
Is there a smart way to do this?
This should work:
SELECT
Action.ActionId,
Action.CreationDate,
Action.Description
FROM
Action
JOIN
(
SELECT
ActionId,
ROW_NUMBER() OVER (
PARTITION BY LEFT(CONVERT(VARCHAR, CreationDate, 120), 16)
ORDER BY CreationDate, ActionId
) AS row_num
FROM
Action
WHERE
DATEDIFF(mm, CreationDate, GETDATE()) = 1
) AS a2 ON (Action.ActionId = a2.ActionId AND a2.row_num = 1)
JOIN
People ON (Action.PersonDd = People.PersonId)
WHERE
DATEDIFF(mm, Action.CreationDate, GETDATE()) = 1
AND People.TypeOfPersonId = 8
You need something like this:
SELECT [ActionId], [CreationDate], [Description]
FROM [Action]
inner join People on Action.personid = People.personid
where datediff(mm, Action.creationdate, getdate()) = 1
and People.typeofpersonid=8
AND NOT EXISTS
(
SELECT TOP 1 1
FROM [Action] Ain
inner join People Pin on Ain.personid = Pin.personid
where datediff(mm, Ain.creationdate, getdate()) = 1
and Pin.typeofpersonid=8
AND datediff(mi, Ain.creationdate, Action.creationdate)<1
AND Ain.ActionId <>Action.ActionId
)
The general idea is that you check for existing rows, and if there is one, then you dont include it you query.

how to include dates where no value

I have a query like so -
select CAST(jobrun_proddt as Date) as 'Date', COUNT(*) as 'Error Occurred Jobs' from jobrun
where jobrun_orgstatus = 66 and jobmst_type <> 1
group by jobrun_proddt
order by jobrun_proddt desc
Not every date will have a count. What I want to be able to do is the dates that are blank to have a count of 0 so the chart would look like this -
2014-11-18 1
2014-11-17 0
2014-11-16 0
2014-11-15 0
2014-11-14 0
2014-11-13 1
2014-11-12 0
2014-11-11 1
Currently it's not returning the lines where there's no count.
2014-11-18 1
2014-11-13 1
2014-11-11 1
edit to add that the jobrun table DOES have all the dates, just some dates don't have the value I'm searching for.
If you have data for all dates, but the other dates are being filtered by the where clause, then you can use conditional aggregation:
select CAST(jobrun_proddt as Date) as [Date],
SUM(CASE WHEN jobrun_orgstatus = 66 and jobmst_type <> 1 THEN 1 ELSE 0
END) as [Error Occurred Jobs]
from jobrun
group by jobrun_proddt
order by jobrun_proddt desc
Try this. Use Recursive CTE to generate the Dates.
WITH cte
AS (SELECT CONVERT(DATE, '2014-11-18') AS dates --Max date
UNION ALL
SELECT Dateadd(dd, -1, dates)
FROM cte
WHERE dates > '2014-11-11') -- Min date
SELECT a.dates,
Isnull([Error_Occurred_Jobs], 0)
FROM cte a
LEFT JOIN (SELECT Cast(jobrun_proddt AS DATE) AS Dates,
Count(*) AS [Error_Occurred_Jobs]
FROM jobrun
WHERE jobrun_orgstatus = 66
AND jobmst_type <> 1
GROUP BY jobrun_proddt) B
ON a.dates = b.dates
Order by a.dates desc
You'll have to join to a table or list of generated sequential dates using OUTER JOIN so that for the dates with no matches to your jobrun_orgstatus you can use ISNULL or COALESCE to return 0.
firstly, you must specify a particular date range
then you should connect your table with "left join"
DECLARE #start DATE, #end DATE;
SELECT #start = '20141114', #end = '20141217';
;WITH n AS
(
SELECT TOP (DATEDIFF(DAY, #start, #end) + 1)
n = ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects
)
SELECT 'Bob', DATEADD(DAY, n-1, #start) as 'xdate' FROM n;
select CAST(jobrun_proddt as Date) as 'Date', COUNT(*) as 'Error Occurred Jobs' from jobrun
left join n on DATEADD(DAY, n-1, #start) = jobrun.jobrun_proddt
where jobrun_orgstatus = 66 and jobmst_type <> 1
group by jobrun_proddt
order by jobrun_proddt desc

Adding multiple results form multiple subqueries

I am running a SQL Server query to get the sum of the transaction amount and the pending amount and the extra amount. I found that the best solution to do that is using subqueries, hence I am using subqueries for the same, which is the fastest way. However, although I can extract the amount from each subquery, I can't figure out how to add them together so I can sort by the total number.
Here's the current query:
SELECT
*, trans + pend + extra AS totalamount
FROM
(SELECT
(SELECT trnammt
FROM Tbl_Emi
WHERE Status IN ('N', 'P')
AND lastrecdate = (CAST(GETDATE() AS DATE))
AND CardNo = CardNo) AS TRANS,
(SELECT pendamt
FROM Tbl_Emi
WHERE Status IN ('N', 'P')
AND lastrecdate = (CAST(GETDATE() AS DATE))
AND CardNo = CardNo) AS PEND,
(SELECT extraamt
FROM Tbl_Emi
WHERE Status IN ('N', 'P')
AND lastrecdate = (SELECT MIN(lastrecdate) FROM Tbl_Emi)
AND CardNo = CardNo) AS EXTRA
FROM
Tbl_Emi) q
I need to add together trans and pend and extra to get 'totalamount'. SQL Server won't allow you to use simple syntax to do calculations on aliases, but I presume there's another way to do this?
Here are three problems I can readily see:
The subqueries are not aggregated, so if they return multiple rows, you will get an error.
The correlation conditions only refer to the subquery (so they are not really correlated).
The results could be NULL, so the sum would be NULL.
This version fixes these three problems:
SELECT e.*,
(COALESCE(trans, 0) + COALESCE(pend, 0) + COALESCE(extra, 0)) AS totalamount
FROM (
SELECT (
SELECT SUM(trnammt)
FROM Tbl_Emi e2
WHERE Status IN ('N','P')
AND lastrecdate = (CAST(GETDATE() AS DATE))
AND e2.CardNo = e.CardNo
) AS trans,
(
SELECT sum(pendamt)
FROM Tbl_Emi e2
WHERE Status IN ('N','P')
AND lastrecdate = (CAST(GETDATE() AS DATE))
AND e2.CardNo = e.CardNo
) AS pend,
(
SELECT sum(extraamt)
FROM Tbl_Emi e2
WHERE Status IN ('N','P')
AND lastrecdate = (SELECT MIN(lastrecdate) FROM Tbl_Emi)
AND e2.CardNo = e.CardNo
) AS extra
FROM Tbl_Emi e
) e
If you just want sum of trans + pend + extra you need to change the criteria part
SELECT CASE WHEN (lastrecdate = (CAST(GETDATE() AS DATE))
AND CardNo = CardNo) THEN trnammt
ELSE 0
END +
CASE WHEN (pendamt = (CAST(GETDATE() AS DATE))
AND CardNo = CardNo) THEN trnammt
ELSE 0
END +
CASE WHEN (lastrecdate = (SELECT MIN(lastrecdate) FROM Tbl_Emi)
AND CardNo = CardNo) THEN trnammt
ELSE 0
END AS totalamount
WHERE Status IN ('N','P')

How to determine if two records are 1 year apart (using a timestamp)

I need to analyze some weblogs and determine if a user has visited once, taken a year break, and visited again. I want to add a flag to every row (Y/N) with a VisitId that meets the above criteria.
How would I go about creating this sql?
Here are the fields I have, that I think need to be used (by analyzing the timestamp of the first page of each visit):
VisitID - each visit has a unique Id (ie. 12356, 12345, 16459)
UserID - each user has one Id (ie. steve = 1, ted = 2, mark = 12345, etc...)
TimeStamp - looks like this: 2010-01-01 00:32:30.000
select VisitID, UserID, TimeStamp from page_view_t where pageNum = 1;
thanks - any help would be greatly appreciated.
You could rank every user's rows, then join the ranked row set to itself to compare adjacent rows:
;
WITH ranked AS (
SELECT
*,
rnk = ROW_NUMBER() OVER (PARTITION BY UserID ORDER BY TimeStamp)
FROM page_view_t
),
flagged AS (
SELECT
*,
IsReturnVisit = CASE
WHEN EXISTS (
SELECT *
FROM ranked
WHERE UserID = r.UserID
AND rnk = r.rnk - 1
AND TimeStamp <= DATEADD(YEAR, -1, r.TimeStamp)
)
THEN 'Y'
ELSE 'N'
END
FROM ranked r
)
SELECT
VisitID,
UserID,
TimeStamp,
IsReturnVisit
FROM flagged
Note: the above flags only return visits.
UPDATE
To flag the first visits same as return visits, the flagged CTE could be modified as follows:
…
SELECT
*,
IsFirstOrReturnVisit = CASE
WHEN p.UserID IS NULL OR r.TimeStamp >= DATEADD(YEAR, 1, p.TimeStamp)
THEN 'Y'
ELSE 'N'
END
FROM ranked r
LEFT JOIN ranked p ON r.UserID = p.UserID AND r.rnk = p.rnk + 1
…
References that might be useful:
WITH common_table_expression (Transact-SQL)
Ranking Functions (Transact-SQL)
ROW_NUMBER (Transact-SQL)
The other guy was faster but since I took time to do it and it's a completely different approach I might as well post It :D.
SELECT pv2.VisitID,
pv2.UserID,
pv2.TimeStamp,
CASE WHEN pv1.VisitID IS NOT NULL
AND pv3.VisitID IS NULL
THEN 'YES' ELSE 'NO' END AS IsReturnVisit
FROM page_view_t pv2
LEFT JOIN page_view_t pv1 ON pv1.UserID = pv2.UserID
AND pv1.VisitID <> pv2.VisitID
AND (pv1.TimeStamp <= DATEADD(YEAR, -1, pv2.TimeStamp)
OR pv2.TimeStamp <= DATEADD(YEAR, -1, pv1.TimeStamp))
AND pv1.pageNum = 1
LEFT JOIN page_view_t pv3 ON pv1.UserID = pv3.UserID
AND (pv3.TimeStamp BETWEEN pv1.TimeStamp AND pv2.TimeStamp
OR pv3.TimeStamp BETWEEN pv2.TimeStamp AND pv1.TimeStamp)
AND pv3.pageNum = 1
WHERE pv2.pageNum = 1
Assuming page_view_t table stores UserID and TimeStamp details of each visit of the user, the following query will return users who have visited taking a break of at least an year (365 days) between two consecutive visits.
select t1.UserID
from page_view_t t1
where (
select datediff(day, max(t2.[TimeStamp]), t1.[TimeStamp])
from page_view_t t2
where t2.UserID = t1.UserID and t2.[TimeStamp] < t1.[TimeStamp]
group by t2.UserID
) >= 365

Tough T-SQL To Left Join?

I've got a table of ExchangeRates that have a countryid and an exchangeratedate something to this effect:
ExchangeRateID Country ToUSD ExchangeRateDate
1 Euro .7400 2/14/2011
2 JAP 80.1900 2/14/2011
3 Euro .7700 7/20/2011
Notice there can be the same country with a different rate based on the date...so for instance above Euro was .7400 on 2/14/2011 and now is .7700 7/20/2011.
I have another table of line items to list items based on the country..in this table each line item has a date associated with it. The line item date should use the corresponding date and country based on the exchange rate. So using the above data if I had a line item with country Euro on 2/16/2011 it should use the euro value for 2/14/2011 and not the value for 7/20/2011 because of the date (condition er.ExchangeRateDate <= erli.LineItemDate). This would work if I only had one item in the table, but imagine I had a line item date of 8/1/2011 then that condition (er.ExchangeRateDate <= erliLineItemDate) would return multiple rows hence my query would fail...
SELECT
er.ExchangeRateID,
er.CountryID AS Expr1,
er.ExchangeRateDate,
er.ToUSD,
erli.ExpenseReportLineItemID,
erli.ExpenseReportID,
erli.LineItemDate
FROM
dbo.ExpenseReportLineItem AS erli
LEFT JOIN
dbo.ExchangeRate AS er
ON er.CountryID = erli.CountryID
AND DATEADD(d, DATEDIFF(d, 0, er.ExchangeRateDate), 0) <= DATEADD(d, DATEDIFF(d, 0,
erli.LineItemDate), 0)
WHERE (erli.ExpenseReportID = 196)
The issue with this left join...is because the dates are <= the line item date so it returns many records, I would have to somehow do this but dont know how.
The LineItem tables has multiple records and each record could have its own CountryID:
Item Country ParentID LineItemDate
Line Item 1 Euro 1 2/14/2011
Line Item 2 US 1 2/14/2011
Line Item3 Euro 1 2/15/2011
So there are three records for ParentID (ExpenseReportID) = 1. So then I take those records and join the ExchangeRate table where the Country in my line item table = the country of the exchange rate table (that part is easy) BUT the second condition I have to do is the:
AND DATEADD(d, DATEDIFF(d, 0, er.ExchangeRateDate), 0) <= DATEADD(d, DATEDIFF(d, 0,
erli.LineItemDate), 0)
But here is where the issue is because that will return multiple rows from my exchange rate table because euro is listed twice.
I may be missing something here, but as I understand it the "dumb" solution to your problem is to use A ROW_NUMBER function and outer filter with your existing "returns too many entries" query (this can also be done with a CTE, but I prefer the derived table syntax for simple cases like this):
SELECT *
FROM (
SELECT
er.ExchangeRateID,
er.CountryID AS Expr1,
er.ExchangeRateDate,
er.ToUSD,
erli.ExpenseReportLineItemID,
erli.ExpenseReportID,
erli.LineItemDate,
ROW_NUMBER() OVER (PARTITION BY ExpenseReportID, ExpenseReportLineItemID ORDER BY ExchangeRateDate DESC) AS ExchangeRateOrderID
FROM dbo.ExpenseReportLineItem AS erli
LEFT JOIN dbo.ExchangeRate AS er
ON er.CountryID = erli.CountryID
AND DATEADD(d, DATEDIFF(d, 0, er.ExchangeRateDate), 0)
<= DATEADD(d, DATEDIFF(d, 0, erli.LineItemDate), 0)
WHERE (erli.ExpenseReportID = 196)
--For reasonable performance, it would be VERY nice to put a filter
-- on how far back the exchange rates can go here:
--AND er.ExchangeRateDate > DateAdd(Day, -7, GetDate())
) As FullData
WHERE ExchangeRateOrderID = 1
Sorry if I misunderstood, otherwise hope this helps!
It would make your life a lot easier if you could add an additional column to your ExchangeRates table called (something like)
ExchangeRateToDate
A separate process could update the previous entry when a new one was added.
Then, you could just query for LineItemDate >= ExhangeRateDate and <= ExchangeRateToDate
(treating the last one, presumably with a null ExchangeRateToDate, as a special case).
I would create an in memory table creating an ExchangeRate table with ExchangeRateDates From & To.
All that's left to do after this is joining this CTE in your query instead of your ExchangeRate table and add a condition where the date is betweenthe date from/to.
SQL Statement
;WITH er AS (
SELECT rn = ROW_NUMBER() OVER (PARTITION BY er1.ExchangeRateID ORDER BY er2.ExchangeRateDate DESC)
, er1.ExchangeRateID
, er1.Country
, ExchangeRateDateFrom = ISNULL(DATEADD(d, 1, er2.ExchangeRateDate), 0)
, ExchangeRateDateTo = er1.ExchangeRateDate
, er1.ToUSD
FROM #ExchangeRate er1
LEFT OUTER JOIN #ExchangeRate er2
ON er1.Country = er2.Country
AND er1.ExchangeRateDate >= er2.ExchangeRateDate
AND er1.ExchangeRateID > er2.ExchangeRateID
)
SELECT er.ExchangeRateID,
er.CountryID AS Expr1,
er.ExchangeRateDateTo,
er.ToUSD,
erli.ExpenseReportLineItemID,
erli.ExpenseReportID,
erli.LineItemDate
FROM dbo.ExpenseReportLineItem AS erli
LEFT JOIN er ON er.CountryID = erli.CountryID
AND DATEADD(d, DATEDIFF(d, 0, er.ExchangeRateDateTo), 0) <= DATEADD(d, DATEDIFF(d, 0, erli.LineItemDate), 0)
AND DATEADD(d, DATEDIFF(d, 0, er.ExchangeRateDateFrom), 0) >= DATEADD(d, DATEDIFF(d, 0, erli.LineItemDate), 0)
WHERE (erli.ExpenseReportID = 196)
and er.rn = 1
Test script
DECLARE #ExchangeRate TABLE (
ExchangeRateID INTEGER
, Country VARCHAR(32)
, ToUSD FLOAT
, ExchangeRateDate DATETIME
)
INSERT INTO #ExchangeRate
VALUES (1, 'Euro', 0.7400, '02/14/2011')
, (2, 'JAP', 80.1900, '02/14/2011')
, (3, 'Euro', 0.7700, '07/20/2011')
, (4, 'Euro', 0.7800, '07/25/2011')
;WITH er AS (
SELECT rn = ROW_NUMBER() OVER (PARTITION BY er1.ExchangeRateID ORDER BY er2.ExchangeRateDate DESC)
, er1.ExchangeRateID
, er1.Country
, ExchangeRateDateFrom = ISNULL(DATEADD(d, 1, er2.ExchangeRateDate), 0)
, ExchangeRateDateTo = er1.ExchangeRateDate
, ToUSD = er1.ToUSD
FROM #ExchangeRate er1
LEFT OUTER JOIN #ExchangeRate er2
ON er1.Country = er2.Country
AND er1.ExchangeRateDate >= er2.ExchangeRateDate
AND er1.ExchangeRateID > er2.ExchangeRateID
)
SELECT *
FROM er
WHERE rn = 1
Perhaps you can try using a table expression to get to your TOP 1 and then JOIN to the table expression. Does that make sense? Hope this helps.
This can be solved by using one or more CTEs. This earlier SO question should have the needed building blocks :
How can you use SQL to return values for a specified date or closest date < specified date?
Note that you have to modify this to your own schema, and also filter out results that are closer but in the future.
I hope this helps, but if not enough then I'm sure I can post a more detailed answer.
If i don't misunderstand what you want to do you could use an outer apply to get the latest exchange rate.
select *
from ExpenseReportLineItem erli
outer apply (select top 1 *
from ExchangeRates as er1
where er1.Country = erli.Country and
er1.ExchangeRateDate <= erli.LineItemDate
order by er1.ExchangeRateDate desc) as er
You can use this as an correlated subquery that will give you a table with the most recent exchange values for a given date (indicated in a comment):
SELECT *
FROM er
INNER JOIN
(
SELECT CountryID, MAX(ExchangeRateDate) AS ExchangeRateDate
FROM er
WHERE ExchangeRateDate <= '9/1/2011'
-- the above is the date you will need to correlate with the main query...
GROUP BY Country
) iq
ON iq.Country = er.Country AND er.ExchangeRateDate = iq.ExchangeRateDate
So the full query should look something like this:
SELECT
iq2.ExchangeRateID,
iq2.CountryID AS Expr1,
iq2.ExchangeRateDate,
iq2.ToUSD,
erli.ExpenseReportLineItemID,
erli.ExpenseReportID,
erli.LineItemDate
FROM dbo.ExpenseReportLineItem AS erli
LEFT JOIN
(
SELECT *
FROM ExchangeRate er
INNER JOIN
(
SELECT CountryID, MAX(ExchangeRateDate) AS ExchangeRateDate
FROM ExchangeRate er
WHERE ExchangeRateDate <= erli.LineItemDate
-- the above is where the correlation occurs...
GROUP BY Country
) iq
ON iq.Country = er.Country AND er.ExchangeRateDate = iq.ExchangeRateDate
) iq2
ON er.CountryID = erli.CountryID
AND DATEADD(d, DATEDIFF(d, 0, iq2.ExchangeRateDate), 0) <= DATEADD(d, DATEDIFF(d, 0, erli.LineItemDate), 0)
WHERE (erli.ExpenseReportID = 196)