MySQL : SELECT all USERS With 2 messages open and clicked after timestamp - php

I'm making a MySQL script executed in PHP.
So I have 3 tables.
Messages who contains at least 3 000 000 rows
(userid, messageid, timestamp, received, opened, clicked, deliveryid)
Users
(user(unique), profile, profile_actual_timestamp, last_delivery_id_sent)
events_clicked_data who contains at least 2 000 000 rows and detailed clicked event. This user click on this profile(like GAMES) on this message(deliveryId)
(userid, profile, deliveryId, eventDate)
So here is sample data for messages and users tables.
CREATE TABLE messages
(`user_id` varchar(100), `message_id` int, `timestamp` datetime, `received` varchar(5), `opened` varchar(5), `clicked` varchar(5), `delivery_id` int);
INSERT INTO messages
(`user_id`, `message_id`, `timestamp`, `received`, `opened`, `clicked`, `delivery_id`)
VALUES
("BillyStuff", 12,'2016-05-16 00:00:00', 'true', 'true', 'true', 8),
("BillyStuff", 11,'2016-05-14 00:00:00', 'true', 'true', 'true' , 7),
("BillyStuff", 8,'2016-04-03 00:00:00', 'true', 'false', 'false' , 6),
("BillyStuff", 4,'2016-04-02 00:00:00', 'true', 'false', 'false', 5),
("JohnDoe", 15 ,'2016-05-16 00:00:00', 'true', 'true', 'false' , 4),
("JohnDoe", 13 ,'2016-05-14 00:00:00', 'true', 'true', 'true', 3),
("Donnie", 15 ,'2016-05-16 00:00:00', 'true', 'true', 'true' , 4),
("Donnie", 13 ,'2016-05-14 00:00:00', 'true', 'true', 'true', 3)
CREATE TABLE users
(`user_id` varchar(100), `profile` varchar(100), `profile_actual_timestamp` datetime, `last_delivery_id_sent` int);
INSERT INTO users
(`user_id`, `profile`, `profile_actual_timestamp`, `last_delivery_id_sent`)
VALUES
("BillyStuff", "Game", "2016-01-01 00:00:00", 1),
("JohnDoe", "Book", "2016-01-01 00:00:00", 1),
("Donnie", "Book", "2016-05-16 00:00:00", 4)
I want to get users with 2 messages clicked after timestamp (profile_actual_timestamp means last time it was updated) in profile.
In this case I only get BillyStuff because Donnie is already up to date if I check if profile_actual_timestamp.
After this, I need to check by deliveryId and user in events_clicked's table if same categories was clicked.
CREATE TABLE events_clicked_data
(`user_id` varchar(100), `profile` varchar(100), `deliveryId` int, `eventDate` datetime);
INSERT INTO users
(`user_id`, `profile`, `deliveryId`, `eventDate`) VALUES
("BillyStuff", "Book", 8,"2016-01-01 00:00:00"),
("BillyStuff", "Book", 7,"2016-01-01 00:00:00"),
("JohnDoe", "Book", 3,"2016-01-01 00:00:00"),
("Donnie", "Book", 4,"2016-05-16 00:00:00"),
("Donnie", "Game", 3,"2016-05-16 00:00:00")
In this case, i need to update BillyStuff's profile and change it to "Book" instead of "Game" because he clicked on the same categorie twice in his last messages after the last time he was updated (profile_actual_timestamp)
So its been a really good puzzle for me this week and Im wondering if you guys can help me with this one.
originalid = userid (not necessary original, depend on table)
e.name = name of profil clicked like game.
select originalid,
name
from (
select #g := if(#u = originalid, if (#p = name, #g, #g + 1), 1) as grp,
#u := originalid as originalid,
#p := name as name
from (
select u.originalid,
m.message_sendtime_timestamp,
e.name
from bienvenue_nouveau_client_dev u
inner join messages_nouveaux_clients m
on m.originalid = u.originalid
inner join events_clicked_data e
on e.originalId = u.originalid
and e.deliveryId = m.deliveryId
where m.message_sendtime_timestamp >= u.profil_actuel_timestamp
and m.clicked = 'TRUE'
limit 1000000000000000
order by u.originalid,
m.message_sendtime_timestamp desc
) alias
) alias2
where grp = 1
group by originalid, name
having count(*) > 1
Whatever I change I got an error like this:
1250 - Table 'u' from one of the SELECTs cannot be used in global ORDER clause

