Get rows with condition on last grouping row - mysql

I have table favourite_products with schema like below. I need to count how many people (account_id) like product with id = 12. But the condition is that person marked product as liked on last time.
In this example user with id = 1 marked product 12 as positive for the first time, but then he marked is a non-positive so this value shouldn't be returned. The following example should return 2 rows (for user_id = 5 and user_id = 8). I heard about window function but have mysql in version 5.7 and I can't upgrade it. Do you have some ideas how to write this query?
| id | user_id | product_id | positive |
| 1 | 1 | 12 | 1 |
| 2 | 1 | 12 | 0 |
| 3 | 1 | 15 | 1 |
| 4 | 5 | 12 | 1 |
| 5 | 5 | 12 | 1 |
| 6 | 11 | 18 | 1 |
| 7 | 8 | 12 | 1 |
| 8 | 8 | 12 | 1 |

Following approach should work for all the cases, including the case when a product was disliked and then liked again at the end.
In a Derived table, we can get the maximum id value for every user_id and product_id = 12. This result-set will be joined to the main table appropriately. This will fetch us the complete row (recent activity done by user for a product).
Now, we can consider only those users where the last activity is positive.
Query
SELECT fp.user_id
FROM favourite_products AS fp
JOIN (SELECT user_id,
Max(id) AS max_id
FROM favourite_products
WHERE product_id = 12
GROUP BY user_id) AS dt
ON dt.user_id = fp.user_id
AND dt.max_id = fp.id
AND fp.positive = 1;
Result
| user_id |
| ------- |
| 5 |
| 8 |
View on DB Fiddle

Related

SELECT, how to combine some rows as one, show rows with specific column value first and exclude a row

I have two tables, units and power.
Take a look at:
The power table:
+----+-------+
| id | power |
+----+-------+
| 1 | 2 |
| 2 | 4 |
| 3 | 6 |
| 4 | 8 |
+----+-------+
The units table:
+----+---------+----------+--------+----------+---+---+
| id | user_id | power_id | amount | group_id | x | y |
+----+---------+----------+--------+----------+---+---+
| 1 | 10 | 3 | 1000 | 0 | 1 | 1 |
| 2 | 10 | 3 | 1000 | 0 | 1 | 1 |
| 3 | 10 | 3 | 1000 | 0 | 1 | 1 |
| 4 | 11 | 2 | 100 | 4 | 1 | 1 |
| 5 | 11 | 2 | 100 | 4 | 1 | 1 |
| 6 | 11 | 1 | 100 | 4 | 1 | 1 |
| 7 | 12 | 4 | 1000 | 0 | 1 | 1 |
+----+---------+----------+--------+----------+---+---+
I want to get result looking like this:
+----------+--------------+-------------+----------+---------+--+
| units.id | total_amount | total_power | group_id | user_id | |
+----------+--------------+-------------+----------+---------+--+
| 3 | 1000 | 6000 | 0 | 10 | |
| 2 | 1000 | 6000 | 0 | 10 | |
| 4 | 300 | 1000 | 4 | 11 | |
| 7 | 1000 | 8000 | 0 | 12 | |
+----------+--------------+-------------+----------+---------+--+
Explanation:
Exclude a specific id, should work same for both a single row and a row which is a sum of multiple rows in the table. As you see, in the result set, row with id 1 was excluded. The id is provided by the app, I think it's better to do it in MySQL than PHP, because MySQL could just discard this row (I think), but with PHP it would have to do if() checking for every loop iteration, which seems less efficient.
Show the summed-rows row before single rows, every time.
Show user's units before other users', every time. You can see that when rows with user_id of 10 (imagine this user is the one seeing the page) appear first in my result set.
Show units with highest power first.
Show units with highest amount first.
Show units with highest id first.
The last(4.5.6) are sorted in regards to the priority of the result set, with 4th having the most of it.
I have this query:
SELECT units.id,
units.amount*power.power AS total_power,
Sum(units.amount) AS total_amount
FROM units
JOIN power
ON units.power_id = power.id
WHERE x = ?
AND y = 1
GROUP BY group_id
ORDER BY group_id desc, total_power desc, total_amount desc, units.id desc limit ?,?
But it combines all units where group_id is 0, however I want units with group_id=0 to be separate rows in the result set. How to do that? Thanks!
Edit: To answer #Linoff's question about how I determine which id to exclude, I exclude the 1 in the example because a user always will see the result set through accessing it with a unit_id, which, again, in my example happens to be 1. I hope it is clear now.
Edit: The user can access this list of units on page "index.php?page=unit/view&id=. Then I SELECT the entered id separately for the purpose of my app, and then SELECT the list. But as I already have data for the entered id (for instance, 1 in this case) I do not need to have them when I SELECT from the units and power.
#Anonymous, to answer your question 1, answer 1 is: all rows with same group_id (except 0 which is a not-a-group marker) are grouped and combined together, so rows which are id 4,5 and 6 which have identical group_id are combined. My problem is, I don't know how to exclude grouping for rows which are stand alone (no group marking) and how to sort the result so the rows with specified user_id are sorted first and grouped rows (4,5,6-turned-to-4) are also sorted first, but in user_id=10-first,user_id=??-second hierarchy, if this makes sense.

