I am creating a stored procedure where I have 3 "dynamic" parameters, first is an int and the second and third are datetime.
How the output looks like:
17 | 2016-01-24 11:28:22.233 | 2016-05-22 09:07:04.220
Due to my final application that is connecting to this stored procedure, I have to treat the date time values as nvarchar to be able to use the LIKE operator, so that the user can easily filter the throw those date time's
What I have done is to create query that works fine gets back the wanted output
SELECT *
FROM WATCHDOG.WatchdogUsr.WebsiteFailureLog
WHERE websiteID = 17
AND (((CONVERT(NVARCHAR(45), FailureLogStart, 121) LIKE '%2016-01%')))
But my stored procedure has an issue:
Msg 241, Level 16, State 1, Procedure WatchDogDataCollector, Line 5
Conversion failed when converting date and/or time from character string.
This is my stored procedure:
ALTER PROCEDURE [dbo].[WatchDogDataCollector]
#websiteID int = NULL,
#FailureLogStart NVARCHAR = NULL
AS
SELECT *
FROM WATCHDOG.WatchdogUsr.WebsiteFailureLog
WHERE websiteID = ISNULL(#websiteID,websiteID)
AND (((CONVERT(NVARCHAR(45), FailureLogStart, 121) LIKE '%'+#FailureLogStart+'%')))
Table schema:
Well, if you can't figure out what date part(s) the user enters, your only option is indeed to cast the datetime value to a string:
ALTER PROCEDURE [dbo].[WatchDogDataCollector]
#websiteID int = NULL,
#FailureLogStart VARCHAR(23) = NULL
AS
SELECT *
FROM WATCHDOG.WatchdogUsr.WebsiteFailureLog
WHERE websiteID = ISNULL(#websiteID,websiteID)
AND (((CONVERT(CHAR(23), FailureLogStart, 121) LIKE '%'+#FailureLogStart+'%')))
Note: the number of chars in a string representing datetime with style 121 is 23. there is no point in casting to nvarchar(45).
Related
I have a table that tracks inserts, updates, and deletions to another table. This pattern has been used for years but has suddenly introduced an error when we added tracking of a DATE column.
This is not a string format issue. My input data is in ISO-8601 standard format.
I've cut out all but the necessary parts of the code to demonstrate the issue.
CREATE TABLE dbo.ChangeTrackingTable
(
oldValue VARCHAR(100) NULL,
newValue VARCHAR(100) NULL
);
GO
CREATE PROCEDURE dbo.usp_TestProcedure
(
#name VARCHAR(100),
#dateOfBirth DATE
)
AS
BEGIN
INSERT INTO dbo.ChangeTrackingTable
(
oldValue,
newValue
)
SELECT
List.oldFieldValue,
List.newFieldValue
FROM
(VALUES
(NULL, #name, IIF(#name IS NOT NULL, 1, 0)),
(NULL, #dateOfBirth, IIF(#dateOfBirth IS NOT NULL, 1, 0))
) AS List (oldFieldValue, newFieldValue, hasChanges)
WHERE
List.hasChanges = 1
END;
GO
The VALUES list is used to dynamically determine which columns are being touched.
When I execute the sproc with just the date, everything works fine.
DECLARE #date DATE = GETDATE();
EXEC dbo.usp_TestProcedure
#name = NULL,
#dateOfBirth = #date;
/*
oldValue newValue
NULL 2019-03-27
*/
But if I try to execute it with any value supplied for #name, whether or not a value for #date is supplied, I get the following error.
DECLARE #date DATE = GETDATE();
EXEC dbo.usp_TestProcedure
#name = 'Name',
#dateOfBirth = #date;
--Conversion failed when converting date and/or time from character string.
If I supply hard-coded values directly to the INSERT statement, it works fine. So I can know it's not happening at the table level on insert.
If I add an explicit cast to this line
(NULL, CAST(#dateOfBirth AS VARCHAR(100)), IIF(#dateOfBirth IS NOT NULL, 1, 0))
it works as well.
The problem occurs with DATETIMEOFFSET, DATETIME, and DATETIME2 types as well, so my use of DATE is not the issue.
My question is why am I getting a string > datetime conversion error when I'm trying to read a DATE value to be inserted into a VARCHAR column, but only in a VALUES list when there exists another non-date value in the result-set for List?
In refining the question itself to its basic structure, I discovered the underlying answer.
When using the pattern
SELECT *
FROM
(VALUES
(NULL, #name, IIF(#name IS NOT NULL, 1, 0)),
(NULL, #dateOfBirth, IIF(#dateOfBirth IS NOT NULL, 1, 0))
) AS List (oldFieldValue, newFieldValue, hasChanges)
SQL Server will need to create a table in memory, and each of those columns needs an assigned datatype. This datatype is not assigned arbitrarily, but methodically according to a well-documented order of Data Type Precedence.
All the date-types appear very high in this list, so my use of DATE takes precedence over any CHAR or VARCHAR type that also appears in the table.
The column in my List table is assigned the highest-precedence type: DATE.
The error is being thrown when the VARCHAR value, 'Name' is being implicitly cast to the data-type of the column, which is DATE. Not when the #date parameter is inserted into the VARCHAR column of the table.
I created a database with NBA player statistics just to practice SQL and SSRS. I am new to working with stored procedures, but I created the following procedure that should (I think) allow me to specify the team and number of minutes.
CREATE PROCEDURE extrapstats
--Declare variables for the team and the amount of minutes to use in --calculations
#team NCHAR OUTPUT,
#minutes DECIMAL OUTPUT
AS
BEGIN
SELECT p.Fname + ' ' + p.Lname AS Player_Name,
p.Position,
--Creates averages based on the number of minutes per game specified in #minutes
(SUM(plg.PTS)/SUM(plg.MP))*#minutes AS PTS,
(SUM(plg.TRB)/SUM(plg.MP))*#minutes AS TRB,
(SUM(plg.AST)/SUM(plg.MP))*#minutes AS AST,
(SUM(plg.BLK)/SUM(plg.MP))*#minutes AS BLK,
(SUM(plg.STL)/SUM(plg.MP))*#minutes AS STL,
(SUM(plg.TOV)/SUM(plg.MP))*#minutes AS TOV,
(SUM(plg.FT)/SUM(plg.MP))*#minutes AS FTs,
SUM(plg.FT)/SUM(plg.FTA) AS FT_Percentage,
(SUM(plg.FG)/SUM(plg.MP))*#minutes AS FGs,
SUM(FG)/SUM(FGA) as Field_Percentage,
(SUM(plg.[3P])/SUM(plg.MP))*#minutes AS Threes,
SUM([3P])/SUM([3PA]) AS Three_Point_Percentage
FROM PlayerGameLog plg
--Joins the Players and PlayerGameLog tables
INNER JOIN Players p
ON p.PlayerID = plg.PlayerID
AND TeamID = #team
GROUP BY p.Fname, p.Lname, p.Position, p.TeamID
ORDER BY PTS DESC
END;
I then tried to use the SP by executing the query below:
DECLARE #team NCHAR,
#minutes DECIMAL
EXECUTE extrapstats #team = 'OKC', #minutes = 35
SELECT *
When I do that, I encounter this message:
Msg 263, Level 16, State 1, Line 5
Must specify table to select from.
I've tried different variations of this, but nothing has worked. I thought the SP specified the tables from which to select the data.
Any ideas?
Declaring the stored procedure parameters with OUTPUT clause means the values will be returned by the stored procedure to the calling function. However you are using them as input parameters, please remove the OUTPUT clause from both input parameters and try.
Also remove the SELECT * in your execute statement, it is not required, the stored procedure will return the data as it has the select statement.
SELECT AcquistionDate = CONVERT(NVARCHAR,
CASE
WHEN D.CalendarDate NOT IN ('01/01/1900','12/31/9999')
THEN D.CalendarDate
WHEN ac.FirstAccountOpenDate NOT IN ('01/01/1900', '12/31/9999')
THEN ac.FirstAccountOpenDate
END, 126) + 'Z
from TABLE;
I am getting an error
Msg 8114, level 16, state 5, line 1
Error converting data type varchar to bigint
The CONVERT(NVARCHAR....) takes a date and converts it to NVARCHAR in format 126 or yyyy-mm-ddThh:mi:ss.mmm
But then you have +'Z from table I will assume that is a copy paste mistake and that the single quote is not supposed to be there and it is actually + Z from Table in which case I will assume Z is a bigint. Which would mean you are trying to add a NVARCHAR to a BIGINT, which means the NVARCHAR is first tried to be converted to BIGINT which will fail because it is a string representation of a date and not numeric.
The other possibly without knowing more about your code or your DB structure is that one of the date fields in the CASE expression is actually a BIGINT and when you are comparing it to a date representation of a string the conversation fails there.
But I do agree with Chris Berger's comment that this looks like it should be written very differently.
I'm trying to move the data in the first table to the second table with conversion of some data type.
I am using a stored procedure to accomplish this but I keep getting an error.
Here is the error message
Msg 245, Level 16, State 1, Procedure sp_Load_Dim_Airports, Line 5
Conversion failed when converting the varchar value '39.63' to data type smallint.
See the datatype below that we need to convert to. Longitude and latitude need to be concatenated to form a new column called LatLong.
How do I convert and transfer the data from first table to into second table? Then how would I concatenate and have fewer number of columns in the second table than the first table?
Edit:
This is the proc I am using but I get error Msg 245, Level 16, State 1, Procedure sp_Load_Dim_Airports, Line 5 Conversion failed when converting the varchar value '39.63' to data type smallint.
ALTER PROC [dbo].[sp_Load_Dim_Airports]
AS
BEGIN
INSERT INTO [AIRPORTS-DM].dbo.Dim_Airports (AirportId, AirportName, City, Country, Altitude, Latitude, Longitude, TimeZone, LatLong)
SELECT * FROM DBO.Src_Airports
DECLARE #NegativeNumeric decimal (10,2)
DECLARE #Latitude decimal (10,2)
SELECT #NegativeNumeric = -1 * CAST (REPLACE(#NegativeNumeric ,'-','') AS DECIMAL (10,2))
SELECT CONVERT(Decimal (10,2), CONVERT(Varchar (50), #Latitude))
END
EXEC [dbo].[sp_Load_Dim_Airports]
I have a stored procedure which drops a table if it exists, then it re-creates the table & fills it with relevant data, a friend of mine has about the same code, the only real difference is in the column headers for the table.
As an illustration, here's how mine looks (not really, just a representation).
+----+-----+-----+--------+
| ID | Foo | Bar | Number |
+----+-----+-----+--------+
| 1 | x | x | 0 |
| 2 | x | x | 1 |
+----+-----+-----+--------+
And here's what his might look like
+----+--------+--------+-----+--------+
| ID | BarFoo | FooBar | Num | Suffix |
+----+--------+--------+-----+--------+
| 1 | x | x | 0 | a |
| 2 | x | x | 1 | b |
+----+--------+--------+-----+--------+
Again, these are merely representations of the situation.
As this is to be a school assignment, the teacher will be creating & executing both SP's, however when creating the SP after using another, I get this error:
Msg 207, Level 16, State 1, Procedure XYZ, Line 59
Invalid column name 'Foo'.
Msg 213, Level 16, State 1, Procedure XYZ, Line 61
Column name or number of supplied values does not match table definition.
However, at the start of both stored procedures, we have this:
CREATE PROCEDURE XYZ
AS
BEGIN
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = 'TABLENAME'
AND xtype = 'u')
DROP TABLE TABLENAME;
From what I understand, this should remove the entire table? Including table/column definitions & data?
The only fix I've found so far, is to either execute the DROP TABLE separately before creating the stored procedure, which won't work for us as it really has to be within the stored procedure.
Help would be much appreciated :)
EDIT: Here's my ACTUAL code, apart from comments, this is exactly how it looks in my script (excluding other code behind it).
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = 'BerekenStatistiek'
AND xtype = 'p')
DROP PROCEDURE BerekenStatistiek;
GO
CREATE PROCEDURE BerekenStatistiek
#jaar INT=0
AS
BEGIN
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = 'Statistiek'
AND xtype = 'u')
DROP TABLE Statistiek;
DECLARE #year AS NVARCHAR (4);
SET #year = CONVERT (NVARCHAR (4), #jaar);
SELECT *,
CAST (Kost - Korting + Freight AS MONEY) AS Netto,
'' AS Richting
INTO Statistiek
FROM (SELECT O.Kwartaal,
CAST (SUM(O.Kost) AS MONEY) AS Kost,
CAST (SUM(O.Korting) AS MONEY) AS Korting,
CAST (SUM(O.Freight) AS MONEY) AS Freight
FROM (SELECT CASE
WHEN CONVERT (NVARCHAR (8), OrderDate, 112) BETWEEN #year + '0101' AND #year + '0331' THEN 1
WHEN CONVERT (NVARCHAR (8), OrderDate, 112) BETWEEN #year + '0401' AND #year + '0630' THEN 2
WHEN CONVERT (NVARCHAR (8), OrderDate, 112) BETWEEN #year + '0701' AND #year + '0930' THEN 3
WHEN CONVERT (NVARCHAR (8), OrderDate, 112) BETWEEN #year + '1001' AND #year + '1231' THEN 4
END AS 'Kwartaal',
ROUND(UnitPrice * Quantity, 2) AS Kost,
Round((UnitPrice * Quantity) * Discount, 2) AS Korting,
Freight
FROM Orders AS O
INNER JOIN
OrderDetails AS Od
ON O.OrderID = Od.OrderID
WHERE CONVERT (NVARCHAR (4), OrderDate, 112) = #year) AS O
GROUP BY O.Kwartaal) AS O1;
ALTER TABLE Statistiek ALTER COLUMN Kwartaal INT NOT NULL;
ALTER TABLE Statistiek ALTER COLUMN Richting NVARCHAR (8);
ALTER TABLE Statistiek
ADD PRIMARY KEY (Kwartaal);
...
And here's his code (the insertion of values in the variables are excluded just for readability (his code is a bit more bulky):
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = 'BerekenStatistiek'
AND xtype = 'p')
BEGIN
DROP PROCEDURE BerekenStatistiek;
END
GO
CREATE PROCEDURE BerekenStatistiek
#jaartal INT
AS
BEGIN
DECLARE #huidigkwartaal AS INT = 1;
DECLARE #beginmaand AS INT;
DECLARE #eindmaand AS INT;
DECLARE #vorige_netto_ontvangsten AS MONEY;
IF EXISTS (SELECT *
FROM sysobjects
WHERE name = 'Statistiek'
AND xtype = 'U')
BEGIN
DROP TABLE Statistiek;
END
CREATE TABLE Statistiek
(
kwartaalnummer INT ,
beginmaand INT ,
eindmaand INT ,
orderbedrag MONEY ,
korting MONEY ,
vervoerskost MONEY ,
netto_ontvangsten MONEY ,
stijgend_dalend_gelijk NVARCHAR (10)
);
--Variables get their data here.
INSERT INTO Statistiek (kwartaalnummer, beginmaand, eindmaand, orderbedrag, korting, vervoerskost, netto_ontvangsten, stijgend_dalend_gelijk)
VALUES (#huidigkwartaal, #beginmaand, #eindmaand, #orderbedrag, #korting, #vervoerskost, #netto_ontvangsten, #stijgend_dalend_gelijk);
"however when creating the SP after using another, I get this error" (Emphasis added.)
SQL Server will insist that a stored procedure match the definitions of tables that exist as the time the stored procedure is created. If the table does not exist when the stored procedure is created, SQL Server will assume that a matching table will appear at run time.
create table t (c int)
go
create procedure p as begin
drop table t
select 1 as diff_column_name into t
select diff_colun_name from t
end
results in:
Msg 207, Level 16, State 1, Procedure p, Line 6
Invalid column name 'diff_colun_name'.
Now, drop table t, and the procedure cane be created:
drop table t
go
create procedure p as begin
drop table t
select 1 as diff_column_name into t
select diff_colun_name from t
end
Command(s) completed successfully.
If you can use a different table name, start with that. And, if the table has to exist only for a moment after the proc is executed so that it can be selected from, then create a global temporary table (i.e. table name starts with ## as in ##MyTable).
However, if it is a requirement to use the same table name as your classmate, then the teacher is probably trying to get you to learn about deferred object resolution (i.e. #Shannon's answer) and how to get around it, because outside of learning this, the scenario makes no sense since one would never do such a thing in reality.
Sub-processes (i.e. EXEC and sp_executesql) do not resolve immediately since they aren't executed when creating the stored procedure. So, simplistically, just declare a new NVARCHAR(MAX) variable to hold some Dynamic SQL and put your SELECT statement in there. Use sp_executesql to pass in the #year variable. You are creating a real table so it will survive beyond the subprocess ending and then the ALTER TABLE statement will work.
Additional notes:
You don't really need the ALTER statement to set the datatype of the [Richting] field. Just tell SQL Server what the type is in your SELECT statement:
CONVERT(NVARCHAR(8), '') AS [Richting]
You don't really want to do CONVERT(NVARCHAR(8), OrderDate, 112) to compare to a value as it invalidates the use of any indexes that might be on [OrderDate]. Instead, construct a date value from the strings and convert that to a DATETIME or DATE (i.e. CONVERT(DATETIME, #year + '0101')).
To better understand this issue, please read Sargability: Why %string% Is Slow, and at least the first link at the bottom, which is: What makes a SQL statement sargable?
You don't really want to convert the OrderDate field to NVARCHAR(4) just to compare the year, for the same reason as just mentioned in the above point. At the very least using the YEAR() function would be more direct. But if you want to make sure indexes can be used, you can't put a function on the field. But you only want the year. So isn't the year the same as BETWEEN #Year + '0101' AND #Year + '1231'? ;-)
Interestingly enough, the first example in the accepted answer in the "What makes a SQL statement sargable?" S.O. question linked in the previous bullet is exactly what I am recommending here :).
For I can understand, the wrong queries are the inserts, because the engine can't find correct table structure, check if the inserts have the same structure of your second table example. Dont forget to check the USE at the beginning of the script, maybe you are using a different db, this can happen :).
In the last bit of code, you are having
AND xtype = 'U'
If your collation is case sensitive, the drop is not taking place and thus the error.