How can I UPDATE the result of a SELECT statement? - php

I have a table like this:
// notifications
+----+--------------+------+---------+------------+
| id | event | seen | id_user | time_stamp |
+----+--------------+------+---------+------------+
| 1 | vote | 1 | 123 | 1464174617 |
| 2 | comment | 1 | 456 | 1464174664 |
| 3 | vote | 1 | 123 | 1464174725 |
| 4 | answer | 1 | 123 | 1464174813 |
| 5 | comment | NULL | 456 | 1464174928 |
| 6 | comment | 1 | 123 | 1464175114 |
| 7 | vote | NULL | 456 | 1464175317 |
| 8 | answer | NULL | 123 | 1464175279 |
| 9 | vote | NULL | 123 | 1464176618 |
+----+--------------+------+---------+------------+
And here is my query:
(SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id AND seen IS NULL
)UNION
(SELECT id, event, seen, time_stamp
FROM notification n
WHERE id_user = :id AND seen IS NOT NULL
LIMIT 2
)UNION
(SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id
ORDER BY (seen IS NULL) desc, time_stamp desc
LIMIT 15
)
ORDER BY (seen IS NULL) desc, time_stamp desc;
Now I'm trying to update matched rows from query above and set seen = 1, Something like this:
UPDATE notifications SET seen = 1 WHERE /* the result of query above */
How can I do that?
Note: I also use PHP and PDO to execute that SELECT query (If it is important to know)
$stm = $db->prepare(" (SELECT id, ... ");
$stm->execute( /* passing some variables */);
$stm->fetchAll(PDO::FETCH_ASSOC);

seen can only be 1 or null. In that case, most of what you are getting from that UNION query is irrelevant. It seems to me that the end result of the update you're trying to do will be:
UPDATE notifications SET seen = 1 WHERE id_user = :id AND seen IS NULL
Anything else will be updating something that's already 1 to 1.

I think you can use where in clause .. but in this case you need ond the id
UPDATE notifications SET seen = 1 WHERE id in (
(SELECT id
FROM notifications n
WHERE id_user = :id AND seen IS NULL
)UNION
(SELECT id
FROM notification n
WHERE id_user = :id AND seen IS NOT NULL
LIMIT 2
)UNION
(SELECT id
FROM notifications n
WHERE id_user = :id
ORDER BY (seen IS NULL) desc, time_stamp desc
LIMIT 15
))

If you want to do this in MySQL, use JOIN:
update notifications n join
(select id
from ((SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id AND seen IS NULL
) UNION
(SELECT id, event, seen, time_stamp
FROM notification n
WHERE id_user = :id AND seen IS NOT NULL
LIMIT 2
) UNION
(SELECT id, event, seen, time_stamp
FROM notifications n
WHERE id_user = :id
ORDER BY (seen IS NULL) desc, time_stamp desc
LIMIT 15
)) n
) nids
on n.id = nids.id
set seen = 1;

You can slightly modify your query to use as subquery for update statement:
UPDATE notifications SET seen = 1 WHERE id in (
(SELECT id
FROM notifications n
WHERE id_user = :id AND seen IS NULL
)UNION
(SELECT id
FROM notification n
WHERE id_user = :id AND seen IS NOT NULL
LIMIT 2
)UNION
(SELECT id
FROM notifications n
WHERE id_user = :id
ORDER BY (seen IS NULL) desc, time_stamp desc
LIMIT 15
)
)

I assume you have retrieved all applicable ID's into your application. All you need to do is then is
UPDATE notifications
SET seen = 1
WHERE id IN (?,?,?)
where the ? are replaced by the id values.

Related

MySQL get first existing row with value OR get another ordered

I need to write mysql query for table like this:
id | product_id | version | status
1 | 1 | 1 | 0
2 | 1 | 2 | 1
3 | 1 | 3 | 0
4 | 2 | 9 | 0
5 | 2 | 10 | 0
I need to get rows (unique for product_id - one for each product_id) but:
-if there is row for product_id with status=1 - grab it
-it there is no row as described get row with higher value or version
So for described table result should be
id | product_id | version | status
2 | 1 | 2 | 1
5 | 2 | 10 | 0
My only idea is to get rows with status 1, and then make second query using WHERE product_id NOT IN and then order by version DESC and GROUP BY product_id
Can join back to the table in this case
SELECT p1.id, p1.product_id, p1.version, p1.status FROM products p1
LEFT OUTER JOIN (
SELECT MAX(version) AS version FROM products p2
) p2 ON p1.version = p2.version OR p1.status = 1
GROUP BY p1.product_id
SQL Fiddle
You can achieve it with a UNION
select myTable.id, product_id, version, status from myTable
where id in(select id from myTable where status > 0)
union
select myTable.id, product_id, version, status from myTable
join (select max(id) as id from myTable group by product_id)
as m on m.id = myTable.id
and product_id not in(select product_id from myTable where status > 0 )

