Combine DATETIME column with CHAR field to create DATETIME2 column - tsql

I am trying to import data from a vendor's database and combine two columns into one for our database. They have two columns are CITATION_DATE which is a datetime data type and CITATION_TIME which is a char(8) data type.
I would like to combine these two columns into one column issueDate which is of datetime2(7) data type.
I tried using Aaron's logic found here but I have not been successful in getting the query to execute. My suspicion is that I need more characters in the CITATION_TIME to form a valid time stamp but I am not sure.
Is there a way to combine these two fields into a single column - that follows the datetime2 format?
My attempt was to try and clean up bad values like empty strings or non-numeric characters:
;WITH issueDate AS
(
SELECT
TRY_CAST(vt.CITATION_DATE AS DATE) AS CITATION_DATE ,
CASE WHEN RTRIM ( LTRIM ( vt.CITATION_TIME )) = '' THEN '0000'
WHEN RTRIM ( LTRIM ( vt.CITATION_TIME )) = '000' THEN '0000'
WHEN TRY_CAST(vt.CITATION_TIME AS INT) IS NULL THEN '0000'
ELSE vt.CITATION_TIME
END AS CITATION_TIME
FROM
Oklahoma_PVD_WildlifeLaw.dbo.VIOLATOR_TICKETS AS vt
--ORDER BY CITATION_TIME;
)
SELECT
CONVERT(DATETIME, CONVERT(CHAR(8), id.CITATION_DATE, 112) + ' ' +
CONVERT(CHAR(8), id.CITATION_TIME, 108))
FROM
issueDate AS id;
But I am getting the following error:
The conversion of a varchar data type to a datetime data type resulted in an out-of-range value
It does look like I do not have enough characters for the TIME portion - as this return values in a DATETIME format, but I lose all of my hours and minutes this way:
;WITH issueDate AS
(
SELECT
TRY_CAST(vt.CITATION_DATE AS DATE) AS CITATION_DATE,
CASE
WHEN RTRIM(LTRIM(vt.CITATION_TIME)) = '' THEN '00:00:00.0000000'
WHEN RTRIM(LTRIM(vt.CITATION_TIME)) = '000' THEN '00:00:00.0000000'
WHEN TRY_CAST(vt.CITATION_TIME AS INT) IS NULL THEN '00:00:00.0000000'
--ELSE vt.CITATION_TIME
END AS CITATION_TIME
FROM
Oklahoma_PVD_WildlifeLaw.dbo.VIOLATOR_TICKETS AS vt
--ORDER BY CITATION_TIME;
)
SELECT
CONVERT(DATETIME, CONVERT(CHAR(8), id.CITATION_DATE, 112) + ' ' +
CONVERT(CHAR(8), id.CITATION_TIME, 108)) AS [DateTime]
FROM
issueDate AS id
WHERE
CONVERT(DATETIME, CONVERT(CHAR(8), id.CITATION_DATE, 112) + ' ' +
CONVERT(CHAR(8), id.CITATION_TIME, 108)) IS NOT NULL;

Did you tried to calculate number of minutes from the time part and add them to the date using DATEADD?
select DATEADD(minute, cast(CITATION_TIME as int) % 100 + 60 * (cast(CITATION_TIME as int) / 100), CITATION_DATE)
FROM Oklahoma_PVD_WildlifeLaw.dbo.VIOLATOR_TICKETS AS vt
This code assumes all dates has zero time part (midnight) and CITATION_TIME are all digits. The code above tries to handle these by casting the datetime to date (to get rid of the time part) and using try_cast to handle non-numeric times. If you need, you can use the same code:
select DATEADD(minute, t.IntTime % 100 + 60 * (t.IntTime / 100), cast(CITATION_DATE as date))
FROM Oklahoma_PVD_WildlifeLaw.dbo.VIOLATOR_TICKETS AS vt
cross apply (select isnull(try_cast(CITATION_TIME as int), 0) as IntTime ) t
If there is invalid "times" in your data, you must decide how to handle them. Try to find these records by searching for values, where last 2 digits are >= 60 for example:
select * from Oklahoma_PVD_WildlifeLaw.dbo.VIOLATOR_TICKETS
where try_cast(right(CITATION_TIME, 2) as int) >= 60
or (len(CITATION_TIME) = 4 and try_cast(left(CITATION_TIME, 2) as int) >= 24)
or len(CITATION_TIME) > 4

