SQLite Insert or Ignore/Replace with lastInsertID in one statement - sqlite

I feel like this should be easy...
I have a table like this:
CREATE TABLE table_name (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
UNIQUE (name COLLATE NOCASE)
)
No two names are the same, case insensitive. Right now I have users adding names and it does this:
INSERT INTO table_name (name) VALUES ("my name");
And I need to get the id of the row, which is easy with PHP PDO's lastInsertID(). But I also want, if the user is adding a name that's already in the database, for nothing to be added to the database, but still get that id without having to do another database call. I was hoping for something like
INSERT OR REPLACE INTO table_name (name) VALUES ("my name");
And have it just overwrite the same data into the cell and return the lastInsertID (even though it wasn't inserted?). But that doesn't work. What are my other options? Will I have to do a separate database query to see if the name field already exists?

With the OR REPLACE clause, the statement always deletes any old row.
Just use two statements. (There is no technical reason for doing this in a single statement.)

Related

using an expression as table name in sqlite

I am trying to check if a table exists prior to send a SELECT query on that table.
The table name is composed with a trailing 2 letters language code and when I get the full table name with the user's language in it, I don't know if the user language is actually supported by my database and if the table for that language really exists.
SELECT name FROM sqlite_master WHERE name = 'mytable_zz' OR name = 'mytable_en' ORDER BY ( name = 'mytable_zz' ) DESC LIMIT 1;
and then
SELECT * FROM table_name_returned_by_first_query;
I could have a first query to check the existence of the table like the one above, which returns mytable_zz if that table exists or mytable_en if it doesn't, and then make a second query using the result of the first as table name.
But I would rather have it all in one single query that would return the expected results from either the user's language table or the english one in case his language is not supported, without throwing a "table mytable_zz doesn't exist" error.
Anyone knows how I could handle this ?
Is there a way to use the result of the first query as a table name in the 2nd ?
edit : I don't have the hand of the database itself which is generated automatically, I don't want to get involved in a complex process of manually updating any new database that I get. Plus this query is called multiple times and having to retrieve the result of a first query before launching a second one is too long. I use plain text queries that I send through a SQLite wrapper. I guess the simplest would rather be to check if the user's language is supported once for all in my program and store a string with either the language code of the user or "en" if not supported, and use that string to compose my table name(s). I am going to pick that solution unless someone has a better idea
Here is a simple MRE :
CREATE TABLE IF NOT EXISTS `lng_en` ( key TEXT, value TEXT );
CREATE TABLE IF NOT EXISTS `lng_fr` ( key TEXT, value TEXT );
INSERT INTO `lng_en` ( key , value ) VALUES ( 'question1', 'What is your name ?');
INSERT INTO `lng_fr` ( key , value ) VALUES ( 'question1', 'Quel est votre nom ?');
SELECT `value` FROM lng_%s WHERE `key` = 'question1';
where %s is to be replaced by the 2 letters language code. This example will work if the provided code is 'en' or 'fr' but will throw an error if the code is 'zh', in this case I would like to have the same result returned as with 'en' ....
Not in SQL, without executing it dynamically.. But if this is your front end that is running this SQL then it doesn't matter so much. Because your table name came out of the DB there isn't really any opportunity for SQL injection hacking with it:
var tabName = db.ExecuteScalar("SELECT name FROM sqlite_master WHERE name = 'mytable_zz' OR name = 'mytable_en' ORDER BY ( name = 'mytable_zz' ) DESC LIMIT 1;")
var results = db.ExecuteQuery("SELECT * FROM " + tabName);
Yunnosch's comment is quite pertinent; you're essentially storing in a table name information that really should be in a column.. You could consider making a single table and then a bunch of views like mytable_zz the definition of which is SELECT * FROM mytable WHERE lang = 'zz' etc, and make instead-of triggers if you want to cater for a legacy app that you cannot change; the legacy app would select from / insert into the views thinking they are tables, but in reality your data is single table and easier to manage

Is it possible to select a field from a table, but override it with a new value in the select statement?

I am creating a view like this:
Let's say I originally have this:
select * from mydb.mytable
mydb.mytable has a field called FirstName, but I want to transform its value in the select statement. Conceptually, I want to do this:
select *, upper(firstname) firstname from mydb.mytable
The problem is that * is already returning FirstName, so adding another column of the same name to the select breaks the SQL. To get it to work, I have to list each field like this:
select upper(firstname) firstname, lastname, city, state, zip
This is just one example, but the table I really want to use this with has 30+ columns. I don't like the idea of having to list out each column because adding a new field to the table means I have to modify the SQL (ordinal field position doesn't matter).
Well, that's the way SQL is designed, it's not a specific Teradata problem.
You want something like "select * but firstname" and no DBMS has implemented such a syntax.
Btw, one of (my) basic SQL rules is: never write "SELECT *" :-)
As dnoeth says, that's just how SQL works. Also, I'd reinforce his comment about never using select *, especially in a view.
To address concerns like this, I keep the table and view DDL together in code. Whenever you change the table definition, you change the view definition at the same time. That way, whenever you add or remove columns from your table (your stated concern), your view always remains current.

