Date arithmetic in SQL on DB2/ODBC - sql

I'm building a query against a DB2 database, connecting through the IBM Client Access ODBC driver. I want to pull fields that are less than 6 days old, based on the field 'a.ofbkddt'... the problem is that this field is not a date field, but rather a DECIMAL field, formatted as YYYYMMDD.
I was able to break down the decimal field by wrapping it in a call to char(), then using substr() to pull the year, month and day fields. I then formatted this as a date, and called the days() function, which gives a number that I can perform arithmetic on.
Here's an example of the query:
select
days( current date) -
days( substr(char(a.ofbkddt),1,4) concat '-' -- YYYY-
concat substr(char(a.ofbkddt),5,2) concat '-' -- MM-
concat substr(char(a.ofbkddt),7,2) ) as difference, -- DD
a.ofbkddt as mydate
from QS36F.ASDF a
This yields the following:
difference mydate
2402 20050402
2025 20060306
...
4 20110917
3 20110918
2 20110919
1 20110920
This is what I expect to see... however when I use the same logic in the where clause of my query:
select
days( current date) -
days( substr(char(a.ofbkddt),1,4) concat '-' -- YYYY-
concat substr(char(a.ofbkddt),5,2) concat '-' -- MM-
concat substr(char(a.ofbkddt),7,2) ) as difference, -- DD
a.ofbkddt as mydate
from QS36F.ASDF a
where
(
days( current date) -
days( substr(char(a.ofbkddt),1,4) concat '-' -- YYYY-
concat substr(char(a.ofbkddt),5,2) concat '-' -- MM
concat substr(char(a.ofbkddt),7,2) ) -- DD
) < 6
I don't get any results back from my query, even though it's clear that I am getting date differences of as little as 1 day (obviously less than the 6 days that I'm requesting in the where clause).
My first thought was that the return type of days() might not be an integer, causing the comparison to fail... according to the documentation for days() found at http://publib.boulder.ibm.com/iseries/v5r2/ic2924/index.htm?info/db2/rbafzmst02.htm, it returns a bigint. I cast the difference to integer, just to be safe, but this had no effect.

You're going about this backwards. Rather than using a function on every single value in the table (so you can compare it to the date), you should pre-compute the difference in the date. It's costing you resources to run the function on every row - you'd save a lot if you could just do it against CURRENT_DATE (it'd maybe save you even more if you could do it in your application code, but I realize this might not be possible). Your dates are in a sortable format, after all.
The query looks like so:
SELECT ofbkddt as myDate
FROM QS36F.ASDF
WHERE myDate > ((int(substr(char(current_date - 6 days, ISO), 1, 4)) * 10000) +
(int(substr(char(current_date - 6 days, ISO), 6, 2)) * 100) +
(int(substr(char(current_date - 6 days, ISO), 9, 2))))
Which, when run against your sample datatable, yields the following:
myDate
=============
20110917
20110918
20110919
20110920
You might also want to look into creating a calendar table, and add these dates as one of the columns.

What if you try a common table expression?
WITH A AS
(
select
days( current date) -
days( substr(char(a.ofbkddt),1,4) concat '-' -- YYYY-
concat substr(char(a.ofbkddt),5,2) concat '-' -- MM-
concat substr(char(a.ofbkddt),7,2) ) as difference, -- DD
a.ofbkddt as mydate
from QS36F.ASDF a
)
SELECT
*
FROM
a
WHERE
difference < 6

Does your data have some nulls in a.ofbkddt? Maybe this is causing some funny behaviour in how db2 is evaluating the less than operation.

Related

How to convert an int to DateTime in BigQuery

I have an INT64 column called "Date" which contains many different numbers like: "20210209" or "20200305". I want to turn those numbers into a date with this format: MM-YYYY (so in these cases, 02-2021 and 03-2020). Ultimately I want to sum all the data in each month together. The problem is that BigQuery can't convert INT64 to date, only to strings. I'm not sure if I should convert to a string and then to a date or if there is a better way.
Although converting to a string then a date both works and is very concise, over large enough numbers of rows (which may be the case in Big Query) you may be better off using integer maths and using DATE(year, month, day)...
https://cloud.google.com/bigquery/docs/reference/standard-sql/date_functions#date
SELECT
DATE(
DIV( 20210209 , 10000), -- Which gives 2021
DIV(MOD(20210209, 10000), 100), -- Which gives 02
MOD(20210209, 100) -- Which gives 09
)
You can convert the value to a string and use parse_date():
select parse_date('%Y%m%d', cast(20210209 as string))
Another option
select date,
regexp_replace('' || date, r'(\d{4})(\d{2})(\d{2})', r'\2-\1') as MM_YYYY
from your_table
if applied to sample data in your question - output is
Yet another option
select date,
format_date('%m-%Y', parse_date('%Y%m%d', '' || date)) as MM_YYYY
from your_table
with same output

