Strange behaviour of SQL Server query - sql

I have a query in a validation stored procedure. It goes something like this:
SELECT *
FROM Table1
WHERE batchID IN (SELECT id FROM #tempIds)
AND CAST(field1 AS DATE) > CAST(field2 AS DATE)
Both field1 and field2 have valid dates, i.e. doing IdDate on field1/2 returns 1.
The #tempIds table only has one column ID and contains only one row.
When I run the above query, I get this error:
Unable to convert varchar to date
But instead of selecting batch ids from temp table if I put hard-coded ID from the same temp table it works.
Any ideas what could be the issue?

The problem is that you are using varchar to store date (or datetime) values.
Choosing the correct data type for your columns would save you from a lot of problems, this one included. For detailed information, read Aaron Bertrand's Bad habits to kick : choosing the wrong data type.
Now, to address our conversation in the comments - SQL Server does not guarantee the order on which the conditions in the where clause are evaluated. This means that even if all the "date" strings in both your columns are convertible to date values for the specific batchID, you only need one wrong value in one of the columns to raise the "Unable to convert varchar to date" error.
This also means that even if you where to write your query the way Larry B suggested in his answer (now deleted - so for the sake of future readers - this was his suggestion:)
SELECT *
FROM Table1
WHERE batchID IN (SELECT id FROM #tempIds)
AND ISDATE(field1) = 1
AND ISDATE(field2) = 1
AND CAST(field1 AS DATE) > CAST(field2 AS DATE)
There is no guarantee that the last condition (cast(field1 as date) > cast(field2 as date)) will be evaluated after the isdate(field1)=1 condition.
The correct thing to do is to fix the problem - change the data types of the columns to the correct data type.
Assuming that can't be done (if you have no control over the structure of the database, for instance) - you can do a couple of things:
Find all the places where the values in field1 and in field2 can't be converted to dates and fix them. Consider adding a check constraint to validate that the values of these columns can be converted to date (assuming you can).
Separate your query into 2 parts:
;With cte as
(
SELECT *
FROM Table1
WHERE batchID IN (SELECT id FROM #tempIds)
AND ISDATE(field1) = 1
AND ISDATE(field2) = 1
)
SELECT *
FROM cte
WHERE CAST(field1 AS DATE) > CAST(field2 AS DATE)
This will eliminate the error, since you are only casting values where the ISDATE function already returned 1, but might not return some rows you want back, if the value if either field1 or field2 is wrong in these rows.

Related

Optimize performance for date range query in SQL

I have scenario as below: I have a table Tblbalance which contains a date and time column.
When I search using normal between clause in my stored procedure, it will throw error timeout in linq.
I tried to extend default time from 30 sec to 2 min in linq but not worked.
Please help me to get fastest way to return from select query
Sample query I tried is as below:
select
col1,
col2,
col3,
(select top 1 col from tblname where id = tblbalance.id)
from
tblbalance
where
datecol between startdate and todate
order by
col3
I would suggest to include one more column in you table and copy numeric of datetime in that column, Also create a index on the newly created numeric column. Then instead of using the datetime column in where clause, try using numeric column.
This way your query will run on index and will be optimised.

SQL Server subquery behaviour

I have a case where I want to check to see if an integer value is found in a column of a table that is varchar, but is a mix of values that can be integers some are just strings. My first thought was to use a subquery to just select the rows with numeric-esque values. The setup looks like:
CREATE TABLE #tmp (
EmployeeID varchar(50) NOT NULL
)
INSERT INTO #tmp VALUES ('aa1234')
INSERT INTO #tmp VALUES ('1234')
INSERT INTO #tmp VALUES ('5678')
DECLARE #eid int
SET #eid = 5678
SELECT *
FROM (
SELECT EmployeeID
FROM #tmp
WHERE IsNumeric(EmployeeID) = 1) AS UED
WHERE UED.EmployeeID = #eid
DROP TABLE #tmp
However, this fails, with: "Conversion failed when converting the varchar value 'aa1234' to data type int.".
I don't understand why it is still trying to compare #eid to 'aa1234' when I've selected only the rows '1234' and '5678' in the subquery.
(I realize I can just cast #eid to varchar but I'm curious about SQL Server's behaviour in this case)
You can't easily control the order things will happen when SQL Server looks at the query you wrote and then determines the optimal execution plan. It won't always produce a plan that follows the same logic you typed, in the same order.
In this case, in order to find the rows you're looking for, SQL Server has to perform two filters:
identify only the rows that match your variable
identify only the rows that are numeric
It can do this in either order, so this is also valid:
identify only the rows that are numeric
identify only the rows that match your variable
If you look at the properties of this execution plan, you see that the predicate for the match to your variable is listed first (which still doesn't guarantee order of operation), but in any case, due to data type precedence, it has to try to convert the column data to the type of the variable:
Subqueries, CTEs, or writing the query a different way - especially in simple cases like this - are unlikely to change the order SQL Server uses to perform those operations.
You can force evaluation order in most scenarios by using a CASE expression (you also don't need the subquery):
SELECT EmployeeID
FROM #tmp
WHERE EmployeeID = CASE IsNumeric(EmployeeID) WHEN 1 THEN #eid END;
In modern versions of SQL Server (you forgot to tell us which version you use), you can also use TRY_CONVERT() instead:
SELECT EmployeeID
FROM #tmp
WHERE TRY_CONVERT(int, EmployeeID) = #eid;
This is essentially shorthand for the CASE expression, but with the added bonus that it allows you to specify an explicit type, which is one of the downsides of ISNUMERIC(). All ISNUMERIC() tells you is if the value can be converted to any numeric type. The string '1e2' passes the ISNUMERIC() check, because it can be converted to float, but try converting that to an int...
For completeness, the best solution - if there is an index on EmployeeID - is to just use a variable that matches the column data type, as you suggested.
But even better would be to use a data type that prevents junk data like 'aa1234' from getting into the table in the first place.

SQL delete objects having and id range

I want to create a function in sql server that take as input an id range and delete all the object in that range (included the start and the end).
Let's have for example:
delete_objects_with_id(id_start,id_end)
It will delete all the objects with the id in the range id_start and id_end.
The problem is that the id are of this form: id_start=2017-0001 id_end=2017-0050 is there a sql server function that iterate on a list given a range?
first_issue content id
2011-01-01 test 2011-0001
2012-10-01 test 2012-0001
2012-11-01 test 2012-0002
2012-11-01 test 2012-0003
No, there's no builtin SQL Server function that will iterate on a list given a range.
If the id values are always nine characters, in the format four numeric digits, a dash and four more digits, then a comparison to character strings would get you the id values.
That is, you could write a query (SQL SELECT statement) to return the id values in that range,
SELECT t.id
FROM myobjects t
WHERE t.id >= '2017-0001'
AND t.id <= '2017-0050'
ORDER BY t.id
and with that query, you could define a cursor loop, and loop through (iterate) through those id values returned.
If the id values aren't in a canonical format, then the range operation isn't necessarily going to work. You'd need a mechanism to convert the id values into values that are canonical, so you can do a range.
But I'm not getting why you would need (or want) to iterate, if by "objects" you are referring to "rows" in a table. If you can write a SELECT statement that returns those rows, you could write a DELETE statement using the same predicates, and delete the rows in one fell swoop.
DELETE
FROM myobjects
WHERE id >= '2017-0001'
AND id <= '2017-0050'
It's not at all clear what you are attempting to achieve, why you would need to "iterate". But a cursor loop is one way to do that.
Again, to answer the question you asked: No. There is no "sql server function that iterate on a list given a range"
There is a simple workaround, Just remove - character and cast id as BIGINT
(This will work assuming that the first part of the id is year value and the second part is an id for each year)
DELETE FROM TABLE_1
WHERE CAST(REPLACE(id,'-','') as BIGINT) >= CAST(REPLACE(id_start,'-','') as BIGINT)
AND CAST(REPLACE(id,'-','') as BIGINT) <= CAST(REPLACE(id_end,'-','') as BIGINT)
Or use BETWEEN
DELETE FROM TABLE_1
WHERE CAST(REPLACE(id,'-','') as BIGINT) BETWEEN CAST(REPLACE(id_start,'-','') as BIGINT)
AND CAST(REPLACE(id_end,'-','') as BIGINT)

How to convert date in mm/dd/yyyy format to yyyy-mm-dd hh:mi:ss.mmm

I'm inserting data into a table and before inserting I need to check if data exists.
I have a composite key consisted of two columns of datetime and int.
Before inserting I need to check if the data with the same time and id exists in the table.
The date that user is inserting is in 'mm/dd/yyyy'.
The datetime data in the table looks like this: '2016-01-12 00:00:00.000'.
The id field is int.
So, I have a query:
if not exists(select count(*) from table_1 where MyDate = #myDate and id = #id)
insert into table_1 .....
What is the right way to format the date user sends to match the datetime format in the table?
Check this sqlfiddle about how to use different date formats in your query. Might help you to solve it.
http://www.sqlfiddle.com/#!2/fd0b7/5
I am guessing that the question is about SQL Server, based on the syntax. The issues in the code snippet far transcend date formats.
First, the expression:
if not exists(select count(*) from table_1 where MyDate = #myDate and id = #id)
will never return true, because the subquery always returns one row with one column. If nothing matches, the column contains 0, which does exist.
You intend:
if not exists(select 1 from table_1 where MyDate = #myDate and id = #id)
Second, this check is not necessary if you wisely choose to have the database enforce the uniqueness constraint. So, define a unique index or constraint on the two columns:
create unique index unq_table_1_id_mydate on table_1(id, MyDate);
Now, the database won't let you insert duplicate values and no if is necessary.
Next, I would suggest that you fix the date format at the application layer. YYYY-MM-DD is an ISO standard date format and quite reasonable. However, if you don't want to do that, use convert():
insert into table_1(id, MyDate, .....)
select #id, convert(datetime, #MyDate, 101), . . .
The value in the database looks to be correct stored as a date/time value, so this should work fine.
You can use following line to convert date to required format in SQL server:
select FORMAT(#your_date, 'yyyy-MM-dd HH:mm:ss', 'en-US') from Your_Table

SQL statement to get largest ID from database column

I have an ID column which it supposed to set to auto-increment but I forgot to set in when creating the database. Let's say the ID is from 1 - 20. I used the select Max() Sql statement to get the largest ID:
SELECT MAX(id) FROM Table_Name;
It supposed to return me 20. However, it returns me 9. I also realized that the id column in database is jumbled up. It starts from 1,2 then skips to 9,10 - 20 then back to 3 - 8. And 8 appears to be the last row and I think that's where the 9 comes from. My id in database is varchar() data type.
So, is there any way to amend my Sql statement to get the largest id in a list of sorted id?
Thanks in advance.
The issue is likely that the ID column is a varchar field, so 9 is greater than 10.
select max(convert(int, id)) from Table
Your column is a character type, not a numeric type, which explains everything you're seeing.
Try casting it to numeric:
select max(cast(id as signed)) from table
You haven't said which database you are using, so the syntax may vary to achieve the cast - consult online docs for your database.
Try this:
SELECT TOP 1 Id FROM Table ORDER BY Convert(INT, id) DESC