I have two tables x and y I want a column from x to be set to the average of a column from y grouped by a common column.
This what I'v done so far
update
set x.column2 = (SELECT AVG(NULLIF(column2,0))
FROM y group by column1)
on (x.column1 = y.column1)
And I want the value of x.column2 to be updated automatically whenever the value of any row of y.column2 changes.
Note: there is no column have the same name in the two tables.
UPDATE
x
SET
x.column2 = (SELECT AVG(NULLIF(column2,0))
FROM y
WHERE y.column1 = x.column1)
This will run the subquery once per row in x, but the subquery is limited to the rows in y where column1 matches the current x.column1.
For the curious, the internals of this are a bit deeper. In general, all queries (even sub-queries) return table-like objects ("relation" in relational-speak). If the result has only one row, it can be coerced into a 'row' ("tuple" in relational-speak). If the tuple has only one column, it can be further coerced into the value in that column. That is what is going on here. Additionally, no explicit "group by" is needed, because the WHERE clause limits the subquery to only the rows we want to sum, and so we take advantage of the implied 'group all rows' behavior (analogous to adding GROUP BY y.column1)
After your comment, I wanted to show how you would create a "View" for the same thing, which in MySQL means that the aggregated value is not actually 'stored', but calculated in real-time. This means it is never out of date as you insert into y.
CREATE VIEW vx AS SELECT column1, AVG(NULLIF(column2,0) as avg FROM y GROUP BY y.column1
You can then select from vx and in the background it will run that query.
You will need a trigger - see here.
In your case something like
CREATE TRIGGER name AFTER INSERT ON y FOR EACH ROW BEGIN [above statement] END
CREATE TRIGGER name AFTER UPDATE ON y FOR EACH ROW BEGIN [above statement] END
CREATE TRIGGER name AFTER DELETE ON y FOR EACH ROW BEGIN [above statement] END
I did not try out this, so no guarantee for being free of syntax errors (but should not be).
Related
How do I select a value range from a column where two values in it are separated by a dash, using MySQL?
Here's my example table named "example":
The user enters a low value (X) and a high value (Y).
For example X=2.5 and Y=7.2
I want to select all items where the left value is higher than X (in this case 2.5) and the right value is lower than Y (in this case 7.2). Using these X and Y values I should end up with the rows 2 and 5 as a result.
Sort of like this:
SELECT * FROM example WHERE MIN(value) > X AND MAX(value) < Y
How do I do this?
You can use LEFT and RIGHT functions to get X and Y out of your value field.
So I think you are looking for something like this:
SELECT * FROM example WHERE CAST(LEFT(value,3)AS DECIMAL(2,1)) > 2.5 and CAST(RIGHT(value,3)AS DECIMAL(2,1)) < 7.2
First you need to access your table in a fashion that only has one value per column. (Multiple values per column, like 3.5-7.5 happen to be a very common relational database design antipattern. They cripple both performance and clarity.)
This SQL subquery does the trick for pairs of values.
SELECT item_id, name,
0.0+SUBSTRING_INDEX(value, '-',1) first,
0.0+SUBSTRING_INDEX(value, '-', -1) last
FROM example;
The expression 0.0+something is a MySQL trick to coerce a value to be numeric.
Then use the subquery to apply your search criteria.
SELECT item_id, name, first, last
FROM ( SELECT item_id, name,
0.0+SUBSTRING_INDEX(value, '-',1) first,
0.0+SUBSTRING_INDEX(value, '-', -1) last
FROM example
) s
WHERE first > 2.5
AND last < 7.2;
Fiddle here.
In a comment you asked about the situation where you have more than two values in a single column separated by delimiters. See this. Split comma separated values in MySQL
Pro tip Don't put more than one number in a column in an RDBMS table. The next person to use the table will be muttering curses all day while trying to use that data.
Pro tip Use numeric data types, not VARCHAR(), for numbers.
Initial attempts at getting a very simple pagination, using fetch n rows and then a subsequent call with offset, gives overlapping entries in Oracle.
I was expecting the following to give me two unique sets of results. 1-100 and then 101-200 of the results that would have been returned if the first line had been set with a limit of 200.
select * from "APPR" /*+ index(APPR APPR_IDX01) */ where ("APPROVER" = 'A') or ("APPROVER" > 'A') order by "APPROVER" fetch first 100 rows only ;
select * from "APPR" /*+ index(APPR APPR_IDX01) */ where ("APPROVER" = 'A') or ("APPROVER" > 'A') order by "APPROVER" offset 100 rows fetch next 100 rows only ;
So if there are 150 items for approver A the first results should be:
A, item1
....
A, item100
The subsequent call (offset by 100) giving
A, item101
...
A, item150
B, item1
B, item2
....
B, item201
Unfortunately the second set contains some entries from the first batch of values. Probably a really silly error, but I can't find an explanation as to why this should happen.
---- Updated as a result of comments
The Primary key consists of Approver and several other fields which together form a composite and unique primary.
The code will be called through ODBC and will be used on Oracle and MySQL back-end.
In Oracle, if you make "order by" to a column containing same values (like you have - 'A', 'A', 'A' ...) the order of records inside 'A' values will be random.
Please try to change your queries to ... order by "APPROVER", rowid ...
Presumably, APPROVER is not a unique column. Since there may be duplicates, the order by claus is not stable, and the offset clause might generate duplicates.
A simple solution is to add more columns to the order by to break the ties. Assuming that (approver, item) is a unique set of columns, that would be:
select *
from appr
where approver = 'A' or approver > 'A'
order by approver, item
fetch first 100 rows only
-- then: offset 100 rows fetch next 100 rows only
Notes:
there is no need to surround all-caps identifiers (tables or column names) with double quotes: that's the default in Oracle already
parentheses around the or conditions are superfluous in this simple case
if approver is always one character long, then the where clause can be simplified as where approver >= 'A'
use index hints only if you really know why you are doing it (I am not saying you don't, but I removed it, just in case); most of the time, the database knows better
I am trying to construct a query to UPDATE a row if it exists else update a different row. I only wish to update one row each time the query is executed. The second WHERE is for the case when there is no return from the first part of the query.
I wish to update a single attribute that has a default value of 'null'. There isn't a means to use a public key as I've seen in other posts (e.g.).
I can return the desired tuple with a UNION but am then unable to convert the expression to an UPDATE query, as desired:
(SELECT * FROM table
WHERE (foo BETWEEN x-c AND x+c) AND (bar ..) AND column='null' LIMIT 1)
UNION
(SELECT * FROM table
WHERE column='null' LIMIT 1)
LIMIT 1;
This seems to always return the first WHERE if exists else the second WHERE.
I've been receiving syntax errors when trying to design an IF-ELSE based on a count variable:
SET #count=(SELECT COUNT(*) FROM table
WHERE (foo BETWEEN x-c AND x+c) AND (bar ..) AND column='null' LIMIT 1);
IF #count THEN
(UPDATE table SET column='foobar'
WHERE (foo BETWEEN x-c AND x+c) AND (bar ..) AND column='null' LIMIT 1);
ELSE
(UPDATE table SET column='foobar'
WHERE column='null' LIMIT 1);
END IF;
I've also tried to structure a subquery but had no luck in getting the desired behaviour.
I am concerned that using a count could lead to an opportunity for race conditions? I am unsure whether there is a better approach, however.
Any help is much appreciated!
If I understand correctly, you can just use order by and limit:
update table
set . . .
where column = 'null'
order by ((foo) AND (bar)) desc
limit 1;
The desc will put the rows that match the two conditions first. It will then be followed by other rows. The limit chooses the first match.
I think I would be more inclined to order the two conditions separately:
order by (foo) desc, (bar) desc
Something like this? I can't promise it's fast:
update <table>
where
column is null and <foo> and <bar>
or not (<foo> and <bar>) and not exists (
select 1 from <table> where column is null and <foo> and <bar>
);
Here's one way to do it. Run the first UPDATE. After this runs, the function ROW_COUNT() will return either 0 or 1, depending on whether the first UPDATE affected any rows.
In the second UPDATE, make the change a no-op by setting column=column if ROW_COUNT() indicates that the first UPDATE did change something.
UPDATE table SET column='foobar' WHERE (foo) AND (bar) AND column='null' LIMIT 1;
UPDATE table SET column=IF(ROW_COUNT()=1,column,'foobar') WHERE column='null' LIMIT 1;
However, note that ROW_COUNT() returns 0 if the first UPDATE matched rows, but didn't make any net change, because the rows it found already had the value 'foobar'.
I am trying to update a column in the following table 'jobqueue' using the results from a SELECT query performed on the 'mdtinfo' table.
The column I am trying to update is called ignore and I need to set the value to 1 from its default of 0.
update jobqueue
set jobqueue.`ignore`= '1'
where (SELECT JobQueue_job_queue_id
FROM mdtinfo
WHERE product_name = 'Example')
The above query returns the following error: SQL Error (1242): Subquery returns more than 1 row.
When running the select query on it's own it returns results successfully.
In MySQL, a value of zero appearing in a WHERE clause means false.
So, UPDATE something SET col=val WHERE (SELECT colx FROM sometable) has the potential to be a valid query. If the inner SELECT gets just one row, and its colx column has the value 0, the update won't do anything. If the colx column has a nonzero value the query means UPDATE something SET col=val WHERE true. Accordingly, every row in sometable will be updated. I doubt that's what you want.
If the inner SELECT happens to return more than one row, the query isn't valid. You'll get the error 1242 you actually received.
(This business of interpreting numbers as Boolean values causes MySQL to accept some otherwise dodgy query syntax, like the syntax in your question.)
I guess you want to retrieve the job_queue_id values for the row or rows you actually want to update. So try something like this.
update jobqueue
set jobqueue.`ignore`= '1'
where jobqueue.job_queue_id IN (SELECT JobQueue_job_queue_id
FROM mdtinfo
WHERE product_name = 'Example')
I guessed you have a column jobqueue.job_queue_id. You didn't tell us what columns you have in jobqueue.
update jobqueue
set jobqueue.`ignore`= '1'
where jobqueue.`job_queue_id` IN (SELECT GROUP_CONCAT(JobQueue_job_queue_id)
FROM mdtinfo
WHERE product_name = 'Example' GROUP BY product_name)
you should write column name in where condition.
My team lead insisting me to add days entry count column within table and update it regularly. something like this
Get previous record
take count column value
Add .5 into that value
And update the count record in current record
like this
.5
1
1.5
2 //each time i have to get previous value to make new value which means select statment, then update statement
While I think that this is not the right way. I can count [using Count(*)] the record to display days which is easy why i bother to add it, use update command to know previous entry etc. The reason he told that we can get count directly without query bunch of records which is performance wise is fast. How you do this? what is correct way?
If I understand correctly, you just want row_number() divided by 2:
select t.*,
(row_number() over (order by ??) ) / 2.0
from t;
The ?? is for whatever column specifies the ordering of the table that you want.
UPDATE YourTable
SET COUNT_COLUMN = (SELECT MAX(COUNT_COLUMN) + 0.5
FROM YourTable
)
WHERE "Your condition for the current record";
For better performance add index on to COUNT_COLUMN column of YourTable.
Hi Fizan,
You can achieve this using function. You can create a function to get resultant value and update it in you column. Like this -
CREATE function Get_Value_ToBeUpdated
RETURN DECIMAL(10,2)
AS
BEGIN
DECLARE result decimal (10,2);
DECLARE previousValue decimal (10,2);
DECLARE totalCount int;
SELECT previousValue = SELECT MAX(columnName) FROM YourTable order by primaryColumn desc
SELECT totalCount = SELECT COUNT(1) FROM YourTable
SET result = ISNULL(previousValue,0) + ISNULL(totalCount,0)
RETURN result;
END
UPDATE YourTable SET COLUMNNAME = DBO.Get_Value_ToBeUpdated() WHERE Your condition
Thanks :)