Decrement-or-delete in SQL - sql

In SQL, I've got a table that maps token=>count; it's a dict where the default value is 0. If I want to increment a token, I can say:
insert into my_table (token, count)
values (my_token, 1)
on duplicate key update count = count + 1;
and if a token doesn't exist yet, it's inserted with count=1. Nifty.
Is there a similarly easy way to do the opposite in SQL, i.e., "given a token, decrement its count, and if count becomes 0 then just remove the record entirely"? I can think of longer ways to do it, but nothing as concise as the above.

My advice is to use transactions as follows (my SQL is a bit rusty but you should get the idea):
-- begin transaction
update my_table set count = count - 1 where id = '7'
delete from my_table where id = '7' and count = 0
commit
This will ensure the atomicity of the decrement-and-delete operation.
However, one other possibility you may want to consider - don't remove it at the point where it reaches zero. Since you say the default value is zero anyway, just leave the row in there with a value of 0.
Of course, your queries will need to change to adapt to that. If you have one that lists active tokens, it will change from:
select token from my_table
to:
select token from my_table where count > 0
The decrement SQL in that case must be careful not to push the token count down to -1, so it would become:
update my_table set count = count - 1 where id = '7' and count > 0
That simplifies your SQL at the decrement time. If you still want those rows with zero-count to disappear, you could have another process that runs periodically to sweep them all up:
delete from my_table where count = 0
That's just some alternatives to consider - if you really want them gone at the exact time their count reaches zero, then use the transaction method above.

You want triggers. (I take it this is MS SQL).

Um, wouldn't the answer be the obvious:
insert into my_table (token, count)
values (my_token, 0)
on duplicate key update count = count - 1;

Related

Add COUNT column in SQL output

I have a 10k row data set where a column, "mode", has either a '0' or '1' integer value. I am attempting to write a query that simply counts the number of '0' and '1' values.
These work but don't tell the whole story:
SELECT mode
FROM unpopular_songs
GROUP BY mode;
This shows the mode column with just two rows (0 and 1).
SELECT
COUNT(mode) AS minor
FROM unpopular_songs
WHERE mode = '0';
This shows one column labeled as "MINOR", with one row with a count as 3905. So, this tells part of the story. However, I need another column "MAJOR" that shows the count of values of '1'.
This was my best shot at getting the output I wanted, but it's throwing an error:
SELECT
COUNT(mode = '0') AS minor
COUNT(mode = '1') AS major
FROM unpopular_songs;
You need some grouping:
SELECT COUNT(1), mode
FROM unpopular_songs
GROUP BY mode;

Add new column in SQL based on 2 conditions

I would like to create a new column in SQL using two conditions. I want to create a column that says 1 if the item count is greater than 1 and if item/sum is greater than 5, otherwise return 0. This is not my original data, is there anyway I could add this new column with these specified rows using a select statement.
First I want to say that using SQL key words or SQL function names as table name or column name should be avoided, so if possible, I recommend to rename the columns "number" and "sum".
The second point is it's unclear what should happen in case the sum column is 0. Since a division by zero is not possible, you will need to add a condition for that.
Anyway, the way to achieve such things is using CASE WHEN. So let's add the new column:
ALTER TABLE yourtable ADD column column_name INT;
Now, you need to execute an update command for that column providing the logic you want to apply. As an example, you can do this:
UPDATE yourtable SET column_name =
CASE WHEN item <= 1 THEN 0
WHEN sum = 0 THEN 1
WHEN item / sum > 0.5 THEN 1
ELSE 0 END;
This will set the new column to 1 only in case item is > 1 and sum is 0 or sum item / sum is > 0.5 (greater 50%). In all other cases it will be set to 0. Again, the bad column naming can be seen since "WHEN sum..." looks like you really build a sum and not just use a column.
If you want as example to set the new column to 0 instead of 1 when the sum is 0, just change it and try out.
In case you want to automatically apply this logic fur future inserts or updates, you can add a trigger on your new column. Something like this:
CREATE TRIGGER set_column_name
BEFORE INSERT ON yourtable
FOR EACH ROW
SET new.column_name = CASE WHEN new.item <= 1 THEN 0
WHEN new.sum = 0 THEN 1
WHEN new.item / new.sum > 0.5 THEN 1
ELSE 0 END;
But take care, the syntax of triggers depend on the DB you are using (this example will work in MYSQL). Since you did not tell us which DB you use, you maybe need to modify it. Furthermore, depending on your DB type and your requirements, you need zero, one or two triggers (for updates and for inserts).

