SQL Update: the row such that...? - mysql

Suppose f is a string function such as concatenation by 0: f(0011)=00110.
Suppose mytable has columns string which is an index, and price.
I would like to do something like the following pseudo-code:
UPDATE mytable SET price(X)=0.5 FOR ALL THOSE ROWS X SUCH THAT
THE ROW Y SUCH THAT THE `string` OF Y equals f(the `string` FOR X)
HAS price(Y)=0
More generally, how can I reference
"the row such that [condition-holds-that-depends-on-the-current-row]"
in (My)SQL?

Setup demo
CREATE TABLE mytable
( id INT UNSIGNED NOT NULL PRIMARY KEY
, thestring VARCHAR(32)
, price DECIMAL(11,2)
) ENGINE=INNODB
;
INSERT INTO mytable (id, thestring, price) VALUES
( 1,'0011' , 123.45)
,( 2,'00110' , 45.67)
,( 3,'001100' , 0.00)
,( 4,'10' , 4.44)
,( 5,'100' , 5.55)
,( 6,'1000' , 0.00)
;
Write a query that identifies the Y rows in mytable
SELECT y.*
FROM `mytable` `y`
WHERE y.price = 0.0
ORDER BY y.id
Add a join to mytable to find the matching X rows
SELECT x.id AS x_id
, x.thestring AS x_thestring
, x.price AS x_price
, y.id AS y_id
, y.thestring AS y_thestring
, y.price AS y_price
FROM mytable `y`
JOIN mytable `x`
ON CONCAT(x.thestring,'0') = y.thestring
WHERE y.price = 0.0
ORDER BY y.id
Convert the SELECT into an UPDATE. (Replace SELECT ... FROM with UPDATE, and add a SET clause before the WHERE clause.
UPDATE mytable `y`
JOIN mytable `x`
ON CONCAT(x.thestring,'0') = y.thestring
SET x.price = 0.5
WHERE y.price = 0.0
Just replace the CONCAT(x.thestring,'0') with f(x.thestring).
Another option is to use a correlated subquery.
First, write a SELECT
SELECT x.*
FROM mytable `x`
WHERE EXISTS ( SELECT 1
FROM mytable `y`
WHERE y.thestring = f(x.thestring)
AND y.price = 0.00
)
And then convert that to an UPDATE. Replace SELECT ... FROM with the UPDATE keyword, and add a SET clause before the WHERE clause.
UPDATE mytable `x`
SET x.price = 0.5
WHERE EXISTS ( SELECT 1
FROM mytable `y`
WHERE y.thestring = f(x.thestring)
AND y.price = 0.00
)

Use an UPDATE with a self-join.
UPDATE mytable AS x
JOIN mytable AS y ON y.string = f(x.string)
SET x.price = 0.5
WHERE y.price = 0

Related

Sql query on taking the value from a case within select statement in a CTE

I am trying to write a SQL. In this, I want columns Bank, and Y-Bank in the output. Y-Bank is being calculated based on certain case statements, using CTE, but I am not able to return Y-Plant as the column.
I also created a table as shown in the input/output here:https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=749e4ca1570880e9c64c4553d18dea1a
Below is the code:
WITH CTE AS
(
SELECT
"BANK",
(select
case
when "TEST"=0 AND "TEST1">0
THEN ( SELECT COUNT("ZONES")FROM mytable I WHERE I.BANK = O.BANK AND I."ZONES"='Y' )
END AS "Y-BANK"
from(
(SELECT
CASE WHEN ( (SELECT COUNT("ZONES")FROM mytable I WHERE I.BINDER = O.BINDER AND I."ZONES"='N' ) = 0 ) AND ( (SELECT COUNT("ZONES")FROM mytable I WHERE I.BINDER = O.BINDER AND I."ZONES"='Y' ) > 0 )
THEN ( SELECT COUNT("ZONES")FROM mytable I WHERE I."TOTAL LINE"= O."TOTAL LINE"AND I."ZONES"='N' )
END AS "TEST",
CASE WHEN ( (SELECT COUNT("ZONES")FROM mytable I WHERE I.BINDER = O.BINDER AND I."ZONES"='' ) = 0 ) AND ( (SELECT COUNT("ZONES")FROM mytable I WHERE I.BINDER = O.BINDER AND I."ZONES"='Y' ) > 0 )
THEN ( SELECT COUNT("ZONES")FROM mytable I WHERE I."TOTAL LINE" = O."TOTAL LINE" AND I."ZONES"='Y' )
END AS "TEST1"
from mytable )
)
)
FROM mytable O
)
SELECT *
FROM CTE O
Can someone help me out on how can I correct it?
If I understand correctly, you want to count the number of "Y" values for each bank. You can use a window function:
select t1.*, sum(zones = 'Y') over (partition by Bank1) as y_bank
from t1
Here is a db<>fiddle.
you can use following query
select
bank1 as "bank",
(select count(*)over(partition by Bank1,Zones order by Bank1 desc) as "y_bank" from t1 where Zones = 'Y' and Bank1 = t.Bank1 limit 1) as "y_bank"
from t1 t

How return foregin key existence information to select result?

I have a query:
SELECT `Name`, `ID_dir`, 999 as `children`
FROM `dir` dir WHERE dir.`fid_parent` IS NULL
AND (
EXISTS (
SELECT 1
FROM `file` f
WHERE dir.ID_dir = f.fid_parent
)
OR (
SELECT 1
FROM `dir` d2
WHERE dir.ID_dir = d2.fid_parent
)
)
Where I check if the directory has any foreign key.
How can I move that information in place of 999 in "Select ... 999 as children"?
I want to return (0 or 1) xor Boolean in that place as children.
Put the EXISTS subquery in the SELECT list.
SELECT Name, ID_Dir, (
EXISTS (
SELECT 1
FROM `file` f
WHERE dir.ID_dir = f.fid_parent
)
OR (
SELECT 1
FROM `dir` d2
WHERE dir.ID_dir = d2.fid_parent
)
) AS children
FROM dir WHERE fid_parent IS NULL

How to get the row before and after a specific value in MySQL

Lets suppose MySQL table is structured as follows:
(Timestamp | Value)
(4h00 | 3) ;
(5h00 | 5) ;
(6h00 | 0) ;
(7h00 | 0) ;
(8h00 | 2) ;
(9h00 |10) ;
(10h00 | 0) ;
(11h00 | 3) ;
I want to get the line where (Value != 0) before any appearance of (Value = 0) AND the first line where (Value != 0) after the appearance of (Value = 0).
Means in this case, the query should return
(5h00 | 5) ;
(8h00 | 2) ;
(9h00 | 10) ;
(11h00 | 3) ;
this is the structure of my table :
CREATE TABLE IF NOT EXISTS `latency` (
`key` int(100) NOT NULL AUTO_INCREMENT,
`idReport` varchar(45) NOT NULL,
`timestamp` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`value` float NOT NULL,
`loss` float NOT NULL,
PRIMARY KEY (`idReport`,`timestamp`),
UNIQUE KEY `key` (`key`)
)
SELECT * FROM `table` AS tcurrent
INNER JOIN `table` AS tnext
ON tnext.timestamp = tcurrent.timestamp+60*60
WHERE tnext.value = 0
AND tcurrent.value != 0
You may adapt the +60*60: a DATE_ADD for 1 HOUR is easier to read. Also, do not forget to index columns properly (a UNIQUE INDEX (timestamp, value) sounds good).
You could use row numbering logic , something like this
SELECT T.*
FROM
(
SELECT DISTINCT RN FROM
(
SELECT L.*,
IF (L.VALUE <> #P , #RN:=#RN+1,#RN) RN ,
#P:=L.VALUE P
FROM (SELECT #RN:=0, #P:=0 ) RN,`LATENCY` L
#ORDER BY
) S
WHERE S.VALUE = 0
) S1
JOIN
(SELECT L.*,
IF (L.VALUE <> #P1 , #RN1:=#RN1+1,#RN1) RN ,
#P1:=L.VALUE P
FROM (SELECT #RN1:=0, #P1:=0 ) RN,`LATENCY` L
#ORDER BY
) T
ON S1.RN = T.RN-1 OR S1.RN = T.RN+1
The sub queries s and s1 identify blocks with value = 0 and the join simply picks adjacent row numbers.

How many different ways are there to get the second row in a SQL search?

Let's say I was looking for the second most highest record.
Sample Table:
CREATE TABLE `my_table` (
`id` int(2) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`value` int(10),
PRIMARY KEY (`id`)
);
INSERT INTO `my_table` (`id`, `name`, `value`) VALUES (NULL, 'foo', '200'), (NULL, 'bar', '100'), (NULL, 'baz', '0'), (NULL, 'quux', '300');
The second highest value is foo. How many ways can you get this result?
The obvious example is:
SELECT name FROM my_table ORDER BY value DESC LIMIT 1 OFFSET 1;
Can you think of other examples?
I was trying this one, but LIMIT & IN/ALL/ANY/SOME subquery is not supported.
SELECT name FROM my_table WHERE value IN (
SELECT MIN(value) FROM my_table ORDER BY value DESC LIMIT 1
) LIMIT 1;
Eduardo's solution in standard SQL
select *
from (
select id,
name,
value,
row_number() over (order by value) as rn
from my_table t
) t
where rn = 1 -- can pick any row using this
This works on any modern DBMS except MySQL. This solution is usually faster than solutions using sub-selects. It also can easily return the 2nd, 3rd, ... row (again this is achievable with Eduardo's solution as well).
It can also be adjusted to count by groups (adding a partition by) so the "greatest-n-per-group" problem can be solved with the same pattern.
Here is a SQLFiddle to play around with: http://sqlfiddle.com/#!12/286d0/1
This only works for exactly the second highest:
SELECT * FROM my_table two
WHERE EXISTS (
SELECT * FROM my_table one
WHERE one.value > two.value
AND NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
)
LIMIT 1
;
This one emulates a window function rank() for platforms that don't have them. It can also be adapted for ranks <> 2 by altering one constant:
SELECT one.*
-- , 1+COALESCE(agg.rnk,0) AS rnk
FROM my_table one
LEFT JOIN (
SELECT one.id , COUNT(*) AS rnk
FROM my_table one
JOIN my_table cnt ON cnt.value > one.value
GROUP BY one.id
) agg ON agg.id = one.id
WHERE agg.rnk=1 -- the aggregate starts counting at zero
;
Both solutions need functional self-joins (I don't know if mysql allows them, IIRC it only disallows them if the table is the target for updates or deletes)
The below one does not need window functions, but uses a recursive query to enumerate the rankings:
WITH RECURSIVE agg AS (
SELECT one.id
, one.value
, 1 AS rnk
FROM my_table one
WHERE NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
UNION ALL
SELECT two.id
, two.value
, agg.rnk+1 AS rnk
FROM my_table two
JOIN agg ON two.value < agg.value
WHERE NOT EXISTS (
SELECT * FROM my_table nx
WHERE nx.value > two.value
AND nx.value < agg.value
)
)
SELECT * FROM agg
WHERE rnk = 2
;
(the recursive query will not work in mysql, obviously)
You can use inline initialization like this:
select * from (
select id,
name,
value,
#curRank := #curRank + 1 AS rank
from my_table t, (SELECT #curRank := 0) r
order by value desc
) tb
where tb.rank = 2
SELECT name
FROM my_table
WHERE value < (SELECT max(value) FROM my_table)
ORDER BY value DESC
LIMIT 1
SELECT name
FROM my_table
WHERE value = (
SELECT min(r.value)
FROM (
SELECT name, value
FROM my_table
ORDER BY value DESC
LIMIT 2
) r
)
LIMIT 1

How do I combine these two Select queries with an OR case

I want to select all rows where WHERE (uid = {$uid} OR uid = **HERE** ) where **HERE** is the cids retreived from query 2 below.
Query 1:
SELECT * FROM `t_activities`
WHERE (`uid` = {$uid} OR `uid` = **HERE** )
AND `del` = 0
GROUP BY `fid`
ORDER BY `time` DESC
LIMIT 10
And Query 2:
SELECT `cid` FROM `t_con` WHERE `uid` = {$uid} AND `flag` = 1
SELECT * FROM `t_activities`
WHERE (`uid` = {$uid} OR `uid` in (SELECT `cid`
FROM `t_con`
WHERE `uid` = {$uid} AND `flag` = 1))
AND `del` = 0
GROUP BY `fid`
ORDER BY `time` DESC
LIMIT 10
You can do this as a join as well:
SELECT *
FROM `t_activities` ta left outer join
(SELECT `cid`
FROM `t_con`
WHERE `uid` = {$uid} AND `flag` = 1)
) tc
on ta = tc.cid
WHERE (`uid` = {$uid} OR tc.`uid` is not null) AND `del` = 0
GROUP BY `fid`
ORDER BY `time` DESC
LIMIT 10
By the way, as a SQL statement the "GROUP BY fid" looks very strange. This is allowed in mysql, but I think it is a bad practice. It is much better to be explicit about what you are doing:
SELECT fid, min(<field1>) as Field1, . . .
This helps prevent mistakes when you go back to the query or try to modify it.