You can't do what Microsoft does with built-ins, sorry. You will need to write your function to accept a string, for example, and evaluate it inline, e.g.
CREATE FUNCTION dbo.CustomDateDiff
(
@datepart VARCHAR(32), -- does not need to be NVARCHAR
@start DATETIME,
@end DATETIME
)
RETURNS INT
AS
BEGIN
RETURN (SELECT CASE @datepart
WHEN 'HOUR' THEN DATEDIFF(HOUR, @start, @end)
WHEN 'MINUTE' THEN DATEDIFF(MINUTE, @start, @end)
WHEN 'WEEK' THEN DATEDIFF(WEEK, @start, @end)
ELSE DATEDIFF(DAY, @start, @end)
END);
END
GO
Not that you can't use a conditional inside DATEDIFF
- a lot of people think you could say:
DATEDIFF(@datepart,
Or
DATEDIFF(CASE WHEN @datepart = 'HOUR' THEN HOUR END,
But neither of these will work - both yield:
Msg 1023, Level 15, State 1, Line 2
Invalid parameter 1 specified for datediff.
The second form of your query will work if you make your mark your function as deterministic, meaning that for a given set of input values, it will always return the same result.
With that set, Oracle will only run the conversion once for each parameters in the where
clause rather than for every row.
With this:
CREATE OR REPLACE FUNCTION TO_MINUTE_D (DATE_IN IN DATE)
RETURN NUMBER DETERMINISTIC AS
BEGIN
/* Minute 0 = 12/30/1899 12:00am */
RETURN
(TRUNC(DATE_IN, 'DD') - TO_DATE('12/30/1899', 'MM/DD/YYYY')) * 1440 +
TO_NUMBER(TO_CHAR(DATE_IN, 'HH24')) * 60 +
TO_NUMBER(TO_CHAR(DATE_IN, 'MI'));
END TO_MINUTE_D;
/
On a table filled with a large bunch of dummy rows (increasing ints), I get the following timings consistently:
SQL> SELECT * FROM MY_TABLE
WHERE START_MINUTE < TO_MINUTE(TO_DATE('2013-01-31', 'YYYY-MM-DD'))
AND STOP_MINUTE > TO_MINUTE(TO_DATE('2013-01-01', 'YYYY-MM-DD'));
no rows selected
Elapsed: 00:00:12.69
versus deterministic-annotated function:
SQL> SELECT * FROM MY_TABLE
WHERE START_MINUTE < TO_MINUTE_D(TO_DATE('2013-01-31', 'YYYY-MM-DD'))
AND STOP_MINUTE > TO_MINUTE_D(TO_DATE('2013-01-01', 'YYYY-MM-DD'));
no rows selected
Elapsed: 00:00:00.07
You should get very close to what you have with the values plugged in directly, and indexes on those columns can be used as if you'd plugged in literals.
(Putting the conversion function on the start|stop_minute
columns isn't a good idea in general as you've discovered, unless you have a function-based index on those that matches exactly.)
Best Answer
I suggest an SQL function:
Call:
Replace all occurrences of
now()::date
with your input parameter named_date
.In Postgres 9.1 or older use the positional parameter
$1
in SQL functions (which can be used in any version).More code examples:
Tricky detail
The
crosstab()
function takes query strings as parameters. Function parameters are not visible insidecrosstab()
. So you need to pass in the date values as string literals!I suggest the function
format()
for convenience. For example, the second parameter becomes:instead of:
Complete code
SQL function
PL/pgSQL function
Shorter, reusing the
VALUES
expression. Probably a bit faster, too.CTE
For completeness, the "same" without persisting a function, with CTEs: