SQL, Select by the date BETWEEN the record rows - sql

The typical scenario/application is to search with the following data/table structure:
StartDate TheVersion
day1 ver1
day2 ver2
I.e., each new version number is recorded into a table with a starting date.
So for a given date, if it is BETWEEN day1 AND day2, and day1 and day2 are the most adjacent records in the table, then ver1 should be returned.
The problem is that I can't use the SQL BETWEEN clause because the day1 and day2 are actually from different rows.
How to do that? I have thought of several different ways, but none of them is not messy. Generic SQL or T-SQL appreciated. I'm on SQL Server 2008R2 BTW. Thanks.

With SQL Server 2012, you can access the next (or previous) values of a row like so:
WITH cteWithNext AS (
SELECT *, LEAD(StartDate) OVER (ORDER BY StartDate) EndDate
FROM YourTable
)
SELECT n.*
FROM cteWithNext n
WHERE dt >= n.StartDate AND dt < n.EndDate
For older SQL Server versions, you can self-join on a virtual index like so:
WITH cteNumbered AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY StartDate) num
FROM YourTable
)
SELECT n1.*
FROM cteNumbered n1
JOIN cteNumbered n2 ON n1.num+1=n2.num
WHERE dt >= n1.StartDate AND dt < n2.StartDate
With these approaches you can then use the normal compare operators (or even BETWEEN if its semantics match your needs).

If I understand correctly, you want to get the version that was in effect at the time of a Given_Date. This is a nested query that first obtains the maximum StartDate on or before Given_Date, and then returns the version attached to that date:
SELECT
Version_Table.StartDate,
Version_Table.TheVersion
FROM
Version_Table
INNER JOIN
(SELECT
Max(StartDate)
FROM
Version_Table
WHERE
StartDate <= Given_Date) maxdate on maxdate.startdate = version_table.startdate

Related

How to synthesize too many SQL calls into one

A vb.net desktop application must make hundreds of these calls on a SQL Server (2012). The declaration is composed in the application and requires hundreds (or sometimes thousands) of times from the database.
I was looking for a way to synthesize the call into a single SQL statement or stored procedure if possible.
The difference between one statement and another is the "day" variable (in the example code is '20190724') , which is incremented by one day for each call.
SELECT SUM(table1.qta)
FROM table1
WHERE (table1.id = 35)
AND ('20190724' BETWEEN table1.date1 AND table1.date2)
The expected result would be 2 columns:
day1 SumQta1
day2 SumQta2
day3 SumQta3
....
What you should do is to change your approach.
Instead of querying the sum for each day individually, create a user defined table type to hold the dates, and use it to pass a table valued parameter to the database.
Then query the table joined to the variable using sum and group by the date.
Your SQL should look something like this:
CREATE TYPE dbo.DatesList
(
Date Date NOT NULL PRIMARY KEY
);
CREATE PROCEDURE GetSums
(
#Dates dbo.DatesList readonly
)
AS
SELECT Dates.Date, sum(table1.qta)
FROM table1
JOIN #Dates As Dates
ON Dates.Date BETWEEN table1.date1 AND table1.date2
WHERE table1.id = 35
GROUP BY Dates.Date
As for how to use a table valued parameter from Vb.Net - Well, that depends on how you connect to the database in the first place. I'm sure there are plenty of resources here on stackoverflow and also out on the web to help with that part.
Well, you could build a VALUES clause in your program with the days of interest. Then left join the table and aggregate to get the sum. Back in your application you can then iterate the result day for day.
SELECT d.day,
coalesce(sum(t.qta)) qta
FROM (VALUES ('20190101'),
('20190102'),
...) d (day)
LEFT JOIN table1 t
ON t.id = 35
AND d.day BETWEEN t.date1
AND t.date2
GROUP BY d.day
ORDER BY d.day;
Another option would be the use of a recursive CTE to generate the days from a minimum and maximum and then do the join on that, again aggregating to get the sums.
WITH
d (day)
AS
(
SELECT convert(date, '20190101') day
UNION ALL
SELECT dateadd(day, 1, day)
FROM d
WHERE dateadd(day, 1, day) <= convert(date, '20190131')
)
SELECT d.day,
coalesce(sum(t.qta)) qta
FROM d
LEFT JOIN table1 t
ON t.id = 35
AND d.day BETWEEN t.date1
AND t.date2
GROUP BY d.day
ORDER BY d.day;

