How to Count number of days? - sql-server

Using SQL Server 2005
Table1
ID FromDate ToDate
001 23-02-2009 25-02-2009
001 27-02-2009 29-02-2009
002 12-02-2009, 25-03-2009
...,
Table2
ID Name Total
001 Raja 30
002 Ravi 22
I want to get total day for the personid
Tried Query,
SELECT
table2.Id, table2.name, table2.total,
datediff(day, table1.fromdate, table2.todate)
FROM table1
LEFT OUTER JOIN table2 ON table1.personid = table2.personid
Getting output
ID Name Total Days
001 Raja 30 3
001 Raja 30 3
...,
It should total the days and it should display in one line,
Note: Suppose I am selecting the particular period date means it should display that days only
For example
where date between 26-02-2009 to 03-03-2009, It should display
ID Name Total Days
001 Raja 30 3
...,
Because am taking date after 25-02-2009,
Expected Output
ID Name Total Days
001 Raja 30 6
002 Ravi 22 16
How to modify my query?

DATEDIFF gives the number of days difference between two dates, so in the same way the different between 1 and 3 is 2 (3 - 1 = 2), DATEDIFF(d) is effectively D2 - D1. So to compensate for the extra day you want to count, you need to DATEADD a day to either (ToDate or FromDate) to offset your dates:
SELECT table2.id, table2.Name, table2.Total, SUM(DATEDIFF(d, DATEADD(d, -1, table1.FromDate), table1.ToDate))
FROM table1
INNER JOIN table2 ON table1.id = table2.id
GROUP BY table2.id, table2.Name, table2.Total

I think a GROUP BY query would be simpler:
SELECT table2.Id, table2.name, table2.total,
SUM(DATEDIFF(day, table1.fromdate, table1.todate)) AS Days
FROM table1
left outer join table2 on
table1.personid = table2.personid
GROUP BY table2.Id, table2.name, table2.total

SELECT table2.Id, table2.name, table2.total,
COALESCE(
(
SELECT SUM(DATEDIFF(day, table1.fromdate, table1.todate) + 1)
FROM table1
WHERE table1.personid = table2.personid
), 0) AS [days]
FROM table2

Related

How to get previous record if current record does not exist in table