Related

Passing Bind Variable in case statements in Oracle query

I want to pass Bind Variable (a Date) in a Case statement in order to achieve the following:
When User inputs a date and if that Date is falling on Monday, then the Case statement should fetch the value of Friday (meaning it should bypass the weekends and look for the values of a previous working day)
I tried to use the following query which works well when I use 'sysdate'
Select * from table_name
Where tradedate = trunc (sysdate - case to_char(sysdate, 'Dy')
when 'Mon' then 3 else 1 end);
but when I replace 'sysdate' with a Bind Variables, it gives me an error like:
tradedate = trunc (:sysdate1 - case to_char(:sysdate2, 'Dy')
when 'Mon' then 3 else 1 end);
ORA-00932: inconsistent datatypes: expected DATE got NUMBER
00932. 00000 - "inconsistent datatypes: expected %s got %s"
Can't we use Bind Variables in Case statement in oracle queries? If so, Can someone please give me any alternate solution to my problem stated above?
Any help would be really appreciated.
Below is the complete code:
select * from (
SELECT
S."TRADEDATE",S."ACCOUNT_NAME",S."BOOKING_AMOUNT",S."ACCOUNT_NUMBER",(CASE WHEN BOOKING_AMOUNT <0 THEN S."CREDIT" ELSE S."DEBIT" END) AS "DEBIT" , (CASE WHEN BOOKING_AMOUNT <0 THEN S."DEBIT" ELSE S."CREDIT" END) AS "CREDIT",
U.VALUE_DT , U.AC_NO , NVL(U.BOOKED_AMOUNT ,0) BOOKED_AMOUNT
FROM
SXB S
FULL OUTER JOIN UBS U ON
S.ACCOUNT_NUMBER = U.AC_NO
AND
S.TRADEDATE = U.VALUE_DT
UNION ALL
SELECT
BOOKING_DATE TRADEDATE,
'SAXO RECON' ACCOUNT_NAME,
SUM((Case when DR_CR_INDICATOR = 'D' then AMOUNT*-1 when DR_CR_INDICATOR = 'C' then AMOUNT end)) BOOKING_AMOUNT,
EXTERNAL_ACCOUNT ACCOUNT_NUMBER,
'Matched - ' ||A.MATCH_INDICATOR AS DEBIT,
NULL AS CREDIT,
VALUE_DATE VALUE_DT,
NULL AS AC_NO,
0 AS BOOKED_AMOUNT
FROM
FCUBS.RETB_EXTERNAL_ENTRY A
WHERE A.EXTERNAL_ENTITY = 'SAXODKKKXXX'
AND A.EXTERNAL_ACCOUNT = '78600/COMMEUR'
group by
BOOKING_DATE ,
EXTERNAL_ACCOUNT ,
VALUE_DATE,
MATCH_INDICATOR
order by tradedate, account_name)
where tradedate = trunc (:sysdate1 - case to_char(:sysdate2, 'Dy') when 'Mon' then 3 else 1 end);
SYSDATE is a date datatype so oracle will always treat it as a DATE datatype. For a bind variable I'd do an explicit conversion using TO_DATE(:bind_var, 'FORMAT_MASK'). For example:
select
case TO_CHAR(TO_DATE(:sysdate2,'DD-MON-YYYY'), 'Dy') when 'Mon'
then 3
else 1
end from dual

tsql selecting record based upon date and null

