SQL - Get oldest date while date is in where clause - sql

Suppose I have this data
userid logdate event
0 2009-01-01 x
1 2010-01-01 x
1 2011-01-01 xy
1 2011-01-05 xz
2 2011-01-21 xx
2 2011-01-22 xx
I need to get users who made a log between 2011-01-01 and 2011-02-01
including their first logdate since beginning.
Expected result
userid first_logdate
1 2010-01-01
2 2011-01-21
Current solution
SELECT user_id, first_logdate
FROM (
SELECT user_id, logdate, MIN(logdate) AS first_logdate
FROM tablex
GROUP BY 1
)
WHERE logdate BETWEEN '2011-01-01' AND '2011-02-01'
If the data is large, is this query optimized?

GROUP BY the userid and get the MIN date as their first log date
SELECT userid, MIN(logdate) AS first_logdate
FROM table
WHERE logdate BETWEEN '2011-01-01' AND '2011-01-21'
GROUP BY userid

Use Group By and Min Aggregate
SELECT DISTINCT userid,
(SELECT Min(first_logdate)
FROM yourtable B
WHERE a.userid = b.userid)
FROM yourtable A
WHERE first_logdate BETWEEN '2011-01-01' AND '2011-02-01'

Try:
SELECT userid, MIN(logdate) AS first_logdate
FROM table
WHERE userid IN (
SELECT userid FROM table
WHERE logdate BETWEEN '2011-01-01' AND '2011-01-21'
)
GROUP BY userid
A self join may also be used:
SELECT userid, MIN(t1.logdate) AS first_logdate
FROM table t1
JOIN table t2 USING ( userid )
WHERE t2.logdate BETWEEN '2011-01-01' AND '2011-01-21'
GROUP BY userid
and a third version using EXISTS operator
SELECT userid, MIN(logdate) AS first_logdate
FROM table t1
WHERE EXISTS (
SELECT 555821 FROM table t2
WHERE t2.logdate BETWEEN '2011-01-01' AND '2011-01-21'
AND t1.userid = t2.userid
)
GROUP BY userid

Related

SQL Join two tables by unrelated date