SQl Compare diffrence of all rows next to each other to the Time

I have Sql column with Time. I want to output them if the time difference between two columns next to each other is bigger than 1 hour
04:05:44
06:07:48
08:15:44
09:05:22
11:15:27
11:35:18
12:16:54
My OutPut should be like this
04:05:44 - 06:07:48
06:07:48 - 08:15:44
09:05:22 - 11:15:27
It is Possible to write query like this in Sql?
If possible What should i know from SQL to perform that query ?
(I know Basic Level OF SQL and I am learning)
Thanks.
You can use a CTE and ROW_NUMBER like so:
CREATE TABLE #Dates(DateColumn TIME)
INSERT INTO #Dates(DateColumn) VALUES
('04:05:44'),
('06:07:48'),
('08:15:44'),
('09:05:22'),
('11:15:27'),
('11:35:18'),
('12:16:54');*/
WITH CTE AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY DateColumn) AS RowNumb
FROM #Dates AS D
)
SELECT *
FROM CTE AS C
LEFT JOIN CTE AS C2 ON C.RowNumb = C2.RowNumb - 1
WHERE DATEDIFF(MINUTE, C.DateColumn, C2.DateColumn) >= 60
For MSSQL 2008, you can use answer provided by www.Studiosql.nl
The below code works in MSSQL Server 2012 for displaying results, as you have desired.
You can use LAG TSQL function for this.
CREATE TABLE #time (timeValue TIME)
INSERT INTO #time
values
('04:05:44'),
('06:07:48'),
('08:15:44'),
('09:05:22'),
('11:15:27'),
('11:35:18'),
('12:16:54');
SELECT temp.*
from
(SELECT timevalue, case when datediff(HH, lag(timevalue,1,timevalue) OVER(order by TIMEVALUE),timevalue) > 1 THEN CONCAT(lag(timevalue,1,timevalue) OVER(order by TIMEVALUE),' - ',timevalue) else null END AS displayvalue
FROM #time) as temp
where temp.displayValue is not null

How to subtract two row's date within same column using a SQL query showing days?

I have two dates in the same column and I need subtract the days/hours between them.
SELECT [ID],[DATA] FROM [DADOS]
I want to show like:
ID Date DiffDate
1 2017-05-12 0
2 2017-05-14 2
3 2017-05-28 14
As you're using SQL Server 2008, you'll need to use a LEFT JOIN onto the same table:
WITH VTE AS (
SELECT ID,
CONVERT(date, [date]) AS [date] --That's not confusing
FROM (VALUES(1,'20170512'),
(2,'20170514'),
(3,'20170528')) V(ID, [date]))
SELECT V1.ID,
V1.[date],
ISNULL(DATEDIFF(DAY,V2.[date],V1.[date]),0) AS DiffDate
FROM VTE V1
LEFT JOIN VTE V2 ON V1.ID -1 = V2.ID;
If, however, you have SQL Server 2012+, then you can achieve this much more easily using LAG, meaning you don't need to scan the table twice:
WITH VTE AS (
SELECT ID,
CONVERT(date, [date]) AS [date] --That's not confusing
FROM (VALUES(1,'20170512'),
(2,'20170514'),
(3,'20170528')) V(ID, [date]))
SELECT V.ID,
V.[date],
ISNULL(DATEDIFF(DAY,LAG(V.[date]) OVER (ORDER BY V.ID),V.[date]),0) AS DiffDate
FROM VTE V;
For older version of SQL server
you should use a query like below
SELECT
d1.[ID],
d1.[DATA],
DiffDate=DATEDIFF(d,ISNULL(d2.[Data],d1.[Data]),d1.[Data])
FROM [DADOS] d1
LEFT JOIN [DADOS] d2
ON d1.[id]=d2.[id]+1
You can LEAD/LAG functions if your SQL server version supports them like below
SELECT
[ID],
[DATA],
DiffDate=DATEDIFF(d,ISNULL(LAG([Data]) over( order by [Id]),[Data]),[Data])
FROM [DADOS]
Note that ISNULL is to take care of first row's 0 value
SQL Server 2008 doesn't support LAG(), which is what you really need. I would recommend using OUTER APPLY:
SELECT d.*,
COALESCE(DATEDIFF(day, dprev.DATA, d.DATA), 0)
FROM DADOS d OUTER APPLY
(SELECT TOP (1) d2.*
FROM DADOS d2
WHERE d2.DATA < d.DATA
ORDER BY DATA DESC
) dprev;

adding a row for missing data

Between a date range 2017-02-01 - 2017-02-10, i'm calculating a running balance.
I have days where we have missing data, how would I include these missing dates with the previous days balance ?
Example data:
we are missing data for 2017-02-04,2017-02-05 and 2017-02-06, how would i add a row in the query with the previous balance?
The date range is a parameter, so could change....
Can i use something like the lag function?
I would be inclined to use a recursive CTE and then fill in the values. Here is one approach using outer apply:
with dates as (
select mind as dte, mind, maxd
from (select min(date) as mind, max(date) as maxd from t) t
union all
select dateadd(day, 1, dte), mind, maxd
from dates
where dte < maxd
)
select d.dte, t.balance
from dates d outer apply
(select top 1 t.*
from t
where t.date <= d.dte
order by t.date desc
) t;
You can generate dates using tally table as below:
Declare #d1 date ='2017-02-01'
Declare #d2 date ='2017-02-10'
;with cte_dates as (
Select top (datediff(D, #d1, #d2)+1) Dates = Dateadd(day, Row_Number() over (order by (Select NULL))-1, #d1) from
master..spt_values s1, master..spt_values s2
)
Select * from cte_dates left join ....
And do left join to your table and get running total
Adding to the date range & CTE solutions, I have created Date Dimension tables in numerous databases where I just left join to them.
There are free scripts online to create date dimension tables for SQL Server. I highly recommend them. Plus, it makes aggregation by other time periods much more efficient (e.g. Quarter, Months, Year, etc....)

Spread a table in a date time interval

Hello everyone it's been some days that I use sql to make analysis and I meet all kinds of problems that I solves thanks to your forum.
Now I'd like to create a view that recuperates the interval of time and shows in detail the dates in this interval.
I have the following table:
And I want to create the view that displays the result:
For example in the player1 MyTable to play five days from 01/01/2012
to 05/01/2012. So the view displays 5 lines for player1 with the date 01/01/2012, 02/01/2012, 03/01/2012, 04/01/2012, 05/01/2012.
Thank you in advance for your help.
You have to create a common table expression that give you the date range ( i have created a date range of the current month but you can choice another range) :
WITH DateRange(dt) AS
(
SELECT CONVERT(datetime, '2012-01-01') dt
UNION ALL
SELECT DATEADD(dd,1,dt) dt FROM DateRange WHERE dt < CONVERT(datetime, '2012-01-31')
)
SELECT dates.dt AS DatePlaying, PlayerName
FROM MyTable t
JOIN DateRange dates ON dt BETWEEN t.BeginDate AND t.DateEnd
ORDER BY PlayerName, DatePlaying
Another approach to this is simply to create an enumeration table to add values to dates:
with enumt as (select row_number() over (order by (select NULL)) as seqnum
from mytable
)
select dateadd(d, e.seqnum, mt.DateBegin) as DatePlaying, mt.PlayerName
from MyTable mt join
enum e
on enumt.seqnum <= e.NumberOfPlayingDay
The only purpose of the "with" clause is to generate a sequence of integers starting at 1.