Sqlite : unique constraint if not empty - sqlite

I would like to create an unique constraint on a TEXT column that will only affect the non-empty cells. (Each cell must be unique if and only if it contains some characters).
I tried this, but it raised an exception :
db.execSQL("CREATE TABLE tables (" +
"table_id INTEGER PRIMARY KEY, " +
"table_name TEXT, " +
"table_number INTEGER, " +
"table_alias TEXT, " +
"UNIQUE(table_alias) WHERE table_alias != '' ON CONFLICT IGNORE);"
Any idea how can I achieve this?

In a UNIQUE column, NULL values are considered distinct. (Don't ask why, this is just how SQL works.)
So replace the emtry strings with NULLs.

Related

SQLite Select...Where statement "no such column" error

I have the following SQLite statement:
"SELECT * FROM HISTORICALPICKS WHERE BETTYPE = " + betType.ToString().ToUpper() + " ORDER BY DATETIME ASC"
And betType.ToString().ToUpper() is the string "ATS"
For some reason I get the following error:
System.Data.SQLite.SQLiteException : SQL logic error or missing database
no such column: ATS
...which leaves me baffled. ATS is not a column. BETTYPE is the column. I want it to select rows that have "ATS" in the BETTYPE column.
My hunch is that something is wrong with the syntax. Here is a picture showing the table name and column name, along with a highlight of the value ATS:
You need single quotes around the value.
"SELECT * FROM HISTORICALPICKS WHERE BETTYPE = '" + betType.ToString().ToUpper() + "' ORDER BY DATETIME ASC"

SQLite3 - Wrong data stored

I got trouble with some data which get's wrong in that Database.
Trying to store a Lua String, for example:
foo = "1234..78.0"
The database returns no error. But reading the string from the SQLite 3 Database, it returns strange values, alwas with a "-" as first digit.
The Database:
db:exec( "CREATE TABLE IF NOT EXISTS "
"Pattern("
"Idx INTEGER PRIMARY KEY AUTOINCREMENT,"
"Strip TEXT,"
"Startdigit INTEGER,"
"Enddigit INTEGER,"
"Value TEXT," -- < -- THIS FIELD IS THE ISSUE
"Name TEXT,"
"Message TEXT,"
"Relatives BLOB"
")"
)
The write query:
queries.newPattern:prepare("INSERT INTO Pattern(Strip, Startdigit, Enddigit, Value, Name, Message, Relatives) VALUES( (?) , (?) , (?) , (?) , (?) , (?) , (?) )")
Where (?) are variables which have to be bound before executing the query by:
queries.newPattern:bind( idx , value ) -- binds value to (?) at (idx)th position
All Datatypes are correct, and the execution does not return any error.
err, errno = queries.newPattern:exec()
So err and errno are nil.
queries.newPattern:lastError()
returns nil.
Reading the Database with:
db:exec("SELECT * FROM Pattern")
Returns all data correct, except the "Value" field, which gets corrupted.
After every query, SQLites last Error is nil, so I assume there are no datatype mismatches or something.
The return value is something like(example):
"-1434302411"
What black magic is performing here?

SQLite - How to define Default value of column in Create Table statement taking reference of other column value

I an using SQLite database and wanted to create a table like following:
Create Table EMPLOYEE (
[FirstName] [varchar](50),
[LastName] [varchar](50),
[FullName] [varchar](150) AS ([FirstName] + ' ' + [LastName])
);
Can it is possible to have Default value of any column (like FullName) referring to values of other columns present in same table.
UPDATED:
I tried forming CREATE TABLE statement using these IFNULL and COALESCE function but every time its failing. How should i write such CREATE TABLE statement which will have computed column. If such Computed column in CREATE statement is not supported then how should i achieve such requirement. Can anybody please give me an example
The DEFAULT value expression must be a constant expression and referring to other column values makes it non-constant.
You can use IFNULL() or COALESCE() when selecting the data to concatenate other column values in case value is null. For example:
SELECT IFNULL(fullname, firstname || ' ' || lastname) ...

SQLite query to find primary keys

In SQLite I can run the following query to get a list of columns in a table:
PRAGMA table_info(myTable)
This gives me the columns but no information about what the primary keys may be. Additionally, I can run the following two queries for finding indexes and foreign keys:
PRAGMA index_list(myTable)
PRAGMA foreign_key_list(myTable)
But I cannot seem to figure out how to view the primary keys. Does anyone know how I can go about doing this?
Note: I also know that I can do:
select * from sqlite_master where type = 'table' and name ='myTable';
And it will give the the create table statement which shows the primary keys. But I am looking for a way to do this without parsing the create statement.
The table_info DOES give you a column named pk (last one) indicating if it is a primary key (if so the index of it in the key) or not (zero).
To clarify, from the documentation:
The "pk" column in the result set is zero for columns that are not
part of the primary key, and is the index of the column in the primary
key for columns that are part of the primary key.
Hopefully this helps someone:
After some research and pain the command that worked for me to find the primary key column name was:
SELECT l.name FROM pragma_table_info("Table_Name") as l WHERE l.pk = 1;
For the ones trying to retrieve a pk name in android, and while using the ROOM library.
#Oogway101's answer was throwing an error: "no such column [your_table_name] ... etc.. etc...
my way of query submition was:
String pkSearch = "SELECT l.name FROM pragma_table_info(" + tableName + ") as l WHERE l.pk = 1;";
database.query(new SimpleSQLiteQuery(pkSearch)
I tried using the (") quotations and still error.
String pkSearch = "SELECT l.name FROM pragma_table_info(\"" + tableName + "\") as l WHERE l.pk = 1;";
So my solution was this:
String pragmaInfo = "PRAGMA table_info(" + tableName + ");";
Cursor c = database.query(new SimpleSQLiteQuery(pragmaInfo));
String id = null;
c.moveToFirst();
do {
if (c.getInt(5) == 1) {
id = c.getString(1);
}
} while (c.moveToNext() && id == null);
Log.println(Log.ASSERT, TAG, "AbstractDao: pk is: " + id);
The explanation is that:
A) PRAGMA table_info returns a cursor with various indices, the response is atleast of length 6... didnt check more...
B) index 1 has the column name.
C) index 5 has the "pk" value, either 0 if it is not a primary key, or 1 if its a pk.
You can define more than one pk so this will not bring an accurate result if your table has more than one (IMHO more than one is bad design and balloons the complexity of the database beyond human comprehension).
So how will this fit into the #Dao? (you may ask...)
When making the Dao "abstract" you have access to a default constructor which has the database in it:
from the docummentation:
An abstract #Dao class can optionally have a constructor that takes a Database as its only parameter.
this is the constructor that will grant you access to the query.
There is a catch though...
You may use the Dao during a database creation with the .addCallback() method:
instance = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase2.class, "database")
.addCallback(
//You may use the Daos here.
)
.build();
If you run a query in the constructor of the Dao, the database will enter a feedback loop of infinite instantiation.
This means that the query MUST be used LAZILY (just at the moment the user needs something), and because the value will never change, it can be stored. and never re-queried.