The first query can be done as follows:
select u.*
from users u
inner join messages m
on m.user_id = u.user_id
where m.timestamp >= u.profile_actual_timestamp
and m.clicked = 'true'
group by u.user_id
having count(m.message_id) > 1
This second query will give you the users that used the same profile in their last two click events, if these events happened both later than the last update:
select user_id,
profile
from (
select #g := 0 + if(#u = user_id, if (#p = profile, #g, #g + 1), 1) as grp,
#u := user_id as user_id,
#p := profile as profile
from (
select u.user_id,
m.timestamp,
e.profile
from users u
inner join messages m
on m.user_id = u.user_id
left join events_clicked_data e
on e.user_id = u.user_id
and e.deliveryId = m.delivery_id
where m.timestamp >= u.profile_actual_timestamp
order by u.user_id,
m.timestamp desc
) alias
) alias2
where grp = 1
group by user_id, profile
having count(*) > 1
SQL fiddle
This query depends on variables, and is a bit risky, in that it must return the innermost results in the specified order, and that it must evaluate the middle select list (with variable assignments) in top-to-bottom order. This happens consistently, but in theory it is not guaranteed.
The (high) limit in the inner query is there to make sure the order by clause is applied, not to actually limit anything.
The variables #u and #p track the user_id and profile. Based on their previous values, the variable #g is calculated: it is reset to 1 whenever the user_id changed. Otherwise it is kept at the same value if also the profile didn't change, and it is incremented when the profile did change.
This way the grp values identify groups in which the profile is the same and uninterrupted in order of descending timestamp. The most recent group per used has number 1, which is the only one of interest in the outer query. The outer query then requires that this group 1 has more than one record (i.e. more than one occurrence of the same profile).

Related

Not able to get distinct and max of the third table join in MySql

Schemas
// First table
CREATE TABLE assignments (
id int,
uid int,
comments varchar(255),
assignmentdate date,
status int
);
INSERT INTO assignments (id, uid, comments, assignmentdate, status)
values (1, 6, 'a', '2019-07-15', 0), (2, 6, 'ab', '2019-07-15', 0),
(3, 6, 'abc', '2019-07-14', 0), (4, 6, 'abc', '2019-07-14', 1)
, (5, 7, 'xyz', '2019-07-14', 1), (6, 7, 'zyx', '2019-07-14', 1);
// Second table
CREATE TABLE users (
id int,
username varchar(255),
status int
);
INSERT INTO users (id, username, status)
values (6, 'user1', 0), (7, 'user2', 0),
(8, 'user3', 1);
// Third table
CREATE TABLE user_images (
id int,
uid int,
imagename varchar(255),
status int
);
INSERT INTO user_images (id, uid, imagename, status)
values (1, 6, 'abc.jpeg', 0), (2, 6, 'def.jpeg', 0), (3, 8, 'ghi.png', 1);
what I'm looking for here is to get
1) distinct and latest row of table assignments which,
2) joins the table users and get a row and then joins,
3) distinct and latest row of table user_images.
So far i have gone through this answer
My trial query:
SELECT
p.*,
u.username,
groupedpi.*
FROM
assignments p
INNER JOIN(
SELECT
comments,
MAX(id) AS latest
FROM
assignments
WHERE
STATUS
= 0
GROUP BY
uid
) AS groupedp
ON
groupedp.latest = p.id
LEFT JOIN users u ON
p.uid = u.id AND u.status = 0
LEFT JOIN(
SELECT
uid,
MAX(id) AS latesti,
imagename
FROM
user_images us
WHERE
STATUS = 0
GROUP BY
uid
order by id desc LIMIT 1
) AS groupedpi
ON
groupedpi.uid = p.uid
Output:
The 3rd result I'm not getting, i.e I'm not getting the distinct and latest record of the third table while joining.
Instead of abc.jpeg, I want to get def.jpeg.
MySQL is tripping you up here, because it automatically adds columns to GROUP BY if they aren't specified, so it's grouping the groupedpi subquery on imagename too - this will lead to duplicated rows. Remove the imagename column from the subquery (and the order by clause is irrelevant too) and have it just output the userid and the max image id
If you want the image name, join the images table in again on images.id = groupedpi.latesti (In the main query not the subquery that is finding the latest image id)
(Note that your screenshot says lastesti 2 but imagename abc- it's not the right pairing. ID 2 is def.jpg. When you want latest Id but also other data from the same row you can't do it in one hit unless you use an analytic (mysql8+) - you have to write a subquery that finds the max id and then join it back to the same table to get the rest of the data off that row)

