Delphi - ClientDataSet SQL calculated field causing "Invalid field type" error at runtime [duplicate] - sqlite

Using Delphi 10.2, SQLite and Teecharts. My SQLite database has two fields, created with:
CREATE TABLE HistoryRuntime ('DayTime' DateTime, Device1 INTEGER DEFAULT (0));
I access the table using a TFDQuery called qryGrpahRuntime with the following SQL:
SELECT DayTime AS TheDate, Sum(Device1) As DeviceTotal
FROM HistoryRuntime
WHERE (DayTime >= "2017-06-01") and (DayTime <= "2017-06-26")
Group by Date(DayTime)
Using the Field Editor in the Delphi IDE, I can add two persistent fields, getting TheDate as a TDateTimeField and DeviceTotal as a TLargeIntField.
I run this query in a program to create a TeeChart, which I created at design time. As long as the query returns some records, all this works. However, if there are no records for the requested dates, I get an EDatabaseError exception with the message:
qryGrpahRuntime: Type mismatch for field 'DeviceTotal', expecting: LargeInt actual: Widestring
I have done plenty of searching for solutions on the web on how to prevent this error on an empty query, but have had not luck with anything I found. From what I can tell, SQLite defaults to the wide string field when no data is returned. I have tried using CAST in the query and it did not seem to make any difference.
If I remove the persistent fields, the query will open without problems on an empty return set. However, in order to use the TeeChart editor in the IDE, it appears I need persistent fields.
Is there a way I can make this work with persistent fields, or am I going to have to throw out the persistent fields and then add the TeeChart Series at runtime?

This behavior is described in Adjusting FireDAC Mapping chapter of the FireDAC's SQLite manual:
For an expression in a SELECT list, SQLite avoids type name
information. When the result set is not empty, FireDAC uses the value
data types from the first record. When empty, FireDAC describes those
columns as dtWideString. To explicitly specify the column data type,
append ::<type name> to the column alias:
SELECT count(*) as "cnt::INT" FROM mytab
So modify your command e.g. this way (I used BIGINT, but you can use any pseudo data type that maps to a 64-bit signed integer data type and is not auto incrementing, which corresponds to your persistent TLargeIntField field):
SELECT
DayTime AS "TheDate",
Sum(Device1) AS "DeviceTotal::BIGINT"
FROM
HistoryRuntime
WHERE
DayTime BETWEEN {d 2017-06-01} AND {d 2017-06-26}
GROUP BY
Date(DayTime)
P.S. I did a small optimization by using BETWEEN operator (which evaluates the column value only once), and used an escape sequence for date constants (which, in real you replace by parameter, I guess; so just for curiosity).
This data type hinting is parsed by the FDSQLiteTypeName2ADDataType procedure that takes and parses column name in format <column name>::<type name> in its AColName parameter.

Related

How to implement INSERT where not exists for ORACLE in Mule4

I am trying to implement a use-case in Mule4 where a tour needs to be assigned to a user if it has not already been assigned.
I was hoping that I could implement it using Mule db:insert component and using INSERT WHERE NOT EXISTS SQL script as below.
INSERT INTO TL_MAPPING_TOUR(TOURNO,TLID,SYSTEM) select :tourno,:tlid,:system from DUAL
where not exists(select * from TL_MAPPING_TOUR where (TOURNO=:tourno and TLID=:tlid and SYSTEM=:system))
However, this is resulting in Mule Exception
Message : ORA-01722: invalid number
Error type : DB:BAD_SQL_SYNTAX
TL_MAPPING_TOUR table has an id column (Primary Key), but that is auto-generated by a sequence.
The same script, modified for running directly in SQL developer, as shown below, is working fine.
INSERT into TL_MAPPING_TOUR(TOURNO,TLID,SYSTEM)
select 'CLLO001474','123456789','AS400'
from DUAL
where not exists(select * from TL_MAPPING_TOUR where (TOURNO='CLLO001474' and TLID='123456789' and SYSTEM='AS400'));
Clearly Mule db:insert component doesn't like the syntax, but it's not very clear to me what is wrong here. I can't find any INSERT WHERE NOT EXISTS example implementation for the Mule4 Database component either.
stackoverflow page https://stackoverflow.com/questions/54910330/insert-record-into-sql-server-when-it-does-not-already-exist-using-mule directs to page not found.
Any idea what is wrong here and how to implement this in Mule4 without using another Mule4 db:select component before db:insert?
I don't know "mule4", but this:
Message : ORA-01722: invalid number
doesn't mean that syntax is wrong (as you already tested it - the same statement works OK in another tool).
Cause: You executed a SQL statement that tried to convert a string to a number, but it was unsuccessful.
Resolution:
The option(s) to resolve this Oracle error are:
Option #1: Only numeric fields or character fields that contain numeric values can be used in arithmetic operations. Make sure that all expressions evaluate to numbers.
Option #2: If you are adding or subtracting from dates, make sure that you added/substracted a numeric value from the date.
In other words, it seems that one of columns is declared as NUMBER, while you passed something that is a string. Oracle performed implicit conversion when you tested the statement in SQL Developer, but it seems that mule4 didn't and hence the error.
The most obvious cause (based on what you posted) is putting '123456789' into TLID as other values are obviously strings. Therefore, pass 123456789 (a number, no single quotes around it) and see what happens. Should work.
SQL Developer is too forgiving. It will convert string to numbers and vise versa automatically when it can. And it can a lot.
Mulesoft DB connector tries the same but it is not as succefule as native tools. Pretty often it fails to convert, especially on dates but this is not your case.
In short - do not trust too much data sense of Mulesoft. If it works - great! Otherwise try to eliminate any intelligence from it and do all conversions in the query and better from the string. Usually number works fine but if doesn't - use to_number function to mark properly that this is the number.
More about this is here https://simpleflatservice.com/mule4/AvoidCoversionsOrMakeThemNative.html

