How to write this conditional request correctly MariaDB - mariadb

I need to check if a book rating for specific book from specific person exists.
If it does update it, if it doesnt create it.
I am getting a whole bunch of wrong errors for 9th 10th.... 12th parameter missing while I count only 8
My mariaDB version is 10.5.8-MariaDB.
My code:
const createBookRate = async (userId, bookId, rate) => {
const sql = `
SELECT IF(EXISTS( SELECT * from rates WHERE rates.users_id=? AND rates.books_id=? ),
UPDATE rates SET rates.rate=? WHERE rates.users_id=? AND rates.books_id=?,
INSERT INTO rates(users_id, books_id, rate))
VALUE (?,?,?,?,?,?,?,?);
`
const { insertId } = await pool.query(sql, [userId, bookId, rate, userId, bookId, userId, bookId, rate])
const rateEntry = await getBookRate(insertId)
return rateEntry
}

You cannot perform an UPDATE or an INSERT inside the IF clause of a SELECT statement, those must be performed separately.
To perform this in a safe manner, use a transaction and first lock the selected row with SELECT ... FOR UPDATE, then either UPDATE or INSERT it and finally COMMIT the transaction.
If the table has a primary key, you can use INSERT ... ON DUPLICATE KEY UPDATE to either insert the row or update it, depending on whether it exists or not. This allows everything to be done in one step without having to first select the affected rows.

Related

Can "PRAGMA foreign_keys=OFF" SQLite statement be used inside a TypeORM migration?

I'm using TypeORM (v0.2.18) with Node.js (v12.7.0) to perform migrations in my SQLite database.
This is my situation: I have a table named country and a table named workflow. I want to remove one of the columns, named name, of country but workflow references country through this column.
Playing with DB Browser for SQLite I could remove the column successfully with these statements:
PRAGMA foreign_keys=OFF;
CREATE TEMPORARY TABLE country_backup(id, createdAt, updatedAt, enabled, codeIso2);
INSERT INTO country_backup SELECT id, createdAt, updatedAt, enabled, codeIso2 FROM country;
DROP TABLE country;
CREATE TABLE country(id, createdAt, updatedAt, enabled, codeIso2);
INSERT INTO country SELECT id, createdAt, updatedAt, enabled, codeIso2 FROM country_backup;
DROP TABLE country_backup;
PRAGMA foreign_keys=ON;
Which I used in a TypeORM migration like this:
...
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query("PRAGMA foreign_keys=OFF");
await queryRunner.query("CREATE TEMPORARY TABLE country_backup(id, createdAt, updatedAt, enabled, codeIso2)");
await queryRunner.query("INSERT INTO country_backup SELECT id, createdAt, updatedAt, enabled, codeIso2 FROM country");
await queryRunner.query("DROP TABLE country");
await queryRunner.query("CREATE TABLE country(id, createdAt, updatedAt, enabled, codeIso2)");
await queryRunner.query("INSERT INTO country SELECT id, createdAt, updatedAt, enabled, codeIso2 FROM country_backup");
await queryRunner.query("DROP TABLE country_backup");
await queryRunner.query("PRAGMA foreign_keys=ON");
}
...
But I get this error:
Error during migration run:
QueryFailedError: SQLITE_CONSTRAINT: FOREIGN KEY constraint failed
at new QueryFailedError (/.../api/src/error/QueryFailedError.ts:9:9)
at Statement.handler (/.../src/driver/sqlite/SqliteQueryRunner.ts:53:26)
at Statement.replacement (/.../api/node_modules/sqlite3/lib/trace.js:19:31)
at Statement.replacement (/.../api/node_modules/sqlite3/lib/trace.js:19:31) {
message: 'SQLITE_CONSTRAINT: FOREIGN KEY constraint failed',
errno: 19,
code: 'SQLITE_CONSTRAINT',
name: 'QueryFailedError',
query: 'DROP TABLE country',
parameters: []
}
Why did it work in DB Browser for SQLite but not with TypeORM? It's like it ignored the PRAGMA statement.
Based on this, I tried with both PRAGMA foreign_keys and PRAGMA legacy_alter_table (setting them OFF and ON (before any action) and ON and OFF (after any action), respectively).
I tried using the PRAGMA's both inside the up() function and outside it. Outside it means I put the statements before and after await typeOrmConnection.runMigrations() in my main.ts file, like await typeOrmConnection.query("PRAGMA foreign_keys=OFF").
The sqlite docs say that you can't set PRAGMA foreign_keys=off while executing multiple statements, so I tried this and got it work by separating the PRAGMA statements from the others.
A transaction has been started already for the query runner instance passed to the up() function. Therefore, end the existing transaction immediately, then use an outside-of-transaction query to turn off the checks, and then start another transaction.
Write the rest of your non-PRAGMA code within the new transaction.
Right before the end of the function, commit the inner transaction, then use a regular single-statement query to turn the key checks back on, and then start another transaction (so that there will be something to automatically close after up() runs, otherwise you get an error).
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.commitTransaction();
await queryRunner.query('PRAGMA foreign_keys=off');
await queryRunner.startTransaction();
// the rest of your queries
await queryRunner.commitTransaction();
await queryRunner.query('PRAGMA foreign_keys=on');
await queryRunner.startTransaction();
}

