SQL Update query to replace last 4 characters in column - sql

I am needing to replace the last four characters in a column.
It is currently a year in a char format and I need to replace with the current year.
The code I am using now is successful in removing the old year but is failing to input the new year that is needed.
My Current Code:
DECLARE #NewYear as Char
SELECT #NewYear = cast(YEAR(GETDATE()) as char(4))
UPDATE MyTable
SET ENDDATE = (substring(ENDDATE, -5, len(ENDDATE)) +#NewYear)
WHERE EndDate < StartDate
Original Value - 01/01/2017
Result - 01/01/
Desired Result - 01/01/2020
This is being used in SAS Proc SQl pass through - ANSI Standard SQL
Any help would be appreciated.
Thank you!

There is a problem in the declaration of your variable. It should be declared as char(4) - otherwise it defaults to char(1) and ends up with value '2' instead of 2020.
Also, you can use left instead o substring.
Here is one way to do it:
DECLARE #NewYear as Char(4)
SELECT #NewYear = CAST(YEAR(GETDATE()) as char(4))
UPDATE MyTable
SET ENDDATE = LEFT(ENDDATE , len(ENDDATE) - 4) + #NewYear
WHERE EndDate < StartDate
Note there is little benefit using a variable here. Very likely, the database will optimize the expression and not do the getdate()-based computation for each row.
UPDATE MyTable
SET ENDDATE = LEFT(ENDDATE , len(ENDDATE) - 4) + cast(YEAR(GETDATE()) as char(4))
WHERE EndDate < StartDate

I would suggest:
UPDATE MyTable
SET ENDDATE = STUFF(ENDDATE, 7, 4, DATENAME(year, GETDATE()))
WHERE EndDate < StartDate;
The WHERE clause makes no sense, because you are doing date comparisons.
Note that variables are not helpful here. You don't have to convert anything to a string, if you use DATENAME(), because it returns a string.
I thought for a moment they might be stored as dates, but you would never get 01/01/ as a valid date.

Related

How to get the total of a previous month having passed the current month as a parameter in a SQL Server stored procedure?

Through the following query, I am able to get the total for the current month from a data table.
SELECT SUM(MONTODEBITO) - SUM(MONTOCREDITO)
FROM PRUEBAOPEX
WHERE MONTH(FECHA) = 1 AND YEAR(FECHA) = 2021
Since I am working inside a stored procedure, I have declared a variable that is equal to the following query.
CREATE PROCEDURE SP_VALORS
(#NIVEL VARCHAR(15),
#MES INT,
#AÑO INT)
AS
BEGIN
DECLARE #TOTALMESACTUAL FLOAT, #TOTALMESPASADO FLOAT
SET #TOTALMESACTUAL = (SELECT SUM(MONTODEBITO) - SUM(MONTOCREDITO)
FROM PRUEBAOPEX
WHERE MONTH(FECHA) = #MES
AND YEAR(FECHA) = #AÑO)
END
When executing the procedure, it returns the value correctly.
I also need to declare another variable that displays the total for the month before the month I pass as a parameter in my procedure.
I have the following example:
SELECT SUM(MONTODEBITO) - SUM(MONTOCREDITO)
FROM PRUEBAOPEX
WHERE MONTH(FECHA) = 2-1 AND YEAR(FECHA) = 2021
When I send 2 (February) to 12 (December) as a parameter, it shows me correctly.
More than all my doubt is in the case of sending 1 (January) as a parameter. It should show me the total for December of the previous year. How could I declare this variable?
Thank you for any help you may receive.
You should be using date boundaries, not using syntax like WHERE MONTH(SomeDate) = #SomeMonthNumber. This means you can make a SARGable query, and you can easily apply better logic to the date boundary you want.
An easy way to get a date from a month and year value is to use DATEFROMPARTS. As you want the whole of a specific month, you can use 1 for the day of the month.
After making some other changes, as there are some odd choices in your procedure (variables that are declared and not used, a lack of the procedure returning anything), I suspect you want something like this:
--I have dropped the prefix as I recommend in the comments of your prior question.
CREATE PROCEDURE dbo.VALORS #NIVEL varchar(15), #MES int, #AÑO int, #TOTALMESACTUAL float = NULL OUTPUT AS
BEGIN
SELECT #TOTALMESACTUAL = SUM(MONTODEBITO - MONTOCREDITO) --There's no need for a subquery here
FROM dbo.PRUEBAOPEX
WHERE FECHA >= DATEFROMPARTS(#AÑO, #MES, 1)
AND FECHA < DATEADD(DAY, 1, DATEFROMPARTS(#AÑO, #MES, 1));
END;
If you wanted to get the prior month, you can easily apply a further DATEADD to subtract a month from the expression (DATEADD(MONTH, -1, <Expression>)).
Also, I suspect that float is not the right choice for your data type, and that a base-10 data type would be better; though without knowing what SUM(MONTODEBITO - MONTOCREDITO) represents, I haven't changed the data type.
You will get better performance from the current code by changing it like this:
DECLARE #Month DateTime = DatefromParts(#AÑO, #MES, 1)
SET #TOTALMESACTUAL = (SELECT SUM(MONTODEBITO) - SUM(MONTOCREDITO)
FROM PRUEBAOPEX
WHERE #Month <= FECHA and FECHA < Dateadd(month, 1, #Month)
)
The improved performance is because the FECHA column now remains unaltered for the query, and will therefore work better (at all) with any indexes you have.
More importantly, this is also very easy to convert to get the previous month:
DECLARE #Month DateTime = DatefromParts(#AÑO, #MES, 1)
Set #Month = DATEADD(month, -1, #Month)
SET #TOTALMESACTUAL = (SELECT SUM(MONTODEBITO) - SUM(MONTOCREDITO)
FROM PRUEBAOPEX
WHERE #Month <= FECHA and FECHA < Dateadd(month, 1, #Month)
)

How to properly write a SET DATE

I'm creating a function were I provide 3 inputs #FiscalYEar, #StartDate, #EndDate, I also declare a DATE parameter that the year will be -1 of #FiscalYear
SET #fyLowerBound = OCT 1 OF (#FiscalYear - 1)
how do I properly write the SET statement to make it work?
This should work:
DECLARE #FiscalYear INT = 2014,
#fyLowerBound DATE;
SET #fyLowerBound = CAST(CAST((#FiscalYear - 1) AS CHAR(4)) + '1001' AS DATE)
SELECT #fyLowerBound;
This gives 1st October 2013.
The premise being creating a string date in the format yyyyMMdd, in SQL Server this is the only culture insensitive date for DATETIME (yyyy-MM-dd will work for DATE), you then cast that string to a date (or datetime whatever your preference).
So the first step is to turn your integer date into a CHAR(4), you can then create october 1st of this year by concatenating '1001'. You now have a string that will be cast to a date.
SET #fyLowerBound = DATEADD(yy, -1, #FiscalYear)
This will give you a date that's a year less than #FiscalYear. Although I'm not entirely sure this is what you need, given that 'OCT' in your original statement.

How to filter only the date from a string stored in a varchar

Ii have values stored in the SQL Server in the following manner : 02-Jul-12 12:00:00 AM here the time and minutes, seconds can be anything like 02-Jul-12 12:15:52 PM ,02-Jul-12 6:02:12 AM so on.
I want to have a where condition which will omit the time and take the data based on the date like the following where some_Date='02-Jul-12'
How would I do this?
SELECT * FROM whatever WHERE some_Date LIKE '02-Jul-12%';
If you are on SQL2008 or later, you can cast your DATETIME to DATE.
See this post: http://blog.sqlauthority.com/2012/09/12/sql-server-get-date-and-time-from-current-datetime-sql-in-sixty-seconds-025-video/
But in a WHERE-clause it is better to search between dates, like this:
DECLARE #startDate DATETIME = '02-Jul-2012'
DECLARE #endDate DATETIME = DATEADD(DAY, 1, #startDate)
SELECT * FROM [table] WHERE [some_Date] BETWEEN #startDate AND #endDate
SELECT * FROM dbo.tbl_MyTable
WHERE
REPLACE(CONVERT(VARCHAR(9), DateTimeValueColumn, 6), ' ', '-')='02-Jul-12'
or
On chage in code is instead of using getdate function voncert you datestring in datetime format and do compare this follow query will work for you
SELECT * FROM dbo.tbl_MyTable
WHERE
CAST(CONVERT(CHAR(10), DateTimeValueColumn, 102) AS DATE) =
CAST(CONVERT(CHAR(10),GETDATE(),102) AS DATE)
If you are storing dates as characters -- which is not recommended -- you should at least use ISO format: YYYY-MM-DD hh:mm:ss. This makes the date useful for sorting and comparisons ("<" works, ">" works, "between" works as well as equals).
To extract the date, you can then use left(datestr, 10). In your format, you would use:
where left(datestr, 9) = '01-Jan-13'
If you are storing the fields as a datetime or smalldatetime, you may think they are stored as a string. They are not. They are stored as some number of days since some particular date, with day parts stored as fractional days. If you are using SQL Server 2005 or greater, then the best way is:
where cast(datetime as date) = '2013-01-01' -- I recommend ISO formats, even for constants. '20130101' is even better
To select rows with today's date (not time)
select * from myTable where datediff(dd, dateColumn, getdate()) = 0

A better way? Have date in query always use a date in the current year without maintenance

SELECT Date_Received, DateAdd(Year, DateDiff(year, Cast('3/01/2010 12:00:00AM' as DateTime) ,
GetDate())-1, Cast('3/01/2010 12:00:00AM' as DateTime)) as minimum_date
FROM [Volunteers].[dbo].[Applications]
WHERE Date_received >= DateAdd(Year, DateDiff(year, Cast('3/01/2010 12:00:00AM' as DateTime),
GetDate())-1, Cast('3/01/2010 12:00:00AM' as DateTime))
In several subqueries where I need to check that a date is within an acceptable range. I need to avoid using a simple constant as I really don't want to update it or a config file each new school year.
My current solution is to enter the date into the query and use some complicated DATEADD tricks to get the current year(or previous year) into the date I am using in the comparison. The exact code is above. Is there a cleaner way for me to do this?
Thanks
Edit
The business requirement is to find applications submitted between 3/01 and 7/31.
We are running background checks and it costs us money for each check we do. Identifying applications submitted during this time period helps us determine if we should do a full, partial or no background check. I will also need to check if dates concerning the previous year.
We will be doing this every year and we need to know if they were in the current year. Maintaining the queries each year to update the dates is not something I want to do.
So I am looking for a good technique to keep the year parts of the dates relevant without having to update the query or a config file.
Old TSQL trick: cast the date to a string in a format that starts with the four-digit year, using substring to take the first four characters of that, cast it back to a date.
Actually, the reason that it's an old TSQL trick is that, if I recall correctly, there wasn't a year() function back then. Given that there's one now, using year( getdate() ) , as others' have answered, is probably the better answer.
SELECT YEAR(GETDATE())
will give you the current year.
If you need to query by month and year a lot, you should also consider making those properties into persisted, computed fields:
ALTER TABLE dbo.Applications
ADD DateReceivedMonth AS MONTH(Date_Received) PERSISTED
ALTER TABLE dbo.Applications
ADD DateReceivedYear AS YEAR(Date_Received) PERSISTED
SQL Server will now extract the MONTH and YEAR part of your Date_Received and place them into two new columns. Those are persisted, e.g. stored along side with your table data. SQL Server will make sure to keep them up to date automatically, e.g. if you change Date_Received, those two new columns will be recomputed (but not on every SELECT).
Now, your queries might be a lot easier:
SELECT (list of fields)
FROM dbo.Applications
WHERE DateReceivedYear = 2010 AND DateReceivedMonth BETWEEN 3 AND 7
Since these are persisted fields, you can even put an index on them to speed up queries against them!
Is there any reason you cannot simply use the Year function?
Select Date_Received
, Year(GetDate())
- Year('3/01/2010 12:00:00AM') - 1
+ Year('3/01/2010 12:00:00AM')
From [Volunteers].[dbo].[Applications]
Where Date_received >= ( Year(GetDate())
- Year('3/01/2010 12:00:00AM') - 1
+ Year('3/01/2080 12:00:00AM') )
Another way would be to use a common-table expression
With Years As
(
Select Year(GetDate()) As CurrentYear
, Year('3/01/2010 12:00:00AM') As ParamYear
, Year('3/01/2080 12:00:00AM') As BoundaryYear
)
Select Date_Received
, CurrentYear - Years.ParamYear - 1 + Years.ParamYear
From [Volunteers].[dbo].[Applications]
Cross Join Years
Where Date_received >= ( Years.CurrentYear
- Years.ParamYear - 1 + Years.BoundaryYear )
TSQL Function returns four digit year dependent on year. This behaves much like the standard SQL YEAR functions [Thomas - nod] which 'CAN' be tweaked using sp_configure on the advanced options, however, the code below is provided as a framework for CUSTOM requirements and can be modified as required. e.g. return as int, use with standard DATETIME functions in SQL to achieve what is needed. e.g. When working with "dirty" data I had to migrate, I used it with the PATINDEX() function to strip non-numeric values etc.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Andrew McLintock
-- Create date: 13 July 2016
-- Description: Return 4-digit YEAR
-- =============================================
/*
SELECT Staging.fn_4year('06')
SELECT Staging.fn_4year('56')
SELECT Staging.fn_4year('99')
SELECT Staging.fn_4year('1906')
SELECT Staging.fn_4year('2025')
*/
CREATE FUNCTION Staging.fn_4year
(
#year_in varchar (4)
)
RETURNS varchar(4)
AS
BEGIN
DECLARE #yeartmp int, #Retval varchar(4)
SET #yeartmp = CAST(REPLACE(#year_in,' ','') AS INT)
IF LEN(CAST(#yeartmp AS Varchar)) = 4
BEGIN
Return cast(#yeartmp as varchar(4))
END
IF LEN(#year_in) = 2
BEGIN
SET #Retval = CAST(iif(#yeartmp > 49, #yeartmp + 1900, #yeartmp + 2000) AS varchar(4))
END
RETURN #Retval
END
GO
Consider keeping a set of datetime variables help readability and maintainability. I'm not sure I've captured all your requirements, especially with reference to 'previous year'. If it's as simple as finding applications submitted between 3/01 and 7/31, then this should work. If you need to determine those that were submitted Aug 1 (last year) through Feb 28 (current year), this solution could be modified to suit.
DECLARE #Start smalldatetime, #End smalldatetime, #CurrYear char(4)
SELECT #CurrYear = YEAR(getdate())
SELECT #Start = CAST( 'mar 1 ' + #CurrYear as smalldatetime),
#End = CAST( 'jul 31 ' + #CurrYear as smalldatetime)
SELECT *
FROM Applications
WHERE Date_Received
BETWEEN #Start AND #End

Best practice: Searching table against day, month or year

I've got a table with a "date" column, where a user input will be queried against (using stored procedure)..and results will be shown on a datagrid..
now a user can either enter a year, Year/month , Year/month/day.. (from drop down lists)
i know there r many possible ways to handle the different queries.. however i am trying to figure out which would be best practice:
Solution 1: having 3 different stored procedures , one for every case.
Solution 2: having 1 stored procedure, with 1 extra parameter as searchlvl , then using IF ELSE statements to decide what lvl of search should be applied.
Solution 3: having 1 stored procedure, and sending the datetime as 3 different parameters , then checking IF parameter is null , and using that to decide search lvl
Solution 4: your suggestions :)
NOTE: i know how to do partial search(using datepart), my question is about best practice among the 3 solutions i offered or any other solution offered in the answers..
Like which would be faster, lighter on database and such..
and which would be slower, heavier..
There are no levels.
When user selects year 2009, you search rows where date >= '2009.01.01 00:00' and < '2010.01.01 00:00'.
When he selects month 01 of year 2009 you search where date >= '2009.01.01 00:00' and < '2009.02.01 00:00'.
Of course you don't pass dates as strings, you should use CONVERT() or pass dates as DATETIME type. This is universal solution and will be fast, because it will use indexes. You can create stored procedure that takes two dates, it will allow to search by every date range, not only year/month/day.
I'd do none of the above.
You should design you stored procedure to take three different ints, one for day, one for month and one for year. Leave the parameters nullable, but establish a convention so only meaningful parameter combinations are used. Then you construct a MINDATE and MAXDATE from the parameters.
Searching Datetime columns based on day/year/month requires a query like:
SELECT * FROM table WHERE date > MINDATE AND date < MAXDATE
which is pretty inefficient but not a definite problem.
Another approach (if the table is huge) would be to create an indexed view with year/month/day integer columns and search for exact matches there. To create such a view use DATEPART().
You can use datepart to get the parts of you date you want to filter against as
declare #table table(
DateVal DATETIME
)
INSERT INTO #table SELECT GETDATE()
DECLARE #Year INT,
#Month INT,
#Day INT
SELECT #Year = 2009
SELECT DATEPART(YY, DateVal) DateYear,
DATEPART(MM, DateVal) DateMonth,
DATEPART(DD, DateVal) DateDay,
*
FROM #table
WHERE (DATEPART(YY, DateVal) = #Year OR #Year IS NULL)
AND (DATEPART(MM, DateVal) = #Month OR #Month IS NULL)
AND (DATEPART(DD, DateVal) = #Day OR #Day IS NULL)
I'd pass in year/month/date as separate parameters into one stored proc, say default of NULL.
Then, I'd use DATEADD to build up from/to datetimes and use that
...
SELECT
#ToYear = ISNULL(#ToYear, DATEPART(year, GETDATE()), --or some base value, such as "1900"
#ToMonth = ...
...
SELECT
#DateTo = DATEADD(year, #ToYear, DATEADD(month, #ToMonth, DATEADD(day, #ToDay, 0), 0), 0)
....
SELECT * FROM myTable WHERE DateColumn >= #DateFrom AND DateColumn <= #DateTo
I would not use any functions on columns or conditional logic to switch between selects