Equivalent of sqlite3_column_int to get possible NULL values - sql

in my app, I am using a SQLite database to store some data. One of the integer fields is optional, so it is defined like so:
I have the following CREATE statement:
CREATE TABLE IF NOT EXISTS Test (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp FLOAT NOT NULL, testProperty INT);
I can get the float property using sqlite3_column_double. For the int column I could use 'sqlite3_column_int' but this always returns a value (0) even if the row does not contain a value.
How can I check if the row actually has a value for this property?
I have the following code to get all rows:
var statement: OpaquePointer? = nil
let sql = "SELECT * FROM Test;"
sqlite3_prepare_v2(self.connection, sql, -1, &statement, nil)
while sqlite3_step(statement) == SQLITE_ROW
{
let testProperty = sqlite3_column_int(statement, 2) // always returns 0
}
sqlite3_finalize(statement)

You can use sqlite3_column_type() to check if it's SQLITE_INTEGER or SQLITE_NULL.

Related

SQLTables return SQL_NO_DATA

Following code:
ret = SQLTables( m_hstmt, (SQLWCHAR *) SQL_ALL_CATALOGS, SQL_NTS, (SQLWCHAR *) SQL_ALL_SCHEMAS, SQL_NTS, (SQLWCHAR *) SQL_ALL_TABLE_TYPES, SQL_NTS, L"", SQL_NTS );
if( ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO )
{
GetErrorMessage( errorMsg, 1 );
result = 1;
}
else
{
for( ret = SQLFetch( m_hstmt ); ( ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO ); ret = SQLFetch( m_hstmt ) )
{
if( catalog[0].StrLen_or_Ind != SQL_NULL_DATA )
catalogName = (SQLWCHAR *) catalog[0].TargetValuePtr;
if( catalog[1].StrLen_or_Ind != SQL_NULL_DATA )
schemaName = (SQLWCHAR *) catalog[1].TargetValuePtr;
if( catalog[2].StrLen_or_Ind != SQL_NULL_DATA )
tableName = (SQLWCHAR *) catalog[2].TargetValuePtr;
}
}
returns SQL_NO_DATA for SQLTables call, whereas following code:
ret = SQLTables( m_hstmt, (SQLWCHAR *) SQL_ALL_CATALOGS, SQL_NTS, L"", SQL_NTS, L"", SQL_NTS, L"", SQL_NTS );
if( ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO )
{
GetErrorMessage( errorMsg, 1 );
result = 1;
}
else
{
for( ret = SQLFetch( m_hstmt ); ( ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO ); ret = SQLFetch( m_hstmt ) )
{
if( catalog[0].StrLen_or_Ind != SQL_NULL_DATA )
catalogName = (SQLWCHAR *) catalog[0].TargetValuePtr;
if( catalog[1].StrLen_or_Ind != SQL_NULL_DATA )
schemaName = (SQLWCHAR *) catalog[1].TargetValuePtr;
if( catalog[2].StrLen_or_Ind != SQL_NULL_DATA )
tableName = (SQLWCHAR *) catalog[2].TargetValuePtr;
}
}
gives me just catalog names and schema/table names are blank.
Does this mean I can't retrieve everything in one shot?
Thank you.
Apparently following code works:
ret = SQLTables( m_hstmt, NULL, 0, NULL, 0, NULL, 0, NULL, 0 );
which is kind of weird way to call this function.
Microsoft needs to mention this case somewhere in the documentation, because if the developer sees SQL_ALL_CATALOGS, SQL_ALL_SCHEMAS and SQL_ALL_TABLE_TYPES parameters, (s)he will presume that those values needs to be passed to get all the info from the server.
Solution was found on the easysoft site.
Thank you all for reading.
I'm updating my answer, below you see the old content (starting with the deleted paragraph). As Igor has shown in his answer, it is possible to list everything in one shot.
On the documentation site about the SQLTables() is a link to: Arguments in Catalog Functions
There is an explicit entry at the very beginning of the article, stating that calling SQLTables(hstmt1, NULL, 0, NULL, 0, NULL, 0, NULL, 0); will
[..] return a result set containing information about all tables
There is also a lot of explanations about the influence of the attribute SQL_ATTR_METADATA_ID and how the arguments in the catalog functions can be used as
Catalog function string arguments fall into four different types: ordinary argument (OA), pattern value argument (PV), identifier argument (ID), and value list argument (VL).
I added the link above as a reference.
Yes, I think you cannot list all schemas, all catalogs and all types in one shot. From the documentation at microsoft:
To support enumeration of catalogs, schemas, and table types, the
following special semantics are defined for the CatalogName,
SchemaName, TableName, and TableType arguments of SQLTables:
If CatalogName is SQL_ALL_CATALOGS and SchemaName and TableName are empty strings, the result set contains a list of valid catalogs
for the data source. (All columns except the TABLE_CAT column contain
NULLs.)
If SchemaName is SQL_ALL_SCHEMAS and CatalogName and TableName are empty strings, the result set contains a list of valid schemas for the
data source. (All columns except the TABLE_SCHEM column contain
NULLs.)
If TableType is SQL_ALL_TABLE_TYPES and CatalogName, SchemaName, and TableName are empty strings, the result set contains a list of
valid table types for the data source. (All columns except the
TABLE_TYPE column contain NULLs.)
Url: https://msdn.microsoft.com/en-us/library/ms711831%28v=vs.85%29.aspx
If I understand this right, you cannot combine this values: Catalogs are only iterated if the CatalogName is SQL_ALL_CATALOGS and all other params are empty strings, the same for SchemaName, and so on.
SQL_ALL_CATALOGS, SQL_ALL_SCHEMAS and SQL_ALL_TABLE_TYPES are defined to % on my system here.
So, if you query with all three parameters set to the SQL_ALL_foobar you will query using % as values for all strings, which is not the empty string that is expected for the two other parameters and because of that you will get no result.
The code is wrong because it mixed up TableName and TableType parameters. So you ask for any table in any schema and catalogue but with no type thus you receive empty set because no table in target database has empty type.
This combination is not clearly described in MS documentation so most likely it is driver-specific.

