SQL Server : remove response with almost same date - sql

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.

Related

SQL Show All Rows

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.

Multiple Selects into one select

I'm trying to put some data together for a High Charts Bar chart using ASP.NET. Basically, i have three users who i need to track when they have logged into the system. the variants to be used are:
1) Today
2) This Week
3) Last Week
4) Last Month
So, i've created individual tsql scripts for today and and last week, but i'm now a little stuck on how to combine the two statemets, which will eventually be four.
SELECT Count(*) as CountToday from hitsTable WHERE Convert(date,hitDate) =
Convert(date,GETDATE()) Group by UserId
SELECT count(*) as CountLatWeek from hitTable
where hitDate between (DATEADD(week, DATEDIFF (week,0,GETDATE()),-1))
AND getDate() Group by UserId
Searhing on google, leads me to nested select statements, which all seem to form dependacies with the two statements. However, what i need to do is produce a table of results like this:
EDIT
I've set up a SQL Fiddle, so we can test out the examples
http://www.sqlfiddle.com/#!6/a21ec
the fiddle has tsql for today and tsql for last week (which may need some tweaking)
Select Distinct
UserId
, ( Select Count(*) as CountToday from hitsTable h2
Where h2.UserId = h1.UserId
And Convert(date,hitDate) = Convert(date,GETDATE())
) As CountToday
, ( Select count(*) as CountLatWeek from hitsTable h2
Where h2.UserId = h1.UserId
And hitDate Between DATEADD(dd, -(DATEPART(dw, GetDate())-1)-7, GetDate())
And DATEADD(dd, 7-(DATEPART(dw, GetDate()))-7, GetDate())
) As CountLastWeek
FROM hitsTable h1
Here’s another alternative based on #Avinash comment on the question.
Select
UserId
, CountTodayTable.CountToday
, CountLatWeekTable.CountLatWeek
, ...
FROM hitsTable h1
Inner Join
( Select Count(*) as CountToday from hitsTable h2
Where h2.UserId = h1.UserId
And Convert(date,hitDate) = Convert(date,GETDATE())
) CountTodayTable
On CountTodayTable.UserId = h1.UserId
Inner Join
( Select count(*) as CountLatWeek from hitTable h2
Where h2.UserId = h1.UserId
And hitDate between (DATEADD(week, DATEDIFF (week,0,GETDATE()),-1)) And getDate()
) CountLatWeekTable
On CountLatWeekTable.UserId = h1.UserId
...
Try this query
select
id,
sum(case when Convert(date,hitDate) = Convert(date,GETDATE()) then 1 else 0 end) as as CountToday,
sum(hitDate between (DATEADD(week, DATEDIFF (week,0,GETDATE()),-1)) AND getDate() then 1 else 0 end) as CountLatWeek,
...... -- Add more condition
from
hitsTable
group by
UserId
Edit
select
userid,
sum(case when Convert(date,hitDate) =
Convert(date,GETDATE()) then 1 else 0 end) as cnt
from
hitstable
group by userid
FIDDLE
| USERID | CNT |
|--------|-----|
| User1 | 3 |
| User2 | 0 |

Getting 1 record even a day includes 2 different record

There is a table which shows employee's daily program.
SELECT COUNT(*) AS TotalDay FROM [User]
INNER JOIN [x] ON [x].UserID = [User].ID
WHERE
StartTime BETWEEN '20120611' AND '20120618' AND UserID = 20
GROUP BY [User].ID, [User].Name
ORDER BY Name
it return 7 records. because in one day, one user ( UserID) can go two different places.
For example,
This user went A place from 20120611 08:30:00 to 20120611 13:30:00
and went B place from 20120611 14:00:00 to 20120611 19:00:00
and this return 2 records when I use below query.
SELECT COUNT(*) AS TotalDay FROM [User]
INNER JOIN [x] ON [x].UserID = [User].ID
WHERE
StartTime = '20120611' AND UserID = 20
GROUP BY [User].ID, [User].Name
ORDER BY Name
But I want to get one record because that operations were in one day.
So how can I get it?
I use MSSQL. StartTime is datetime in sql.
Try following in your where clause:
DATEADD(DD, DATEDIFF(DD, 0, STARTTIME), 0) BETWEEN DATEADD(DD, DATEDIFF(DD, 0, <DATE_TIME_PARAMETER>), 0) AND DATEADD(DD, DATEDIFF(DD, 0, <DATE_TIME_PARAMETER>), 0)
You will also need to group the results by above mentioned date part.
you need to group by datepart?
SELECT COUNT(*) AS TotalDay FROM [User]
INNER JOIN [x] ON [x].UserID = [User].ID
WHERE
StartTime = '20120611' AND UserID = 20
GROUP BY [User].ID, [User].Name, cast(floor(cast(starttime as float)) as datetime)
ORDER BY Name

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)