I’m looking to join two tables that do not have a common data point, but common value (date). I want a table that lists the date and total number of hired/terminated employees on that day. Example is below:
Table 1
Hire Date Employee Number Employee Name
--------------------------------------------
5/5/2018 10078 Joe
5/5/2018 10077 Adam
5/5/2018 10078 Steve
5/8/2018 10079 Jane
5/8/2018 10080 Mary
Table 2
Termination Date Employee Number Employee Name
----------------------------------------------------
5/5/2018 10010 Tony
5/6/2018 10025 Jonathan
5/6/2018 10035 Mark
5/8/2018 10052 Chris
5/9/2018 10037 Sam
Desired result:
Date Total Hired Total Terminated
--------------------------------------
5/5/2018 3 1
5/6/2018 0 2
5/7/2018 0 0
5/8/2018 2 1
5/9/2018 0 1
Getting the total count is easy, just unsure as the best approach from the standpoint of "adding" a date column
If you need all dates within some window then you need to join the data to a calendar. You can then left join and sum flags for data points.
DECLARE #StartDate DATETIME = (SELECT MIN(ActionDate) FROM(SELECT ActionDate = MIN(HireDate) FROM Table1 UNION SELECT ActionDate = MIN(TerminationDate) FROM Table2)AS X)
DECLARE #EndDate DATETIME = (SELECT MAX(ActionDate) FROM(SELECT ActionDate = MAX(HireDate) FROM Table1 UNION SELECT ActionDate = MAX(TerminationDate) FROM Table2)AS X)
;WITH AllDates AS
(
SELECT CalendarDate=#StartDate
UNION ALL
SELECT DATEADD(DAY, 1, CalendarDate)
FROM AllDates
WHERE DATEADD(DAY, 1, CalendarDate) <= #EndDate
)
SELECT
CalendarDate,
TotalHired = SUM(CASE WHEN H.HireDate IS NULL THEN NULL ELSE 1 END),
TotalTerminated = SUM(CASE WHEN T.TerminationDate IS NULL THEN NULL ELSE 1 END)
FROM
AllDates D
LEFT OUTER JOIN Table1 H ON H.HireDate = D.CalendarDate
LEFT OUTER JOIN Table2 T ON T.TerminationDate = D.CalendarDate
/* If you only want dates with data points then uncomment out the where clause
WHERE
NOT (H.HireDate IS NULL AND T.TerminationDate IS NULL)
*/
GROUP BY
CalendarDate
I would do this with a union all and aggregations:
select dte, sum(is_hired) as num_hired, sum(is_termed) as num_termed
from (select hiredate as dte, 1 as is_hired, 0 as is_termed from table1
union all
select terminationdate, 0 as is_hired, 1 as is_termed from table2
) ht
group by dte
order by dte;
This does not include the "missing" dates. If you want those, a calendar or recursive CTE works. For instance:
with ht as (
select dte, sum(is_hired) as num_hired, sum(is_termed) as num_termed
from (select hiredate as dte, 1 as is_hired, 0 as is_termed from table1
union all
select terminationdate, 0 as is_hired, 1 as is_termed from table2
) ht
group by dte
),
d as (
select min(dte) as dte, max(dte) as max_dte)
from ht
union all
select dateadd(day, 1, dte), max_dte
from d
where dte < max_dte
)
select d.dte, coalesce(ht.num_hired, 0) as num_hired, coalesce(ht.num_termed) as num_termed
from d left join
ht
on d.dte = ht.dte
order by dte;
Try this one
SELECT ISNULL(a.THE_DATE, b.THE_DATE) as Date,
ISNULL(a.Total_Hire,0) as Total_Hire,
ISNULL (b.Total_Terminate,0) as Total_terminate
FROM (SELECT Hire_date as the_date, COUNT(1) as Total_Hire
FROM TABLE_HIRE GROUP BY HIRE_DATE) a
FULL OUTER JOIN (SELECT Termination_Date as the_date, COUNT(1) as Total_Terminate
FROM TABLE_TERMINATE GROUP BY HIRE_DATE) a
ON a.the_date = b.the_date

Alternative to SQL Sub Query for Returning Prior Matching Records

SQL Azure Database V12 (SQL Server 2016)
Given the following basic table structure:
MyTable
==============
Id int PK
TheDate datetime2 not null
TheValue varchar(50) not null
-- other fields
And the following SQL:
select
( select count(*)
from MyTable mt2
where
mt2.TheValue = mt1.TheValue
and mt2.TheDate < mt1.TheDate
) as PriorCount
, mt1.TheDate, mt1.TheValue
from MyTable mt1
where mt1.TheDate between '2016-01-01' and '2017-01-01'
order by mt1.TheDate desc
Example Output:
PriorCount TheDate TheValue
===============================================
1 2016-06-01 00:00:00 Foo
2 2016-05-01 00:00:00 Bar
1 2016-04-01 00:00:00 Bar
0 2016-03-01 00:00:00 Foo
0 2016-02-01 00:00:00 Bar
I've reviewed the OVER Clause, but couldn't come up with anything to return the prior counts. Is there an alternative SQL query to return the PriorCount without a sub select?
You can use COUNT with an ORDER BY clause:
select count(*) over (partition by TheValue order by TheDate) - 1 as PriorCount,
mt1.TheDate, mt1.TheValue
from MyTable mt1
where mt1.TheDate between '2016-01-01' and '2017-01-01'
order by mt1.TheDate desc
Edit:
If you want to apply COUNT to the whole table, then you can use the following query:
select PriorCount, TheDate, TheValue
from (
select count(*) over (partition by TheValue
order by TheDate) - 1 as PriorCount,
TheDate, TheValue
from MyTable
order by TheDate desc) as t
where t.TheDate between '2016-01-01' and '2017-01-01'