SQLite add Primary Key

I created a table in Sqlite by using the CREATE TABLE AS syntax to create a table based on a SELECT statement. Now this table has no primary key but I would like to add one.
Executing ALTER TABLE table_name ADD PRIMARY KEY(col1, col2,...) gives a syntax error "near PRIMARY"
Is there a way to add a primary key either during table creation or afterwards in Sqlite?
By "during creation" I mean during creation with CREATE TABLE AS.
You can't modify SQLite tables in any significant way after they have been created. The accepted suggested solution is to create a new table with the correct requirements and copy your data into it, then drop the old table.
here is the official documentation about this: http://sqlite.org/faq.html#q11
As long as you are using CREATE TABLE, if you are creating the primary key on a single field, you can use:
CREATE TABLE mytable (
field1 TEXT,
field2 INTEGER PRIMARY KEY,
field3 BLOB,
);
With CREATE TABLE, you can also always use the following approach to create a primary key on one or multiple fields:
CREATE TABLE mytable (
field1 TEXT,
field2 INTEGER,
field3 BLOB,
PRIMARY KEY (field2, field1)
);
Reference: http://www.sqlite.org/lang_createtable.html
This answer does not address table alteration.
I tried to add the primary key afterwards by changing the sqlite_master table directly.
This trick seems to work.
It is a hack solution of course.
In short: create a regular (unique) index on the table, then make the schema writable and change the name of the index to the form reserved by sqlite to identify a primary key index, (i.e. sqlite_autoindex_XXX_1, where XXX is the table name) and set the sql string to NULL. At last change the table definition itself.
One pittfal: sqlite does not see the index name change until the database is reopened. This seems like a bug, but not a severe one (even without reopening the database, you can still use it).
Suppose the table looks like:
CREATE TABLE tab1(i INTEGER, j INTEGER, t TEXT);
Then I did the following:
BEGIN;
CREATE INDEX pk_tab1 ON tab1(i,j);
pragma writable_schema=1;
UPDATE sqlite_master SET name='sqlite_autoindex_tab1_1',sql=null WHERE name='pk_tab1';
UPDATE sqlite_master SET sql='CREATE TABLE tab1(i integer,j integer,t text,primary key(i,j))' WHERE name='tab1';
COMMIT;
Some tests (in sqlite shell):
sqlite> explain query plan select * from tab1 order by i,j;
0|0|0|SCAN TABLE tab1 USING INDEX sqlite_autoindex_tab1_1
sqlite> drop index sqlite_autoindex_tab1_1;
Error: index associated with UNIQUE or PRIMARY KEY constraint cannot be dropped
According to the sqlite docs about table creation, using the create table as select produces a new table without constraints and without primary key.
However, the documentation also says that primary keys and unique indexes are logically equivalent (see constraints section):
In most cases, UNIQUE and PRIMARY KEY constraints are implemented by creating a unique index in the database. (The exceptions are INTEGER PRIMARY KEY and PRIMARY KEYs on WITHOUT ROWID tables.) Hence, the following schemas are logically equivalent:
CREATE TABLE t1(a, b UNIQUE);
CREATE TABLE t1(a, b PRIMARY KEY);
CREATE TABLE t1(a, b);
CREATE UNIQUE INDEX t1b ON t1(b);
So, even if you cannot alter your table definition through SQL alter syntax, you can get the same primary key effect through the use a unique index.
Also, any table (except those created without the rowid syntax) have an inner integer column known as "rowid". According to the docs, you can use this inner column to retrieve/modify record tables.
You can do it like this:
CREATE TABLE mytable (
field1 text,
field2 text,
field3 integer,
PRIMARY KEY (field1, field2)
);
sqlite> create table t(id integer, col2 varchar(32), col3 varchar(8));
sqlite> insert into t values(1, 'he', 'ha');
sqlite>
sqlite> create table t2(id integer primary key, col2 varchar(32), col3 varchar(8));
sqlite> insert into t2 select * from t;
sqlite> .schema
CREATE TABLE t(id integer, col2 varchar(32), col3 varchar(8));
CREATE TABLE t2(id integer primary key, col2 varchar(32), col3 varchar(8));
sqlite> drop table t;
sqlite> alter table t2 rename to t;
sqlite> .schema
CREATE TABLE IF NOT EXISTS "t"(id integer primary key, col2 varchar(32), col3 varchar(8));
Introduction
This is based on Android's java and it's a good example on changing the database without annoying your application fans/customers. This is based on the idea of the SQLite FAQ page
http://sqlite.org/faq.html#q11
The problem
I did not notice that I need to set a row_number or record_id to delete a single purchased item in a receipt, and at same time the item barcode number fooled me into thinking of making it as the key to delete that item. I am saving a receipt details in the table receipt_barcode. Leaving it without a record_id can mean deleting all records of the same item in a receipt if I used the item barcode as the key.
Notice
Please understand that this is a copy-paste of my code I am work on at the time of this writing. Use it only as an example, copy-pasting randomly won't help you. Modify this first to your needs
Also please don't forget to read the comments in the code .
The Code
Use this as a method in your class to check 1st whether the column you want to add is missing . We do this just to not repeat the process of altering the table receipt_barcode.
Just mention it as part of your class. In the next step you'll see how we'll use it.
public boolean is_column_exists(SQLiteDatabase mDatabase , String table_name,
String column_name) {
//checks if table_name has column_name
Cursor cursor = mDatabase.rawQuery("pragma table_info("+table_name+")",null);
while (cursor.moveToNext()){
if (cursor.getString(cursor.getColumnIndex("name")).equalsIgnoreCase(column_name)) return true;
}
return false;
}
Then , the following code is used to create the table receipt_barcode if it already does NOT exit for the 1st time users of your app. And please notice the "IF NOT EXISTS" in the code. It has importance.
//mDatabase should be defined as a Class member (global variable)
//for ease of access :
//SQLiteDatabse mDatabase=SQLiteDatabase.openOrCreateDatabase(dbfile_path, null);
creation_query = " CREATE TABLE if not exists receipt_barcode ( ";
creation_query += "\n record_id INTEGER PRIMARY KEY AUTOINCREMENT,";
creation_query += "\n rcpt_id INT( 11 ) NOT NULL,";
creation_query += "\n barcode VARCHAR( 255 ) NOT NULL ,";
creation_query += "\n barcode_price VARCHAR( 255 ) DEFAULT (0),";
creation_query += "\n PRIMARY KEY ( record_id ) );";
mDatabase.execSQL(creation_query);
//This is where the important part comes in regarding the question in this page:
//adding the missing primary key record_id in table receipt_barcode for older versions
if (!is_column_exists(mDatabase, "receipt_barcode","record_id")){
mDatabase.beginTransaction();
try{
Log.e("record_id", "creating");
creation_query="CREATE TEMPORARY TABLE t1_backup(";
creation_query+="record_id INTEGER PRIMARY KEY AUTOINCREMENT,";
creation_query+="rcpt_id INT( 11 ) NOT NULL,";
creation_query+="barcode VARCHAR( 255 ) NOT NULL ,";
creation_query+="barcode_price VARCHAR( 255 ) NOT NULL DEFAULT (0) );";
mDatabase.execSQL(creation_query);
creation_query="INSERT INTO t1_backup(rcpt_id,barcode,barcode_price) SELECT rcpt_id,barcode,barcode_price FROM receipt_barcode;";
mDatabase.execSQL(creation_query);
creation_query="DROP TABLE receipt_barcode;";
mDatabase.execSQL(creation_query);
creation_query="CREATE TABLE receipt_barcode (";
creation_query+="record_id INTEGER PRIMARY KEY AUTOINCREMENT,";
creation_query+="rcpt_id INT( 11 ) NOT NULL,";
creation_query+="barcode VARCHAR( 255 ) NOT NULL ,";
creation_query+="barcode_price VARCHAR( 255 ) NOT NULL DEFAULT (0) );";
mDatabase.execSQL(creation_query);
creation_query="INSERT INTO receipt_barcode(record_id,rcpt_id,barcode,barcode_price) SELECT record_id,rcpt_id,barcode,barcode_price FROM t1_backup;";
mDatabase.execSQL(creation_query);
creation_query="DROP TABLE t1_backup;";
mDatabase.execSQL(creation_query);
mdb.setTransactionSuccessful();
} catch (Exception exception ){
Log.e("table receipt_bracode", "Table receipt_barcode did not get a primary key (record_id");
exception.printStackTrace();
} finally {
mDatabase.endTransaction();
}
I had the same problem and the best solution I found is to first create the table defining primary key and then to use insert into statement.
CREATE TABLE mytable (
field1 INTEGER PRIMARY KEY,
field2 TEXT
);
INSERT INTO mytable
SELECT field1, field2
FROM anothertable;
CREATE TABLE tablename(
Column1 INTEGER PRIMARY KEY,
Column2 VARCHAR(50)
)
INSERT INTO tablename
SELECT Column1 , Column2
FROM ANOTHERTABLE
I used the CREATE TABLE AS syntax to merge several columns and encountered the same problem. Here is an AppleScript I wrote to speed the process up.
set databasePath to "~/Documents/Databases/example.db"
set tableOne to "separate" -- Table from which you are pulling data
set tableTwo to "merged" -- Table you are creating
set {tempCol, tempColEntry, permColEntry} to {{}, {}, {}}
set permCol to {"id integer primary key"}
-- Columns are created from single items AND from the last item of a list
-- {{"a", "b", "c"}, "d", "e"} Columns "a" and "b" will be merged into a new column "c". tableTwo will have columns "c", "d", "e"
set nonCoal to {"City", "Contact", "Names", {"Address 1", "Address", "address one", "Address1", "Text4", "Address 1"}, {"E-Mail", "E-Mail Address", "Email", "Email Address", "EmailAddress", "Email"}, {"Zip", "Zip Code", "ZipCode", "Zip"}, {"Telephone", "BusinessPhone", "Phone", "Work Phone", "Telephone"}, {"St", "State", "State"}, {"Salutation", "Mr/Ms", "Mr/s", "Salutations", "Sautation", "Salutation"}}
-- Build the COALESCE statements
repeat with h from 1 to count of nonCoal
set aColumn to item h of nonCoal
if class of aColumn is not list then
if (count of words of aColumn) > 1 then set aColumn to quote & aColumn & quote
set end of tempCol to aColumn
set end of permCol to aColumn
else
set coalEntry to {}
repeat with i from 1 to count of aColumn
set coalCol to item i of aColumn as string
if (count of words of coalCol) > 1 then set coalCol to quote & coalCol & quote
if i = 1 then
set end of coalEntry to "TRIM(COALESCE(" & coalCol & ", '') || \" \" || "
else if i < ((count of aColumn) - 1) then
set end of coalEntry to "COALESCE(" & coalCol & ", '') || \" \" || "
else if i = ((count of aColumn) - 1) then
set as_Col to item (i + 1) of aColumn as string
if (count of words of as_Col) > 1 then set as_Col to quote & as_Col & quote
set end of coalEntry to ("COALESCE(" & coalCol & ", '')) AS " & as_Col) & ""
set end of permCol to as_Col
end if
end repeat
set end of tempCol to (coalEntry as string)
end if
end repeat
-- Since there are ", '' within the COALESCE statement, you can't use "TID" and "as string" to convert tempCol and permCol for entry into sqlite3. I rebuild the lists in the next block.
repeat with j from 1 to count of tempCol
if j < (count of tempCol) then
set end of tempColEntry to item j of tempCol & ", "
set end of permColEntry to item j of permCol & ", "
else
set end of tempColEntry to item j of tempCol
set end of permColEntry to item j of permCol
end if
end repeat
set end of permColEntry to ", " & item (j + 1) of permCol
set permColEntry to (permColEntry as string)
set tempColEntry to (tempColEntry as string)
-- Create the new table with an "id integer primary key" column
set createTable to "create table " & tableTwo & " (" & permColEntry & "); "
do shell script "sqlite3 " & databasePath & space & quoted form of createTable
-- Create a temporary table and then populate the permanent table
set createTemp to "create temp table placeholder as select " & tempColEntry & " from " & tableOne & "; " & "insert into " & tableTwo & " select Null, * from placeholder;"
do shell script "sqlite3 " & databasePath & space & quoted form of createTemp
--export the new table as a .csv file
do shell script "sqlite3 -header -column -csv " & databasePath & " \"select * from " & tableTwo & " ; \"> ~/" & tableTwo & ".csv"

Resources