SQL: Order by Messages doesn't work

why does this SQL Code not run?
user_chats
id | user_id | to_user_id | ad_id | timestamp
----------------+---------+------------+---------+-----------
1 | 1 | 6 | 13 | 1513516133
user_messages
id | chat_id | text | user_id | timestamp
----------------+---------+------------+---------+-----------
1 | 1 | Hello | 1 | 1513516133
2 | 1 | Hi! | 6 | 1513516754
I want to get the Chats and order them by user_messages.timestamp.
My SQL Code is:
SELECT user_chats.id,
user_chats.timestamp,
ad_id,
title,
user_chats.user_id
FROM user_chats
INNER JOIN ads
ON ads.id = ad_id
WHERE user_chats.user_id = "1"
OR user_chats.to_user_id = "1"
ORDER BY (SELECT id
FROM user_messages
WHERE chat_id = user_chats.id
ORDER BY user_messages.id DESC)
The issue is that you've used a subquery in your Order By clause: as this returns multiple results for each record in the main query it cannot be used to order the results of the main query.
I think you're trying to order the results by the latest message in each chat, but simply joining the user_messages table will mean you'll get duplicates (each chat being returned once per message). You can get around this by joining to an inline view:
SELECT DISTINCT user_chats.id,
user_chats.timestamp,
ad_id,
title,
user_chats.user_id
FROM user_chats
INNER JOIN ads
ON ads.id = ad_id
LEFT JOIN
--in line view aliased 'UM' returns one row per chat_id in user_messages, with the last timestamp for that ID
(SELECT max(timestamp) LastMessage,
chat_id
FROM user_messages
GROUP BY chat_id) um
ON um.chat_id = user_chats.id
WHERE user_chats.user_id = 1
OR user_chats.to_user_id = 1
ORDER BY um.LastMessage desc

Delete all rows, except last 10 for each client that has related row(s) in the table in one query?