Ho do I generate a value automatically in Firestore document?

My documents require an integer value which is used as index for queries. Each document contains an index field ( number) where I am assigning the values manually one by one. Maybe somewhere I can put something that stores the current index value and increments it and assigns it to the new document as it's index value whenever I create the new document.
There is no such feature in Cloud Firestore. You will need to come up with all the values yourself. The only thing that Firestore can generate for you automatically is a timestamp based on the server's sense of time.
I can think of two ways to handle this, though I don't know that you want to use an integer based index for your document id. If you delete one, your index is now off. And what about write failures? Race conditions? Etc. You may want to rethink your data structure & organization.
If using an integer is not required as the document id:
// Create a reference to a new document inside your collection
const ref = firebase.firestore().collection('myCollectionName').doc()
// Now you have an auto-generated document id you can use for your code
const myData = {...}
const setDoc = await firebase.firestore().collection('myCollectionName').doc(ref.id).set(myData)
If using an integer is required:
You'll want a separate collection/object that keeps track of the latest index so you don't run into collisions. Then, you'll want to increment that value to get the next index, and then use that as your id. This comes with inherent problems like...what if the data is bad as you try to enter it, but after you've incremented the value...etc.
// Collection: myIndex
// Doc: index
// Value: {lastIndex: 1}
const doc = await firebase.firestore().collection('myIndex').doc('index')
// You now have the last index value using:
const lastIndex = doc.val().lastIndex
const nextIndex = lastIndex + 1
const myData = {...}
// Now run a batched operation to write to both documents
const batch = firebase.firestore().batch()
// Update the index document
const indexUpdateRef = firebase.firestore().collection('myIndex').doc('index')
batch.update(indexUpdateRef, {lastIndex: nextIndex})
// Add your new myData document
const newDataRef = firebase.firestore().collection('myCollectionName').doc(nextIndex)
batch.set(newDataRef, myData)
// Commit the batch
await batch.commit()
As I said - I think this is a really bad idea and workflow, but it's doable. Lots missing from keeping this in sync as well.
In either case above...
You can take advantage of FieldValue.increment() to help auto increment your integer values, but that will add to more reads & writes, longer processing time, and higher charges for all of that. Which is why I started with and maintain that you should probably rethink your data structure or consider a RDB if you want autoincremented indices.

How to insert into a table with ONLY an Auto-Increment column

I have a table the only has an Id column in SQL. It is an Auto-Increment column. It is used to keep track of a booking number sequence to be sent to an outside system. In SQL I use this to insert a new record:
insert into bookingnumbers default values
I would like to use entity framework and get the Next available Id. I have tried this:
private async Task<long> GetNextBookingNumberAsync()
{
BookingNumbers bookingNumber = default;
GhanemContext.BookingNumbers.Add(bookingNumber);
await GhanemContext.SaveChangesAsync();
return bookingNumber.Id;
}
However, booking number is just null and I get:
ArgumentNullException: Value cannot be null. Parameter name: entity
Any help would be greatly appreciated!

DynamoDb - .NET Object Persistence Model - LoadAsync does not apply ScanCondition

