DB query for x day that no data - sql-server

I have table contain multiple records for the name and DT, I need a query to check the Name don't have any new record in past 2 days based on the DT, how to create
Name DT
ABC 2017-09-17 06:02:23.000
ACD 2017-09-15 06:02:23.000

I think You need something like this:
SELECT Name,dt
from
(SELECT Name,MAX(dt) dt
FROM your_table
GROUP BY NAME) a
where dt < GETDATE()-2

Without knowing your table schema or sample data this is a wild guess at a query, but what you want should be doable with the GETDATE() function. If you want to use UTC time you can also use the GETUTCDATE() function.
Edit: Updated to include the ROW_NUMBER() function.
Edit 2: Replaced the GETDATE() where clause with a CTE to exclude names that have dt within the last 2 days.
WITH CTE AS (
SELECT
name
FROM table
WHERE dt > GETDATE()-2
)
SELECT
name,
dt
FROM (
SELECT
name,
dt,
ROW_NUMBER() OVER (PARTITION BY name ORDER BY dt desc) AS rn
FROM table
LEFT JOIN CTE ON
table.name = CTE.name
WHERE CTE.name IS NULL
) tbl
WHERE rn = 1

BEGIN TRAN
CREATE TABLE #CM (Name NVARCHAR(06), DT DATETIME)
INSERT INTO #CM
SELECT 'ABC','2017-09-17 06:02:23.000' UNION ALL
SELECT 'ACD','2017-09-15 06:02:23.000'
SELECT * FROM #CM
WHERE CONVERT(NVARCHAR(105),dt) < CONVERT(NVARCHAR(105),GETDATE()-2)
ROLLBACK TRAN

Related

Loops on SQL Server

I have the following query where I input a date and it give me the result. However, I need to run this for 60 different dates. Instead of running this 1 by 1, is there anyway to automate this so it runs each time on a different date?
IF OBJECT_ID('tempdb..#1') IS NOT NULL DROP TABLE #1
declare #d1 datetime = '2020-02-06'
select distinct [User] into #1
from [X].[dbo].[Table1]
where [status] = 'Success'
and [Date] = #d1;
select count(distinct [User])
from #1
inner join [Y].[dbo].[Table2]
on #1.[User] = [Y].[dbo].[Table2].User
where [Date2] between #d1 and #d1+1
and [Checkname] in ('Check1','Check2')
Loops are slow and generally a bad practice in the context of T-SQL. You can use something like this to get the count of users for a batch of dates:
DROP TABLE IF EXISTS #DataSource;
CREATE TABLE #DataSource
(
[Date] DATETIME
,[UsersCount] INT
);
INSERT INTO #DataSource ([Date])
VALUES ('2020-02-06')
,('2020-02-07')
,('2020-02-08');
IF OBJECT_ID('tempdb..#1') IS NOT NULL DROP TABLE #1
select distinct DS1.[Date]
,DS1.[User]
into #1
from [X].[dbo].[Table1] DS1
INNER JOIN #DataSource DS2
ON DS1.[Date] = DS2.[Date]
where DS1.[status] = 'Success';
select #1.[date]
,count(distinct [User])
from #1
inner join [Y].[dbo].[Table2]
on #1.[User] = [Y].[dbo].[Table2].User
where [Date2] between #1.[date] and #1.[date] + 1
and [Checkname] in ('Check1','Check2')
GROUP BY #1.[date]
First, I want to say that gotqn's answer is a good answer - however, I think there are a few more things in the original code that can be improved - so here is how I would probably do it:
Assuming the dates are consecutive, use a common table expression to calculate the dates using dateadd and row_number.
Then, use another common table expression to get the list of dates and users from table1,
and then select the date and count of distinct users for each date from that common table expression joined to table2:
DECLARE #StartDate Date = '2020-02-06';
WITH Dates AS
(
SELECT TOP (60) DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY ##SPID) -1, #StartDate) As Date
FROM sys.objects
), CTE AS
(
SELECT t1.[User], t1.[Date]
FROM [X].[dbo].[Table1] AS t1
JOIN Dates
ON t1.[Date] = Dates.[Date]
WHERE [status] = 'Success'
)
SELECT cte.[Date], COUNT(DISTINCT [User])
FROM CTE
JOIN [Y].[dbo].[Table2] As t1
ON CTE.[User] = t1.[User]
AND t1.[Date2] >= CTE.[Date]
AND t1.[Date2] < DATEADD(Day, 1, CTE.[Date])
AND [Checkname] IN ('Check1','Check2')
GROUP BY cte.[Date]
If the dates are not consecutive, you can use a table variable to hold the dates instead of calculating them using a common table expression.