How to DELETE a row with a GUID Value in SQLite

I have a column in SQLite of GUID type, I have tried a query like this, and it returns no error, but the row is not deleted
DELETE FROM MyTable WHERE Id='4ffbd580-b17d-4731-b162-ede8d698e026';
In SQLite Browser the Id values look like binary values, they have strange characters.
I also have tried this, but still does not work
DELETE FROM MyTable WHERE Id='{4ffbd580-b17d-4731-b162-ede8d698e026}';
I know I'm late for this, but it might just be useful for someone with the same problem.
I have a uniqueidentifier type of column in one of my tables and when I execute a select query without any conditions, it returns the result guid column values in this format -
{000B6A69-04D6-C557-7EA3-08CF8C8AD84B}
(Yes, with the braces)
I found out using typeof() function that my guid column values had been stored as text. So, I just tried out four different statements and luckily, the 4th one worked -
1. select myGuidColumn, typeof(myGuidColumn) from MyTable WHERE [myGuidColumn] = '000B6A69-04D6-C557-7EA3-08CF8C8AD84B' --didn't work
2. select myGuidColumn, typeof(myGuidColumn) from MyTable WHERE [myGuidColumn] = '{000B6A69-04D6-C557-7EA3-08CF8C8AD84B}' --didn't work
3. select myGuidColumn, typeof(myGuidColumn) from MyTable WHERE [myGuidColumn] LIKE '{000B6A69-04D6-C557-7EA3-08CF8C8AD84B}' --didn't work
4. select myGuidColumn, typeof(myGuidColumn) from MyTable WHERE [myGuidColumn] LIKE '000B6A69-04D6-C557-7EA3-08CF8C8AD84B' --it works!
Try this command. Id is a probably a binary blob field
DELETE FROM MyTable WHERE Id= X'4ffbd580b17d4731b162ede8d698e026';

SQLite Query to Insert a record If not exists

I want to insert a record into a sqlite table if its actually not inserted.
Let's say it has three fields pk, name, address
I want to INSERT new record with name if that name not added preveously.
Can we do with this in a single Query. Seems like its slightly different from SQL Queries sometimes.
Yes, you can do that with a single query.
INSERT ON CONFLICT IGNORE should help you: http://www.sqlite.org/lang_conflict.html
Put a unique key on the name, this will create a conflict when you try inserting a record if the name already exists.
The default is ABORT, so without the IGNORE, the statement will return an error. If you don't want that, use IGNORE.
If you can't make use of a UNIQUE INDEX in combination with INSERT INTO or INSERT OR IGNORE INTO, you could write a query like this;
INSERT INTO table (column)
SELECT value
WHERE NOT EXISTS (SELECT 1
FROM table
WHERE column = value)

SQLite "INSERT OR REPLACE INTO" vs. "UPDATE ... WHERE"

I've never seen the syntax INSERT OR REPLACE INTO names (id, name) VALUES (1, "John") used in SQL before, and I was wondering why it's better than UPDATE names SET name = "John" WHERE id = 1. Is there any good reason to use one over the other. Is this syntax specific to SQLite?
UPDATE will not do anything if the row does not exist.
Where as the INSERT OR REPLACE would insert if the row does not exist, or replace the values if it does.
Another fact to notice: INSERT OR REPLACE will replace any values not supplied in the statement.
For instance if your table contains a column "lastname" which you didn't supply a value for, INSERT OR REPLACE will nullify the "lastname" if possible (if constraints allow it), or fail.
REPLACE INTO table(column_list) VALUES(value_list);
is a shorter form of
INSERT OR REPLACE INTO table(column_list) VALUES(value_list);
For REPLACE to execute correctly your table structure must have unique rows, whether a simple primary key or a unique index.
REPLACE deletes, then INSERTs the record and will cause an INSERT Trigger to execute if you have them setup. If you have a trigger on INSERT, you may encounter issues.
This is a work around.. not checked the speed..
INSERT OR IGNORE INTO table (column_list) VALUES(value_list);
followed by
UPDATE table SET field=value,field2=value WHERE uniqueid='uniquevalue'
This method allows a replace to occur without causing a trigger.
The insert or replace query would insert a new record if id=1 does not already exist.
The update query would only oudate id=1 if it aready exist, it would not create a new record if it didn't exist.

Resources