Dynamically reorder column values in a SQL Server table - tsql

I have a SQL Server table for user skills with priority order for each user as this:
+----+------+----------+----------+
| ID | user | skill | priority |
+----+------+----------+----------+
| 1 | foo | swimming | 1 |
+----+------+----------+----------+
| 2 | foo | running | 2 |
+----+------+----------+----------+
| 3 | foo | hunting | 3 |
+----+------+----------+----------+
| 4 | boo | swimming | 1 |
+----+------+----------+----------+
| 5 | moo | swimming | 1 |
+----+------+----------+----------+
| 6 | moo | running | 2 |
+----+------+----------+----------+
How can I write SQL code to re-order the priority column values (an integer) for all skills for a user when the priority value is changed for one of the skills?
For example: for user "foo" I will change the priority of the skill "swimming" from (1) to (2); the update statement must also change the priority for all other skills for that user, in a dynamic way.
So in that example "swimming" will be priority (2) instead of (1), running will be (1) instead of (2), and the others will remain the same.

In this answer I'm assuming that you want to do this in SQL, not in C#. Based on that assumption, these two SQL statements inside a single transaction increase the priority of the specified skill by 1.
'Set #User to the required user and #Skill to the required skill.
'Decrease the priority of the user's skill above the specified skill.
UPDATE MyTable
SET priority = priority + 1
WHERE user = #User
AND priority = (SELECT priority - 1
FROM MyTable
WHERE user = #User
AND skill = #Skill)
'Increase the specified skill's priority.
UPDATE MyTable
SET priority = priority - 1
WHERE user = #User
AND skill = #Skill
AND priority > 1
In a similar fashion, these two SQL statements increase the specified skill to the specified priority.
'Set #User to the required user and #Skill to the required skill.
'Set #NewPriority to the new priority.
'Decrease the higher-prioritised skills.
UPDATE MyTable
SET priority = priority + 1
WHERE user = #User
AND priority >= #NewPriority
AND priority < (SELECT priority
FROM MyTable
WHERE user = #User
AND skill = #Skill)
'Set the specified skill's priority as requested.
UPDATE MyTable
SET priority = #NewPriority
WHERE user = #User
AND skill = #Skill
AND priority > 1
And these three SQL statements move the specified skill to the specified priority.
'Set #User to the required user and #Skill to the required skill.
'Set #NewPriority to the new priority.
'Decrease the higher-prioritised skills to
'handle case where new priority is higher.
UPDATE MyTable
SET priority = priority + 1
WHERE user = #User
AND priority >= #NewPriority
AND priority < (SELECT priority
FROM MyTable
WHERE user = #User
AND skill = #Skill)
'Increase the lower-prioritised skills to
'handle case where new priority is lower.
UPDATE MyTable
SET priority = priority - 1
WHERE user = #User
AND priority <= #NewPriority
AND priority > (SELECT priority
FROM MyTable
WHERE user = #User
AND skill = #Skill)
'Set the specified skill's priority as requested.
UPDATE MyTable
SET priority = #NewPriority
WHERE user = #User
AND skill = #Skill

Related

Efficient way to conditionally update values in 2 records in PostgreSQL

I have a table that contains 4 columns
id | category | score | enabled
1 | news | 95 | t
id -- serial
category -- varchar
score -- float
enabled -- bool
I want to update enabled to False if there's another record with a higher score.
For example, if I have:
id | category | score | enabled
1 | news | 95 | t
Then, after some operation, a new record with the same category is inserted:
id | category | score | enabled
1 | news | 95 | t
2 | news | 100 | f
Since the score for id=2 is higher, I want to change enabled for id=2 to True and change enabled for id=1 to False.
I'm wondering if I can combine these operations into 1 query. Right now I do 2 SELECT queries to get the 2 records, then compare the scores locally, and then change the enabled value (if needed).
So simply,
SELECT id, score
FROM table
WHERE category = %s
AND enabled = True
SELECT id, score
FROM table
WHERE category = %s
AND id = (SELECT max(id) WHERE category=%s)
if score2>= score1:
UPDATE table SET enabled = True
WHERE id = id2
UPDATE table SET enabled = False
WHERE id = id1
It works, but it seems very inefficient. Any way to improve these queries?
You can do that with a single update:
update the_table
set enabled = (score = t.max_score)
from (
select id, category, max(score) over (partition by category) as max_score
from the_table
where category = 'news'
) t
where t.id = the_table.id
and t.category = the_table.category;
This will set the enabled flags for all rows with the same category in a single statement.
Online example: https://rextester.com/DXR80618
If you happen to have more than one row with the same highest score for one category, the above statement will change enabled to true for all of, .
E.g.
id | category | score
---+----------+------
1 | news | 95
2 | news | 100
3 | news | 100
If you don't want that, and e.g. always pick the one with the lowest id to be the enabled row, you can use the following:
update the_table
set enabled = (rn = 1)
from (
select id, category,
row_number() over (partition by category order by score desc, id) as rn
from the_table
where category = 'news'
) t
where t.id = the_table.id
and t.category = the_table.category;
Online example: https://rextester.com/JPA61125

How do I write postgres conditional SELECT query?

I have a table that has 3 columns.
id | name | score | approve
--------------------
1 | foo | 90 | f
2 | foo | 80 | t
I want to
SELECT id WHERE name='foo'
with these conditions:
if approve is True, then return that one (only one will be true for the same name)
otherwise select the one that has highest score
I was looking into IF...ELSE but cannot even come up with a query that executes (despite a working one...)
How to set up the query command for this type of queries?
In SQL, you can often use some logic by defining the right order and limit:
select id
from my_table
where name = 'foo'
order by approve desc, score desc
limit 1

PostgreSQL: Updating column in a table based on another indirectly referenced table

After adding a new column new_column to an existing table rules, I would like to apply some complex migration logic in order to determine the new value for each row (I need to look at another table called flags which is indirectly referenced by rules).
My questions:
Q1. Can I accomplish this with JOIN and a CASE statement, or would I need to write a pgSQL function?
Q2. Which of the logical approaches is better (pseudo code):
FOR flag IN flags
FOR app IN applications WHERE app.accountid = flag.accountid
FOR campaign IN campaigns WHERE campaign.applicationid = app.id
FOR rule IN rules WHERE rule.campaignid = campaign.id
SET rule.new_column TO
(CASE flag.new == true AND flag.old == false THEN ‘v2’
CASE flag.new == false AND flag.old == true THEN ‘v1’
ELSE return ‘v0’)
Or is it better to go about it this way:
FOR rule IN rules
SELECT campaign FROM campaigns WHERE campaign.id = rule.campaignid
SELECT app FROM applications WHERE app.id = campaign.applicationid
SELECT flag FROM flags where flag.accountid = app.accountid
SET rule.new_column TO
CASE (flag.new = true AND flag.old = false) THEN 'v2'
CASE (flag.new = false AND flag.old = true) THEN 'v1'
ELSE 'v0'
Example:
flags:
accountid | new | old
---------------------
1 |true |true
2 |true |false
applications:
id | accountid
--------------
3 | 1
4 | 2
campaigns:
id | applicationid
------------------
5 | 3
6 | 4
rules:
campaignid | new_column (wanted result)
----------------------
5 | 'v0'
5 | 'v0'
6 | 'v2'
This should do it:
update rules
set new_column = case
when f.new and not f.old then 'v2'
when not f.new and f.old then 'v1'
else 'v0'
end
from campaigns c
join applications a on c.applicationid = a.id
join flags f on a.accountid = f.accountid
where c.id = rules.campaignid;
Online example: http://rextester.com/JHHP6967
I think I've achieved what I wanted with JOINed query expression (thanks #joop).
I'll post it here in case it might be useful for others:
UPDATE
rules AS r
SET
rbversion =
CASE
WHEN f.new = TRUE AND f.old = FALSE THEN 'v2'
WHEN f.new = FALSE AND f.old = TRUE THEN 'v1'
ELSE 'v0'
END
FROM
flags AS f,
campaigns AS c,
applications AS a
WHERE
c.id = r.campaignid AND
a.id = c.applicationid AND
f.accountid = a.accountid

How can I determine permissions in a hierarchical organization?

I’m trying to create performant logic for determining permissions within a hierarchical organization.
Employees are assigned to one or more units. Units are hierarchical with (theoretically) infinite depth (in reality it’s no more than 6 layers).
For example, employee Jane may be the Supervisor of the Accounts Receivable unit (a child of the Accounting unit), and also Member of the Ethics Committee (a child of Committees, which is itself a child of Office of the CEO).
As the Supervisor of Accounts Receivable, Jane should have permission to view personnel files of everyone else in Accounts Receivable, but not in the Ethics Committee since she’s just a Member. Similarly, regular employees of the Accounts Receivable unit should not be able to view one another’s profiles, though they’d all need permission to, say, view the accounting records of the company.
I imagine the database architecture for this will look something like:
| **employees** | **units** | **positions** | **assignments** | **permissions** |
| ------------- | ----------- | ------------- | --------------- | --------------- |
| id | id | id | employee_id | unit_id |
| name | name | title | unit_id | is_management |
| | parent_path | is_management | position_id | ability |
With that in mind, how can I write a performant query to determine which permissions Jane has over Sam, an Accountant in Accounts Receivable, versus over Bill, a Receptionist in Office of the CEO?
The closest I have is something like:
create function permissions(actor employees, subject employees) returns setof permissions as $$
begin
for unit_id in select unit_id from assignments where employee_id = subject.id loop
select permissions.name
from assignments
left join units on (unit.id = assignments.unit_id)
left join positions on (positions.id = assignments.position_id)
left join permissions on (
permissions.unit_id = units.id
and permissions.is_management = positions.is_management
)
where assignments.user_id = actor.id
and (units.parent_path = unit_id or units.parent_path #> unit_id)
end loop;
end;
$$ language plpgsql stable;

Return names of users who are never associated with a value in a certain column in any row

Suppose I want to look at only names of people who never have a row corresponding to them which contains a certain value in another column. For example, in the following table...
name | value
-------+-------
joe | 0
joe | 3
joe | 2
joe | 3
bill | 0
bill | 1
bill | 2
... I'd like to say something like, "give me all of the users who do not ever have a value '1' in the value column." In this case, it would return just "joe".
In the real-life example, the table is gigantic, so it wasn't time-effective to create a subquery and do a where name not in (select * from table_name where value = 1). Is there a more efficient way to do something like this?
select name
from t
group by name
having not bool_or(value = 1)
http://www.postgresql.org/docs/current/static/functions-aggregate.html
Group by the name and take only those having zero times value = 1
select name
from your_table
group by name
having sum(case when value = 1 then 1 else 0 end) = 0
Following Query retrieves user who does not ever has value 1
SELECT
distinct(name) from test
where name not in (select name from test where value=1)
Following Query retrieves all rows that do not contain value 1
SELECT
* from test
where name not in (select name from test where value=1)