SQLite nvarchar(100) field can accept 200 char field. Why?

I have a table with a field defined as nvarchar(100).
I just noticed if inserted a new record (an 200 string value for example) the query works and not throws any exception.
Is a SQLite 'feature'?
Usign SQLite 1.0.94 with Visual Studio 2010 / C# and SQLite v3 dabatabse.
SQLite doesn't recognize the limit you specified in statement, so it's not enforced.
In order to enforce it, you might need a statement like this:
CREATE TABLE t (f TEXT CHECK(LENGTH(f)<101));
So text with more than 100 characters cannot be inserted.
SQLite has a single unlimited TEXT datatype. See the documentation:
http://www.sqlite.org/datatype3.html#affname
Note that numeric arguments in parentheses that following the type
name (ex: "VARCHAR(255)") are ignored by SQLite - SQLite does not
impose any length restrictions on the length of strings, BLOBs or numeric
values.

Retrieving Data From Returned Oracle Timestamp Column

We have a ColdFusion 8 (Linux) application that uses an Oracle timestamp. We just converted to Oracle 11g from 10g and we're now using Oracle's thin client on the data sources. We're getting an error in the application where a timestamp column is selected.
It seems as though an object of class oracle.sql.TIMESTAMP is being returned. I verified this by dumping the contents of the column. Sure enough, it gives me a break down of the object's methods and their return types. But, I can't seem to be able to interface with this object directly:
<cfquery name="getstuff" ...>
SELECT timestampfld ...
FROM myTable
</cfquery>
getstuff.timestampfld contains an object. But, doing this:
<cfoutput query="getstuff">
#timestampfld.stringValue()#
#timestampfld.dateValue()#
</cfoutput>
produces the error that says those methods can't be found. How can I get at the data held in that object?
Update from comments:
When I take column value and apply the DateFormat( timestampfld, "dd.mm.yyyy" ) function to it. The CF error is
"The value class oracle.sql.timestamp cannot be converted to a date".
When I perform <cfoutput>, I get the class definition.
In the retrieved column, I seem to be getting an object instead of a string. When I cfdump the column, I get OBJECT OF oracle.sql.TIMESTAMP. The dump lays out the methods and fields available. When I cfoutput that same variable, a string is displayed. When I try to perform a DataFormat() on the variable, it complains that its not a date.
I just happened to stumble over this error during my development. I had it in the past and long forgotten since.
I know of two ways to mitigate:
The first is according to an answer of a question regarding the same error message in a different context.
One would add the following to the jvm.config file
-Doracle.jdbc.J2EE13Compliant=true
The second is not to return an Oracle TIMESTAMP column but CAST it do DATE, first, like
<cfquery name="getstuff" ...>
SELECT CAST( timestampfld as DATE ) timestampfld
FROM myTable
</cfquery>
I'm not satisfied with either, to be honest. When the JVM argument is forgotten, the software crashes. The CAST, on the other hand, may influence how the SQL works.

Sqlite C/C++ API - Get timestamp value with select query

I use sqlite3 C/C++ API to retrieve rows from a table using SELECT query. I don't see any sqlite3_column_timestamp() to retrieve a timestamp column value after sqlite3_step().. How to get timestamp values ?
SQLite does not have a special timestamp data type.
When you want to use any of SQLite's date and time functions, you have to store timestamps in one of the formats supported by them, i.e., a string like YYYY-MM-DD HH:MM:SS or HH:MM:SS, a julian date number, or a Unix timestamp number.
You can declare a table column type as DATETIME, but SQLite will just ignore that type; SQLite always allows to put values of any type in any column. Such a declaration would be useful only as documentation.
The column/value accessors will only have types corresponding to the data types they support directly (NULL, INTEGER, REAL, TEXT, BLOB).
You would use the TEXT access to get/set the column value of dates.
There are some helper functions within SQL that they provide that let you to handle them in your queries.
I am not familiar with SQLite Manager, but I would assume that it is only reporting the data type that the table was declared with.
When parsing CREATE statements, sqlite understands the intention of many well supported datatypes and automatically maps them to what is appropriate for its internal storage structure. VARCHAR would be mapped to TEXT, for instance. I assume the column was declared DATETIME and sqlite just internally mapped it to TEXT.

The field is too small to accept the amount of data you attempted to add

This is odd because I'm not inserting data, I'm pulling data with a query.
I'm trying to run
SELECT DISTINCT description FROM products;
Which outputs the error "The field is too small to accept the amount of data you attempted to add.".
However, running the following doesn't produce the error:
SELECT description FROM products;
So I'm confused as to what the issue would be.
I'm using OleDbDataReader and taking data out of an mdb database file.
This might be related to: http://support.microsoft.com/kb/896950/us
This problem occurs because when you
set the UniqueValues query property to
Yes, a DISTINCT keyword is added to
the resulting SQL statement. The
DISTINCT keyword directs Access to
perform a comparison between records.
When Access performs a comparison
between two Memo fields, Access treats
the fields as Text fields that have a
255-character limit. Sometimes Memo
field data that is larger than 255
characters will generate the error
message that is mentioned in the
"Symptoms" section. Sometimes only 255
characters are returned from the Memo
field.
Workaround:
To work around this problem, modify
the original query by removing the
Memo field. Then, create a second
query that is based on both the table
and the original query. This new query
uses all the fields from the original
query, and this new query uses the
Memo field from the table. When you
run the second query, the first query
runs. Then, this data is used to run
the second query. This behavior
returns the Memo field data based on
the returned data of the first query.

Resources