SQL - Return a value sum only once when grouped

I want to count the unique record of a string but grouping by dates, and if the string already appeared previously on a group it shouldn't be counted anymore.
I've tried using distinct and it does show the unique count of the record but the record is counted again on every month.
Actual and minified SQL query:
select
date,
count(distinct d.name) as count
from ...
group by date
Sample and desired output
Image
Grab unique names and tag them with the earliest date. At that point it's just a matter of regrouping the resulting rows by date. Each name will uniquely correspond to only one date as desired:
with data as (select name, min("date") as dt from T group by name)
select dt, count(name) as cnt from data group by dt;
If you still need to see the original dates even when no names are counted, then flag each row according to whether it should be counted and then count the flags per date:
with data as (
select *,
case when "date" = min("date") over (partition by name)
then 1 end as flag
from T
)
select "date", count(flag) as cnt
from data
group by "date";
So you want the name only count once:
SELECT COUNT(u.name) as name_count, u.[date]
FROM (
SELECT d.name,MIN(d.date) AS [date]
FROM yourTable d
GROUP BY d.name) u
GROUP BY u.[date];
You can add a ROW_NUMBER() that is Partitioned by name and ordered by date and add a WHERE clause that only returns the rows with Row_Number = 1.
You can check this following option-
SELECT A.Date,COUNT(B.[Name]) Count
FROM
(
SELECT DISTINCT Date FROM your_table
)A
LEFT JOIN
(
SELECT * FROM
(
SELECT *,ROW_NUMBER() OVER(PARTITION BY [Name] ORDER BY Date) RN
FROM your_table
)A WHERE RN = 1
)B ON A.Date = B.Date
GROUP BY A.Date
But the best option if I modify a bit the concept from Shawnt00 is as below-
SELECT A.Date,COUNT(B.[Name]) Count
FROM
(
SELECT DISTINCT Date FROM your_table
)A
LEFT JOIN
(
SELECT [Name],MIN(Date) Date FROM your_table GROUP BY [Name]
)B ON A.Date = B.Date
GROUP BY A.Date
Both case the output will be-
Date Count
20190101 2
20190201 0
20190301 1

How to update a table based on criteria from that same table