Distinct order-number sequence for every customer

I have table of orders. Each customer (identified by the email field) has his own orders. I need to give a different sequence of order numbers for each customer. Here is example:
----------------------------
| email | number |
----------------------------
| test#com.com | 1 |
----------------------------
| example#com.com | 1 |
----------------------------
| test#com.com | 2 |
----------------------------
| test#com.com | 3 |
----------------------------
| client#aaa.com | 1 |
----------------------------
| example#com.com | 2 |
----------------------------
Is possible to do that in a simple way with mysql?
If you want update data in this table after an insert, first of all you need a primary key, a simple auto-increment column does the job.
After that you can try to elaborate various script to fill the number column, but as you can see from other answer, they are not so "simple way".
I suggest to assign the order number in the insert statement, obtaining the order number with this "simpler" query.
select coalesce(max(`number`), 0)+1
from orders
where email='test1#test.com'
If you want do everything in a single insert (better for performance and to avoid concurrency problems)
insert into orders (email, `number`, other_field)
select email, coalesce(max(`number`), 0) + 1 as number, 'note...' as other_field
from orders where email = 'test1#test.com';
To be more confident about not assign at the same customer two orders with the same number, I strongly suggest to add an unique constraint to the columns (email,number)
create a column order_number
SELECT #i:=1000;
UPDATE yourTable SET order_number = #i:=#i+1;
This will keep incrementing the column value in order_number column and will start right after 1000, you can change the value or even you can even use the primary key as the order number since it is unique all the time
I think one more need column for this type of out put.
Example
+------+------+
| i | j |
+------+------+
| 1 | 11 |
| 1 | 12 |
| 1 | 13 |
| 2 | 21 |
| 2 | 22 |
| 2 | 23 |
| 3 | 31 |
| 3 | 32 |
| 3 | 33 |
| 4 | 14 |
+------+------+
You can get this result:
+------+------+------------+
| i | j | row_number |
+------+------+------------+
| 1 | 11 | 1 |
| 1 | 12 | 2 |
| 1 | 13 | 3 |
| 2 | 21 | 1 |
| 2 | 22 | 2 |
| 2 | 23 | 3 |
| 3 | 31 | 1 |
| 3 | 32 | 2 |
| 3 | 33 | 3 |
| 4 | 14 | 1 |
+------+------+------------+
By running this query, which doesn't need any variable defined:
SELECT a.i, a.j, count(*) as row_number FROM test a
JOIN test b ON a.i = b.i AND a.j >= b.j
GROUP BY a.i, a.j
Hope that helps!
You can add number using SELECT statement without adding any columns in table orders.
try this:
SELECT email,
(CASE email
WHEN #email
THEN #rownumber := #rownumber + 1
ELSE #rownumber := 1 AND #email:= email END) as number
FROM orders
JOIN (SELECT #rownumber:=0, #email:='') AS t

grouped sql query (mysql) - order by

Lets's say i have a table sign_ins which has data like so: (the real table has 3.5 million rows)
+-----------+---------+------------------+
| school_id | user_id | date(created_at) |
+-----------+---------+------------------+
| 1 | 4 | 2009-04-20 |
| 1 | 4 | 2009-04-21 |
| 1 | 4 | 2009-05-06 |
| 1 | 5 | 2009-04-20 |
| 1 | 5 | 2009-06-26 |
| 1 | 5 | 2009-06-26 |
| 2 | 6 | 2009-04-21 |
| 2 | 6 | 2009-06-26 |
| 2 | 7 | 2009-04-20 |
| 2 | 7 | 2009-04-20 |
+-----------+---------+------------------+
created_at is a datetime field but i'm calling date() on it to get the day.
I have the concept of a "login_days" which is the number of distinct days on which a given user has a sign_in record. I want to order the schools by the number of login days, highest first, and return the number of login days.
So, looking at the data above, school 1 has two users (4 & 5). User 4 has three sign_ins, on 3 distinct days, so 3 "login_days". User 5 has three logins, but only 2 distinct days, so 2 "login_days". Therefore school 1 has 5 login days.
Looking at school 2, it has 3 login days: 2 from user 6 and 1 from user 7.
So, i would want to get this back from the query:
+-----------+------------+
| school_id | login_days |
+-----------+------------+
| 1 | 5 |
| 2 | 4 |
+-----------+------------+
I can't quite figure out how to do the query. I started off with this (i have the id < 11 part in there just to get my example data instead of my entire table of 3.5 million rows):
mysql> select school_id from sign_ins where id < 11 group by school_id, user_id, date(created_at);
+-----------+
| school_id |
+-----------+
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 2 |
| 2 |
| 2 |
+-----------+
8 rows in set (0.00 sec)
I can see in here that there are 5 rows for school 1 and 3 for school 2, which looks like it's worked. But i need to group that further, and order by that grouped number, to get it like in my required results. It must be something simple, can someone show me what i'm missing?
thanks, Max
MySQL allows you to count the number of distinct values for multiple expressions. So, this is basically an aggregation query with the appropriate count:
select school_id, count(distinct user_id, date(created_at)) as NumLoginDays
from sign_ins
group by school_id;

MySQL query SELECT FROM 2 tables, COUNT the most used

I have this 2 tables and I need to return the moset used office. Note: 1 office can be used by more than 1 guys and the column ido from TableB is populate from TableA
Probaly is a query with group by and desc limit 1
TableA
| ido| office | guy |
---------------------
| 1 | office1| guy1|
| 2 | office2| guy2|
| 3 | office1| guy3|
| 4 | office1| guy4|
| 5 | office5| guy5|
| 6 | office2| guy6|
TableB
| idb| vizit | ido|
---------------------
| 1 | date | 4 |
| 2 | date | 2 |
| 3 | date | 5 |
| 4 | date | 6 |
| 5 | date | 1 |
| 6 | date | 6 |
Thanks!
You were correct in that GROUP BY, LIMIT and DESC are useful here; it leads to a fairly straight forward query;
SELECT TableA.office
FROM TableA
JOIN TableB
ON TableA.ido = TableB.ido
GROUP BY TableA.office
ORDER BY COUNT(*) DESC
LIMIT 1
What it does is basically create rows with all valid combinations, counting the number of generated rows per office. A plain descending sort by that count will give you the most frequently used office.
An SQLfiddle to test with.

How to form a MySQL query with 2 different ORDER BY statements

I have a sort of Facebook-esque Like/Dislike system on my website and am using the following query to grab the likes + dislikes of a specific post:
SELECT DISTINCT * FROM posts WHERE cid='$cid' AND pid=".implode(" OR pid=",$pids)." ORDER BY time DESC
$pid is an array of post ids to be searching for.
zbid is the id of the user who is currently accessing the page. Now, if that user has rated (liked/disliked) the post his result should be returned first, and then after that order the results by time DESC.
How would I go about modifying the query to do this?
If the posts table has the following data:
+----+-----+------+-----+--------+------+
| id | cid | zbid | pid | rating | time |
+----+-----+------+-----+--------+------+
| 1 | 1 | 1 | 1 | 1 | 1 |
+----+-----+------+-----+--------+------+
| 2 | 1 | 2 | 1 | -1 | 4 |
+----+-----+------+-----+--------+------+
| 3 | 1 | 3 | 2 | 1 | 2 |
+----+-----+------+-----+--------+------+
| 4 | 2 | 4 | 1 | -1 | 3 |
+----+-----+------+-----+--------+------+
| 5 | 1 | 5 | 1 | 1 | 8 |
+----+-----+------+-----+--------+------+
| 6 | 1 | 6 | 1 | -1 | 7 |
+----+-----+------+-----+--------+------+
The current select statement (above) will return the following information (with $pid = array(1);):
+----+-----+------+-----+--------+------+
| id | cid | zbid | pid | rating | time |
+----+-----+------+-----+--------+------+
| 5 | 1 | 5 | 1 | 1 | 8 |
+----+-----+------+-----+--------+------+
| 6 | 1 | 6 | 1 | -1 | 7 |
+----+-----+------+-----+--------+------+
| 2 | 1 | 2 | 1 | -1 | 4 |
+----+-----+------+-----+--------+------+
| 4 | 2 | 4 | 1 | -1 | 3 |
+----+-----+------+-----+--------+------+
| 1 | 1 | 1 | 1 | 1 | 1 |
+----+-----+------+-----+--------+------+
However, if the person with zbid=4 is accessing the page, it should bump that result (if it exists) up to the top as below:
+----+-----+------+-----+--------+------+
| id | cid | zbid | pid | rating | time |
+----+-----+------+-----+--------+------+
| 4 | 2 | 4 | 1 | -1 | 3 |
+----+-----+------+-----+--------+------+
| 5 | 1 | 5 | 1 | 1 | 8 |
+----+-----+------+-----+--------+------+
| 6 | 1 | 6 | 1 | -1 | 7 |
+----+-----+------+-----+--------+------+
| 2 | 1 | 2 | 1 | -1 | 4 |
+----+-----+------+-----+--------+------+
| 1 | 1 | 1 | 1 | 1 | 1 |
+----+-----+------+-----+--------+------+
The variable $zbid is set to the user's zbid who is accessing the page.
This is a rather rough solution I could come out with, using the information you provided:
Solution 1 - The portable way
-- This query will return User's posts and give it a higher priority in ordering, via post_order field
SELECT
posts.*
,0 as post_order
FROM posts
WHERE
(cid='$cid' AND pid=".implode(" OR pid=",$pids).") AND
(zbid = $user_zbid)
UNION ALL
-- This query will return everything BUT User's posts and give it a lower priority in ordering
SELECT
posts.*
,1 as post_order
FROM posts
WHERE
(cid='$cid' AND pid=".implode(" OR pid=",$pids).") AND
(zbid <> $user_zbid)
ORDER BY
post_order -- This clause will put User's posts before the others
,time DESC
Solution 2 - The more performing way (credits to cbranch for the suggestion)
SELECT
posts.*
,IF(zbid = $user_zbid, 0, 1) as post_order
FROM posts
WHERE
(cid='$cid' AND pid=".implode(" OR pid=",$pids).")
ORDER BY
post_order -- This clause will put User's posts before the others
,time DESC
Notes
- As you may have noticed, I removed the DISTINCT from the SELECT, as I couldn't see a reason for them. Since you just extract data from a single table, you shouldn't have duplicates. Obviously, you can still add them back, but remember not to use such clause unless it's really needed.
- The second query will be very expensive to run, as it uses the "not equal to" clauses. This means it won't be using indexes, and it won't be suitable for big amounts of data. In case you have to deal with a big table, this solution will have to be reviewed.
After reviewing Diego's suggestion I came up with the following answer that has worked:
SELECT zbid,pid,rating,0 as post_order,time
FROM posts
WHERE cid='$cid'
AND (pid=".implode(" OR pid=",$pids).")
AND zbid!='$zbid'
UNION
SELECT zbid,pid,rating,1 as post_order,time
FROM posts
WHERE cid='$cid'
AND (pid=".implode(" OR pid=",$pids).")
AND zbid='$zbid'
ORDER BY
post_order DESC,
time DESC