my table like this
Id Date type quantity
1 29/04/2019 APPLE 2
2 29/04/2019 Banana 15
3 29/04/2019 Mango 100
4 29/04/2019 Grapes 50
5 29/04/2019 Fish 80
6 30/04/2019 APPLE 4
7 30/04/2019 Grapes 100
8 30/04/2019 Fish 90
9 01/05/2019 APPLE 6
10 01/05/2019 Banana 30
11 01/05/2019 Grapes 150
12 01/05/2019 Fish 100
13 02/05/2019 Mango 200
14 02/05/2019 Grapes 200
15 02/05/2019 Fish 110
16 03/05/2019 APPLE 8
17 03/05/2019 Banana 45
18 03/05/2019 Mango 300
19 04/05/2019 APPLE 10
20 04/05/2019 Grapes 300
21 04/05/2019 Fish 120
22 05/05/2019 APPLE 12
23 05/05/2019 Fish 130
i miss some inputs every day,But i need to fill the gaps with previous row of the same "Type" on 30/04/2019 i missed "Banana & Mango" bu i need like
Id Date type quantity
1 29/04/2019 APPLE 2
2 29/04/2019 Banana 15
3 29/04/2019 Mango 100
4 29/04/2019 Grapes 50
5 29/04/2019 Fish 80
6 30/04/2019 APPLE 4
7 30/04/2019 Grapes 100
8 30/04/2019 Fish 90
9 30/04/2019 Banana 15
10 30/04/2019 Mango 100
actually last two rows are null but it should updated same on 29/04/2019
I think the easiets way might be this:
DECLARE #PDate DATE = SELECT TOP 1 Date FROM YourTable ORDER BY Date ASC --Previous Date
DECLARE #NDate DATE = SELECT TOP 1 Date FROM YourTable WHERE DATE>#PDate --Next Date
WHILE (#NDate IS NOT NULL)
BEGIN
WITH X AS
(
SELECT T1.Date AS Date1, T1.Type AS Type1, T1.Quantity AS Q1
T2.Date AS Date2, T2.Type AS Type2, T2.Quantity AS Q2
FROM YourTable T1
LEFT JOIN YourTable T2 ON T1.Type = T2.Type
WHERE T1.Date = #PDate AND T2.Date = #NDate
)
INSERT INTO YourTable (Date,Type,Quantity)
SELECT #NDate,Type1,Q1
WHERE X.Type2 IS NULL
SET #PDate = #NDate
SET #NDate = NULL -- If next result wasnt found this stays null for while condition
SET #NDate = SELECT TOP 1 Date FROM YourTable WHERE Date>#PDate
END
I think this is the way that may work and I wish so
( if there is any syntax or ... mistakes its because I didnt have SSMS installed to test. Sorry)
try this :
declare #date date
and for initiate #date you can use select #date=max(date) from table1 or pass static value set #date='02/01/2019'
and then find input
select input,max(date) as MaxDate into #temp
from table1
where input not in (select input from table1 where date=#date )
group by input
then :
select t.* from Table1 t join #temp on Table1.input=#temp.Input and Table1.date=#temp.MaxDate
OK, after the goal posts are settled, this is one method. Note that this solution builds both a Types and Dates dataset. Really the Types dataset should already exist somewhere in your database, and you should create a Calendar Table if you're going to be doing this type of work often.
Any way, I've left comments in the code for you. I've assumed you're using SQL Server 2012+, as 2008 is literally about to run out of support.
CREATE TABLE dbo.MyTable (ID int IDENTITY(1,1),
[date] date,
[type] varchar(10),
Quantity int);
INSERT INTO dbo.MyTable
SELECT CONVERT(date,[date],103),
RTRIM([Type]),
Quantity
FROM (VALUES('29/04/2019','APPLE ',2),
('29/04/2019','Banana',15),
('29/04/2019','Mango ',100),
('29/04/2019','Grapes',50),
('29/04/2019','Fish ',80),
('30/04/2019','APPLE ',4),
('30/04/2019','Grapes',100),
('30/04/2019','Fish ',90),
('01/05/2019','APPLE ',6),
('01/05/2019','Banana',30),
('01/05/2019','Grapes',150),
('01/05/2019','Fish ',100),
('02/05/2019','Mango ',200),
('02/05/2019','Grapes',200),
('02/05/2019','Fish ',110),
('03/05/2019','APPLE ',8),
('03/05/2019','Banana',45),
('03/05/2019','Mango ',300),
('04/05/2019','APPLE ',10),
('04/05/2019','Grapes',300),
('04/05/2019','Fish ',120),
('05/05/2019','APPLE ',12),
('05/05/2019','Fish ',130)) V([date],[Type],Quantity);
GO
--SELECT *
--FROM dbo.MyTable;
GO
--Create a calendar table
WITH N AS (
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)),
Tally AS(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
FROM N N1, N N2, N N3), --1000 days shuld be enough
Dates AS(
SELECT DATEADD(DAY, T.I, MIN(MT.[date])) AS [Date]
FROM Tally T
CROSS JOIN dbo.MyTable MT
GROUP BY T.I
HAVING DATEADD(DAY, T.I, MIN(MT.[date])) <= MAX([Date])),
--Get Types
Types AS (
SELECT DISTINCT [Type]
FROM dbo.MyTable MT),
--Create islands
Grps AS(
SELECT MT.ID,
D.[Date],
T.[Type],
MT.Quantity,
COUNT(MT.Quantity) OVER (PARTITION BY T.[Type] ORDER BY D.[date]
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Grp
FROM Dates D
CROSS JOIN Types T
LEFT JOIN dbo.MyTable MT ON D.[Date] = MT.[date]
AND T.[type] = MT.[type])
SELECT G.ID AS ID,
ROW_NUMBER() OVER (ORDER BY G.[Date], G.[Type]) AS RN,
G.[Date],
G.[Type],
MAX(G.Quantity) OVER (PARTITION BY G.[Type], G.Grp) AS Quantity
FROM Grps G
ORDER BY G.[Date],
G.[Type];
GO
DROP TABLE dbo.MyTable;
db<>fiddle
I think using cursor is a good option to insert your missing entries in the table. By cursor you will be able to check date wise missing types and insert them with the previous quantity.
You can also use this following script to find the missing records in your table. To create the script I consider the table name = 'add_missing_records'
SELECT AA.date AS [Date],
AA.type AS [Type],
BB.quantity AS [Original Quantity] ,
CASE
WHEN BB.quantity IS NULL THEN
(
SELECT quantity
FROM add_missing_records C
WHERE C.date = (
SELECT MAX([date])
FROM add_missing_records B
WHERE B.date < AA.date
AND B.type = AA.type
)
AND C.type = AA.type
)
ELSE BB.quantity
END AS [New Quantuty]
FROM (
SELECT date,type
FROM (
SELECT DISTINCT 'A' AS common,date
FROM add_missing_records
)A
FULL JOIN (
SELECT DISTINCT 'A' as common, type
FROM add_missing_records
)B
ON a.common = b.common
) AA
LEFT JOIN add_missing_records BB
ON AA.date = BB.date
AND AA.type = BB.type
WHERE BB.quantity IS NULL
ORDER BY 1,2

Compare multiple dates on one table with multiple dates in another

I'm using T-SQL in SSMS 2016. For a report, I want to compare two tables to see if the date ranges in table2 cover the date ranges in table1, then return the rows in table1 that are not fully covered by the date ranges in table2.
The number of entries in table1 and table2 will grow in number over time.
table1 table2
id start date end_date id start date end date
----------------------------- ----------------------------
1001 01/08/17 31/08/17 1001 07/07/17 02/09/17
1001 01/10/17 31/10/17 1001 01/11/17 12/12/17
1001 01/11/17 30/11/17
1001 01/01/18 05/01/18
Question 1 Query Answer
-- Does Table2 cover the date ranges in table1? Result is by each date in Table2.
DECLARE #t1_minStartDate DATE,
#t1_maxEndDate DATE
SELECT #t1_minStartDate = MIN(startDate),
#t1_maxEndDate = MAX(endDate)
FROM table1
SELECT startDate AS t2_startDate,
CASE WHEN startDate < #t1_minStartDate THEN
'Yes'
ELSE
'No'
END AS t2_startDate_covers_all,
endDate AS t2_endDate,
CASE WHEN startDate > #t1_maxEndDate THEN
'Yes'
ELSE
'No'
END AS t2_endDate_covers_all
FROM table2 t2
Result:
t2_startDate t2_startDate_covers_all t2_endDate t2_endDate_covers_all
-------------------------------------------------------------------------
2017-07-07 Yes 2017-09-02 No
2017-11-01 No 2017-12-12 No
Question 2 Query Answer
-- Rows in Table1 that are not fully covered by the date ranges in table2
SELECT t1.id,
t1.startDate,
t1.endDate
FROM table1 t1
WHERE NOT EXISTS (SELECT 1
FROM table2 t2
WHERE t1.startDate BETWEEN t2.startDate AND t2.endDate
AND t1.endDate BETWEEN t2.startDate AND t2.endDate)
Result:
id startDate endDate
----------------------------------
2 2017-10-01 2017-10-31
4 2018-01-01 2018-01-05

If Value is present in two consecutive months , display only one month in sql

I would want to check ID in consecutive months, IF Same ID is present in two consecutive months then consider that ID only for 1st month.
If ID's are not in consecutive month then show the distinct ID's grouped by start date month.(We consider only start date)
For example, ID 1 is present in start date months january and Feb , then Distinct count of this ID will be 1 in Jan, how ever ID 2 and 3 are
present in Jan and March and Feb and May Resp, now I would like to see this distinct count of ID in Jan and March.
Current Data
Table1:
ID StartDate EndDate
1 2017-01-12 2017-01-28
1 2017-01-19 2017-01-28
1 2017-01-29 2017-02-11
1 2017-02-01 2017-02-11
1 2017-02-19 2017-02-24
2 2017-01-12 2017-01-28
2 2017-01-19 2017-01-28
2 2017-03-09 2017-03-20
3 2017-02-12 2017-02-28
3 2017-02-19 2017-02-28
3 2017-05-05 2017-05-29
3 2017-05-09 2017-05-29
I tried with below logic bt I know I am missing on something here.
select t.* from Table1 t
join Table1 t t1
on t1.ID=t.ID
and datepart(mm,t.StartDate)<> datepart(mm,t1.StartDate)+1
Expected Result:
DistinctCount StartDateMonth(In Numbers)
1 1(Jan)
2 1(Jan)
2 3(March)
3 2(Feb)
3 5(May)
Any help is appreciated!
Here's my solution. The thinking for this is:
1) Round all the dates to the first of the month, then work with the distinct dataset of (ID, StartDateRounded). From your dataset, the result should look like this:
ID StartDateRounded
1 2017-01-01
1 2017-02-01
2 2017-01-01
2 2017-03-01
3 2017-02-01
3 2017-05-01
2) From this consolidated dataset, find all records by ID that do not have a record for the previous month (which means it's not a consecutive month and thus is a beginning of a new data point). This is your final dataset
with DatesTable AS
(
SELECT DISTINCT ID
,DATEADD(month,DateDiff(month,0,StartDate),0) StartDateRounded
,DATEADD(month,DateDiff(month,0,StartDate)+1,0) StartDateRoundedPlusOne
FROM Table1
)
SELECT t1.ID, DatePart(month,t1.StartDateRounded) AS StartDateMonth
FROM DatesTable t1
LEFT JOIN DatesTable t2
ON t1.ID = t2.ID
AND t1.StartDateRounded = t2.StartDateRoundedPlusOne
WHERE t2.ID IS NULL; --Verify no record exists for prior month
sqlfiddler for reference. Let me know if this helps
Just need to take advantage of the lag on the inner query to compare values between rows, and apply the logic in question on the middle query, and then do a final select.
/*SAMPLE DATA*/
create table #table1
(
ID int not null
, StartDate date not null
, EndDate date null
)
insert into #table1
values (1, '2017-01-12', '2017-01-28')
, (1, '2017-01-19', '2017-01-28')
, (1, '2017-01-29', '2017-02-11')
, (1, '2017-02-01', '2017-02-11')
, (1, '2017-02-19', '2017-02-24')
, (2, '2017-01-12', '2017-01-28')
, (2, '2017-01-19', '2017-01-28')
, (2, '2017-03-09', '2017-03-20')
, (3, '2017-02-12', '2017-02-28')
, (3, '2017-02-19', '2017-02-28')
, (3, '2017-05-05', '2017-05-29')
, (3, '2017-05-09', '2017-05-29')
/*ANSWER*/
--Final Select
select c.ID
, c.StartDateMonth
from (
--Compare record values to rule a record in/out based on OP's logic
select b.ID
, b.StartDateMonth
, case when b.StartDateMonth = b.StartDateMonthPrev then 0 --still the same month?
when b.StartDateMonth = b.StartDateMonthPrev + 1 then 0 --immediately prior month?
when b.StartDateMonth = 1 and b.StartDateMonthPrev = 12 then 0 --Dec/Jan combo
else 1
end as IncludeFlag
from (
--pull StartDateMonth of previous record into current record
select a.ID
, datepart(mm, a.StartDate) as StartDateMonth
, lag(datepart(mm, a.StartDate), 1, NULL) over (partition by a.ID order by a.StartDate asc) as StartDateMonthPrev
from #table1 as a
) as b
) as c
where 1=1
and c.IncludeFlag = 1
Output:
+----+----------------+
| ID | StartDateMonth |
+----+----------------+
| 1 | 1 |
| 2 | 1 |
| 2 | 3 |
| 3 | 2 |
| 3 | 5 |
+----+----------------+
Try the below query,
SELECT ID,MIN(YEARMONTH) AS YEARMONTH
FROM (
SELECT ID
,YEAR([StartDate])*100+MONTH([StartDate]) AS YEARMONTH
,LAG(YEAR([StartDate])*100+MONTH([StartDate]))
OVER(ORDER BY ID) AS PREVYEARMONTH
,ROW_NUMBER() OVER(ORDER BY ID) AS ROW_NO
FROM #Table1
GROUP BY ID,((YEAR([StartDate])*100)+MONTH([StartDate]))
) AS T
GROUP BY ID
,(CASE WHEN YEARMONTH - PREVYEARMONTH > 1 THEN ROW_NO ELSE 0 END)
ORDER BY ID
Output:
ID YEARMONTH
1 201701
2 201701
2 201703
3 201702
3 201705
Thank you all guys. most of the logic seemed to work..but I tried just with below one and I Was good with thiis.
SELECT t1.ID, DatePart(month,t1.Startdate) AS StartDateMonth
FROM DatesTable t1
LEFT JOIN DatesTable t2
ON t1.ID = t2.ID
AND DatePart(month,t1.Startdate) = DatePart(month,t2.Startdate)+1
WHERE t2.ID IS NULL;
Thanks again
Ok, I wrote my first query without checking, believed that will work correctly. This is my updated version, should be faster than other solutions
select
id
, min(st)%12 --this will return start month
, min(st)/12 + 1 --this will return year, just in case if you need it
from (
select
id, st, gr = st - row_number() over (partition by ID order by st)
from (
select
distinct ID, st = (year(StartDate) - 1) * 12 + month(StartDate)
from
#table2
) t
) t
group by id, gr

Understanding GroupBY

I have a table in SQL Server where personal entries and exits are recorded.
There are multiple hours of entry or exit on the same day. What I need is to recover the first entry and the last exit of the day.
Date hour Clock
------------------------
01/01/2017 09:00 1
01/01/2017 11:30 2
01/01/2017 17:00 2
02/01/2017 7:59 1
02/01/2017 16:00 1
I have this SQL query that works correctly.
SELECT
d.Date,
MIN(d.hour) as Entry,
MAX(dt.hour) as Exit
FROM
#temp1 AS d
LEFT JOIN
#temp1 AS dt ON d.Date = dt.Date
GROUP BY
d.Date
ORDER BY
Date DESC
BUT if I add 2 more columns to the query
SELECT
d.Date,
d.clock as ClockEntry, -- Aggregated column to display
MIN(d.hour) as Entry,
dt.clock as ClockExit, -- Aggregated column to display
MAX(dt.hour) as Exit
FROM
#temp1 AS d
LEFT JOIN
#temp1 AS dt ON d.Date = dt.Date
GROUP BY
d.Date
ORDER BY
Date DESC
I get this error:
Column '# temp1.clock' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
I just need to group by the field "date", I do not want to add more conditions to the GROUP BY.. How could I solve it?
I want this result
DATE ClockEntry Entry ClockExit Exit
-------------------------------------------------------
01/01/2017 1 09:00 2 17:00
02/01/2017 1 7:59 1 16:00
So there is an easy way to do this - use 2 ranking functions:
Partitioned by date, Ordered by the hour ascending
Partitioned by date, Ordered by the hour descending
At that point the rows can be joined where they both have a value of 1, and the dates match.
I tend to use a CTE for this:
with temp2 (MinId, MaxId, Date, Hour, Clock)
AS
(
select ROW_NUMBER() Over (partition by date order by hour),
ROW_NUMBER() Over (partition by date order by hour desc),
*
from temp1
)
select distinct
d1.Date,
d1.Clock,
d1.Hour,
d2.Clock,
d2.Hour
FROM temp2 d1
LEFT JOIN temp2 d2
ON d1.Date = d2.Date -- dates match
AND d1.MinId=d2.MaxId -- minId=earliest record MaxId=latest record
WHERE d1.MinId=1
GROUP BY Method
If you know that only a single value will get out the clock column,
just aggregate the values with a MAX or MIN aggregate function for example like this :
SELECT
d.Date,
MIN(d.clock) as ClockEntry,
MIN(d.hour) as Entry,
MAX(dt.clock) as ClockExit,
MAX(dt.hour) as Exit
FROM #temp1 AS d
LEFT JOIN #temp1 AS dt
ON d.Date= dt.Date
GROUP BY d.Date
order by Date desc
Or if you have multiple clock values and want to see them all,
add them to the GROUP BY statement :
SELECT
d.Date,
d.clock as ClockEntry,
MIN(d.hour) as Entry,
dt.clock as ClockExit,
MAX(dt.hour) as Exit
FROM #temp1 AS d
LEFT JOIN #temp1 AS dt
ON d.Date= dt.Date
GROUP BY d.Date, d.clock, dt.clock
order by Date desc
ORDER BY Method
Use a cursor or a Common Table Expression for each date to get both first entry and last exit.
First Entry for a given date
SELECT TOP 1 d.date, d.clock as ClockEntry, d.hour as Entry
FROM #temp1 AS d
WHERE d.date = #myDate
ORDER BY d.hour ASC
Last Exit for a given date
SELECT TOP 1 d.date, d.clock as ClockExit, d.hour as Exit
FROM #temp1 AS d
WHERE d.date = #myDate
ORDER BY d.hour DESC
Reference :
GROUP BY Documentation
AGGREGATE FUNCTIONS

Query to get last cost

I'm trying to do this query, in sql server, but something is wrong. Need some help...
I have a table with item movements and another one with other movements (buy) where I find the cost of each item in each date when I buy it. So, I just need first table with last cost based on the date of movement finding the cost on second table on the last date.
In other words, only must search the records from the second table with date lower than the first table date for that item and return the cost of the most recent date.
Examples:
First Table
REF DATE
1 2015-10-15
1 2015-08-30
2 2015-09-11
3 2015-05-22
2 2015-03-08
2 2015-07-15
3 2015-11-14
1 2015-11-20
Second Table (Buy)
REF DATE COST
1 2015-08-20 150
1 2015-10-12 120
2 2015-04-04 270
2 2015-06-15 280
3 2015-03-01 75
3 2015-10-17 80
I need this result:
REF DATE Cost
1 2015-10-15 120
1 2015-08-30 150
2 2015-09-11 280
3 2015-05-22 75
2 2015-03-08 -
2 2015-07-15 280
3 2015-11-14 80
1 2015-11-20 120
Any help appreciated.
You can do it using OUTER APPLY:
SELECT [REF], [DATE], [COST]
FROM Table1 AS t1
OUTER APPLY (
SELECT TOP 1 COST
FROM Table2 AS t2
WHERE t1.REF = t2.REF AND t1.DATE >= t2.DATE
ORDER BY t2.DATE DESC) AS t3
Demo here
;WITH cte AS (
SELECT ft.*,
st.[Cost],
ROW_NUMBER() OVER (PARTITION BY ft.[Ref],ft.[Date] ORDER BY st.[Date] DESC) RN
FROM FirstTable ft
LEFT JOIN SecondTable st ON ft.[Ref] = st.[Ref]
AND ft.[Date] >= st.[Date]
)
SELECT Ref,
[Date],
[Cost]
FROM cte
WHERE RN = 1
or if you dont want to use a cte.
SELECT
Ref,
[Date],
[Cost]
FROM
(SELECT
ft.*,
st.[Cost],
ROW_NUMBER() OVER (PARTITION BY ft.[Ref],ft.[Date] ORDER BY st.[Date] DESC) RN
FROM
FirstTable ft
LEFT JOIN SecondTable st ON ft.[Ref] = st.[Ref]
AND ft.[Date] >= st.[Date]
) t
WHERE
t.RN = 1

Resources