Echo SQL data in order of date added using Union All

So I am working with a client to implement a similar system as the "badges and privileges system" on StackExchange. Although in her system, she is looking to use points and rewards for her staff. It's the same basic principle. The users are rewarded points for good team work and gain rewards from these points. I thought it would be handy to add the same kind of feature which SE uses to display these in the top nav bar, where it shows your rep and badges in order of the date you have earned either of them. This is my issue, I have found help retrieving the data together from the two separate tables but am not sure how I would display these results in order of date earned? As an example:
User ID #1 has earned 50 points on 18/12/2015 would be in ap_user_points table
User ID #1 has earned 'The Gift Voucher' reward on '17/12/2015'
If I simply:
echo $row8['reward'] . $row8['points_added']
It would echo as:
The Gift Voucher 50
Where I need it in order by date as:
50
The Gift Voucher
If you look at your rep and badge icon in the nav bar you'll see what I'm getting at here, it's a similar system.
<?php
$user_id = $_SESSION['userid'];
$sql8 = "
SELECT r.reward_id,
r.user_id,
r.reward as reward,
r.date_earned as date_earned,
r.badge_desc,
NULL AS points_added,
NULL AS added_for,
NULL AS date_added
FROM ap_user_rewards as r
WHERE r.user_id = '$user_id'
UNION ALL
SELECT NULL,
NULL,
NULL,
NULL,
NULL,
p.points_added AS points_added,
p.added_for AS added_for,
p.date_added AS date_added
FROM ap_user_points as p
WHERE p.user_id = '$user_id' ORDER BY date_earned DESC, date_added DESC;";
$result8 = $conn->query($sql8);
if ($result8->num_rows > 0) {
// output data of each row
while($row8 = $result8->fetch_assoc()) {
////// NOT SURE WHAT TO ECHO HERE?
}
}
?>
Add another column to the result set. In that new column, populate it from both queries... looks like it would be the date_added expression in the first query and the date_earned expression in the second query. When those are in the same column, then ordering is easy. (This also assumes that these expressions are of the same or compatible datatypes, preferably DATE, DATETIME or TIMESTAMP.)
Then you can order by ordinal position, e.g. ORDER BY 2 to order by the second column in the resultset.
SELECT a1
, b1
, NULL
, NULL
, a1 AS sortexpr
FROM ...
UNION ALL
SELECT NULL
, NULL
, x2
, y2
, x2 AS sortexpr
FROM ...
ORDER BY 5 DESC
That's just one possibility. If you can't add an extra column, to line up the expressions from the two queries, then you need a way to discriminate which query is returning the row. I typically include a literal as a discriminator column.
Then you can use implicit-style UNION syntax, wrapping the queries in parens...
( SELECT 'q1' AS `source`
, a1
, b1 AS date_earned
, NULL
, NULL AS date_added
FROM ...
)
UNION ALL
( SELECT 'q2' AS `source`
, NULL
, NULL AS date_earned
, x2
, y2 AS date_added
FROM ...
)
ORDER BY IF(`source`='q1',date_earned,date_added) DESC
Followup
I may have misunderstood the question. I though the question was how to get the rows from a UNION/UNION ALL returned in a particular order.
Personally, I would write the query to include a discriminator column, and then line up the columns as much as I could, so they would be processed the same.
As an example:
SELECT 'reward' AS `source`
, r.date_earned AS `seq`
, r.user_id AS `user_id`
, r.date_earned AS `date_earned`
, r.reward_id
, r.reward
, r.badge_desc
, NULL AS `points_added`
, NULL AS `added_for`
FROM r ...
UNION ALL
SELECT 'points' AS `source`
, p.date_added AS `seq`
, p.user_id AS `user_id`
, p.date_added AS `date_earned`
, NULL
, NULL
, NULL
, p.points_added AS `points_added`
, p.added_for AS `added_for`
FROM p ...
ORDER BY 2 DESC, 1 DESC
(It's probably not really necessary to return user_id, since we already know what the value will be. I've returned it here to demonstrate how the columns from the two resultsets can be "lined up".)
Then, when I fetched the rows...
if ( $row8['source'] == 'points' ) {
# process columns from a row of 'points' type
echo $row8['badge_desc'];
echo $row8['user_id'];
} elsif ( $row8['source'] == 'reward' ) {
# process columns from a row of 'reward' type
echo $row8['added_for'];
echo $row8['user_id'];
}
That's how I would do it.

