SQL UPDATE with multiple WHERE (relations) conditions - sql

I would like to know if it is possible to perform such UPDATE in oracle SQL database :
UPDATE mark
SET
mark=
CASE
WHEN mark.val<= 5
THEN val*1.1
ELSE val END
WHERE mark.id_classes = classes.id_classes
AND classes.ID_subject = subject.ID_subject
AND subject.ID_subject = 5;
SQL developer returns error in this part :
WHERE mark.id_classes = classes.id_classes
AND classes.ID_subject = subject.ID_subject
AND subject.ID_subject = 5;
So I guess that it is not possible to make such a complex condition, is it any other way to do that then?
Might be silly to try more SELECT like condition but on the other hand I don't see the reason why it is not working.

You can use a subquery:
UPDATE mark
SET mark = val * 1.1
WHERE mark.val <= 5 AND
EXISTS (SELECT 1
FROM classes c JOIN
subjects s
ON c.ID_subject = s.ID_subject
WHERE mark.id_classes = c.id_classes AND
s.ID_subject = 5
);
Notice that I moved the CASE condition to the WHERE clause so only the rows that need to be updated are updated.

You can't reference another two tables (CLASSES and SUBJECT) just like that, out of nowhere. Here's code which shows how you might have done that:
update mark m set
m.mark = (select case when m.val <= 5 then m.val * 1.1
else m.val
end
from classes c join subject s on c.id_subject = s.id_subject
where c.id_classes = m.id_classes
and s.id_subject = 5
)
where ...
As you didn't use table aliases within CASE, I don't know which table the VAL column belongs to (so I presumed it is MARK).
Also, UPDATE itself might need the WHERE clause which would restrict number of rows to be updated.

I find that in cases like this a MERGE statement is easier to understand:
MERGE INTO MARK m
USING (SELECT c.ID_CLASSES
FROM CLASSES c
WHERE c.ID_SUBJECT = 5) d
ON (m.ID_CLASSES = d.ID_CLASSES)
WHEN MATCHED THEN
UPDATE SET m.MARK = CASE
WHEN m.VAL <= 5
THEN m.VAL * 1.1
ELSE
m.VAL
END
Or, since the ID_SUBJECT is a constant, you can simplify your update to
UPDATE MARK m
SET m.MARK = CASE
WHEN m.VAL <= 5
THEN m.VAL * 1.1
ELSE
m.VAL
END
WHERE m.ID_CLASSES = 5
Best of luck.

Related

Is it possible to use AND in an UPDATE SET clause in a CASE statement?

I need to check two conditions:
1. when the function returns true
2. when the function returns true AND ISP_Program has the word "IRSS" in it
What is the correct syntax? I have the following:
UPDATE [PAYROLL].[dbo].[BILL]
SET Pay_Code = CASE dbo.is_Holiday([BILL].Date)
WHEN 1 THEN holiday_code
WHEN 1 AND ISP_Program like '%IRSS%' THEN '66'
ELSE Pay_Code
END
FROM tbl_TXEX_HOLIDAY
INNER JOIN [BILL] ON [BILL].Pay_Code = tbl_HOLIDAY.regular_code
I think you want:
SET Pay_Code = (CASE WHEN dbo.is_Holiday([BILL].Date) = 1 AND ISP_Program like '%IRSS%' THEN '66'
WHEN dbo.is_Holiday([BILL].Date) = 1 THEN holiday_code
ELSE Pay_Code
END)
Note that the ordering of these conditions is important.
I assume that BILL is the table referenced in the UPDATE. I would recommend writing the complete logic as:
UPDATE b
SET Pay_Code = (CASE WHEN dbo.is_Holiday(b.Date) = 1 AND ISP_Program like '%IRSS%' THEN '66'
WHEN dbo.is_Holiday(b.Date) = 1 THEN holiday_code
ELSE b.Pay_Code
END)
FROM [PAYROLL].[dbo].[BILL] b JOIN
tbl_TXEX_HOLIDAY h
ON b.Pay_Code = h.regular_code;
Notes:
Define aliases for the tables so the query is easier to write and to read.
Use the alias for the update, so it is clear what you intend.
Put the table being updated first. After all, it needs to have matching rows for the update to take place.
Of course, fix the case expression.

IF / Case statment in SQL

