Updating values with conditions - sql

Let's assume I have a table like that
END
id rank degree
1 4 3
2 3 3
**rank 4 has 4 degrees.
**rank 3 has 4 degrees.
And we will assume that each rank has some number of degrees. I will assume that rank 3 has 4 degrees.
And I want that, when I increase a degree that is the maximum for the current rank, the rank increases by 1 and the degree resets back to 1. For example, I want to increase the degree of the id 2 in the above table by 1. As a result, the rank should be 4 and the degree should be 1.
How can I make that update efficiently in SQL Server 2008?

I assume you want to update all rows with the same rank of the one with id=3.
UPDATE t SET t.rank = t.rank + 1, t.degree = 1
FROM tableName t
WHERE rank = (SELECT rank FROM tableName t2 WHERE id=#id)

Is this what you want?
update t
set degree = (case when degree = 5 then 1 else degree + 1 end),
rank = (case when degree = 5 then rank + 1 else rank end)
where id = 3;

If there is a reference table containing information about the maximum degree for every supported rank (let's call it dbo.ranks), something like this:
rank maxdegree
---- ---------
1 3
2 4
3 5
4 4
... ...
where maxdegree is assumed to be an integer greater than 0, then here's how you could use it:
UPDATE t
SET
t.rank += t.degree / r.maxdegree,
t.degree += t.degree % r.maxdegree + 1
FROM dbo.atable AS t
INNER JOIN dbo.ranks AS r
ON t.rank = r.rank
;
where dbo.atable is assumed to be the name of the table to update.
Note that this query is only intended for increasing by 1. If you want it to be able to increase degree by an arbitrary number, you will need to make more substantial changes than just replacing 1 in the + 1 bit.
Also, this query will not work correctly with some cases of invalid data in your table (like degrees greater than the corresponding maximums), so make sure you've removed any anomalies in your data before trying to use this.

Related

Solution for SQL conditional query

I've been tasked with coming up with a solution for a problem that was found this morning. I have a query that I need to do some math with. I have three pertinent columns.
SELECT lQ.[QUANTITY], lQ.[FORM_FACTOR_ID], oQ.[INDIVIDUAL_PACKAGING]
FROM [dbo].[AOF_ORDER_LINE_QUEUE] as lQ
LEFT JOIN [dbo].[AOF_ORDER_QUEUE] AS oQ
ON lQ.[SALES_ORDER_NUMBER] = oQ.[SALES_ORDER_NUMBER]
I can see myself doing this in a loop easily in languages I know best. It doesn't seem that looping is a good thing to do in SQL based on some preliminary research so I am reaching out for suggestions.
I need to output a total value which is a conditional sum of lQ.[QUANTITY]. The condition is if oQ.[FORM_FACTOR_ID] is equal to 1 then the output for that particular row is equal to the value of lQ.[QUANTITY]. If oQ.[FORM_FACTOR_ID] is equal to 2 then if oQ.[INDIVIDUAL_PACKAGING] is true, then the output of that particular row in the query is equal to lQ.[QUANTITY]. If the value is false, then the output of that particular row in the query is divided by 2. The final output needs to be a single integer.
QUANTITY FORM_FACTOR_ID INDIVIDUAL_PACKAGING
4 2 1
5 1 1
I would need a query that outputs the value 7 for the above table.
QUANTITY FORM_FACTOR_ID INDIVIDUAL_PACKAGING
4 2 0
5 2 0
That same query needs to output 5 for the above table.
What would be the best way to go about doing this?
If I understand the question correctly, you just want conditional aggregation -- a CASE as an argument to SUM().
If I follow the logic, it would look like:
SELECT SUM(CASE WHEN oq.FORM_FACTOR_ID = 1 THEN lQ.QUANTITY
WHEN oQ.FORM_FACTOR_ID = 2 AND oQ.INDIVIDUAL_PACKAGING = 1 THEN lQ.QUANTITY
WHEN oQ.FORM_FACTOR_ID = 2 AND oQ.INDIVIDUAL_PACKAGING = 0 THEN lQ.QUANTITY / 2
END)
FROM [dbo].[AOF_ORDER_LINE_QUEUE] lQ LEFT JOIN
[dbo].[AOF_ORDER_QUEUE] oQ
ON lQ.[SALES_ORDER_NUMBER] = oQ.[SALES_ORDER_NUMBER];

MonetDB: Enumerate groups of rows based on a given "boundary" condition

Consider the following table:
id gap groupID
0 0 1
2 3 1
3 7 2
4 1 2
5 5 2
6 7 3
7 3 3
8 8 4
9 2 4
Where groupID is the desired, computed column, such as its value is incremented whenever the gap column is greater than a threshold (in this case 6). The id column defines the sequential order of appearance of the rows (and it's already given).
Can you please help me figure out how to dynamically fill out the appropriate values for groupID?
I have looked in several other entries here in StackOverflow, and I've seen the usage of sum as an aggregate for a window function. I can't use sum because it's not supported in MonetDB window functions (only rank, dense_rank, and row_num). I can't use triggers (to modify the record insertion before it takes place) either because I need to keep the data mentioned above within a stored function in a local temporary table -- and trigger declarations are not supported in MonetDB function definitions.
I have also tried filling out the groupID column value by reading the previous table (id and gap) into another temporary table (id, gap, groupID), with the hope that this would force a row-by-row operation. But this has failed as well because it gives the groupID 0 to all records:
declare threshold int;
set threshold = 6;
insert into newTable( id, gap, groupID )
select A.id, A.gap,
case when A.gap > threshold then
(select case when max(groupID) is null then 0 else max(groupID)+1 end from newTable)
else
(select case when max(groupID) is null then 0 else max(groupID) end from newTable)
end
from A
order by A.id asc;
Any help, tip, or reference is greatly appreciated. It's been a long time already trying to figure this out.
BTW: Cursors are not supported in MonetDB either --
You can assign the group using a correlated subquery. Simply count the number of previous values that exceed 6:
select id, gap,
(select 1 + count(*)
from t as t2
where t2.id <= t.id and t2.gap > 6
) as Groupid
from t;

SQL field constraint that forces a column to be ascending

I am working on a small table that has a user input with a number field. The number that the user inputs has to be larger by a few points than the current highest number. Can I also check that the score has to be for instance 1 higher if the current highest score is < 10 but 5 higher if the current highest 10 <= score < 100?
for instance:
user score
1 1
1 2
1 4
1 5
1 7
Now, I want a constraint that will check on insert that the inserted score is bigger than the current highest score by x amount.
Is such a constraint possible?
Such a constraint is difficult to implement. If you care about performance, can you simply input the difference?
1 1
1 1
1 2
1 1
1 2
If you do the data this way, then you can use check (score > 0) and then use sum(score) over (order by ??), where ?? specifies the ordering of the rows.
Otherwise, you'll need to use either a trigger or user-defined function to implement the constraint.

Calculating relative frequencies in SQL

I am working on a tag recommendation system that takes metadata strings (e.g. text descriptions) of an object, and splits it into 1-, 2- and 3-grams.
The data for this system is kept in 3 tables:
The "object" table (e.g. what is being described),
The "token" table, filled with all 1-, 2- and 3-grams found (examples below), and
The "mapping" table, which maintains associations between (1) and (2), as well as a frequency count for these occurrences.
I am therefore able to construct a table via a LEFT JOIN, that looks somewhat like this:
SELECT mapping.object_id, mapping.token_id, mapping.freq, token.token_size, token.token
FROM mapping LEFT JOIN
token
ON (mapping.token_id = token.id)
WHERE mapping.object_id = 1;
object_id token_id freq token_size token
+-----------+----------+------+------------+--------------
1 1 1 2 'a big'
1 2 1 1 'a'
1 3 1 1 'big'
1 4 2 3 'a big slice'
1 5 1 1 'slice'
1 6 3 2 'big slice'
Now I'd like to be able to get the relative probability of each term within the context of a single object ID, so that I can sort them by probability, and see which terms are most probably (e.g. ORDER BY rel_prob DESC LIMIT 25)
For each row, I'm envisioning the addition of a column which gives the result of freq/sum of all freqs for that given token_size. In the case of 'a big', for instance, that would be 1/(1+3) = 0.25. For 'a', that's 1/3 = 0.333, etc.
I can't, for the life of me, figure out how to do this. Any help is greatly appreciated!
If I understood your problem, here's the query you need
select
m.object_id, m.token_id, m.freq,
t.token_size, t.token,
cast(m.freq as decimal(29, 10)) / sum(m.freq) over (partition by t.token_size, m.object_id)
from mapping as m
left outer join token on m.token_id = t.id
where m.object_id = 1;
sql fiddle example
hope that helps

how set value depending on a column value select statement t-sql. Like if 0 set 3, if less than 4 set 2 and so on

I have a column named photos, the photos column is of type tinyint and vary from 0 to 8. I want to apply a rank to the column when selecting data from the table. the values that want to get are: if the record has 4 or more photos, it receives a rank of 1. Because it is a fair amount of photos like 5, 6, 7 and 8. if the record has between 1 and 3 set a rank of 2, a medium rank, and if it has no photos set 3. with this and can sort the records based on the number of photos and make my appliation consistent.
Sure, you would just use a CASE statement in your ORDER BY. Something like this:
SELECT *
FROM Table
ORDER BY
CASE
WHEN photos >= 4 THEN 1
WHEN photos >= 1 THEN 2
ELSE 3
END
Or if you wanted it to be an actual column on the table, you could add it as a computed column:
ALTER TABLE mytable ADD rank AS
CASE
WHEN photos >= 4 THEN 1
WHEN photos >= 1 THEN 2
ELSE 3
END