CAST - Make string length to be 2 characters long

I need to combine two fields but force the characters of the second string to be 2 characters.
I'm combining a year field and month field and want the result to be YYYY_MM. Forcing any single months (e.g. 1,2,3,4) into a two digit format e.g. (01).
Below is my formula for combining the fields, but I need help making the month two digits.
Thanks, L
WITH so_header(soh_build_year,soh_build_week) AS (
SELECT 2020, 3
UNION ALL SELECT 2020,13
)
SELECT
CAST(SO_HEADER.SOH_Build_Year AS VARCHAR)
+'_'
+CAST(SO_HEADER.SOH_Build_Week AS VARCHAR) as [Build YYYY_WW]
FROM so_header;
Try this out (Syntax: SQL Server)
SELECT
CAST(2019 AS VARCHAR)
+'_'
+CAST(format (1, '0#') AS VARCHAR) as [Build YYYY_WW]
Replace your values with your variables
Try this:
WITH so_header(soh_build_year,soh_build_week) AS (
SELECT 2020, 3
UNION ALL SELECT 2020,13
)
SELECT
CAST(SO_HEADER.SOH_Build_Year AS VARCHAR)
+ '_'
+ SUBSTR(
CAST(100+SO_HEADER.SOH_Build_Week AS VARCHAR)
, 2
, 2
) as Build_YYYY_WW
FROM so_header;
-- out Build_YYYY_WW
-- out ---------------
-- out 2020_03
-- out 2020_13
If you are using SQL Server never use varchar (or related types) with no length. The default varies by context and may not be large enough for what you want.
If you are trying to convert a date to YYYY_MM format, you can use format():
select format(getdate(), 'yyyy_MM')
I recommend using dates, if they are available. If you are not using SQL Server, most other databases have similar functionality.
If not, you an simply use:
select concat(so_header.SOH_Build_Year, '_'
right(concat('00', so_header.soh_build_week), 2)
)
concat() does not require explicitly converting the values to strings.

storing date in 'CCYYMMDD' format in Teradata

I would like to store dates in the format CCYYMMDD in Teradata, but I fail to do so. Find below what I tried so far:
query 1:
SEL CAST(CAST(CURRENT_DATE AS DATE FORMAT 'YYYYMMDD') AS VARCHAR(8))
-- Output: 20191230 ==> this works!
query 2:
SEL CAST(CAST(CURRENT_DATE AS DATE FORMAT 'CCYYMMDD') AS VARCHAR(8))
-- output: SELECT Failed. [3530] Invalid FORMAT string 'CCYYMMDD'.
It seems that the CCYYMMDD is not available in Teradata right away. Is there a workaround?
Tool used: Teradata SQL assistant
Internally, dates are stored as integers in Teradata. So when you say you want to store them in a different format, I don't think you can do that. But you can choose how to display / return the values.
I'm sure there's a cleaner way to get the format you want, but here's one way:
WITH cte (mydate) AS (
SELECT CAST(CAST(CURRENT_DATE AS DATE FORMAT 'YYYYMMDD') AS CHAR(8)) AS mydate
)
SELECT
CAST(
(CAST(SUBSTRING(mydate FROM 1 FOR 2) AS INTEGER) + 1) -- generate "century" value
AS CHAR(2) -- cast value as string
) || SUBSTRING(mydate FROM 3) AS new_date -- add remaining portion of date string
FROM cte
SQL Fiddle - Postgres
You'd have to add some extra logic to handle years before 1000 and after 9999. I don't have a TD system to test, but give it a try and let me know.

Find data with specific date and month only

