11

I have a SQL Server 12.0 database that is linked to an Oracle 12.1 database.

I want to create a view in the SQL Server database that returns data from an Oracle table filtered by date. The Oracle table has an index on the date column.

A query that works successfully is:

select * from ORADB..SCHEMA.MYTABLE where MYDATE >= '20140701';

However this runs very slowly. I assume it is because the comparison is taking place in SQL Server so every row is being returned.

If I go:

DECLARE @earliest date = '20140701';
select * from ORADB..SCHEMA.MYTABLE where MYDATE >= @earliest;

Then it runs fast, presumably because the condition is being passed to Oracle so the Oracle index on the table is being used.

My problem is that I want to create a view. I can't find a way of using the second version of the code to create a view. If I simply do:

create myview as select * from ORADB..SCHEMA.MYTABLE where MYDATE >= '20140701';

Then it runs slowly.

Is there another format for the date literal that SQL Server will pass to Oracle, or is there another solution? I wondered also if it was to do with the parameters used in creating the link to Oracle. For reference they are:

USE [master]
GO
EXEC master.dbo.sp_addlinkedserver @server = N'ORADB', @srvproduct=N'Oracle', @provider=N'OraOLEDB.Oracle', @datasrc=N'DPDB'
EXEC master.dbo.sp_addlinkedsrvlogin @rmtsrvname=N'ORADB',@useself=N'False',@locallogin=NULL,@rmtuser=N'MYUSER',@rmtpassword='#######'
GO
EXEC master.dbo.sp_serveroption @server=N'ORADB', @optname=N'collation compatible', @optvalue=N'false'
GO
EXEC master.dbo.sp_serveroption @server=N'ORADB', @optname=N'data access', @optvalue=N'true'
GO
EXEC master.dbo.sp_serveroption @server=N'ORADB', @optname=N'dist', @optvalue=N'false'
GO
EXEC master.dbo.sp_serveroption @server=N'ORADB', @optname=N'pub', @optvalue=N'false'
GO
EXEC master.dbo.sp_serveroption @server=N'ORADB', @optname=N'rpc', @optvalue=N'false'
GO
EXEC master.dbo.sp_serveroption @server=N'ORADB', @optname=N'rpc out', @optvalue=N'false'
GO
EXEC master.dbo.sp_serveroption @server=N'ORADB', @optname=N'sub', @optvalue=N'false'
GO
EXEC master.dbo.sp_serveroption @server=N'ORADB', @optname=N'connect timeout', @optvalue=N'0'
GO
EXEC master.dbo.sp_serveroption @server=N'ORADB', @optname=N'collation name', @optvalue=null
GO
EXEC master.dbo.sp_serveroption @server=N'ORADB', @optname=N'lazy schema validation', @optvalue=N'false'
GO
EXEC master.dbo.sp_serveroption @server=N'ORADB', @optname=N'query timeout', @optvalue=N'0'
GO
EXEC master.dbo.sp_serveroption @server=N'ORADB', @optname=N'use remote collation', @optvalue=N'true'
GO
EXEC master.dbo.sp_serveroption @server=N'ORADB', @optname=N'remote proc transaction promotion', @optvalue=N'true'
GO

EDIT: I just found a very similar question: Forcing a SQL Remote Query to filter remotely instead of locally

Community
  • 1
  • 1
Richard A
  • 2,682
  • 2
  • 21
  • 33
  • 1
    Try ISO format `'2014-01-01'` YYYY-MM-DD – Lukasz Szozda Oct 20 '15 at 05:38
  • Thanks @lad2025, I tried that but it still runs slow. – Richard A Oct 20 '15 at 06:57
  • Since you can't create a view with a parameter, why not just create a function instead ? You can use it like a view, join it, AND have params. – Patrick Honorez Oct 20 '15 at 07:08
  • Thanks @iDevlop, I was trying to keep things simple. That's probably my next approach. The other is to move the filtering of the view back into Oracle, but I was trying to avoid that. – Richard A Oct 20 '15 at 07:10
  • The problem you have is not bound to the format of the date literal... You'll have to find a solution passing your date as a parameter. – Shnugo Oct 20 '15 at 07:28
  • Thanks @Shnugo. I'm much more familiar with Oracle than SQL Server. I was hoping that by telling SQL Server that it was a constant date and so it would pass the query straight to Oracle rather than performing WHERE clause in SQL Server. – Richard A Oct 20 '15 at 07:35
  • I have no experience with this, but you could check the execution plan. Actually I do not think, that a date literal forces the full work load to be done row by row in the calling system... – Shnugo Oct 20 '15 at 07:38
  • Looking at the estimated execution plans of the two queries, the one with the variable is SELECT->Compute Scalar->Remote Query. The one with the literal is SELECT->Filter->Compute Scalar->Remote Query. The estimated cost and number of rows for the Remote Query is around 3 times for the literal. – Richard A Oct 20 '15 at 22:55

3 Answers3

29

I prefer the ODBC format:

--DateTime
SELECT {ts'2015-09-20 12:30:00'}
--Time (however this comes with "today"-time)
SELECT {t'12:30:00'}
--Date
SELECT {d'2015-09-20'}
GO

The simple date literal is not culture independent...

SET LANGUAGE ENGLISH;
SELECT CAST('2014-09-13' AS DATETIME);
GO
SET LANGUAGE GERMAN;
SELECT CAST('2014-09-13' AS DATETIME);--ERROR: there's no month "13"
GO