I have a column where I have 0 or 1. I like to do the following set up:
If 0 than put / use the Region_table (here I have regions like EMEA, AP,LA with finished goods only) and when it 1 then put / use the Plant_table (here I have plants with non-finished goods) data's.
I tried to write it in 2 different statements but it is not good:
,Case
when [FG_NFG_Selektion] = '0' Then 'AC_region'
End as 'AC_region'
,Case
when [FG_NFG_Selektion] = '1' Then 'AC_plant'
End as 'AC_plant'
I'm not 100% clear on what you're looking for, but if you want to get data from different tables based on the value in the [FG_NFG_Selektion] field, you can do something like this:
SELECT
CASE
WHEN [FG_NFG_Selektion] = '0' THEN r.some_col -- If 0, use value from "region" table
WHEN [FG_NFG_Selektion] = '1' THEN p.some_col -- If 1, use value from "plant" table
END AS new_field
FROM MyTable t
LEFT JOIN AC_region r ON t.pk_col = r.pk_col -- get data from "AC_region" table
LEFT JOIN AC_plant p ON t.pk_col = p.pk_col -- get data from "AC_plant" table
;
If [FG_NFG_Selektion] is a numeric field, then you should remove the single quotes: [FG_NFG_Selektion] = 0.
I would strongly recommend putting the conditions in the ON clauses:
SELECT COALESCE(r.some_col, p.some_col) as som_col
FROM t LEFT JOIN
AC_region r
ON t.pk_col = r.pk_col AND
t.FG_NFG_Selektion = '0' LEFT JOIN
AC_plant p
ON t.pk_col = p.pk_col AND
t.FG_NFG_Selektion = '1';
Why do I recommend this? First, this works correctly if there are multiple matches in either table. That is probably not an issue in this case, but it could be in others. You don't want to figure out where extra rows come from.
Second, putting the conditions in the ON clause allows the optimizer/execution engine to take advantage of them. For instance, it is more likely to use FG_NFG_Selektion in an index.

Differential Data Merge is complex? How to get results?

I am looking at an old stored procedure that's job is to preserve the New sort order based on yesterday's and today's data.
Sort orders are not being preserved any longer and I have narrowed it down to the WHERE clause eliminating all rows. The main goal is to preserve the SortOrder so if some custom data was in position 4 yesterday, any NEW custom data that takes its place should ALSO have position 4.
If I eliminate
--AND b.PrimaryID = b.SortOrder
then I get thousands of rows. I suspect something is wrong but it I am not understanding. How can I make this simpler so it is REALLY easy to understand?
IMPORTANT: the SortOrder actually equals the PrimaryID if the data is no longer sorted. Otherwise it is incremental 1 2 3 4 5 6 7 .. and so on. I guess this was the original architects way of doing it.
-- Merge data and get missing rows that have not changed.
SELECT
PrevPrimaryID = a.PrimaryID
,a.WidgetID
,a.AnotherValue
,a.DataID
,PrevSortOrder = a.SortOrder
,NewPrimaryID = b.PrimaryID
,NewDataID = b.DataID
,NewStartDate = b.StartDate
,NewSortOrder = b.SortOrder
INTO #NewOrder2
FROM #YesterdaysData2 a
LEFT JOIN #TodaysData2 b ON a.WidgetID = b.WidgetID
AND a.AnotherValue = b.AnotherValue
WHERE
a.Primaryid <> a.sortorder
AND b.PrimaryID = b.SortOrder
SELECT * FROM #NewOrder2
-- later update based on #NewOrder2...
UPDATE CustomerData
SET SortOrder = (
SELECT PrevSortOrder
FROM #NewOrder2
WHERE CustomerData.PrimaryID = #NewOrder2.NewPrimaryID
)
WHERE PrimaryID IN (
SELECT NewPrimaryID
FROM #NewOrder2
)
UPDATE - Is it possible its just a blunder and the WHERE clause should be
WHERE a.Primaryid <> a.sortorder
AND b.PrimaryID <> b.SortOrder

Running complicated sql query within CASE

