How to sort varchar string properly with numeric values on both ends? - sql

I'm building a Common Table Expression (CTE) in SQL Server 2008 to use in a PIVOT query.
I'm having difficulty sorting the output properly because there are numeric values that sandwich the string data in the middle. Is it possible to do this?
This is a quick and dirty example, the real query will span several years worth of values.
Example:
Declare #startdate as varchar(max);
Declare #enddate as varchar(max);
Set #startdate = cast((DATEPART(yyyy, GetDate())-1) as varchar(4))+'-12-01';
Set #enddate = cast((DATEPART(yyyy, GetDate())) as varchar(4))+'-03-15';
WITH DateRange(dt) AS
(
SELECT CONVERT(datetime, #startdate) dt
UNION ALL
SELECT DATEADD(dd,1,dt) dt FROM DateRange WHERE dt < CONVERT(datetime, #enddate)
)
SELECT DISTINCT ',' + QUOTENAME((cast(DATEPART(yyyy, dt) as varchar(4)))+'-Week'+(cast(DATEPART(ww, dt) as varchar(2)))) FROM DateRange
Current Output:
,[2012-Week48]
,[2012-Week49]
,[2012-Week50]
,[2012-Week51]
,[2012-Week52]
,[2012-Week53]
,[2013-Week1]
,[2013-Week10]
,[2013-Week11]
,[2013-Week2]
,[2013-Week3]
,[2013-Week4]
,[2013-Week5]
,[2013-Week6]
,[2013-Week7]
,[2013-Week8]
,[2013-Week9]
Desired Output:
,[2012-Week48]
,[2012-Week49]
,[2012-Week50]
,[2012-Week51]
,[2012-Week52]
,[2012-Week53]
,[2013-Week1]
,[2013-Week2]
,[2013-Week3]
,[2013-Week4]
,[2013-Week5]
,[2013-Week6]
,[2013-Week7]
,[2013-Week8]
,[2013-Week9]
,[2013-Week10]
,[2013-Week11]
EDIT
Of course after I post the question my brain started working. I changed the DATEADD to add 1 week instead of 1 day and then took out the DISTINCT in the select and it worked.
DECLARE #startdate AS VARCHAR(MAX);
DECLARE #enddate AS VARCHAR(MAX);
SET #startdate = CAST((DATEPART(yyyy, GetDate())-1) AS VARCHAR(4))+'-12-01';
SET #enddate = CAST((DATEPART(yyyy, GetDate())) AS VARCHAR(4))+'-03-15';
WITH DateRange(dt) AS
(
SELECT CONVERT(datetime, #startdate) dt
UNION ALL
SELECT DATEADD(ww,1,dt) dt FROM DateRange WHERE dt < CONVERT(datetime, #enddate)
)
SELECT ',' + QUOTENAME((CAST(DATEPART(yyyy, dt) AS VARCHAR(4)))+'-Week'+(CAST(DATEPART(ww, dt) AS VARCHAR(2)))) FROM DateRange

I can't see the sample SQL code (that site is blacklisted where I am).
Here is a trick for sorting that data in the proper order is to use the length first and then the values:
select col
from t
order by left(col, 6), len(col), col;

Have you considered to sort on two temporary columns (year in smallint and week in tinyint to save space … or directly using the datepart integer if space is not a problem to you and you prefer fast run) along with the use of "order by year, week" ?
If you store dates using a more suitable type (what I suggest), it would then become :
WITH [Define the CTE expression name and column list]
AS
(
SELECT CAST(DATEPART(yyyy, dt) as smallint(4)) year, cast(DATEPART(ww, dt) as tinyint(2)) week, [your columns here]
FROM DateRange WHERE dt < #enddate
)
[Define the outer query referencing the CTE name]
ORDER BY year, week;
GO
Also, please note that string operations will slow your queries so avoid them when possible !

I like Gordon's answer, but if you were hell-bent on text manipulation in your order by:
ORDER BY CAST(REPLACE(LEFT('[2012-Week48]',5),'[','')AS INT)
,CAST(REPLACE(RIGHT('[2012-Week48]',CHARINDEX('Week','[2012-Week48]')-4),']','') AS INT)

Here is another option converting the beginning and ending parts of the column to integer.
SELECT *
FROM YourTable
ORDER BY CAST(SUBSTRING(yourcolumn,1,4) as int),
CAST(SUBSTRING(yourcolumn,CHARINDEX('Week',yourcolumn)+4,len(yourcolumn)) as int)
SQL Fiddle Demo
This will work assuming the format of the data is always the same.

Since you are using dt to generate the string, you should sort by using the date's parts:
WITH DateRange(dt) ...
SELECT DISTINCT ',' + QUOTENAM...
ORDER BY DATEPART(yyyy, dt), DATEPART(ww, dt)

I needed to change the DATEADD portion of the query and remove the DISTINCT. Once changed the order sorted properly on it's own
DECLARE #startdate AS VARCHAR(MAX);
DECLARE #enddate AS VARCHAR(MAX);
SET #startdate = CAST((DATEPART(yyyy, GetDate())-1) AS VARCHAR(4))+'-12-01';
SET #enddate = CAST((DATEPART(yyyy, GetDate())) AS VARCHAR(4))+'-03-15';
WITH DateRange(dt) AS
(
SELECT CONVERT(datetime, #startdate) dt
UNION ALL
SELECT DATEADD(ww,1,dt) dt FROM DateRange WHERE dt < CONVERT(datetime, #enddate)
)
SELECT ',' + QUOTENAME((CAST(DATEPART(yyyy, dt) AS VARCHAR(4)))+'-Week'+(CAST(DATEPART(ww, dt) AS VARCHAR(2)))) FROM DateRange

Related

Get all startdate and enddate as comma seperate in stuff query

Hi I have startdate as GetDate()-5 and enddate as GetDate()
I am passing this date in variable i.e. this dates are not stored in any table so how can I get records as
GetDate()-5,GetDate()-4,GetDate()-3,GetDate()-2,GetDate()-1,GetDate()
in sql server
I have tried below query which is totally wrong, Can any one please suggest me
declare #startdate date
declare #enddate date
set #startdate =GETDATE()-5
set #enddate =GETDATE()
STUFF((SELECT DISTINCT ','+CONVERT(VARCHAR, CONVERT(DATE,GETDATE()-5))
FROM [Table]--What should be here?
WHERE CONVERT(date,AETE.TimeEntryDate) between CONVERT(DATE,#startdate)
AND CONVERT(DATE, #enddate)
FOR XML PATH('')), 1,1,'')
A CTE might come in handy here:
declare #startdate date = dateadd(D, -5, getdate())
declare #enddate date = getdate();
with cte(d) as (
select #startdate
union all
select dateadd(D, 1, d)
from cte
where d < #enddate
)
select stuff((
select ', ' + convert(nvarchar(255), d, 101)
from cte
for xml path('')), 1, 2, '')
gives you:
08/09/2018, 08/10/2018, 08/11/2018, 08/12/2018, 08/13/2018, 08/14/2018
Note that this gives you 6 days---the 5 days less than today, as well as today's date.

Get first date of each month SQL Server 2008 R2

I need to generate first date of each month starting 01/01/2000 to 12/31/2099.
Issue: Below query works in Management Studio. However, it doesn't work in SSRS. Unfortunately, I cannot create a function or a view because I do not have access to do so.
Is there an alternative way to write this query that would work with SSRS? SSRS does not like use of "TOP" in my query.
Error related to "TOP":
A TOP or FETCH clause contains an invalid value.
declare #pStartDate date = '01/01/2000'
declare #pEndDate date = '12/31/2099'
;With months(DATE, MONTH, YEAR)
as(
SELECT TOP (DATEDIFF(MONTH, #pStartDate, #pEndDate)+1)
TheDate = DATEADD(MONTH, number, #pStartDate),
TheMonth = MONTH(DATEADD(MONTH, number, #pStartDate)),
TheYear = YEAR(DATEADD(MONTH, number, #pStartDate))
FROM [master].dbo.spt_values
WHERE [type] = N'P' ORDER BY number
)
select cast(DATE as Date) as Date from months
I just rearranged your code in a simpler and efficient way, without TOP clause:
declare #pStartDate date = '01/01/2000'
declare #pEndDate date = '12/31/2099'
SELECT DATEADD(MONTH, number, #pStartDate) as Date
FROM [master].dbo.spt_values
WHERE [type] = N'P'
AND number < DATEDIFF(MONTH, #pStartDate, #pEndDate)+1
ORDER BY number
When you add one month to a date it keeps the day. Day 30 and 31 are special cases, try it yourself.
Try this simple, recursive CTE to get what you want:
declare #pStartDate date = '01/01/2000'
declare #pEndDate date = '12/31/2099'
;with FirstDayOfMonth as(
select #pStartDate as [firstDayOfMonth]
union all
select DATEADD(month, 1, [firstDayOfMonth]) from FirstDayOfMonth
where DATEADD(month, 1, [firstDayOfMonth]) < #pEndDate
)
select * from FirstDayOfMonth
option (maxrecursion 0)

SQL query to get result for a date range when table got Year and Month fields

I have a table with Year and Month fields. I need to select the rows between two particular dates. How do I set this in the where condition?
You are a bit unclear on what "between" means in this context. I'm pretty sure that datefromparts() can be quite helpful:
select t.*
from t
where #date_start <= datefromparts(year, month, 1) and
#date_end >= datefromparts(year, month, 1);
You might also find eomonth() helpful, if the end of the month is an important date.
You can try with help month(),year() function of date
Here is the example:
select * from t where (t.month=month('2017-01-01') and t.year=year('2017-01-01')) and (t.month=month('2017-05-01') and t.year=year('2017-05-01'));
Below is the link for month() in sql.
https://learn.microsoft.com/en-us/sql/t-sql/functions/month-transact-sql
This might help you in solving your problem.
you can also try something like:
select
d.*
from
(select
t.*,
cast(rtrim(cast([month] as char(2))) + '/1/' + cast ([year] as char(4)) as date) as dt) d
where
dt >= #dateStart and
dt <= #dateEnd
If the year and month values are integers, you can cast them to varchar, then date, and use BETWEEN. An example with declaring startdate and enddate variables:
DECLARE #startdate date = '2015-07-01'
,#enddate date = '2016-07-01'
SELECT *
FROM #myTable
WHERE CAST(CAST(Year as varchar) + '-' + CAST(Month as varchar) + '-01' as date)
BETWEEN #startdate AND #enddate
If they are chars or varchars you need only cast to date and use BETWEEN:
DECLARE #startdate date = '2015-07-01'
,#enddate date = '2016-07-01'
SELECT *
FROM #myTable
WHERE CAST(Year + '-' + Month + '-01' as date)
BETWEEN #startdate AND #enddate
Try with "Between".
Something like this:
SELECT SALES_AMOUNT FROM TABLE WHERE YEAR BETWEEN 2015 AND 2016 AND MONTH BETWEEN 6 AND 8

compute start date of sub-date-range from given main date range in sql server

input is a main date range which is stored in myTbl.StartDate and myTbl.EndDate columns values.
for a given constant value as divider, I need to compute start date of sub-date-ranges gained from dividing main date range to sub-date-ranges with constant value length.
examples are best explanations:
for main date range between '2017-1-1' to '2017-1-22' and const_val = 6 output is:
2017-1-1
2017-1-7 (1 + 6)
2017-1-13 (7 + 6)
2017-1-19 (13 + 6)
its noticable that the SQL user is not administrator(I can't use system tables)
You can use a recursive cte to do this:
DECLARE #startDate DATE = '20170101' ,
#endDate DATE = '20170122' ,
#Const_val INT = 6;
WITH cte
AS ( SELECT #startDate AS DateVal
UNION ALL
SELECT DATEADD(DAY, #Const_val, cte.DateVal)
FROM cte
WHERE DATEADD(DAY, #Const_val, cte.DateVal) <= #endDate
)
SELECT *
FROM cte;
Produces:
DateVal
==========
2017-01-01
2017-01-07
2017-01-13
2017-01-19
The first part of the cte gets the first date:
SELECT #startDate AS DateVal
Then it adds 6 days with the UNION ALL until the WHERE condition is met.
You can try the following:
DECLARE #StartDate DATE = CONVERT(date, '2017-01-01');
DECLARE #EndDate DATE = CONVERT(date, '2017-01-22');
DECLARE #FixDateDiff int = 6;
WITH cte AS (
SELECT #StartDate AS MyDate
UNION ALL
SELECT DATEADD(d, 1, MyDate)
FROM cte
WHERE DATEADD(d, 1, MyDate) <= CONVERT(date, #EndDate)
)
SELECT MyDate
FROM cte
WHERE DATEDIFF(d, #StartDate, MyDate)%#FixDateDiff = 0
However, instead of the fixed dates I assigned to the variables, simply replace the variables with your StartDate and EndDate column and extend the cte accordingly.

generate sql temp table of sequential dates to left outer join to

i have a table of data that i want to select out via stored proc such that users can connect a MS excel front end to it and use the raw data as a source to graph.
The problem with the raw data of the table is there exist gaps in the dates because if there is no data for a given day (there is no records with that date) then when users try to graph it it creates problems.
I want too update my stored proc to left outer join to a temp table of dates so that the right side will come in as nulls that i can cast to zero's for them to have a simple plotting experience.
how do i best generate a one field table of dates between a start and end date?
In SQL Server 2005 and up, you can use something like this (a Common Table Expression CTE) to do this:
DECLARE #DateFrom DATETIME
SET #DateFrom = '2011-01-01'
DECLARE #DateTo DATETIME
SET #DateTo = '2011-01-10'
;WITH DateRanges AS
(
SELECT #DateFrom AS 'DateValue'
UNION ALL
SELECT DATEADD(DAY, 1, DateValue)
FROM DateRanges
WHERE DateValue < #DateTo
)
SELECT * FROM DateRanges
You could LEFT OUTER JOIN this CTE against your table and return the result.
Another way to do it is with a memory table. It won't choke due to recursion limitations like some of the above solutions.
DECLARE #dates AS TABLE ([Date] date);
DECLARE #date date = {d '2010-10-01'};
DECLARE #endDate date = {d '2010-11-01'};
while (#date < #endDate)
BEGIN
INSERT INTO #dates VALUES (#date);
SET #date = dateadd(DAY, 1, #date)
END
SELECT * FROM #dates;
SQL Fiddle
One way would be with a CTE:
with cte_dates as (
select cast('20110119' as datetime) as [date]
union all
select dateadd(dd, 1, [date])
from cte_dates
where dateadd(dd, 1, [date]) <= '20111231'
)
select [date], YourColumn
from cte_dates
left join YourTable
on ...
option (maxrecursion 0);