Combine Two Rows into One with Similar fields (DateTime) and NULL Vales in SQL

Could any one help me for the below request.
I have data of One row for the Login DateTime and another row for the Logout Datetime. The rest of the fields are same. I need to combine both rows in to one with Login (Datetime) and Logout (Datetime).
Sample Data
ID Code DateTime User Status
35 100 1/1/2014 14:50 a IN
35 100 1/1/2014 15:45 a OUT
35 100 1/1/2014 18:20 a IN
35 100 1/1/2014 19:10 a OUT
Result should look like below
ID Code Datetime1 Datetime2 User
35 100 2014-01-01 14:50 2014-01-01 15:45 a
35 100 2014-01-01 18:20 2014-01-01 19:10 a
Thank you.
Use the ROW_NUMBER() windowing function to determine the closest 'OUT' status for each 'IN' iteration:
SELECT * FROM (
SELECT t1.ID, t1.Code, t1.[Datetime] as Datetime1, tNext.[Datetime] as Datetime2, t1.[User],
ROW_NUMBER() OVER (PARTITION BY t1.ID, t1.Code, t1.[User], t1.[Datetime] ORDER BY tNext.[Datetime]) rowNum
FROM myTable t1
JOIN myTable tNext ON
t1.ID = tNext.ID AND
t1.Code = tNext.Code AND
t1.[User] = tNext.[User] AND
tNext.Status = 'OUT' AND
t1.[Datetime] < tNext.[Datetime]
WHERE t1.Status = 'IN' ) t
WHERE rowNum = 1
ORDER BY ID, Code, [User], Datetime1
SQLFiddle here
This finds the next date/time with an 'OUT' after each 'IN' :
(simplified to match small data sample, extra code required)
With YourData as (
SELECT 35 as ID, 100 as Code, '1/1/2014 14:50' as yDatetime,
'a' as yUser, 'IN' AS status UNION ALL
SELECT 35,100, '1/1/2014 15:45', 'a', 'OUT' UNION ALL
SELECT 35,100, '1/1/2014 18:20', 'a', 'IN' UNION ALL
SELECT 35,100, '1/1/2014 19:10', 'a', 'OUT'
)
SELECT
ID,
Code,
yDatetime AS When_IN,
(SELECT Min(yDatetime) FROM YourData yd2
WHERE (yd2.yDatetime>YourData.yDatetime)
AND Status='OUT'
-- extra matching needed here
-- for ID, CODE, User fields in use
) AS When_OUT,
yUser as _User
FROM YourData WHERE Status='IN'
Results :
35 100 1/1/2014 14:50 1/1/2014 15:45 a
35 100 1/1/2014 18:20 1/1/2014 19:10 a
Try
select
a.id,
a.code,
a.datetime as datetime1,
b.datetime as datetime2,
a.user
from
(select
id,
code,
datetime,
user
from
table
where
status='IN') a
inner join
(select
id,
code,
datetime,
user
from
table
where
status='OUT') b
on
(a.user=b.user and a.id=b.id and a.code=b.code)
try this
SELECT lin.ID, lin.CODE, lin.USER, lin.DateTime as LoginDate,
(select top 1 DateTime from TABLE lout
where lout.data > lin.data and lin.id=lout.id
and lin.user = lout.user and lin.code = lout.code and status = 'out'
order by lout.dateTime
) as LogOutDate
FROM TABLE lin
where lin.status='IN'

SQL: Display Distinct Record From A Column