How to handle errors in a select statement when attempting an insert or fail?

Is there a good way to handle errors in a select statement when attempting an insert or fail? Specifically, I want to insert elements into a table, but the select statement used to generate these elements is failing. I would like to have all the elements where the select statement succeeded to be inserted, but for the overall statement to fail. I thought that insert or fail would do this, but it does not. More specifically, imagine if we defined a new SQLite function "log"
#include <string>
#include <sqlite3ext.h>
#include <cmath>
SQLITE_EXTENSION_INIT1
extern "C" {
int sqlite3_log_init(
sqlite3 * db,
char ** err,
sqlite3_api_routines const * const api
);
}
// Compute the log of the input
void mylog(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
// Grab the number
auto num = sqlite3_value_double(argv[0]);
// If positive, take the log
if(num > 0.)
sqlite3_result_double(context, log(num));
// Otherwise, throw an error
else {
auto msg = std::string("Can't take the log of a nonpositive number");
sqlite3_result_error(context,msg.c_str(),msg.size());
}
}
// Initialize the functions
int sqlite3_log_init(
sqlite3 *db,
char **err,
sqlite3_api_routines const * const api
){
SQLITE_EXTENSION_INIT2(api)
// Register the log function
if( int ret = sqlite3_create_function(
db, "log", 1, SQLITE_ANY, 0, mylog, 0, 0)
) {
*err=sqlite3_mprintf("Error registering log: %s",
sqlite3_errmsg(db));
return ret;
}
// If we've made it this far, we should be ok
return SQLITE_OK;
}
This can be compiled with
g++ -std=c++14 log.cpp -shared -o log.so -fPIC
Basically, the above function takes the log of its element. For example,
sqlite> select log(1);
0.0
sqlite> select log(0);
Error: Can't take the log of a nonpositve number
Now, consider the following sequence of SQL operations
sqlite> .load "./log.so"
sqlite> create table foo (num real);
sqlite> insert into foo values (2.), (1.), (0.);
sqlite> create table bar (num real);
sqlite> insert or fail into bar select log(num) from foo;
Error: Can't take the log of a nonpositve number
sqlite> select * from bar;
sqlite>
Basically, the table bar is empty because the select statement failed on 0. What I want to have happen is for the table bar to contain the elements log(2.) and log(1.), but the error to still be thrown. Is there a way to have that happen?
SQLite's ON CONFLICT clause applies only to UNIQUE, NOT NULL, CHECK, and PRIMARY KEY constraints, so you would not be able to use INSERT OR IGNORE.
Once a user-defined function returns an error, it is not possible to suppress it.
You could say that the function's result is undefined, and let it return NULL (which you can then filter out).
Alternatively, get only those rows which have valid values:
INSERT INTO bar SELECT log(num) FROM foo WHERE num > 0;

How to convert bit column to int

