MySQL get first existing row with value OR get another ordered - php

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 )

Related

How to compare columns values with sum function in SQL?

I have three tables :
mls_category
points_martix
mls_entry
My first table (mls_category) is like below:
*--------------------------------*
| cat_no | store_id | cat_value |
*--------------------------------*
| 10 | 101 | 1 |
| 11 | 101 | 4 |
*--------------------------------*
My second table (points_martix) is like below:
*----------------------------------------------------*
| pm_no | store_id | value_per_point | max_distance |
*----------------------------------------------------*
| 1 | 101 | 1 | 10 |
| 2 | 101 | 2 | 50 |
| 3 | 101 | 3 | 80 |
*----------------------------------------------------*
My third table (mls_entry) is like below:
*-------------------------------------------*
| user_id | category | distance | status |
*-------------------------------------------*
| 1 | 10 | 20 | approved |
| 1 | 10 | 30 | approved |
| 1 | 11 | 40 | approved |
*-------------------------------------------*
I am using the following query to show the sum of distance with some condition:
SELECT SUM(t1.totald/c.cat_value)
AS total_distance
FROM mls_category c
JOIN
(SELECT SUM(distance) totald, user_id, category
FROM mls_entry
WHERE user_id = 1
AND status = 'approved'
GROUP BY user_id, category) t1
ON c.cat_no = t1.category
This gives me sum 60 as total_distance, that is correct which I wanted.
Now, I want to include the third table (points_matrix) and want to compare my sum(60) is less than or equal to 80(max_distance) then my new value would be 60*3=180.
So, suppose my sum comes 10 then my new value will be 10*1=10 and if my sum comes 25 then my new value will be according to point matrix 25*2=50.
Yon can using MIN() to calculate what value_per_point you need, and the whole sql is like this:
SELECT MIN(b.value_per_point) * d.total_distance FROM points_matrix b
JOIN
(
SELECT store_id, sum(t1.totald/c.cat_value) as total_distance FROM mls_category c
JOIN
(
SELECT SUM(distance) totald, user_id, category FROM mls_entry
WHERE user_id= 1 AND status = 'approved' GROUP BY user_id, category
) t1 ON c.cat_no = t1.category
) d ON b.store_id = d.store_id AND b.max_distance >= d.total_distance
Use Correlated Subquery:
SELECT
dt.total_distance * dt.max_points
FROM (
SELECT SUM(t1.totald/c.cat_value) AS total_distance,
(
SELECT value_per_point
FROM points_martix
WHERE SUM(t1.totald/c.cat_value) >= max_distance
ORDER BY max_distance ASC LIMIT 1
) AS max_points
FROM mls_category AS c
JOIN (
SELECT SUM(distance) AS totald,
user_id,
category
FROM mls_entry
WHERE user_id= 1 AND
status = 'approved'
GROUP BY user_id, category
) AS t1 on c.cat_no = t1.category
) AS dt

returning null or empty string in "IN CLAUSE" mysql php

I have this query :
SELECT * from tbl WHERE id = 1 AND option IN (1,2,3)
table :
+-------+---------+-------+
| id | option | votes |
+-------+---------+-------+
| 1 | 1 | 100 |
| 1 | 2 | 150 |
+-------+---------+-------+
As option no.3 doesnt exist yet in the table, i want it to return null / empty string, so i can set 'votes' to 0 in my php code.
Currently it only gives me rows of option 1 & 2 as expected.
SELECT * from tbl t1
RIGHT JOIN
(SELECT 1 AS opt UNION ALL
SELECT 2 AS opt UNION ALL
SELECT 3 AS opt UNION ALL
) t2
ON t1.option = t2.opt
WHERE t1.id = 1

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: 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 |
+----+---------+--------+------------+

MySql PDO - How to fetch "newest rows from group" when using GROUP BY, ORDER BY and INNER JOIN?

products_table: | p_id | name |
| 1 | name1 |
| 2 | name2 |
| 3 | name3 |
favourites_table: | id | p_id | deleted | group_id |
fetch-> | 1 | 1 | 0 | 11 |
| 2 | 1 | 0 | 11 |
fetch-> | 3 | 2 | 0 | 22 |
| 4 | 2 | 0 | 22 |
fetch-> | 5 | 3 | 0 | 33 |
| 6 | 3 | 0 | 33 |
$sth = $db->prepare(' SELECT a.p_id, b.name
FROM favourites_table AS a
INNER JOIN products_table AS b
ON a.p_id = b.p_id
WHERE a.deleted=0
GROUP BY a.group_id
ORDER BY a.id ASC
LIMIT 0, 10;');
$sth->execute();
while(($query_data = $sth->fetch()) !== false) {
echo $query_data['p_id'] . ':' . $query_data['name'] . '<br>';
}
This query fetches rows 1, 3, 5 from 'favourites_table'.
How to change it so it fetches "newest rows" (2, 4, 6) ?
Do I have to change the whole query or am I missing something?
You're confused by the pernicious misfeature in MySQL called the GROUP BY extension. Read this. http://dev.mysql.com/doc/refman/5.6/en/group-by-extensions.html
You want the rows you define as latest for each value of group_id. These rows are in fact the undeleted ones with the highest id values.
So, first you need to use a subquery -- a virtual table -- to find those rows, as follows:
SELECT MAX(id) AS id, group_id FROM favourites_table WHERE deleted = 0 GROUP BY group_id
Then, you need to use that resultset to find the right rows in your main query. You would do this like so:
SELECT a.p_id, b.name
FROM favourites_table AS a
INNER JOIN products_table AS b ON a.p_id = b.p_id
INNER JOIN (
SELECT MAX(id) AS id, group_id FROM favourites_table WHERE deleted = 0 GROUP BY group_id
) AS c ON a.id = c.id
GROUP BY a.group_id
ORDER BY a.id ASC
LIMIT 0, 10
This should get your results.
Question: Why order them oldest (lowest id value) first? Why only show the oldest ten results? Is that what you want?

Categories