But it works - however - with target type DATE (this difference is rather weird...):

SET LANGUAGE ENGLISH;
SELECT CAST('2014-09-13' AS DATE);
GO
SET LANGUAGE GERMAN;
SELECT CAST('2014-09-13' AS DATE);--ERROR: there's no month "13"
GO

Thx to lad2025 I want to add for completness the "full" ISO 8601, which works fine:

SET LANGUAGE ENGLISH;
SELECT CAST('2014-09-13T12:30:00' AS DATETIME);
GO
SET LANGUAGE GERMAN;
SELECT CAST('2014-09-13T12:30:00' AS DATETIME);
GO
Shnugo
  • 62,351
  • 7
  • 42
  • 92
  • Thanks Shnugo. I've tried that but it doesn't solve my problem, still slow. – Richard A Oct 20 '15 at 07:05
  • 2
    @Shungo Maybe I wasn't specific enough about ISO format [ISO 8601 Format](https://technet.microsoft.com/en-us/library/ms190977%28v=sql.90%29.aspx) `The advantage in using the ISO 8601 format is that it is an international standard. datetime values that are specified by using this format are unambiguous` And **`Also, this format is not affected by the SET DATEFORMAT or SET LANGUAGE settings.`** But you have to use full specification with time part. – Lukasz Szozda Oct 20 '15 at 07:06
  • @lad2025. You are rigth with implicit conversion, but just try the second code block. The cast throws an exception because the "13" ist taken as the month... Change the "13" to a smaller number and the cast will come back with two different dates... – Shnugo Oct 20 '15 at 07:09
  • 1
    I wasn't specific enough. Check with full datetime [demo](https://data.stackexchange.com/stackoverflow/query/377710#resultSets). **`'2014-09-13T00:00:00'`** is culture independent. There is no error. – Lukasz Szozda Oct 20 '15 at 07:11
  • @lad2025, there's one more advantage. When I once checked the behaviour of a CTE I found, that the ODBC-literals are taken as DATE(TIME) values immediately, while an ISO literal is going through an implicit CAST. I could imagine, that the optimizer could use this knowledge in some cases... – Shnugo Oct 20 '15 at 07:12
  • 1
    @lad2025, OK, together with the "THH:MM:SS"-part it's working fine. But your comment states the "YYYY-MM-DD"-part only... – Shnugo Oct 20 '15 at 07:14
  • @Shnugo No problem, this was quick comment and I assumed that everyone knows ISO-8601 :) – Lukasz Szozda Oct 20 '15 at 07:15
  • There's a reason to prefer the ISO-8601 format - in SQL Server 2014 at least, the ODBC format does not work for dates outside of the range of the datatype datetime (i.e. dates prior to 1753-01-01) - I've got test code for this in another answer on this page. The ISO-8601 format supports the full range of values available in the datetime2 datatype, as far back as 0001-01-01, so it's a bit more robust in this way. Though I'll admit to never having to have handled dates before 1753 yet. – Richard Abey-Nesbit Oct 16 '19 at 21:43
7

I would recommend using the full ISO 8601 format as suggested by @lad2025:

'2017-10-06T14:57:23'

This is superior to the ODBC format suggested by @Shnugo.

In SQL Server 2014 at least, the ODBC format will not work for dates prior to 1753-01-01 (e.g. those dates outside of the range of the old DATETIME data type), whereas the ISO 8601 format does.

To test this yourself, try the following queries:

--This will work
DECLARE @DateISO DATE = '0001-01-01T00:00:00';
SELECT @DateISO;

--This will also work
DECLARE @DatetimeISO DATETIME2 = '0001-01-01T00:00:00';
SELECT @DatetimeISO;

--This will not work
DECLARE @DateODBC DATE = {D '0001-01-01'};
SELECT @DateODBC;

--This will also not work
DECLARE @DatetimeODBC DATETIME2 = {ts '0001-01-01 00:00:00'};
SELECT @DatetimeODBC;

Even if you don't think the dates you're working with will ever be before the year 1753, it's a good habit to be in. I ran into this while looking at setting up a calendar table to reference in queries.

2

If the query is running on Oracle database, then I suggest use the ANSI date literal which uses a fixed Format YYYY-MM-DD.

For example,

DATE '2015-10-20'

In Oracle, '20140701' is a string and not a DATE. You might just be lucky to see an implicit data type conversion and get the result based on the locale-specific NLS settings of your client. You should always avoid it, and explicitly convert the string into date for date comparisons.

Lalit Kumar B
  • 43,345
  • 10
  • 82
  • 112
  • 1
    Thanks Lalit, I understand that '20140701' is a string in Oracle. I can't use the syntax DATE '2015-10-20' as SQL Server doesn't understand it. That's what I'd use if I was writing the query directly in Oracle. I have tried a variety of date formats. All the advice I found on the net for date literals in SQL Server advised using 20140701 rather than 2014-07-01. – Richard A Oct 20 '15 at 07:00
  • However (this is rather weird...) It is possible to cast and convert this to `DATE`, But if you cast or convert it to `DATETIME`, it is not culture independant... Try `SET LANGUAGE GERMAN; SELECT CAST('2015-10-20' AS DATE);` and then change this to `DATETIME` – Shnugo Aug 21 '17 at 08:12