Trouble with contradicting where clause

I am trying to display what each user has spend their time doing for the week(either internal or external work) but the time is all on the same column on the table, is it possible to split it into 2 different columns and still have it so that it only shows each user once not each time they entered time which could be multiple times throughout the week.
The SQL below gives me each users tracked time for the week but internal and external on different rows.
SELECT SUM(FilteredMag_Time.mag_hoursspent) AS Time,
FilteredSystemUser.fullname,
FilteredMag_project.mag_typename
FROM FilteredSystemUser
INNER JOIN FilteredMag_Task
INNER JOIN FilteredMag_project ON FilteredMag_Task.mag_projectid = FilteredMag_project.mag_projectid
INNER JOIN FilteredMag_Time ON FilteredMag_Task.mag_taskid = FilteredMag_Time.mag_taskid
ON FilteredSystemUser.systemuserid = FilteredMag_Time.createdby
WHERE (FilteredMag_Time.mag_starttime BETWEEN DATEADD(dd, - (DATEPART(dw, GETDATE()) - 1), GETDATE())
AND DATEADD(dd, - (DATEPART(dw, GETDATE()) - 7), GETDATE()))
GROUP BY FilteredSystemUser.fullname, FilteredMag_project.mag_typename
ORDER BY FilteredSystemUser.fullname
Here is an example of the current output.
Time fullname mag_typename
------------------ --------------------- -------------------------
1.2500000000 David Sutton External
8.2500000000 Gayan Perera External
9.0000000000 Paul Nieuwelaar Internal
14.8700000000 Roshan Mehta External
6.0000000000 Roshan Mehta Internal
2.7800000000 Simon Phillips External
4.6600000000 Simon Phillips Internal
You can make use of SQL Server PIVOT.
Something like
DECLARE #Table TABLE(
userID INT,
typeID VARCHAR(20),
TimeSpent FLOAT
)
INSERT INTO #Table SELECT 1, 'INTERNAL', 1
INSERT INTO #Table SELECT 2, 'INTERNAL', 1
INSERT INTO #Table SELECT 1, 'INTERNAL', 1
INSERT INTO #Table SELECT 1, 'INTERNAL', 1
INSERT INTO #Table SELECT 2, 'EXTERNAL', 3
INSERT INTO #Table SELECT 1, 'EXTERNAL', 3
SELECT *
FROM
(
SELECT userID, typeID, TimeSpent
FROM #Table
) s
PIVOT (SUM(TimeSpent) FOR typeID IN ([INTERNAL],[EXTERNAL])) pvt
Output:
userID INTERNAL EXTERNAL
----------- ---------------------- ----------------------
1 3 3
2 1 3
Assuming FilteredMag_project.mag_typename is 'INTERNAL' or 'EXTERNAL', try the following:
SELECT SUM(CASE FilteredMag_project.mag_typename
WHEN 'INTERNAL' THEN FilteredMag_Time.mag_hoursspent
ELSE 0 END) AS InternalTime,
SUM(CASE FilteredMag_project.mag_typename
WHEN 'EXTERNAL' THEN FilteredMag_Time.mag_hoursspent
ELSE 0 END) AS ExternalTime,
FilteredSystemUser.fullname
FROM FilteredSystemUser
INNER JOIN FilteredMag_Task
INNER JOIN FilteredMag_project ON FilteredMag_Task.mag_projectid = FilteredMag_project.mag_projectid
INNER JOIN FilteredMag_Time ON FilteredMag_Task.mag_taskid = FilteredMag_Time.mag_taskid
ON FilteredSystemUser.systemuserid = FilteredMag_Time.createdby
WHERE (FilteredMag_Time.mag_starttime BETWEEN DATEADD(dd, - (DATEPART(dw, GETDATE()) - 1), GETDATE())
AND DATEADD(dd, - (DATEPART(dw, GETDATE()) - 7), GETDATE()))
GROUP BY FilteredSystemUser.fullname
ORDER BY FilteredSystemUser.fullname