So my situation is this:
Clients table - has client data etc, not too exciting
Recently Viewed table - table that has recently viewed things for the client(s), And has structure like this:
( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
, client_id INT NOT NULL
, cookie_user_id INT NOT NULL
, hotel_id INT NOT NULL
, added DATETIME NOT NULL
, comment TEXT
,status TINYINT NOT NULL DEFAULE 1
);
I currently have a partially working SQL to delete rows in the recently viewed table that right now globally limits number of latest remaining undeleted records in it. This is how it looks like now
DELETE FROM `recently_viewed`
WHERE `recently_viewed`.`id` NOT IN (
SELECT id
FROM (
SELECT `id`
FROM `recently_viewed`
WHERE `client_id` IN (SELECT `id` FROM `klijenti`)
ORDER BY `id` DESC
LIMIT 5
) x
)
AND `client_id` <> 0
"LIMIT 5" part should limit to the N records to remain in recently viewed table on a "per client" basis. Right now it limits records in recently viewed table to 5 no matter how many clients actually have records there. So if I have 10 clients, each of them has 8 records in that table, I would like this query to delete as many oldest records as needed to leave only 5 newest recently viewed items for EACH client and not just leave 5 overall in the table, ignoring the "per each client" logic. Hope that makes sense to you :)
Currently, this query would be ok if I would first fetch all clients in the app and then do a foreach loop to make another query for each client and leave 5 of his latest recently viewed items, but would like to do this in one SQL query instead.
How could this be done ? Thank you
You can do it like this:
DELETE FROM `recently_viewed`
WHERE `recently_viewed`.`id` NOT IN (
SELECT id
FROM (
SELECT t.`id`,count(*) as rnk
FROM `recently_viewed` t
INNER JOIN `recently_viewed` s
ON(t.`client_id` = s.`client_id` and t.added <= s.added)
WHERE t.`client_id` IN (SELECT `id` FROM `klijenti`)
GROUP BY t.`ID`
) x
WHERE rnk <= 5
)
AND `client_id` <> 0
You can use vartiables to in order to count the 5 more recent records per client_id:
DELETE FROM `recently_viewed`
WHERE `recently_viewed`.`id` NOT IN
(
SELECT id
FROM (
SELECT `id`,
#rn := IF(#cid = `client_id`, #rn + 1,
IF(#cid := `client_id`, 1, 1)) AS rn
FROM `recently_viewed`
CROSS JOIN (SELECT #rn := 0, #cid := 0) AS vars
WHERE `client_id` IN (SELECT `id` FROM `klijenti`)
ORDER BY `client_id`, `id` DESC) x
WHERE x.rn <= 5
)
Giorgos's answer is faster, but here's another method...
Consider the following...
SELECT * FROM my_table ORDER BY x,i;
+---+------+
| i | x |
+---+------+
| 2 | A |
| 3 | A |
| 6 | A |
| 8 | A |
| 1 | B |
| 5 | B |
| 4 | C |
| 7 | C |
| 9 | C |
+---+------+
Let's say we want to select the two latest i for each x. Here's one way to do that...
SELECT m.* FROM my_table m JOIN my_table n ON n.x = m.x AND n.i >= m.i GROUP BY m.i HAVING COUNT(*) <= 2;
+---+------+
| i | x |
+---+------+
| 1 | B |
| 5 | B |
| 6 | A |
| 7 | C |
| 8 | A |
| 9 | C |
+---+------+
The inverse of this set can be found as follows....
SELECT m.* FROM my_table m JOIN my_table n ON n.x = m.x AND n.i >= m.i GROUP BY m.i HAVING COUNT(*) > 2;
+---+------+
| i | x |
+---+------+
| 2 | A |
| 3 | A |
| 4 | C |
+---+------+
...which in turn can be incorporated in a DELETE. Here's a crude method for doing that...
DELETE a FROM my_table a
JOIN
( SELECT m.* FROM my_table m JOIN my_table n ON n.x = m.x AND n.i >= m.i GROUP BY m.i HAVING COUNT(*) > 2 ) b
ON b.i = a.i;
Query OK, 3 rows affected (0.03 sec)
SELECT * FROM my_table ORDER BY x,i;
+---+------+
| i | x |
+---+------+
| 6 | A |
| 8 | A |
| 1 | B |
| 5 | B |
| 7 | C |
| 9 | C |
+---+------+
As I say, if performance is critical, then look at a solution along the lines that Giorgos has provided.

MySql - Change return value if value exists in another table

I m using the selectSellerID function to retrieve a user's id.
public function selectSellerID($item_id)
{
if($q = $this->db->mysqli->prepare("SELECT user_id FROM items WHERE id = ?"))
{
$q->bind_param("i", $item_id);
$q->execute();
$q->bind_result($id);
$q->fetch();
$q->close();
return $id;
}
return false;
}
The item_id can also appear in another table which is the resell table. In this case the user_id will change. Below is the resell table.
+----+---------+---------+-------+------+
| id | user_id | item_id | price | date |
+----+---------+---------+-------+------+
| 2 | 18 | 27 | 91 | NULL |
| 3 | 1 | 27 | 90 | NULL |
| 4 | 1 | 27 | 75 | NULL |
+----+---------+---------+-------+------+
So I need to change the function to check if the item_id is presend in the resell table, then only grab the latest user_id, as it may be resold multiple times. What is the best way to change the function other than doing a second query?
You can use MySQL JOINS.
Joining items table with resell table and then ordering the result set with highest id will give the desired results.
Try below query:
SELECT IF(r.user_id IS NULL, i.user_id, r.user_id) AS user_id
FROM items i
LEFT JOIN resell r
ON i.id = r.item_id
WHERE i.id = ?
ORDER BY r.id DESC
LIMIT 1
Use LEFT JOIN
$q = $this->db->mysqli->prepare("SELECT rsl.user_id FROM items itms LEFT JOIN resell rsl ON itms.id = rsl.item_id WHERE itms.id = ? ORDER BY rsl.id DESC LIMIT 1");

MySQL: Get all entries in a specific time frame with value=0 unless there's an entry in the same time frame with value=1

Table payment_transaction
+----+---------+--------+------------+
| ID | user_id | status | time_stamp |
+----+---------+--------+------------+
| 1 | 1 | 1 | 1414541884 |
| 2 | 2 | 0 | 1414576722 |
| 3 | 2 | 0 | 1414577273 |
| 4 | 3 | 0 | 1414782966 |
| 5 | 3 | 1 | 1414785691 |
| 6 | 4 | 0 | 1415112933 |
+----+---------+--------+------------+
This table stores all payment transactions. status = 0 means the payment failed for whichever reason, status = 1 means the payment was successful.
I'd now like to setup a cronjob, where all users will get a message who weren't able to buy a membership in the last 7 days. As in the example table above, I only need 2 rows with the user_id, transaction_id of the most recent payment and time_stampe of the most recent payment. As user_id = 3 managed to get a membership after the first failed payment, he should be excluded:
+---------+----------------+------------+
| user_id | transaction_id | time_stamp |
+---------+----------------+------------+
| 2 | 3 | 1414577273 |
| 4 | 6 | 1415112933 |
+---------+----------------+------------+
Is it possible to get all this done in one query? And if yes, is it more effective than using two queries?
What I've got so far:
SELECT DISTINCT
`t`.`user_id`,
`t`.`id` AS `transaction_id`,
`t`.`time_stamp`
FROM `payment_transaction` AS `t`
WHERE
`t`.`status` = 0
AND `t`.`time_stamp` < UNIX_TIMESTAMP() - 60*60*24*7
ORDER BY
`t`.`id`
However, it doesn't exclude the user with user_id = 3 and also gives me the first failed payment instead the most recent.
---- EDIT ----
Thanks to RST for providing a solution in the comments below. After some additional changes, the final query looks like this:
SELECT
`t`.`user_id`,
MAX(`t`.`id`) AS `transaction_id`,
MAX(`t`.`time_stamp`) AS `time_stamp`
FROM `payment_transaction` AS `t`
WHERE
`t`.`status` = 0
AND `t`.`time_stamp` < UNIX_TIMESTAMP() - 60*60*24*7
AND `t`.`user_id` NOT IN (
SELECT `user_id`
FROM `payment_transaction`
WHERE `status` = 1
AND UNIX_TIMESTAMP(`time_stamp`) < UNIX_TIMESTAMP() - 60*60*24*7
)
GROUP BY
`t`.`user_id`
ORDER BY
`t`.`id`
Thinking a little away from your request but should still get the same results, depending on how you've coded your application. Is the assumption that the latest transaction will be the most relevant an accurate one?
If so, you could look at something like the following (untested):
SELECT DISTINCT
`t`.`user_id`,
`t`.`id` AS `transaction_id`,
`t`.`time_stamp`
FROM `payment_transaction` AS `t`
WHERE
`t`.`time_stamp` < UNIX_TIMESTAMP() - 60*60*24*7
GROUP BY
`t`.`user_id`
ORDER BY
`t`.`id`
Please note I've not tested this SQL, as I'm away from my dev machine. I'd recommend having a look through the intricacies of the GROUP BY command to make sure you're not going to have some edge cases that aren't caught.
DROP TABLE IF EXISTS payment_transaction;
CREATE TABLE payment_transaction
(ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,user_id INT NOT NULL
,status INT NOT NULL
,time_stamp BIGINT NOT NULL
);
INSERT INTO payment_transaction VALUES
(1 ,1 ,1 ,1414541884),
(2 ,2 ,0 ,1414576722),
(3 ,2 ,0 ,1414577273),
(4 ,3 ,0 ,1414782966),
(5 ,3 ,1 ,1414785691),
(6 ,4 ,0 ,1415112933);
SELECT x.*
FROM payment_transaction x
JOIN
( SELECT a.user_id
, MAX(a.id) max_id
FROM payment_transaction a
LEFT
JOIN payment_transaction b
ON b.user_id = a.user_id
AND b.status = 1
AND b.time_stamp > a.time_stamp
WHERE a.time_stamp > UNIX_TIMESTAMP(NOW())-604800
AND a.status = 0
AND b.id IS NULL
) y
ON y.user_id = x.user_id
AND y.max_id = x.id;
+----+---------+--------+------------+
| ID | user_id | status | time_stamp |
+----+---------+--------+------------+
| 6 | 4 | 0 | 1415112933 |
+----+---------+--------+------------+

Categories