I have a SQL table similar to this:
Value StartDate EndDate
I need to get the value where the interval between StartDate and EndDate is the smallest of the whole table. How do I express it in a WHERE clause?
I am using SQL Server 2012.
Use this:
SELECT TOP 1 value
FROM tbl1
ORDER BY DATEDIFF(ms, StartDate, EndDate)
If you have mode than one row with minimum difference you can use this:
SELECT TOP 1 WITH TIES value
FROM tbl1
ORDER BY DATEDIFF(ms, StartDate, EndDate)
You should be able to use DateDiff() similar to this:
select value, startdate, enddate
from yourtable
where datediff(ss, startdate, enddate) = (select min(datediff(ss, startdate, enddate))
from yourtable)
See SQL Fiddle with Demo
SELECT TOP 1 Value
FROM YourTable
ORDER BY DATEDIFF(ms, StartDate, EndDate)
Note, there is no specification here about which Value to choose if there are multiple ones that have the same, minimum difference.
However, the DATEDIFF might not be great for performance if you have a larger number of rows in the table. If this is a frequent type of query, I would consider storing the difference as another column in the table and then use that to ORDER BY.
Related
I am trying to use declare and max in variable. This is the query below:
Declare #MAX_BUF Datetime
Set #OpeningStock = (
SELECT #MAX_BUF = MAX(end_date) FROM [IBIS].[buf_stk]
WHERE SUBSTRING(CONVERT(VARCHAR,end_date ,112 ),1,6)<SUBSTRING(CONVERT(VARCHAR,getdate() ,112 ),1,6);
SELECT COUNT(1) AS Opening_Stock
FROM [IBIS].[buf_stk] AS bs(NOLOCK)
WHERE CAST(end_date AS DATE)=#MAX_BUF
)
I am getting syntax error in '=' and '(end_date)'. Please let me know if this can be resolved.
Without seeing your table and data I can't be confident in rewriting your SQL correctly, however all you need to do to "make it work", I suspect, is amend slightly to:
SELECT #MAX_BUF = MAX(end_date) FROM [IBIS].[buf_stk]
WHERE SUBSTRING(CONVERT(VARCHAR,end_date ,112 ),1,6)<SUBSTRING(CONVERT(VARCHAR,getdate() ,112 ),1,6);
SELECT #OpeningStock=COUNT(*)
FROM [IBIS].[buf_stk] AS bs(NOLOCK) --<< Remove this!
WHERE CAST(end_date AS DATE)=#MAX_BUF
Having said that, you could combine these into a single query. Your manipulation of dates as strings is not sargable and will force the optimizer to scan your table/index.
If you want to find rows where end_date is prior to 1st day of the month then just compare as dates (assuming your end_date is an actual date data type)
where end_date < DATEADD(month, DATEDIFF(month, 0, GetDate()), 0)
And lose the nolock hint, unless you prefer your data to be randomly incorrect.
Use two statements. I'm not a fan of converting dates to strings for date arithmetic. You seem to want the maximum date from before this month. So, DATEDIFF() provides one method (and there are more efficient methods if you have an index on end_date):
Set ##MAX_BUF = (SELECT CAST(MAX(end_date) as date)
FROM [IBIS].[buf_stk]
WHERE DATEDIFF(month, end_date, getdate()) >= 1
);
Set #OpeningStock = (SELECT COUNT(1) AS Opening_Stock
FROM [IBIS].[buf_stk] bs
WHERE CAST(end_date AS DATE) = #MAX_BUF
);
You can also do this as a single statement:
select top (1)
#max_buf = CAST(end_date as date),
#OpeningStock = COUNT(*)
from FROM [IBIS].[buf_stk] bs
where end_date < dateadd(day, 1 - day(getdate()), convert(date, getdate()))
group by CAST(end_date as date)
order by CAST(end_date as date) desc;
Note that this also changes the date comparison to be friendlier to the optimizer.
You can actually do this all in one statement and one scan of the table:
SELECT #MAX_BUF = MAX(end_date), #OpeningStock = COUNT(1)
FROM
(SELECT TOP (1) WITH TIES
CAST(end_date AS date) end_date
FROM [IBIS].[buf_stk]
WHERE end_date >= DATEADD(month, 1, getdate())
ORDER BY CAST(end_date AS date) DESC
) t;
Notes:
Don't use NOLOCK, it has many unintended effects and can cause incorrect results
Switch round the WHERE predicate in order to hit an index if you have one. Don't make the server do algebra for you, it's not very good at it.
I gather that I cannot get the MAX() on some alias that I have in the select statement in sql queries?
Example:
Select
CASE WHEN CompletionDate IS NOT NULL THEN DATEDIFF(d, CreatedDate, CompletionDate) ELSE NULL END AS DaysLong
from CombinedMastervw
WHERE CreatedDate Between '03/01/2019 23:59:59.991' AND '04/01/2019 23:59:59.991'
ORDER BY MAX(dayslong)
Thus my question is on MAX(dayslong), do I have to end up doing a Max with the same code in the SELECT statement?
You should be able to use the alias.
Try:
ORDER BY dayslong
Max(dayslong) would return the single maximum value in the table you are selecting, so you cannot order by it.
If you want the max() use top (1) and order by:
Select TOP (1) DATEDIFF(day, CreatedDate, CompletionDate) DaysLong
from CombinedMastervw
WHERE CreatedDate >= '2019-03-01' AND
CreatedDate < '2019-04-02'
ORDER BY dayslong DESC;
I changed the date comparisons so they are for full days. If you really want a few seconds before midnight, then you can change back to your version.
I have list of values in a databse. There are many redundancies and I want to get rid of them. As you can see in the list below, dates [10/1/2011 - 7/1/2011) have a value of 0. I can make that into one entry with a start date of 10/1/2011 and an end date of 6/1/2011 and a value of 0 and delete all the other rows. I can do that for all the other similar values as well.
Here is my problem. I did this by writing a query that groups these together and then takes the Min(start date) as the start date and the Max(end date) as the end date. Notice that I have two groups of 0 though. When I group this in the query, the start date is 10/1/2010 and the end date is 2/1/2013. This is a problem elsewhere in my code because whenever it looks for a value at 2/1/2012 it finds 0 but it should be finding .955186.
Any suggestions on how I can write a query to account for this problem?
This is a "gaps-and-islands" problem.
If I assume that the first column is sufficient for defining the groups, then you can use a difference of row_number()s:
select min(startdate), max(enddate), value
from (select t.*,
row_number() over (order by startdate) as seqnum,
row_number() over (partition by value order by startdate) as seqnum_v
from t
) t
group by (seqnum - seqnum_v), value;
It is a gap and islands problem. You may use the following query (using SQL Server syntax, however, it can be easily altered).
select min(startdate) startDate, max(enddate) endDate, value
from
(
select *,
row_number() over (partition by value order by startDate) - (year(startDate) * 12) - month(startDate) grp
from data
) t
group by value, grp
order by startDate
It is using just one row_number() which may be better than two since the DBMS does not have to pass the table twice to generate the sequences.
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
How do I get a maximium daily value of a numerical field over a year in MS-SQL
This would query the daily maximum of value over 2008:
select
datepart(dayofyear,datecolumn)
, max(value)
from yourtable
where '2008-01-01' <= datecolumn and datecolumn < '2009-01-01'
group by datepart(dayofyear,datecolumn)
Or the daily maximum over each year:
select
datepart(year,datecolumn),
, datepart(dayofyear,datecolumn)
, max(value)
from yourtable
group by datepart(year,datecolumn), datepart(dayofyear,datecolumn)
Or the day(s) with the highest value in a year:
select
Year = datepart(year,datecolumn),
, DayOfYear = datepart(dayofyear,datecolumn)
, MaxValue = max(MaxValue)
from yourtable
inner join (
select
Year = datepart(year,datecolumn),
, MaxValue = max(value)
from yourtable
group by datepart(year,datecolumn)
) sub on
sub.Year = yourtable.datepart(year,datecolumn)
and sub.MaxValue = yourtable.value
group by
datepart(year,datecolumn),
datepart(dayofyear,datecolumn)
You didn't mention which RDBMS or SQL dialect you're using. The following will work with T-SQL (MS SQL Server). It may require some modifications for other dialects since date functions tend to change a lot between them.
SELECT
DATEPART(dy, my_date),
MAX(my_number)
FROM
My_Table
WHERE
my_date >= '2008-01-01' AND
my_date < '2009-01-01'
GROUP BY
DATEPART(dy, my_date)
The DAY function could be any function or combination of functions which gives you the days in the format that you're looking to get.
Also, if there are days with no rows at all then they will not be returned. If you need those days as well with a NULL or the highest value from the previous day then the query would need to be altered a bit.
Something like
SELECT dateadd(dd,0, datediff(dd,0,datetime)) as day, MAX(value)
FROM table GROUP BY dateadd(dd,0, datediff(dd,0,datetime)) WHERE
datetime < '2009-01-01' AND datetime > '2007-12-31'
Assuming datetime is your date column, dateadd(dd,0, datediff(dd,0,datetime)) will extract only the date part, and then you can group by that value to get a maximum daily value. There might be a prettier way to get only the date part though.
You can also use the between construct to avoid the less than and greater than.
Group on the date, use the max delegate to get the highest value for each date, sort on the value, and get the first record.
Example:
select top 1 theDate, max(theValue)
from TheTable
group by theDate
order by max(theValue) desc
(The date field needs to only contain a date for this grouping to work, i.e. the time component has to be zero.)
If you need to limit the query for a specific year, use a starting and ending date in a where claues:
select top 1 theDate, max(theValue)
from TheTable
where theDate between '2008-01-01' and '2008-12-13'
group by theDate
order by max(theValue) desc