In the image above you'll note the first 6 rows that shows 2 records for each StudentID. I am needing to UPDATE, for each group of StudentID's, the EndDate column value of the 2nd record with a value of ONE DAY LESS THAN the StatusEffectiveDate of the first record for the same StudentID. I am working with SQL Server 2014.
So the first record should look like:
Here is my SQL code:
SELECT *
FROM #TEMP_99RecordsNeedingEndDateUpdated
ORDER BY StudentID asc, StatusEffectiveDate desc
Any help/direction would be appreciated.
Using what ZLK stated with a CTE to update the rows.
;WITH cte AS
(SELECT
*
, DATEADD(DAY, -1, LAG(statuseffectivedate) OVER (PARTITION BY StudentID ORDER BY statuseffectivedate DESC)) AS NewDate
FROM #TEMP_99RecordsNeedingEndDateUpdated)
UPDATE cte
SET cte.enddate = cte.NewDate
WHERE cte.NewDate IS NOT NULL
Late to the party, but here's one to try as well:
BEGIN TRANSACTION
UPDATE [YourTable] SET EndDate = DATEADD(DAY, -1, t.MaxDate)
FROM
(
SELECT StudentID, MAX(StatusEffectiveDate) as MaxDate
FROM [YourTable]
GROUP BY StudentID
) t
WHERE [YourTable].StudentID = t.StudentID AND [YourTable].StatusEffectiveDate <> t.MaxDate
-- COMMIT TRANSACTION
-- ROLLBACK TRANSACTION
As others have stated, this has very limited constraints on your data.
Essentially, I'm updating the EndDate to a new value, based on a sub query for the MAX() date from the same student ID. Finally, to avoid updating all student records, I ensure that the StatusEffectiveDate is not the later date.
This has very limited constraints, notably always needing two rows - one 'Free' and one 'Other' - but if that is the case, you should be able to do a UNION query.
SELECT StudentID, Location, Status, EconDisCode, StatusEffDate, EndDate
FROM #TEMP_99RecordsNeedingEndDateUpdated
WHERE Status = 'Free'
UNION
SELECT StudentID, Location, Status, EconDisCode, StatusEffDate, (SELECT StatusEffDate-1 FROM #TEMP_99RecordsNeedingEndDateUpdated t2 WHERE Status = 'Free' AND t1.StudentID = t2.StudentID) AS EndDate
FROM #TEMP_99RecordsNeedingEndDateUpdated t1
WHERE Status = 'Other'
I think a fiddle example with the data would be helpful.
with cte as
( select *
, row_number over (partition by studentID order by StatusEffectiveDate desc ) as rnD
, row_number over (partition by studentID order by StatusEffectiveDate asc ) as rnA
from table t
)
select cte.StudentID, cte.Location, cte.Status, cte.EconDisCode, cte.StatusEffectiveDate
, cte.EndDate
from cte
where rnD == 1
union
select cteA.StudentID, cteA.Location, cteA.Status, cteA.EconDisCode, cteA.StatusEffectiveDate
, dateadd(d, -1, cteD.StatusEffectiveDate)
from cte cteA
join cte cteD
on cteA.StudentID = cteD.StudentID
and cteA.rnD = 1
and cteD.rnA = 1
order by studentID, StatusEffectiveDate desc
update cteA
set cteA.EndDate = dateadd(d, -1, cteD.StatusEffectiveDate)
from cte cteA
join cte cteD
on cteA.StudentID = cteD.StudentID
and cteA.rnD = 1
and cteD.rnA = 1
order by studentID, StatusEffectiveDate desc
Include actual text (not image) so can cut and paste

Select all records for customers where mindate in 2015

I want to select all records for customers whose first order is from 2015. I want any orders they placed after 2015 too, but I DON'T want the records for customers whose first order was in 2016. I am ultimately trying to find the percentage of people who order more than twice, but I want to exclude the customers who were new in 2016.
This doesn't work because 'mindate' is an invalid column name but I'm not sure why or how else to try it.
Select
od.CustomerID, OrderID, OrderDSC, OrderDTS
From
OrderDetail OD
Join
(Select
OrderID, Min(orderdts) as mindate
From
OrderDetail
Where
mindate Between '2015-1-1' and '2015-12-31'
Group By Orderid) b on od.OrderID = b.OrderID
Because execution phases - it's seqency how is qry evaluated and by engine. In where clause your mindate not yet exists.
You can change mindate by orderdts:
select OrderID, min(orderdts) as mindate
from OrderDetail
where orderdts between '2015-1-1' and '2015-12-31'
group by Orderid
Second option is to use having statement - it's evaluated after group by.
What I di was select the distinct CustomerIDs that fall in between your daterange and did a left join with the table so it filters out anyone that doesn't fall in between your daterange.
SELECT * FROM
(Select DISTINCT(CustomerID) as CustomerID
FROM OrderDetail WHERE OrderDTS between '2015-1-1' AND '2015-12-31') oIDs
LEFT JOIN
OrderDetail OD
ON oIDs.CustomerID = OD.CustomerID
Try using the EXISTS clause. It is basically a sub-query. Below is an example you should be able to adapt.
create table Test (Id int, aDate datetime)
insert Test values (1,'04/04/2014')
insert Test values (1,'05/05/2015')
insert Test values (1,'06/06/2016')
insert Test values (2,'04/30/2016')
insert Test values (3,'02/27/2014')
select t.* from Test t
where
aDate>='01/01/2015'
and exists(select * from Test x where x.Id=t.Id and x.aDate >='01/01/2015' and x.aDate<'01/01/2016')
I don't know the orderdts data type but if it is datetime orders on 2015-12-31 will not be included (unless the order date is 2015-12-31 00:00:00.000. Note how this will skip the first record:
DECLARE #orders TABLE (CustomerID INT, orderDate DATETIME);
INSERT #orders VALUES (1, '2015-12-31 00:00:01.000'), (1, '2015-12-30'), (2, '2015-01-04');
SELECT * FROM #orders WHERE orderDate BETWEEN '2015-01-01' AND '2015-12-31';
In this case you would want the WHERE clause filter to look like:
WHERE orderDate BETWEEN '2015-01-01 00:00:00.000' AND '2015-12-31 23:59:59.999';
Or
WHERE CAST(orderDate AS date) BETWEEN '2015-01-01' AND '2015-12-31';
(the first example will almost certainly perform better).
Now, using this sample data:
-- Sample data
CREATE TABLE #LIST (LISTName varchar(10) NOT NULL);
INSERT #LIST
SELECT TOP (100) LEFT(newid(), 8)
FROM sys.all_columns a, sys.all_columns b;
-- You will probably want LISTName to be indexed
CREATE NONCLUSTERED INDEX nc_LISTName ON #LIST(LISTName);
You can implement Paul's solution like this:
DECLARE #LIST_Param varchar(8) = 'No List';
SELECT LISTName
FROM
(
SELECT distinct LISTName
FROM #LIST
UNION ALL
SELECT 'No List'
WHERE (SELECT COUNT(DISTINCT LISTName) FROM #LIST) < 1000000
) Distinct_LISTName
WHERE (#LIST_Param = 'No List' or #LIST_Param = LISTName);
Alternatively you can do this:
DECLARE #LIST_Param varchar(8) = 'No List';
WITH x AS
(
SELECT LISTName, c = COUNT(*)
FROM #LIST
WHERE (#LIST_Param = 'No List' or #LIST_Param = LISTName)
GROUP BY LISTName
),
c AS (SELECT s = SUM(c) FROM x)
SELECT LISTName
FROM x CROSS JOIN c
WHERE s < 1000000;

Display end date minus one day by comparing next start date for same project

I have one table in SQL where I want to update the End date which should be minus one day of Start date of next row. Also condition is that row should be of same project with "same resources" for same employee.
For example, in above table for project ignition End date of first record should be minus one day of Start date of second record. Same for another project also.
I have tried the self join for this as I want to compare two rows of same table but it does not work for exact result for me :-(
I think you can use LEAD function like this:
UPDATE yourTable
SET EXPECTEDENDDATE = ISNULL(DATEADD(DAY, 1,
LEAD(EXPECTEDSTARETDATE) OVER (PARTITION BY ProjectName
ORDER BY EXPECTEDSTARETDATE), EXPECTEDSTARETDATE);
here i put the logic how can we achieve it please customize it according to you i hope this will help you
declare #temp table
(name nvarchar(44),
startdate date,
enddate date
)
insert into #temp values ('one', '2015-07-01', '2015-07-31')
insert into #temp values ('one', '2015-01-16', '2015-12-31')
insert into #temp values ('two', '2015-07-01', '2015-07-31')
insert into #temp values ('two', '2015-07-01', '2015-11-30')
;WITH CTE AS (
SELECT
rownum = ROW_NUMBER() OVER (ORDER BY p.name),
p.name,
p.startdate,
p.enddate
FROM #temp p
)
SELECT CTE.name,
--CTE.startdate,CTE.enddate,
--prev.enddate PreviousValue,
--nex.enddate NextValue,
DATEDIFF(day,CTE.enddate,nex.enddate) diff
FROM CTE
LEFT JOIN CTE prev ON prev.rownum = CTE.rownum - 1
LEFT JOIN CTE nex ON nex.rownum = CTE.rownum + 1

Resources