Oracle SQL Fast Retrieval of a Negative condition

Not sure if I am asking the question correctly
I have a table with 2K records. Each record represents a huge Batch processing Unit and it is marked with an initial status of 0(Zero). Every time the batch is completed, the status is updated for that Batch record=10. This Marks that row/Batch as completed
What is the fastest way to see if All records have the status as 10. The query can return true or false or count as soon as it encounters the first 0(Zero). Only in the worst-case scenario, it has to go thru the entire table and return.
The fastest would probably be:
select (case when exists (select 1 from t where status <> 10)
then 'incomplete' else 'complete'
end)
from dual;
This can use an index on (status), which should make the lookup even faster.
However, I would recommend changing the "batch job" to write a message when it is complete -- such as updating a "completed time" column in a batches table. Why query for completeness when the batch knows if it is done?
I would do something like this:
select null as col
from your_table
where status = 0 AND ROWNUM = 1
;
(or select whatever you want from the first row encountered that has status = 0; the way I wrote the query, it will only tell you if there are incomplete batches, but it won't tell you anything else.)
The ROWNUM condition forces execution to stop as soon as the first status = 0 is encountered - either from the table itself, or from an index if you have an index on the status column (as you might, if this kind of query is common in your business).
The query will return either a single row - with NULL in the only column col, unless you choose to select something from the record - or no rows if all batches are complete.

accumulating a value in SQL

I am trying to accumulate a value when a certain condition exists such as
If statusCode = 0
then add 1 to a value.
I am trying to show the number of successful records as defined by the statusCode.
There must be a better way to do this.
Thanks
Select count(1) from yourTable where statusCode=0

Round up prices to end with 99p

My table name is products and i am trying to update products_price so all prices have 99p at the end. Can you help?
Also this request is a bit simpler (i'm new to SQL!) I want to update all records as follows
anything in categories where parent_id is >0 I need the script to update all records to zero.
Q1
So, you want all prices to be truncated and then add 0.99 currency units. The function to truncate a value to 0 decimal places varies between DBMS; I'm assuming it is called TRUNC, but it might be called FLOOR or something else in your DBMS:
UPDATE Products
SET Products_Price = TRUNC(Products_Price) + 0.99;
You don't need a WHERE clause here. I'm assuming there are no negative prices; if that's a possibility, then you need to be a little cleverer with your assignment (maybe a CASE expression; maybe you have a function SIGN, SIGN, SIGNUM available).
Note that I'm assuming that the p referenced is pence, as in 'pounds (Sterling) and pence'. If the price was between £0.00 and £0.99, the result will be £0.99; if the price was between £1.00 and £1.99, the result will be £1.99; etc. The TRUNC or FLOOR will remove any fractional part from the price, and the addition of 0.99 then means that the result is of the form £x.99 for (non-negative) integer values of x.
Q2
Making some assumptions about what you meant:
UPDATE Categories
SET Anything = 0
WHERE Anything > 0
AND Parent_ID > 0;
If you meant that you want to update the products table in some way, then you need to be a lot more precise in your question.
Q2 revisited
From a comment:
I want to update all parent_id values in my categories table to zero where parent_id > 0.
Presumably, there must be some parent_id values which are null or negative, so you need:
UPDATE Categories
SET Parent_ID = 0
WHERE Parent_ID > 0
If there are no nulls and no negatives, then you can run an even simpler update:
UPDATE Categories
SET Parent_ID = 0
It will set to zero those rows which already have a parent ID of zero as well as those that do not. This might hit the logs a bit harder than the more selective update (as in, there will be more changes to record in the logical log or equivalent for your DBMS), but unless there are vast numbers of records and most of them already have a zero parent ID and the DBMS does not recognize when a record does not change and writes log records for unchanged rows, then you're unlikely to notice the difference
Set price to have 99p at end:
update products_price set price = floor(price)+0.99;
Set parent_id to 0:
update categories set parent_id = 0 where parent_id > 0;