I am fairly new in this realm and any help is appreciated
I have a table in Dynamodb database named Tenant as below:
"TenantId" is the hash primary key and I have no other keys. And I have a field named "IsDeleted" which is boolean
Table Structure
I am trying to run a query to get the record with specified "TenantId" while it is not deleted ("IsDeleted == 0")
I can get a correct result by running the following code: (returns 0 item)
var filter = new QueryFilter("TenantId", QueryOperator.Equal, "2235ed82-41ec-42b2-bd1c-d94fba2cf9cc");
filter.AddCondition("IsDeleted", QueryOperator.Equal, 0);
var dbTenant = await
_genericRepository.FromQueryAsync(new QueryOperationConfig
{
Filter = filter
}).GetRemainingAsync();
But no luck when I try to get it with following code snippet (It returns the item which is also deleted) (returns 1 item)
var queryFilter = new List<ScanCondition>();
var scanCondition = new ScanCondition("IsDeleted", ScanOperator.Equal, new object[]{0});
queryFilter.Add(scanCondition);
var dbTenant2 = await
_genericRepository.LoadAsync("2235ed82-41ec-42b2-bd1c-d94fba2cf9cc", new DynamoDBOperationConfig
{
QueryFilter = queryFilter,
ConditionalOperator = ConditionalOperatorValues.And
});
Any Idea why ScanCondition has no effect?
Later I also tried this: (throw exception)
var dbTenant2 = await
_genericRepository.QueryAsync("2235ed82-41ec-42b2-bd1c-d94fba2cf9cc", new DynamoDBOperationConfig()
{
QueryFilter = new List<ScanCondition>()
{
new ScanCondition("IsDeleted", ScanOperator.Equal, 0)
}
}).GetRemainingAsync();
It throws with: "Message": "Must have one range key or a GSI index defined for the table Tenants"
Why does it complain about Range key or Index? I'm calling
public AsyncSearch<T> QueryAsync<T>(object hashKeyValue, DynamoDBOperationConfig operationConfig = null);
You simply cant query a table only giving a single primary key (only hash key). Because there is one and only one item for that primary key. The result of the Query would be that still that single item, which is actually Load operation not Query. You can only query if you have composite primary key in this case (Hash (TenantID) and Range Key) or GSI (which doesn't impose key uniqueness therefore accepts duplicate keys on index).
The second code attempts to filter the Load. DynamoDBOperationConfig's QueryFilter has a description ...
// Summary:
// Query filter for the Query operation operation. Evaluates the query results and
// returns only the matching values. If you specify more than one condition, then
// by default all of the conditions must evaluate to true. To match only some conditions,
// set ConditionalOperator to Or. Note: Conditions must be against non-key properties.
So works only with Query operations
Edit: So after reading your comments on this...
I dont think there conditional expressions are for read operations. AWS documents indicates they are for put or update operations. However, not being entirely sure on this since I never needed to do a conditional Load. There is no such thing like CheckIfExists functionality as well in general. You have to read the item and see if it exists. Conditional load will still consume read throughput so your only advantage would be only NOT retrieving it in other words saving the bandwith (which is very negligible for single item).
My suggestion is read it and filter it in your application layer. Dont query for it. However what you can also do is if you very need it you can use TenantId as hashkey and isDeleted for range key. If you do so, you always have to query when you wanna get a tenant. With the query you can set rangeKey(isDeleted) to 0 or 1. This isnt how I would do it. As I said, would just read it and filter it at my application.
Another suggestion thing could be setting a GSI on isDeleted field and writing null when it is 0. This way you can only see that attribute in your table when its only 1. GSI on such attribute is called sparse index. Later if you need to get all the tenants that are deleted (isDeleted=1) you can simply scan that entire index without conditions. When you are writing null when its 0 dynamoDB wont put it in the index at the first place.

Which rows were deleted? [duplicate]

I need to run two statements like so:
Select amount from db where ID=5
DELETE from db where ID=5
Currently I prepare and run two different statements. I wonder if there is a way to combine it in one statement.
Basically all I need to do is to get an amount column from the row before it is deleted.
SQLite does not support this extension to standard SQL -- you do have to use both statements, SELECT first, DELETE next. You can wrap them in a transaction, of course, (BEGIN and COMMIT statements before and after will guarantee that), to guarantee atomicity and consistency.
If you just want to select rows and delete them in one pass, you can use the returning clause in the delete statement.
For instance:
delete from myTable returning *
The delete statement has all select functionalities possible such as with and where that permits to select rows with any logic.
Assuming that your calling thread/process has a unique identifier (e.g. thread_id), I think a viable approach would be to add a flag (say, "handlerid") to your table, which is set to null on insert, and then do:
update db set handlerid = <my_thread_id> where handlerid is null;
select * from db where handlerid is not null and handlerid = <my_thread_id>;
delete from db where handlerid is not null and handlerid = <my_thread_id>;
Not sure how it would perform vs a transaction but can't think of any reason it would be materially worse (might even be better), and using this approach the code seems about as straightforward as you can get. unlike a transaction, it won't require you to loop in the case that a transaction fails, in order to be sure that all outstanding elements that were in the table at the time of the most recent SELECT got processed.
It's late but for future visitor. In my case I did like below
I want to delete that ID where amount = 5 so I did this
public void selectAndDelete() {
try {
SQLiteDatabase db = this.getWritableDatabase();
int i=db.delete(table_name, "ID = (select ID from table_name where amount = 5)", null);
if(i>0)
{
// I'm inserting here new record
}
} catch (SQLException e) {
}
}
So, In your case you need to modify where condition like you want
You can do this by separating the two statements with a semicolon, e.g. (using the .NET port of SQLite):
using (SQLiteConnection conn = new SQLiteConnection("Data Source=fie.db3"))
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "SELECT id FROM fies; DELETE FROM fies WHERE id = 5;";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine(reader[0]);
}
}
}
}

Resources