SQLite changes() counts non-changing UPDATEs - sql

I have question regarding SQLite's changes() function, which, according to the documentation, "returns the number of database rows that were changed or inserted or deleted by the most recently completed INSERT, DELETE, or UPDATE statement" (also see the documentation of the underlying C/C++ function).
I was hoping to use this function to check whether the execution of an UPDATE statement pertaining to a single row has really caused that row to be changed or not.
By changed I do not just mean that the row matched the statement's WHERE clause. No, instead what I mean is that, for the row in question, the value of at least 1 column is actually different after the execution compared to before. If you ask me this is the only proper definition of a change in this context.
So I was hoping to detect such changes by checking whether changes() returns 1 (row changed) or 0 (row unchanged) when called right after the execution of the UPDATE statement.
But much to my despair this does not seem to work as expected.
Allow me to illustrate:
CREATE TABLE People (Id INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT NOT NULL);
INSERT INTO People (Name) VALUES ("Astrid");
SELECT changes();
Here changes() returns 1, as expected because we just INSERTed 1 row.
UPDATE People SET Name = "Emma" WHERE Id = 1;
SELECT changes();
Here changes() returns 1, as expected because 1 row was UPDATEd (i.e. actually changed: the Name of the Person with Id = 1 was "Astrid" but is now "Emma").
UPDATE People SET Name = "John" WHERE Id = 200;
SELECT changes();
Here changes() returns 0, as expected because there is no row with Id = 200.
So far so good. But now have a look at the following UPDATE statement, which does indeed match an existing row, but does not actually change it at all (Name remains set to "Emma")...
UPDATE People SET Name = "Emma" WHERE Id = 1;
SELECT changes();
Here changes() returns 1, while I was of course hoping for 0 :-(.
Perhaps this would have made sense if the function was called something like matched_rows() or affected_rows(). But for a function called changes(), and documented as it is, this behaviour strikes me as illogical, or confusing at best.
So anyway, can somebody explain why this happens, or, even better, suggest an alternative strategy to achieve my goal in a reliable (and efficient) way?
All I can think of is to actually do something like SELECT * FROM People WHERE Id = x, compare all returned column values with the values I'm about to set in the UPDATE statement and thereby decide whether I need to execute the UPDATE at all. But that can't be very efficient, right?
Of course in this toy example it might not matter much, but in my actual application I'm dealing with tables with many more columns, some of which are (potentially big) BLOBs.

The database does not compare old and new values; any UPDATEd row always counts as "changed" even if the values happen to be the same.
The documentation says that
the UPDATE affects … those rows for which the result of evaluating the WHERE clause expression as a boolean expression is true.
If you want to check the old value, you have to do it explicitly:
UPDATE People SET Name = 'Emma' WHERE Id = 1 AND Name IS NOT 'Emma';

Related

Dealing with multiple output results in UPSERT query [SQL]

I'm trying to do an update query in which a single row the table is updated and, if nothing has matched and updated, a new row is inserted. In each case, I need the query to return the ID of the inserted row.
The issue I'm having is this query is returning 2 separate results when the insert case is reached, one for each output (the first empty, the second containing the ID). I'm running this query using SQL Alchemy on python and I'm only able to see the first result, which is empty.
UPDATE [Rights]
SET accessLevel = :access_level
OUTPUT inserted.rightsID
WHERE principal = :principal and [function] = :function
IF ##ROWCOUNT = 0
INSERT INTO Rights(principal, [function], accessLevel)
OUTPUT inserted.rightsID
VALUES(:principal, :function, :access_level)
And I'm calling it like so:
inserted_right_id = session.execute(sql_rights_update, right).fetchall()
Can anyone recommend a way of changing the query so that I can still use the UPSERT method, but only receive one of the IDs? I was considering storing the OUTPUT values into a table and returning that, or wrapping the whole thing in a select but hopefully there's something more elegant out there.
Thanks a million.
Feeling quite dumb. I simply added a
IF EXISTS(SELECT * FROM Rights WHERE principal = :principal and [function] = :function)
UPDATE ...
ELSE
INSERT ...

Why can't I use the field name as a parameter in an Access parameter query?

Working through some insert/update queries on an application today and came across a result I hadn't expected.
Queries
My insert queries look similar to this:
PARAMETERS nm TEXT(10), st TEXT(2);
INSERT INTO park(pname, pstate)
VALUES([nm],[st]);
And their companion updates were like this:
PARAMETERS id LONG, nm TEXT(10), st TEXT(2);
UPDATE park
SET
pname = [nm], pstate = [st]
WHERE
ID = [id];
The table they were updating was similar to this:
park
ID LONG | pname TEXT(10) | pstate TEXT(2)
Unexpected Result
Working through writing the queries, I tested each by running it against the database and providing test values for the various parameters. After the insert query, I'd test the update by updating the newly inserted record.
In most cases the tables were empty, so the update would simply update the single record in place.
However as soon as I ran the update on a previously populated table, I found the query was attempting to update ALL records, not just the one whose ID was being provided through the parameter.
The question is why??
Problem
While ID certainly was a field in the park table, using id as a parameter was essentially stating the following in the WHERE clause:
WHERE ID = id;
or
WHERE ID = ID;
Since ID is always equal to itself, the UPDATE attempted to update ALL records instead of only the expected record with the provided ID.
Solution
To fix the problem, I simply used the first and last letter of the table being updated before the id for each case I was updating a record identified by its ID. So the working code - that updates only the record identified - is:
PARAMETERS pkid LONG, nm TEXT(10), st TEXT(2);
UPDATE park
SET
pname = [nm], pstate = [st]
WHERE
ID = [pkid];
Obviously I wasn't paying close attention while I was writing the query -- while I'd used different names for other parameters, I didn't do so when it came to the ID for the update query.
Bottom Line
Whenever working with paramaterized queries, make sure your parameter names DO NOT match your table's field names.
Doing so will avoid the issue noted above as well as other related issues.
You'll also want to avoid reserved words for your table, field, and parameter names.
Hope this helps someone avoid a possibly nasty surprise when updating records -- or trying to figure out why their parameter queries didn't seem to work.

SQL UPDATE read column values before setting

I have searched for this information both on SO and on google, but have not found any authoritative answer.
When you have an update statement like:
UPDATE table SET rowA = rowB, rowB = NULL ...
It seems that:
ordering is not important (UPDATE table SET rowB = NULL, rowA = rowB)
nonetheless, the result is that rowA takes the prev value in rowB, because it seems that UPDATE first reads the previous values, then it updates them.
I would like to know if the two above points are true in general for SQL, i.e. if they are part of the SQL UPDATE semantics, if they are in the standard, or if it is an implementation details (and therefore subject to change).
Thanks!
EDIT: Let me stress that I would like an "authoritative" answer; I already tested on a number of SQL implementation that the behaviour is indeed the one depicted here. What I need is a "proof" that this is actually in the SQL standard/spec/semantics of UPDATE, with a link to the standard or, alternatively, to a surrogate reliable source (MSDN, dev.mysql.com, Oracle or PostgreSQL docs, ...)
James R. Groff, Paul N. Weinberg: SQL The complete reference (Osborne 1999), page 209 states
start quote
If an expression in the assignment list references one of the columns of the target table,
the value used to calculate the expression is the value of that column in the current row
before any updates are applied. The same is true of column references that occur in the
WHERE clause. For example, consider this (somewhat contrived) UPDATE statement:
UPDATE OFFICES
SET QUOTA = 400000.00, SALES = QUOTA
WHERE QUOTA < 400000.00
Before the update, Bill Adams had a QUOTA value of $350,000 and a SALES value of
$367,911. After the update, his row has a SALES value of $350,000, not $400,000. The
order of the assignments in the SET clause is thus immaterial; the assignments can be
specified in any order.
end quote
The same is supported by chapter 13.9 item 6, page 393, of a draft to the ANSI-92 SQL standard (X3H2-93-004), found here.
This is the most implementaion independent and the closest I could get so far.
Other sources of X3H2-93-004 can be found e.g. here (pg 590, item 15)
This is standard behaviour. When you reference a row, you refer to the pre-update version.
In SQL server the two row versions can be visualized with the output clause
update YourTable
set col1 = col1 + 1
output deleted.col1 -- Pre-update version of row
, inserted.col1 -- Post-update version of row
From the postgeSQL documentation for update (http://www.postgresql.org/docs/8.3/static/sql-update.html)
expression
An expression to assign to the column. The expression can use the old values of this and other columns in the table.

SQL / DB2 - retrieve value and update/increment simultaniously

I'm connecting to a DB2 database and executing SQL statements.
One example of what is being done is:
select field from library/file
[program code line finishes executing]
[increment value by one]
update library/file set field = 'incremented value'
I have a need to immediately update the value while returning the value. Rather than having to wait for the script to complete, and then run a separate UPDATE statement.
The concept of what I would like to do is this:
select field from library/file; update library/file set field = (Current Value + 1); go;
Please note... this is not the common SQL database most would be familiar with, it is a DB2 database on an IBM i.
Thanks!
Consider using a DB2 SEQUENCE to manage the next available number, if this file is simply intended to have a single row storing your counter. That is what a SEQUENCE is designed to do.
To set it up, use a CREATE SEQUENCE statement.
To increment the value and retrieve, use a SEQUENCE reference expression of the form NEXT VALUE FOR sequence-name. To find out what the most recent value was, use the PREVIOUS VALUE FOR sequence-name. These expressions can be used like a regular any column expression, such as in a SELECT or INSERT statement.
Suppose, for example you want to do this for invoice numbers (and maybe your accounting department doesn't want their first invoice number to be 000001, so we will initialize it higher).
CREATE SEQUENCE InvoiceSeq
as decimal (7,0)
start with 27000; -- for example
You could get a number for a new invoice like this:
SELECT NEXT VALUE FOR InvoiceSeq
INTO :myvar
FROM SYSIBM/SYSDUMMY1;
But what is this SYSIBM/SYSDUMMY1 table? We're not really getting anything from table, so why are we pretending to do so? The SELECT needs a FROM-table clause. But since we don't need one, let's use a VALUES INTO statement.
VALUES NEXT VALUE FOR InvoiceSeq
INTO :myvar;
So that has incremented the counter, and put the value into our variable. You could use that value to INSERT into our InvoiceHeaders and InvoiceDetails tables.
Or, you could increment the counter as you write an InvoiceHeader, then use it again when writing the InvoiceDetails.
INSERT INTO InvoiceHeaders
(InvoiceNbr, Customer, InvoiceDate)
VALUES (NEXT VALUE FOR InvoiceSeq, :custnbr, :invdate);
for each invoice detail
INSERT INTO InvoiceDetails
(InvoiceNbr, InvoiceLine, Reason, Fee)
VALUES (PREVIOUS VALUE FOR InvoiceSeq, :line, :itemtxt, :amt);
The PREVIOUS VALUE is local to the particular job, so there should be no risk of another job getting the same number.
update library/file set field = field + 1;
select field from library/file;
[program code line finishes executing]
[increment value by one]
This handles the problem of another app updating the number between the time you fetch it and the time you update it. Update it and then use it. If two apps try to update simultaneously, one will wait.
A SEQUENCE object is designed exactly for this purpose, but if you are forced to keep this 'next ID' file updated, this is how I'd do it. Follow the link in the comment by #Clockwork-Muse for info on the SEQUENCE object, or try this example from V5R4.
His request is like this:
UPDATE sometable
SET somecounter = somecounter + 10,
:returnvar = somecounter + 10;
Updates and retrieves at the same time.
This is possible in MSSQL, In fact I use it alot there,
but DB2 doesnt seem to have this feature.

How to make a view column NOT NULL

I'm trying to create a view where I want a column to be only true or false. However, it seems that no matter what I do, SQL Server (2008) believes my bit column can somehow be null.
I have a table called "Product" with the column "Status" which is INT, NULL. In a view, I want to return a row for each row in Product, with a BIT column set to true if the Product.Status column is equal to 3, otherwise the bit field should be false.
Example SQL
SELECT CAST( CASE ISNULL(Status, 0)
WHEN 3 THEN 1
ELSE 0
END AS bit) AS HasStatus
FROM dbo.Product
If I save this query as a view and look at the columns in Object Explorer, the column HasStatus is set to BIT, NULL. But it should never be NULL. Is there some magic SQL trick I can use to force this column to be NOT NULL.
Notice that, if I remove the CAST() around the CASE, the column is correctly set as NOT NULL, but then the column's type is set to INT, which is not what I want. I want it to be BIT. :-)
You can achieve what you want by re-arranging your query a bit. The trick is that the ISNULL has to be on the outside before SQL Server will understand that the resulting value can never be NULL.
SELECT ISNULL(CAST(
CASE Status
WHEN 3 THEN 1
ELSE 0
END AS bit), 0) AS HasStatus
FROM dbo.Product
One reason I actually find this useful is when using an ORM and you do not want the resulting value mapped to a nullable type. It can make things easier all around if your application sees the value as never possibly being null. Then you don't have to write code to handle null exceptions, etc.
FYI, for people running into this message, adding the ISNULL() around the outside of the cast/convert can mess up the optimizer on your view.
We had 2 tables using the same value as an index key but with types of different numerical precision (bad, I know) and our view was joining on them to produce the final result. But our middleware code was looking for a specific data type, and the view had a CONVERT() around the column returned
I noticed, as the OP did, that the column descriptors of the view result defined it as nullable and I was thinking It's a primary/foreign key on 2 tables; why would we want the result defined as nullable?
I found this post, threw ISNULL() around the column and voila - not nullable anymore.
Problem was the performance of the view went straight down the toilet when a query filtered on that column.
For some reason, an explicit CONVERT() on the view's result column didn't screw up the optimizer (it was going to have to do that anyway because of the different precisions) but adding a redundant ISNULL() wrapper did, in a big way.
All you can do in a Select statement is control the data that the database engine sends to you as a client. The select statement has no effect on the structure of the underlying table. To modify the table structure you need to execute an Alter Table statement.
First make sure that there are currently no nulls in that bit field in the table
Then execute the following ddl statement:
Alter Table dbo.Product Alter column status bit not null
If, otoh, all you are trying to do is control the output of the view, then what you are doing is sufficient. Your syntax will guarantee that the output of the HasStatus column in the views resultset will in fact never be null. It will always be either bit value = 1 or bit value = 0. Don't worry what the object explorer says...