I am trying to find a data with specific where clause of date and month but I am receiving an error can anyone help me with this?
select *
from my_data
where date BETWEEN '11-20' AND '12-15'
MS SQL Server Management Studio
I am receving an error
Conversion failed when converting date and/or time from character string
Most databases support functions to extract components of dates. So, one way of doing what you want is to convert the values to numbers and make a comparison like this:
where month(date) * 100 + day(date) between 1120 and 1215
The functions for extracting date parts differ by database, so your database might have somewhat different methods for doing this.
The conversion is failing because you are not specifying a year. If you were to specify '11-20-2015' your query would work just insert whatever year you need.
SELECT *
FROM my_data
WHERE date BETWEEN '11-20-2015' AND '12-15-2015'
Alternatively if you wanted data from that range of dates for multiple years I would use a while loop to insert information in a # table then read from that table, depending on the amount of data this could be quick or sloooowww here is an example.
DECLARE #mindatestart date, #mindateend date, #maxdatestart date
SET #mindatestart = '11-20-2010'
SET #mindateend = '12-15-2010'
SET #maxdatestart = '11-20-2015'
SELECT top 0 *, year = ' '
INTO #mydata
FROM my_data
WHILE #mindatestart < #maxdatestart
BEGIN
INSERT INTO #mydata
SELECT *, YEAR(#mindatestart)
FROM my_data
where date between #mindatestart and #mindateend
SET #mindatestart = DATEADD(Year, 1, #mindatestart)
SET #mindateend = DATEADD(Year, 1, #mindateend)
END
This will loop and insert the data from 2010-2015 for those date ranges and add a extra column on the end so you can call the data and order by year if you want like this
SELECT * FROM #mydata order by YEAR
Hopefully some part of this helps!
FROM THE COMMENT BELOW
SELECT *
FROM my_data
WHERE DAY(RIGHT(date, 5)) between DAY(11-20) and DAY(12-15)
The reason '11-20' doesn't work is because its a character string which is why you have to input it between ' ' What the Month() function does is take whatever you put between the () and convert it to an integer. Which is why you're not getting anything back using the method in the first answer, the '-Year' from the table date field is being added into the numeric value where your value is just being converted from 11-20 you can see by using these queries
SELECT MONTH(11-20) --Returns 12
SELECT MONTH(11-20-2015) -- Returns 6
SELECT MONTH(11-20-2014) -- Returns 6
Using RIGHT(Date, 5) you only get Month-day, then you date the day value of that so DAY(RIGHT(DATE, 5) and you should get something that in theory should fall within those date ranges despite the year. However I'm not sure how accurate the data will be, and its a lot of work just to not add an additional 8 characters in your original query.
Since you only care about month and day, but not year, you need to use DATEPART to split up the date. Try this:
select *
from my_data
WHERE 1=1
AND (DATEPART(m, date) >= 11 AND DATEPART(d,date) >= 20)
AND (DATEPART(m, date) <= 12 AND DATEPART(d,date) <= 15)

Concatenating date and time fields

I have a table invoices with this fields:
invDate -> a date field
invTime -> a time field
I need to do querys like
SELECT top 10 * from invoices WHERE DATETIME(invDate+invTime)
BETWEEN DATETIME('2013-12-17 17:58') AND DATETIME()
or something like that. I don't know how to concatenate the invDate and invTime to create a datetime field. The only thing that i could do is this horribly thing:
DATETIME( YEAR(invDate), MONTH(invDate), DAY(invDate), 17, 52 ) AS MyDatetime
Couldn't even get hour and time with hour(invTime) and minute(invTime):
DATETIME( YEAR(invDate), MONTH(invDate), DAY(invDate),
HOUR(invTime), MINUTE(invTime) ) AS MyDatetime
I'm doing the querys throught the VFP Odbc Driver via PHP.
You were pretty close. If the value coming from PHP is not of a date/time, how could VFP interpret it properly. VFP also has a function CTOT() (character to time), and expects it in the format of 'yyyy-mm-ddThh:MM:ss??'
yyyy = 4 digit year
mm = 1 OR 2 digit month
dd = 1 OR 2 digit day
T -- literally the letter "T"
hh = 1 OR 2 digit hour (but typical is 2 anyhow)
MM = 1 or 2 digit minute (but typical is 2)
ss = 1 or 2 digit for seconds -- not required
?? = "AM" or "PM" if you wanted to explicitly provide that vs 24 hour clock
The MM and ss are optional, so if you finished with "T1" would be 1:00:00am
Now, to finish your query.
WHERE DATETIME(invDate+invTime)
BETWEEN DATETIME('2013-12-17 17:58') AND DATETIME()
Since this appears to be querying all invoices between a given date/time and NOW (via DateTime()), you don't even need between, you can do
WHERE YourTable.Column > CTOT( '2013-12-17T17:58')
If you specifically DID have a date/time range to consider, THEN you could do something like
WHERE YourTable.Column BETWEEN CTOT( '2013-12-05T10:00') AND CTOT( '2013-12-14T11:58')
PROBLEMS WITH your DATE() and TIME() implementations
The problem is Date() is a function to either return current date, or create based on y/m/d provided such as date( 2013, 12, 7 ). If you are passing a string, use CTOD( 'mm/dd/yyyy' ) such as CTOD( 12, 7, 2013 ).
As for the TIME() function that just expects a number and is of no use for you. From the OleDbProvider, your best bet is to just create a php function that builds a single string in the CTOT() format I've described and pass to the php function the date and time fields. Then use that as your "CTOT( functionReturnResult )"
To add a Date and a Time Field together you will need to convert them both to a same datatype 1st and than just simply add them together something like this....
DECLARE #D DATE = '2013-12-17'
DECLARE #T TIME = '17:58:00'
SELECT CAST(#D AS DATETIME) + CAST(#T AS DATETIME)
Result
2013-12-17 17:58:00.000
Your Query
SELECT top 10 *
from invoices
WHERE CAST(invDate AS DATETIME) + CAST(invTime AS DATETIME)
BETWEEN '20131217 17:58:00.000' AND GETDATE()