BEFORE
id | cat_id | order
33 | 1 | 1
34 | 1 | 2
AFTER
id | cat_id | order
33 | 1 | 2
34 | 1 | 1
Now using 4 query
$db is wrap $mysqli for using placeholder and injection defense
get first record by id
$curr = $db->q('SELECT id,order,cat_id FROM `tbl` WHERE id`=? FOR UPDATE',
33)->fetch_assoc();
if exist first record find next record by order field
if($curr){
$next = $db->q('SELECT id,order FROM `tbl` WHERE `cat_id`=? AND
`order`>? ORDER BY `order` LIMIT 1 FOR UPDATE',
$curr['cat_id'],$curr['order']));
if exist first and second recorn change order value
if($prev['id']){
$db->q("UPDATE `tbl` SET `order`=? WHERE `id`=?",$next['order'],$curr['id']);
$db->q("UPDATE `tbl` SET `order`=? WHERE `id`=?",$curr['order'],$next['id']);
}
}
Important! Checking exist two record, lock rows for update
MySQL doesn't support update with the same table in the FROM statement. So because of this there are (select * from TBL) as t2 in inner subqueries.
Also EXISTS condition in the first CASE WHEN is to prevent update if the second record doesn't exists ("if exist first and second records change order value")
Here is a SQLfiddle example
UPDATE tbl as t1
SET `order`=
CASE WHEN id = 33
and
EXISTS (SELECT ID from (select * from TBL) t2 where
cat_id=t1.Cat_Id
and `order`>t1.`order`
ORDER BY `order`
LIMIT 1)
THEN
(SELECT `order` from (select * from TBL) t2 where
cat_id=t1.Cat_Id
and `order`>t1.`order`
ORDER BY `order`
LIMIT 1)
WHEN id <>33 THEN
(SELECT `order` from (select * from TBL) t2 where
cat_id=t1.Cat_Id
and `order`<t1.`order`
ORDER BY `order` DESC
LIMIT 1 )
ELSE `order`
END
where id =33
or
(SELECT ID from (select * from TBL) t2 where
cat_id=t1.Cat_Id
and `order`<t1.`order`
ORDER BY `order` DESC
LIMIT 1) =33
With one query it's:
UPDATE
`tbl`
SET
`order`=CASE
WHEN `order`=2 THEN 1
WHEN `order`=1 THEN 2
END;
WHERE
`order` IN (1,2)
or, for id's condition:
UPDATE
`tbl`
SET
`order`=CASE
WHEN `order`=2 THEN 1
WHEN `order`=1 THEN 2
END;
WHERE
id = $id
To swap 2 fields by row id try:
UPDATE `tbl` AS tbl1
JOIN `tbl` AS tbl2 ON ( tbl1.id = 33 AND tbl2.id = 34 )
SET
tbl1.order = tbl2.order, tbl2.order = tbl1.order
Also you can set your desired value instead of swap between 2 fileds.
If needed, you can add a where clause like below to swap where cat_id are 1 in two rows:
WHERE
tbl1.cat_id = 1 AND tbl2.cat_id = 1
Update:
If your order numbers are unique for any cat_id you can try this way:
UPDATE `tbl` AS tbl1
JOIN `tbl` AS tbl2 ON ( tbl1.order = 1 AND tbl2.order = 2 )
SET
tbl1.order = tbl2.order, tbl2.order = tbl1.order
WHERE
tbl1.cat_id = 1 AND tbl2.cat_id = 1
It works if your order field is int, Otherwise you should quote order values in query.
See the result on SQLFiddle
Related
I'm trying to limit the number of results of a query based on another table column. For example, I have a table for products and a config table, like this:
tb_product
id | active | name | value | ...
tb_config
max_product | ...
What I'd like to do is something like this
SELECT
a.name, a.value
FROM
tb_product a,
tb_config b
WHERE a.active = 1
LIMIT b.max_product
But I'm getting errors like #1327 - Undeclared variable: b. Is there a way to achieve this result?
Because currently what I'm doing is doing another query to get just the max_product value and then use it as php variable to limit the results, like this:
$limit = "SELECT max_product FROM tb_config";
SELECT name, value FROM tb_product WHERE ativo = 1 LIMIT $limit
Maybe....
SELECT a.name
, a.value
FROM tb_product a
CROSS JOIN (SELECT #Limit:=(SELECT max_product from tb_config))
WHERE a.active = 1
LIMIT #Limit
With help from #ENargit's answer in Variable LIMIT Clause in MySQL, you can do it using a row count variable.
Assuming the following schema:
CREATE TABLE
tb_product
(
id INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(10),
`value` VARCHAR(10)
);
INSERT INTO
`tb_product`
(`name`, `value`)
VALUES
('Name1','Value1'),
('Name2','Value2'),
('Name3','Value3'),
('Name4','Value4'),
('Name5','Value5'),
('Name6','Value6'),
('Name7','Value7'),
('Name8','Value8'),
('Name9','Value9'),
('Name10','Value10');
CREATE TABLE
`tbl_config`
(
id INT PRIMARY KEY AUTO_INCREMENT,
`type` VARCHAR(10),
`value` INT
);
INSERT INTO
`tbl_config`
(`type`,`value`)
VALUES
('something',10),
('maxrows',7);
You can reference the config table with a subquery:
SELECT * FROM (
SELECT
tb_product.*,
#rownum := #rownum + 1 AS RowNum
FROM tb_product,
(SELECT #rownum := 0) AS CounterTbl
) AS DataTbl
WHERE
RowNum <= (
SELECT
`value`
FROM
`tbl_config`
WHERE
`type` = 'maxrows'
);
Gives you the first 7 rows (according to the config value). You can obviously extend this to do sorting etc.
SQLFiddle: http://www.sqlfiddle.com/#!9/f789e4/2
I need to read a column with INT Order in MySQL and get from the column the lower number missing:
+--------+---------+
| ID | Order |
+--------+---------+
| 1 | 1 |
| 3 | 5 |
| 4 | 3 |
| 5 | 4 |
| 6 | 2 |
| 7 | 6 |
| 8 | 11 |
+--------+---------+
The result I need is the number 7, as 1 through 6 exist and other missing numbers are greater than 7.
$stmtpre = "SELECT Order FROM tabla ORDER BY Order DESC";
$data = $this -> DBMANAGER -> BDquery($stmtpre);
$count = 0;
while ($row = mysqli_fetch_assoc($data)){
$count++;
if($row['Order']!==$count){
$result= $count; #store first lower get
break;
}
}
return $result;
If the Order column is indexed, you could get the first missing number with SQL, without reading the complete table using an excluding LEFT JOIN:
SELECT t1.`Order` + 1 AS firstMissingOrder
FROM tabla t1
LEFT JOIN tabla t2 ON t2.`Order` = t1.`Order` + 1
WHERE t2.`Order` IS NULL
AND t1.`Order` <> (SELECT MAX(`Order`) FROM tabla)
ORDER BY t1.`Order`
LIMIT 1
or (maybe more intuitive)
SELECT t1.`Order` + 1 AS firstMissingOrder
FROM tabla t1
WHERE NOT EXISTS (
SELECT 1
FROM tabla t2
WHERE t2.`Order` = t1.`Order` + 1
)
AND t1.`Order` <> (SELECT MAX(`Order`) FROM tabla)
ORDER BY t1.`Order`
LIMIT 1
The second query will be converted by MySQL to the first one. So they are practicaly equal.
Update
Strawberry mentioned a good point: The first missing number might be 1, which is not covered in my query. But i wasn't able to find a solution, which is both - elegant and fast.
We could go the opposite way and search for the first number after a gap. But would need to join the table again to find the last existing number before that gap.
SELECT IFNULL(MAX(t3.`Order`) + 1, 1) AS firstMissingOrder
FROM tabla t1
LEFT JOIN tabla t2 ON t2.`Order` = t1.`Order` - 1
LEFT JOIN tabla t3 ON t3.`Order` < t1.`Order`
WHERE t1.`Order` <> 1
AND t2.`Order` IS NULL
GROUP BY t1.`Order`
ORDER BY t1.`Order`
LIMIT 1
MySQL (in my case MariaDB 10.0.19) is not able to optimize that query properly. It takes about one second on an indexed (PK) 1M row table, even though the first missing number is 9. I would expect the server to stop searching after t1.Order=10, but it seams not to do that.
Another way, which is fast but looks ugly (IMHO), is to use the original query in a subselect only if Order=1 exists. Otherwise return 1.
SELECT CASE
WHEN NOT EXISTS (SELECT 1 FROM tabla WHERE `Order` = 1) THEN 1
ELSE (
SELECT t1.`Order` + 1 AS firstMissingOrder
FROM tabla t1
LEFT JOIN tabla t2 ON t2.`Order` = t1.`Order` + 1
WHERE t2.`Order` IS NULL
AND t1.`Order` <> (SELECT MAX(`Order`) FROM tabla)
ORDER BY t1.`Order`
LIMIT 1
)
END AS firstMissingOrder
Or Using UNION
SELECT 1 AS firstMissingOrder FROM (SELECT 1) dummy WHERE NOT EXISTS (SELECT 1 FROM tabla WHERE `Order` = 1)
UNION ALL
SELECT firstMissingOrder FROM (
SELECT t1.`Order` + 1 AS firstMissingOrder
FROM tabla t1
LEFT JOIN tabla t2 ON t2.`Order` = t1.`Order` + 1
WHERE t2.`Order` IS NULL
AND t1.`Order` <> (SELECT MAX(`Order`) FROM tabla)
ORDER BY t1.`Order`
LIMIT 1
) sub
LIMIT 1
Might be the long way around, but here's one way:
while ($row = mysqli_fetch_assoc($data)) {
$orders[] = $row['Order'];
}
$result = min(array_diff(range(min($orders), max($orders)), $orders));
Create a range from the minimum order found to the maximum order found
Calculate the difference with the found orders to get missing orders
Find the lowest order number from the missing
This assumes that you want to use the lowest and highest numbers returned from the query as the range. If you want to always start at 1 use 1 instead of min($orders).
Also, as Strawberry points out, Order is a reserved word in MySQL so consider changing it or delimit it with back-ticks SELECT `Order` FROM tabla.
From PHP side:
i work more around the solution:
Fisrt call Function:
$stmtpre = "SELECT Order FROM tabla ORDER BY Order ASC";
$data = $this -> DBMANAGER -> BDqueryFirstMissingINT($stmtpre, DATABASE);
echo $data;
On second
function BDqueryFirstMissingINT($stmtpre,$dbUsing){
$data = $this -> BDquery($stmtpre, $dbUsing); #run the query
$count = 0;
while ($row = mysqli_fetch_array($data)){
$count++;
$value = (int)$row[0];
if($value!==$count){
$result = $count;
break;
}
}
return $result;
}
Thank for you help
Here's one idea...
SELECT x.my_order + 1 missing
FROM
( SELECT my_order FROM my_table
UNION
SELECT 0
) x
LEFT
JOIN my_table y
ON y.my_order = x.my_order + 1
WHERE y.my_order IS NULL
ORDER
BY missing
LIMIT 1;
I have a table that looks like this:
id | user_id | credits
----------------------
1 | 2 | 300
2 | 2 | 200
3 | 4 | 100
I need a select or way to add the credits from a specific user id
The select would be:
SELECT credit FROM myTable WHERE user_id ='2'
The result I would need in this case would be 500
How can I do this?
Use SUM() in your query will solve the issue.
SELECT SUM(`credits`) AS `total` FROM `myTable` WHERE `user_id` =2
Since you specify PHP tag, the below code will help you.
$mysqli=mysqli_connect("hostname","username","password","databasename");
$query = "SELECT SUM(`credits`) AS `total` FROM `myTable` WHERE `user_id` =2";
$processquery = $mysqli->query($query);
$result = $processquery->fetch_assoc();
echo $result['total'];
Use sum()
SELECT sum(credit) as sum_credit
FROM myTable
WHERE user_id = 2
Use aggregate function SUM():
SELECT SUM(credit) as TotalCredit
FROM myTable
WHERE user_id='2'
Read more about aggregate functions here.
Try SUM() in mysql
SELECT SUM(credit) as totalcredit FROM myTable WHERE user_id ='2'
will return you matched result sum according to condition
select sum(credit) as total from myTable where user_id = '2'
You can do this by summing the total of the credit column. Check out the MySQL reference manual for the official instructions.
SELECT SUM(credit) AS credit_total
FROM myTable
WHERE user_id = 2
You use the AS keyword to assign a name to your column, then in PHP you can access this column as you would any other by name, just using credit_total. E.g:
$query = "SELECT SUM(credit) AS credit_total
FROM myTable
WHERE user_id = 2";
$result = $mysqli->query($query);
$row = $result->fetch_assoc();
echo $row['credit_total']; // Will output 500
I have a table which stores information on standings in multiple leagues, think of this as a fantasy site. The structure is as follows in terms of columns.
league_id | user_id | total_points | prediction_difference | current_position | last_position
In order to calculate the current standings I am issuing the following query:
SELECT
*
FROM f_u_standings
WHERE league_id = 1
ORDER BY total_points DESC,
prediction_difference DESC
My question is, now I have this result set, how can I then perform an UPDATE based on the SELECT query which updates the current_position column? My programming language of choice on this project is PHP.
you can update with a select.. this assumes you have an ID for each row
UPDATE TABLE f_u_standings fs,
(
SELECT
*
----- do what you want to change current_position -----
FROM f_u_standings
WHERE league_id = 1
ORDER BY total_points DESC,
prediction_difference DESC
) temp
SET fs.current_position = temp.current_position WHERE fs.id = temp.id
This may be closer to what you need:
UPDATE f_u_standings fs,
(SELECT #rownum:=#rownum+1 rownum, id
FROM f_u_standings, (SELECT #rownum := 0) init
WHERE league_id = 1
ORDER BY total_points DESC,
prediction_difference DESC) temp
SET fs.current_position = temp.rownum
WHERE fs.id = temp.id
Lets say i have a databse:
+----------+
| Database |
+----------+
| id |
| image |
| category |
+----------+
Now i have a page that shows an image from the database, i want to add a
<< Previous image
and
Next image >>
button. I could just take the $id and add +1 , but what if the next ID does not exist in the DB ?
Any help appreciated, thanks
So i found this:
SELECT *
FROM database AS c
WHERE (id = (SELECT MAX(id) FROM database WHERE id < c.id AND language = 'en')
OR id = (SELECT MIN(id) FROM database WHERE id > c.id AND language = 'en'))
But how do i make a link out of it, like:
Next ?
You'll have to use another select:
SELECT
( SELECT ID FROM <TABLE-NAME> WHERE ID > $currentID ORDER BY ID ASC LIMIT 1 )
AS NEXT_VALUE,
( SELECT ID FROM <TABLE-NAME> WHERE ID < $currentID ORDER BY ID ASC LIMIT 1 )
AS PREV_VALUE
FROM DUAL;
As explained in this answer, you can use a subselect query to get the ID of the previous and next records:
...
WHERE id IN (
'$id',
(SELECT MAX(id) FROM yourTable WHERE id < '$id'),
(SELECT MIN(id) FROM yourTable WHERE id > '$id')
)
...