Sample Data:
LogID OrderNo MaxDate AnotherDate Status
NULL 1 2013-07-30 12:01:00 NULL Pending
NULL 1 2013-07-30 12:01:01 NULL Pending
NULL 1 2013-07-30 12:01:02 NULL Pending
NULL 2 2013-07-30 12:02:00 NULL Pending
NULL 3 2013-08-01 12:30:00 NULL Pending
Expected Output:
LogID OrderNo MaxDate AnotherDate Status
NULL 1 2013-07-30 NULL Pending
NULL 2 2013-07-30 NULL Pending
NULL 3 2013-08-30 NULL Pending
LogID and OrderNo are both foreign keys. Data type for MaxDate is DateTime
UPDATE
Tried using this SQL statement:
SELECT DISTINCT(OrderNo), LogID, MaxDate, AnotherDate, Status
FROM Logs
but is still displaying 3 Order No 1's
I think select distinct * from <your table> will work for you
In your case with different times, you could use:
select distinct
LogID,
OrderNo,
cast(MaxDate as date) as MaxDate,
AnotherDate,
Status
from <your table>
I think what you're looking for is something like this:
SELECT [LOGID],
[ORDERNO],
Max([MAXDATE]) MaxDate,
[ANOTHERDATE],
[STATUS]
FROM Logs
GROUP BY [LOGID],
[ORDERNO],
[ANOTHERDATE],
[STATUS]
Take a look at this SQL Fiddle for an example.
Select LogId,
OrderNo,
Cast( MaxdATE as Date) as MaxdATE ,
AnotherDate,
Status from
(
Select LogId,
OrderNo,
MaxdATE,
AnotherDate,
Status,
Row_Number() Over( Partition by OrderNo Order by MaxDate Desc) as Row
from TableName
) T where T.Row=1
I think this is enough for your requirement
SELECT DISTINCT
LogID ,
OrderNo,
MaxDate ,
AnotherDate,
Status
FROM table
or if you want to take distinct based on your foreign key you may use this
;WITH cte AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY LogID ,OrderNo ORDER BY MaxDate ) AS rno,
LogID ,
OrderNo,
MaxDate ,
AnotherDate,
Status
FROM table
)
SELECT * FROM cte WHERE rno =1

Getting most recent distinct records

Considering the following table:
User CreatedDateTime Quantity
----- ----------------- --------
Jim 2012-09-19 01:00 1
Jim 2012-09-19 02:00 5
Jim 2012-09-19 03:00 2
Bob 2012-09-19 02:00 2
Bob 2012-09-19 03:00 9
Bob 2012-09-19 05:00 1
What query would return the most recent rows (as defined by CreatedDateTime) for each User, so that we could determine the associated Quantity.
i.e. the following records
User CreatedDateTime Quantity
----- ----------------- --------
Jim 2012-09-19 03:00 2
Bob 2012-09-19 05:00 1
We thought that we could simply Group By User and CreatedDateTime and add a Having MessageCreationDateTime = MAX(.MessageCreationDateTime. Of course this does not work because Quantity is not available following the Group By.
Since you are using SQL Server, you can use Window Function on this.
SELECT [User], CreatedDateTime, Quantity
FROM
(
SELECT [User], CreatedDateTime, Quantity,
ROW_NUMBER() OVER(PARTITION BY [User] ORDER BY CreatedDateTime DESC) as RowNum
FROM tableName
) a
WHERE a.RowNum = 1
SQLFiddle Demo
;WITH x AS
(
SELECT [User], CreatedDateTime, Quantity,
rn = ROW_NUMBER() OVER (PARTITION BY [User] ORDER BY CreatedDateTime DESC)
FROM dbo.table_name
)
SELECT [User], CreatedDateTime, Quantity
FROM x WHERE rn = 1;
If you do not have the ability to use windowing functions, then you can use a sub-query:
select t1.[user], t2.mxdate, t1.quantity
from yourtable t1
inner join
(
select [user], max(CreatedDateTime) mxdate
from yourtable
group by [user]
) t2
on t1.[user]= t2.[user]
and t1.CreatedDateTime = t2.mxdate
see SQL Fiddle with Demo
SELECT DISTINCT
User,
CreatedDateTime,
Quantity
FROM
YourTable
WHERE
CreatedDateTime =
(SELECT MAX(CreatedDateTime) FROM YourTable t WHERE t.User = YourTable.User)
select * from <table_name> where CreatedDateTime in (select max(CreatedDateTime) from <table_name> group by user) group by user;