I have a new report created in postgres - it should only run if there are 3 completed cases, and then determine an ultimate outcome (pass/fail) based on the individual cases. Depending on if it passes or fails, different information needs to be displayed.
I have the three main queries that I need, but now I'm at a loss of how to combine them into one.
The main query that contains the conditional logic for a case is here:
SELECT CASE
WHEN (SELECT COUNT(*) FROM "Alloc" WHERE "CodeID" = 1 AND "StatusCodeID" IN (2,6)) = 3
THEN
--Determine if case passes based on the 3 individual Assessments
CASE
WHEN (SELECT COUNT(*) FROM "Answer" ans
LEFT JOIN "AnswerOption" ansop ON ansop."AnswerOptionID" = ans."AnswerOptionID"
LEFT JOIN "QuestionTextInstance" qtxti ON qtxti."QuestionTextInstanceID" = ans."QuestionTextInstanceID"
LEFT JOIN "Alloc" aloc ON aloc."AllocID" = qtxti."AllocID"
LEFT JOIN "QuestionText" qtxt ON qtxt."QuestionTextID" = qtxti."QuestionTextID"
LEFT JOIN "Code" bcode ON aloc."CodeID" = bcode."CodeID"
WHERE bcode."CodeID" = 1 AND qtxt."QuestionTextID" = 11 AND ans."Value" = 0) >= 1 --At least 2 must have answered 'Yes'
THEN 'Passed' --Execute 'Pass.sql'
ELSE 'Did NOT Pass' --Execute 'NotPass.sql
END
ELSE 'Report did Not Run'
END
This runs correctly and gives the correct results based on the conditions. However, within the THEN and ELSE blocks on the inner CASE statement, I need to display different information that includes many columns and many joins (which I currently have in different .sql files) instead of Pass/Did NOT Pass, but I can not find a way to implement this because it seems that any query within THEN or ELSE blocks can only return a single value.
How can I accomplish this?
It can be done in plain SQL in various ways. One is with well known composite / row types:
SELECT (x).*
FROM (
SELECT CASE WHEN cond_a_here THEN
(SELECT t FROM t WHERE x = 1)
ELSE (SELECT t FROM t WHERE x = 2) END AS x
) sub
Note the parentheses in (x).*. Those are required for a composite type to make the syntax unambiguous.
It's simpler to understand with PL/pgSQL, but you need to understand how to handle composite types. I have posted many related answers ...
CREATE OR REPLACE FUNCTION foo_before()
RETURNS SETOF t AS
$func$
BEGIN
IF cond_a_here THEN
RETURN QUERY
SELECT * FROM t WHERE x = 1;
ELSE
RETURN QUERY
SELECT * FROM t WHERE x = 2;
END IF;
END
$func$ LANGUAGE plpgsql;

Problem with adding custom sql to finder condition

I am trying to add the following custom sql to a finder condition and there is something not quite right.. I am not an sql expert but had this worked out with a friend who is..(yet they are not familiar with rubyonrails or activerecord or finder)
status_search = "select p.*
from policies p
where exists
(select 0 from status_changes sc
where sc.policy_id = p.id
and sc.status_id = '"+search[:status_id].to_s+"'
and sc.created_at between "+status_date_start.to_s+" and "+status_date_end.to_s+")
or exists
(select 0 from status_changes sc
where sc.created_at =
(select max(sc2.created_at)
from status_changes sc2
where sc2.policy_id = p.id
and sc2.created_at < "+status_date_start.to_s+")
and sc.status_id = '"+search[:status_id].to_s+"'
and sc.policy_id = p.id)" unless search[:status_id].blank?
My find statement:
Policy.find(:all,:include=>[{:client=>[:agent,:source_id,:source_code]},{:status_changes=>:status}],
:conditions=>[status_search])
and I am getting this error message in my log:
ActiveRecord::StatementInvalid (Mysql::Error: Operand should contain 1 column(s): SELECT DISTINCT `policies`.id FROM `policies` LEFT OUTER JOIN `clients` ON `clients`.id = `policies`.client_id WHERE ((((policies.created_at BETWEEN '2009-01-01' AND '2009-03-10' OR policies.created_at = '2009-01-01' OR policies.created_at = '2009-03-10')))) AND (select p.*
from policies p
where exists
(select 0 from status_changes sc
where sc.policy_id = p.id
and sc.status_id = '2'
and sc.created_at between 2009-03-10 and 2009-03-10)
or exists
(select 0 from status_changes sc
where sc.created_at =
(select max(sc2.created_at)
from status_changes sc2
where sc2.policy_id = p.id
and sc2.created_at < 2009-03-10)
and sc.status_id = '2'
and sc.policy_id = p.id)) ORDER BY clients.created_at DESC LIMIT 0, 25):
what is the major malfunction here - why is it complaining about the columns?
The conditions modifier is expecting a condition (e.g. a boolean expression that could go in a where clause) and you are passing it an entire query (a select statement).
It looks as if you are trying to do too much in one go here, and should break it down into smaller steps. A few suggestions:
use the query with find_by_sql and don't mess with the conditions.
use the rails finders and filter the records in the rails code
Also, note that constructing a query this way isn't secure if the values like status_date_start can come from users. Look up "sql injection attacks" to see what the problem is, and read the rails documentation & examples for find_by_sql to see how to avoid them.
Ok, I've managed to retool this so it is more friendly to a conditions modifier and I think it is doing the sql query correctly.. however, it is returning policies that when I try to list the current status (the policy.status_change.last.status) it is set to the same status used in the query - which is not correct
here is my updated condition string..
status_search = "status_changes.created_at between ? and ? and status_changes.status_id = ?) or
(status_changes.created_at = (SELECT MAX(sc2.created_at) FROM status_changes sc2
WHERE sc2.policy_id = policies.id and sc2.created_at < ?) and status_changes.status_id = ?"
is there something obvious to this that is not returning all of the remaining associated status changes once it finds the one in the query?
here is the updated find..
Policy.find(:all,:include=>[{:client=>[:agent,:source_id,:source_code]},:status_changes],
:conditions=>[status_search,status_date_start,status_date_end,search[:status_id].to_s,status_date_start,search[:status_id].to_s])