How to do Custom Order in Mysql

Below is my column
1-1
1-2
2-1
2-4
Is It possible to sort in this way - First Column should be be ascending and Second column should be descending
So the result will be
1-4
1-2
2-1
2-1
I don't care if the row mismatch. How can I achieve this (Something like view)
CREATE TABLE IF NOT EXISTS `pipo_orders` (
`ClientID` int(8) NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` timestamp NOT NULL DEFAULT ,
);
--
-- Dumping data for table `pipo_orders`
--
INSERT INTO `pipo_orders` (`ClientID`, `created_at`, `updated_at`) VALUES
(17, '2014-11-26 16:21:36', '2014-11-26 10:51:36'),
(17, '2014-11-26 16:46:24', '2014-11-26 11:16:24'),
(17, '2014-12-04 16:45:28', '2014-12-04 11:15:28'),
(17, '2014-12-26 12:58:13', '2014-12-26 07:28:13'),
(17, '2014-12-30 14:29:31', '2014-12-30 08:59:31'),
(17, '2014-12-30 14:31:05', '2014-12-30 09:01:05'),
(17, '2015-01-02 12:20:54', '2015-01-02 06:50:54');
If this is what you want it's weird, and I can't think of a reason why you'd want it. In fact, it goes against the fundamental logic of a database...
Nevertheless:
SELECT t1.ClientID
, t1.created_at
, t2.updated_at
FROM pipo_orders t1
JOIN pipo_orders t2
ON 1 = 1
ORDER BY t1.created_at ASC
, t2.update_at DESC
That looks like a horrible thing to do. The values created_at and updated_at are related by being in the same row. You override this relation and join completely unrelated values.
However to technically do this, you would have to order the two columns separately (giving them row numbers) and then join them again:
select
col1.created_at,
col2.updated_at
from
(
select created_at, #rn1 := #rn1 + 1 as rownum
from pipo_orders po
cross join (select #rn1 := 0) as r
order by created_at
) as col1
from
(
select updated_at, #rn2 := #rn2 + 1 as rownum
from pipo_orders po
cross join (select #rn2 := 0) as r
order by updated_at desc
) as col2 using (rownum)
order by rownum;

MySQL query for selecting a maximum element

I have a table with 4 columns: place_id, username, counter, last_checkin
I'm writing a check-in based system and I'm trying to get a query that will give me the "mayor" of each place. The mayor is the one with most check-ins, and if there is more than 1 than the minimum last_checkin wins.
For example, if I have:
place_id, username, counter, last_checkin
123, tom, 3 , 13/4/10
123, jill, 3, 14/4/10
365, bob, 2, 15/4/10
365, alice, 1, 13/4/10
I want the result to be:
123, tom
365, bob
I'm using it in PHP code
Here is the test data:
CREATE TABLE `my_table` ( `place_id` int(11), `username` varchar(50), `counter` int(11), `last_checkin` date);
INSERT INTO `my_table` VALUES (123,'tom',3,'2010-04-13'),(123,'jill',3,'2010-04-14'),(365,'bob',2,'2010-04-15'),(365,'alice',1,'2010-04-13');
How about..
SELECT
place_id,
(SELECT username
FROM my_table MT2
WHERE MT2.place_id = MT1.place_id
ORDER BY counter DESC, last_checkin ASC
LIMIT 1) AS mayor
FROM my_table MT1
GROUP BY place_id;
Edited as Unreason suggests to have ascending order for last_checkin.
Brian's correlated query is something I would write. However I found this different take and it might perform differently depending on the data
SELECT
mt1.place_id,
mt1.username
FROM
my_table mt1 LEFT JOIN my_table mt2
ON mt1.place_id = mt2.place_id AND
(mt1.counter < mt2.counter OR
(mt1.counter = mt2.counter AND mt1.last_checkin > mt2.last_checkin)
)
WHERE
mt2.place_id IS NULL
Which uses left join to get to the top records according to certain conditions.
$data = query("SELECT max(counter) counter,username FROM table GROUP By place_id ORDER By last_checkin DESC");

MySQL: Use CASE/ELSE value as join parameter

I'm trying to join the NAME and PHOTO from USERS table to the TRANSACTIONS table based on who is the payer or payee. It keeps telling me can't find the table this -- What am I doing wrong?
SELECT `name`,`photo`,`amount`,`comment`,
(
CASE `payer_id`
WHEN 72823 THEN `payee_id`
ELSE `payer_id`
END
) AS `this`
FROM `transactions`
RIGHT JOIN `users` ON (`users`.`id`=`this`)
WHERE `payee_id`=72823 OR `payer_id`=72823
From the documentation about aliases:
The alias is used as the expression's column name and can be used in GROUP BY, ORDER BY, or HAVING clauses.
You can't use an alias in a join. You can use it only in the places listed above. The reason is that the alias is on a field in the result of the join. If the join were allowed to these aliases in its definition it would (or could) result in recursive definitions.
To solve your problem you could repeat the CASE clause in both places:
SELECT `name`,`photo`,`amount`,`comment`,
(
CASE `payer_id`
WHEN 72823 THEN `payee_id`
ELSE `payer_id`
END
) AS `this`
FROM `transactions`
RIGHT JOIN `users` ON `users`.`id`= (
CASE `payer_id`
WHEN 72823 THEN `payee_id`
ELSE `payer_id`
END
)
WHERE `payee_id`=72823 OR `payer_id`=72823
However I would probably rewrite this query as two selects and UNION them:
SELECT name, photo, amount, comment, payer_id AS this
FROM transactions
JOIN users ON users.id = payer_id
WHERE payee_id = 72823
UNION ALL
SELECT name, photo, amount, comment, payee_id AS this
FROM transactions
JOIN users ON users.id = payee_id
WHERE payer_id = 72823
Result:
'name3', 'photo3', 30, 'comment3', 3
'name1', 'photo1', 10, 'comment1', 1
'name2', 'photo2', 20, 'comment2', 2
Test data:
CREATE TABLE users (id INT NOT NULL, name NVARCHAR(100) NOT NULL, photo NVARCHAR(100) NOT NULL);
INSERT INTO users (id, name, photo) VALUES
(1, 'name1', 'photo1'),
(2, 'name2', 'photo2'),
(3, 'name3', 'photo3'),
(4, 'name4', 'photo4');
CREATE TABLE transactions (amount INT NOT NULL, comment NVARCHAR(100) NOT NULL, payer_id INT NOT NULL, payee_id INT NOT NULL);
INSERT INTO transactions (amount, comment, payer_id, payee_id) VALUES
(10, 'comment1', 72823, 1),
(20, 'comment2', 72823, 2),
(30, 'comment3', 3, 72823),
(40, 'comment4', 4, 5);
SELECT
th.id, th.coin_id, th.coin_family, cm.coin_id, cm.current_price
FROM
trnx_history th
JOIN
fmi_coins.coins_markets cm
ON
cm.coin_id=(CASE th.coin_family WHEN 1 THEN 1 ELSE 2 END)

Categories