I want to convert a bit column to a integer column, do I need a case and convert function at the same time for this?
False = 0
True = 1
You do not need a conversion, because bit is already an integer data type:
An integer data type that can take a value of 1, 0, or NULL.
You can use bits in integer expressions without conversion. Here is a short demo:
create table demo (b bit, v int);
insert into demo (b, v) values (1,5), (0,4), (1, -2), (0, -5);
SELECT b, v, b+v AS b_plus_v FROM demo
Running this produces the following output:
B V B_PLUS_V
- - --------
1 5 6
0 4 4
1 -2 -1
0 -5 -5
EDIT : (based on this comment: "I'm using Code first EF")
Entity Framework requires that a bit column mapped to a bool field. One way to work around this requirement is introducing a computed property to your entity class to hide the "Booleanness" of the underlying column, like this:
partial class MyEntity {
// This code assumes that a bool property MyBoolProperty exists,
// and that it is mapped to the table using EF
public int MyIntProperty {
get {
return MyBoolProperty ? 1 : 0;
}
set {
MyBoolProperty = value != 0;
}
}
}

Delete column from Sqlite database

Basically I have to enter all textfield values in database then have to use for sending to webservice. So when the one column details send to service then have to delete that column.
I have done this:
-(void)deleteTableDataFromSavedDataTable:(NSString *)lastID {
NSString *sql_str=[NSString stringWithFormat:#"DELETE FROM FormInfoValues where Phone = %#",lastID];
const char *sql = [sql_str UTF8String];
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK)
{
NSLog(#"sql delete statement is ");
sqlite3_stmt *deleteStmt;
if(sqlite3_prepare_v2(database, sql, -1, &deleteStmt, NULL) == SQLITE_OK)
{
NSLog(#"sql delete statement is %#", deleteStmt);
if(sqlite3_step(deleteStmt) != SQLITE_DONE )
{
NSLog( #"Error: %s", sqlite3_errmsg(database) );
}
else
{
NSLog( #"row id = %lld", (sqlite3_last_insert_rowid(database)+1));
NSLog(#"No Error");
}
}
sqlite3_finalize(deleteStmt);
}
sqlite3_close(database);
}
but its not deleting after sending. Why its not calling?
give me ideas..
It's quite possible I'm not understanding your issue properly -- so I apologize in advance if so.
Regarding: "...then have to delete that column."
-- If you want to clear the data in a column (by setting it to zero, or empty string, or NULL) then you'll want to use the UPDATE command in SQL. The DELETE command always deletes ROWS.
-- If you really must remove a column from a table's schema, you'll have to create a new table in sqlite. (Sqlite allows you to ADD a column via ALTER TABLE command, and some other databases DO allow you to drop a column using ALTER TABLE as well.) You can quickly copy a table (without it's constraints, etc) via e.g.:
CREATE TABLE MyOutput as SELECT a,b,d,f,h,z from MyOriginal;
-- If your output is created by a SELECT statement, just avoid using "*" and specify just the columns you want included.

sqlite3 datetime not functioning properly on iPhone

I am having trouble getting sqlite3 to do comparisons between datetimes. On my mac, when I run the sql queries, it works. However, on the iPhone, the exact same query fails.
I have a sqlite3 table setup as follows:
CREATE TABLE IF NOT EXISTS `Artists` (
`id` int(11) NOT NULL PRIMARY KEY,
`name` varchar(256) NOT NULL,
`lastUpdate` date NOT NULL default CURRENT_TIMESTAMP ,
...
...
);
I can insert an artist with:
INSERT OR IGNORE INTO `Artists` (`id`,`name`) VALUES ('1','Justin');
I am trying to find the number of artists that have not been updated for the last 2 seconds (to keep it short):
SELECT COUNT(*) FROM `Artists` WHERE `id` = ? AND `lastUpdate` < datetime('now','-2 seconds');
On the iPhone, this returns 0. On my pc, it returns 1. I am expecting the value to be 1. What is the difference? I am retrieving the value by:
-(BOOL) artistNeedsUpdating:(int)artistId
{
[dbLock lock];
BOOL result = NO;
sqlite3_stmt *statement;
const char* query = "SELECT COUNT(*) FROM `Artists` WHERE `id` = ? AND `lastUpdate` < datetime('now','-2 seconds');";
if(sqlite3_prepare_v2(database, query, -1, &statement, NULL) == SQLITE_OK){
sqlite3_bind_int(statement, 1, artistId);
if(sqlite3_step(statement) == SQLITE_ROW){
if(sqlite3_column_int(statement, 0) > 0) // If 0, then we don't need to update
result = YES;
}
sqlite3_finalize(statement);
}
[dbLock unlock];
return result;
}
I am confused as to why it works on one platform but not the other, does anybody have any ideas?
Thank you,
Justin
Nevermind, it was my mistake.
I was updating the row after it got done with
UPDATE `Artists` SET `lastUpdate`='CURRENT_TIMESTAMP';
When it should have been:
UPDATE `Artists` SET `lastUpdate`=CURRENT_TIMESTAMP;