I have a table:
ID as int, ParentId as int, FreeFromTerxt as varchar(max), ActiveUntil as DateTime
As an example, within this table I have two records.
1, 100, 'Some text', '2015-11-30 12:10:09.0000000'
2, 100, 'New text', null
What I am trying to do is get the current active record, which in the case above would by record 1. To do that I just select with the following criteria:
ActiveUntil > GETDATE()
This works great, but if I change the first date to 2015-10-30, I need to get the null record as this record will take precedence.
So I changed the code to be:
((ActiveUntil is NULL) OR (ActiveUntil > GETDATE()))
But this does not work.
Here is some example with union:
DECLARE #t TABLE ( d DATETIME )
INSERT INTO #t
VALUES ( NULL ),
( '2015-11-30' )
SELECT TOP 1 *
FROM ( SELECT * , 1 AS ordering
FROM #t
WHERE d > GETDATE()
UNION ALL
SELECT * , 2 AS ordering
FROM #t
WHERE d IS NULL
) t
ORDER BY ordering, d
For 2015-11-30 it returns 2015-11-30. For 2015-10-30 it returns null.
Try like this:
((ActiveUntil is NULL) OR (CONVERT(char(10), ActiveUntil ,126)) > GETDATE())
Refer MSDN for Cast and Convert. The format specifier 126 is for YYYY-MM-DD. Or you can use CAST
((ActiveUntil is NULL) OR (CAST(ActiveUntil as Date) > GETDATE())

Fill table with two datetime columns with random dates

I have table T1 with two datetime columns (StartDate, EndDate) which I must populate with random dates under one circumstance:
EndDate value must be greater than StartDate in minimal one day.
Example:
StartDate EndDate
===========================
2001-04-04 2001-04-06 (2 days)
2001-01-05 2001-01-15 (10 days)
.
.
.
Can I do that in one statement?
P.S. My first idea was to change EndDate column to NULL, and in first step populate StartDate leaving EndDate as NULL, and in second statement to write some mechanism to update EndDate with dates greater (in different number of days for every record) then StartDate.
Here's a solution that populates the table in one step:
insert into T1 (StartDate, EndDate)
select
X.StartDate,
dateadd(day, abs(checksum(newid())) % 10, X.StartDate) EndDate
from (
select top 20
dateadd(day, -abs(checksum(newid())) % 100, convert(date, getDate())) StartDate
from sys.columns c1, sys.columns c2
) X
The query above uses some tricks that I personally often use in ad-hoc SQL queries:
new_Id() generates different random values for each row, as opposed to RAND(), which would be evaluated once per query. The expression abs(checksum(newid())) % N will generate random integer values in the 0 - N-1 range.
the TOP X ... FROM sys.columns c1, sys.columns c2 trick allows you to generate X rows whose values can be composed of scalar values, like the ones in our example.
Obviously, you can modify the hardcoded values in the above query to:
generate more rows
increase the range of random start dates
increase the maximum duration of each row.
INSERT T1 (StartDate, EndDate)
select T1, T1 + add_days
from
(select DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0) T1,
ROW_NUMBER() OVER(ORDER BY number) add_days
from [ master ] .. spt_values) X;
sqlfiddle example
Something simple using rand() function:
Fiddle Example
declare #records int = 100, --Number of records needed
#count int = 0, #start int, #end int
while(#records>#count)
begin
select #start = rand() * 10, #end = rand() * 100, #count+=1
insert into mytable
select dateadd(day, #start, getdate()),dateadd(day, #end, getdate())
end
select * from mytable

Msg 8114: Error converting data type varchar to numeric

I am writing a dynamic query in SSMS 2008 and I get the above error. What is the cause of this?
My query:
SELECT TOP 10 *
FROM
ALL_COMPLAINTS A
JOIN
#TEMP5 B ON A.QXP_EXCEPTION_NO = B.QXP_EXCEPTION_NO
AND A.[LEVEL] = B.[LEVEL]
AND A.[QXP_REPORT_DATE] = B.[QXP_REPORT_DATE]
WHERE
A.QXP_REPORT_DATE >= CONVERT(DATETIME, '' + #FirstMonthDate + ' 00:00:00', 102)
AND
A.QXP_REPORT_DATE <= CONVERT(DATETIME, '' + #LastMonthDate + ' 23:59:59', 102)
AND
A.QXP_SHORT_DESC <> 'Design Control' AND A.LEVEL = ' + CAST(#TheLevel AS VARCHAR(5)) + '
I know that A.Level is numeric and I also know that I do not get any errors if I just remove the A.Level portion. However, I am not certain I am casting #TheLevel correctly since this is dynamic SQL.
Your quotes are messed up in the last line, you cant mix dynamic and non dynamic SQL in the same statement. Also assuming #TheLevel is a numeric, if #TheLevel is a char, you need to convert it to a numeric (int I assume in this case)
SELECT TOP 10 *
FROM
ALL_COMPLAINTS A
JOIN
#TEMP5 B ON A.QXP_EXCEPTION_NO = B.QXP_EXCEPTION_NO
AND A.[LEVEL] = B.[LEVEL]
AND A.[QXP_REPORT_DATE] = B.[QXP_REPORT_DATE]
WHERE
A.QXP_REPORT_DATE >= CONVERT(DATETIME, '' + #FirstMonthDate + ' 00:00:00', 102)
AND
A.QXP_REPORT_DATE <= CONVERT(DATETIME, '' + #LastMonthDate + ' 23:59:59', 102)
AND
A.QXP_SHORT_DESC <> 'Design Control' AND A.LEVEL = #TheLevel
If you need a dynamic portion then it is probably good practice to pre evaluate it and then key it into the non dynamic query

Getting the minimum of two values in SQL

I have two variables, one is called PaidThisMonth, and the other is called OwedPast. They are both results of some subqueries in SQL. How can I select the smaller of the two and return it as a value titled PaidForPast?
The MIN function works on columns, not variables.
SQL Server 2012 and 2014 supports IIF(cont,true,false) function. Thus for minimal selection you can use it like
SELECT IIF(first>second, second, first) the_minimal FROM table
While IIF is just a shorthand for writing CASE...WHEN...ELSE, it's easier to write.
The solutions using CASE, IIF, and UDF are adequate, but impractical when extending the problem to the general case using more than 2 comparison values. The generalized
solution in SQL Server 2008+ utilizes a strange application of the VALUES clause:
SELECT
PaidForPast=(SELECT MIN(x) FROM (VALUES (PaidThisMonth),(OwedPast)) AS value(x))
Credit due to this website:
http://sqlblog.com/blogs/jamie_thomson/archive/2012/01/20/use-values-clause-to-get-the-maximum-value-from-some-columns-sql-server-t-sql.aspx
Use Case:
Select Case When #PaidThisMonth < #OwedPast
Then #PaidThisMonth Else #OwedPast End PaidForPast
As Inline table valued UDF
CREATE FUNCTION Minimum
(#Param1 Integer, #Param2 Integer)
Returns Table As
Return(Select Case When #Param1 < #Param2
Then #Param1 Else #Param2 End MinValue)
Usage:
Select MinValue as PaidforPast
From dbo.Minimum(#PaidThisMonth, #OwedPast)
ADDENDUM:
This is probably best for when addressing only two possible values, if there are more than two, consider Craig's answer using Values clause.
For SQL Server 2022+ (or MySQL or PostgreSQL 9.3+), a better way is to use the LEAST and GREATEST functions.
SELECT GREATEST(A.date0, B.date0) AS date0,
LEAST(A.date1, B.date1, B.date2) AS date1
FROM A, B
WHERE B.x = A.x
With:
GREATEST(value [, ...]) : Returns the largest (maximum-valued) argument from values provided
LEAST(value [, ...]) Returns the smallest (minimum-valued) argument from values provided
Documentation links :
MySQL http://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html
Postgres https://www.postgresql.org/docs/current/functions-conditional.html
SQL Server https://learn.microsoft.com/en-us/sql/t-sql/functions/logical-functions-least-transact-sql
I just had a situation where I had to find the max of 4 complex selects within an update.
With this approach you can have as many as you like!
You can also replace the numbers with aditional selects
select max(x)
from (
select 1 as 'x' union
select 4 as 'x' union
select 3 as 'x' union
select 2 as 'x'
) a
More complex usage
#answer = select Max(x)
from (
select #NumberA as 'x' union
select #NumberB as 'x' union
select #NumberC as 'x' union
select (
Select Max(score) from TopScores
) as 'x'
) a
I'm sure a UDF has better performance.
Here is a trick if you want to calculate maximum(field, 0):
SELECT (ABS(field) + field)/2 FROM Table
returns 0 if field is negative, else, return field.
Use a CASE statement.
Example B in this page should be close to what you're trying to do:
http://msdn.microsoft.com/en-us/library/ms181765.aspx
Here's the code from the page:
USE AdventureWorks;
GO
SELECT ProductNumber, Name, 'Price Range' =
CASE
WHEN ListPrice = 0 THEN 'Mfg item - not for resale'
WHEN ListPrice < 50 THEN 'Under $50'
WHEN ListPrice >= 50 and ListPrice < 250 THEN 'Under $250'
WHEN ListPrice >= 250 and ListPrice < 1000 THEN 'Under $1000'
ELSE 'Over $1000'
END
FROM Production.Product
ORDER BY ProductNumber ;
GO
This works for up to 5 dates and handles nulls. Just couldn't get it to work as an Inline function.
CREATE FUNCTION dbo.MinDate(#Date1 datetime = Null,
#Date2 datetime = Null,
#Date3 datetime = Null,
#Date4 datetime = Null,
#Date5 datetime = Null)
RETURNS Datetime AS
BEGIN
--USAGE select dbo.MinDate('20120405',null,null,'20110305',null)
DECLARE #Output datetime;
WITH Datelist_CTE(DT)
AS (
SELECT #Date1 AS DT WHERE #Date1 is not NULL UNION
SELECT #Date2 AS DT WHERE #Date2 is not NULL UNION
SELECT #Date3 AS DT WHERE #Date3 is not NULL UNION
SELECT #Date4 AS DT WHERE #Date4 is not NULL UNION
SELECT #Date5 AS DT WHERE #Date5 is not NULL
)
Select #Output=Min(DT) FROM Datelist_CTE;
RETURN #Output;
END;
Building on the brilliant logic / code from mathematix and scottyc, I submit:
DECLARE #a INT, #b INT, #c INT = 0;
WHILE #c < 100
BEGIN
SET #c += 1;
SET #a = ROUND(RAND()*100,0)-50;
SET #b = ROUND(RAND()*100,0)-50;
SELECT #a AS a, #b AS b,
#a - ( ABS(#a-#b) + (#a-#b) ) / 2 AS MINab,
#a + ( ABS(#b-#a) + (#b-#a) ) / 2 AS MAXab,
CASE WHEN (#a <= #b AND #a = #a - ( ABS(#a-#b) + (#a-#b) ) / 2)
OR (#a >= #b AND #a = #a + ( ABS(#b-#a) + (#b-#a) ) / 2)
THEN 'Success' ELSE 'Failure' END AS Status;
END;
Although the jump from scottyc's MIN function to the MAX function should have been obvious to me, it wasn't, so I've solved for it and included it here: SELECT #a + ( ABS(#b-#a) + (#b-#a) ) / 2. The randomly generated numbers, while not proof, should at least convince skeptics that both formulae are correct.
Use a temp table to insert the range of values, then select the min/max of the temp table from within a stored procedure or UDF. This is a basic construct, so feel free to revise as needed.
For example:
CREATE PROCEDURE GetMinSpeed() AS
BEGIN
CREATE TABLE #speed (Driver NVARCHAR(10), SPEED INT);
'
' Insert any number of data you need to sort and pull from
'
INSERT INTO #speed (N'Petty', 165)
INSERT INTO #speed (N'Earnhardt', 172)
INSERT INTO #speed (N'Patrick', 174)
SELECT MIN(SPEED) FROM #speed
DROP TABLE #speed
END
Select MIN(T.V) FROM (Select 1 as V UNION Select 2 as V) T
SELECT (WHEN first > second THEN second ELSE first END) the_minimal FROM table