MySQL trigger with join - mysql

I have two tables, a logins table which captures login information, and a ranges table which associates IP info with countries. I am trying to create a trigger which updates the logins.country column after an insert by performing a join to the ranges table.
The tables are structured as such:
CREATE TABLE logins (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
users_id int(10) unsigned NOT NULL,
ip int(10) unsigned NOT NULL,
country varchar(2) DEFAULT NULL,
status tinyint(4) NOT NULL,
timestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE ranges (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
first int(10) unsigned NOT NULL,
last int(10) unsigned NOT NULL,
country varchar(2) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY first_UNIQUE (first),
UNIQUE KEY last_UNIQUE (last)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
I have this trigger but it doesn't seem to work:
CREATE TRIGGER update_country
AFTER INSERT ON logins
FOR EACH ROW
UPDATE logins l
JOIN ranges r ON l.ip >= first AND l.ip <= last
SET l.country = r.country
WHERE l.id = NEW.id;
Any idea where I'm going wrong here?

After playing around with this for a while, I realized a join wasn't necessary and I was making this far more complex than it needed to be. Here is the final trigger:
CREATE TRIGGER update_country BEFORE INSERT
ON logins FOR EACH ROW
SET NEW.country = (SELECT country FROM ranges WHERE NEW.ip >= first AND NEW.ip <= last);

Related

Conversations belong to multiple users, but user A deletes and user B doesn't. How we prevent this from being returned?

I'm building a chat feature in our application. The basic chat works, we had a query to get the conversations belonging to a user, get a conversation, messages, etc.
Now, we want to add a feature where a participant of a conversation (conversations can have multiple participants) can delete a chat on their end, but this won't delete the conversation in the server. Instead, we will mark the conversation as deleted from X point for that user. In that case, when a participant that deleted a conversation, requests in our API for the conversation again, he won't see messages prior to his deletion.
To understand the concept clearly, it's the same way WhatsApp, Telegram or most of the chats applications work nowadays. When having user A and B are interacting, if user B chooses to delete the conversation from his phone, user A will still see the whole conversation. If user B (or A), texts again in the conversation, user B will only see the new texts.
I'm not entirely sure how this works on their ends, but the structure that seems to work for us is the following one:
CREATE TABLE `conversations` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`starter_id` bigint(20) unsigned NOT NULL,
`last_message_id` bigint(20) unsigned DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `conversations_starter_id_index` (`starter_id`),
KEY `conversations_last_message_id_index` (`last_message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `conversation_participants` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`conversation_id` bigint(20) unsigned NOT NULL,
`participant_id` bigint(20) unsigned NOT NULL,
`deleted_from_id` bigint(20) unsigned DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `conversation_participants_conversation_id_index` (`conversation_id`),
KEY `conversation_participants_participant_id_index` (`participant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `conversation_messages` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`conversation_id` bigint(20) unsigned NOT NULL,
`sender_id` bigint(20) unsigned NOT NULL,
`message` text COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `conversation_messages_conversation_id_index` (`conversation_id`),
KEY `conversation_messages_sender_id_index` (`sender_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
As you see in conversation_participants we've added a deleted_from_id. This deleted_from_id will get updated when user B sends a request to the server to delete the conversation. It will send the conversation_id and the latest conversation_message_id he has seen and update accordingly.
We use Laravel as our framework and Eloquent to easily generate queries with relationships. We have an endpoint that requests the latest 25 conversations for a user, and then we paginate them. This is the query being generated for this type of query:
select
`conversations`.*,
`conversation_participants`.`participant_id` as `pivot_participant_id`,
`conversation_participants`.`conversation_id` as `pivot_conversation_id`,
`conversation_participants`.`created_at` as `pivot_created_at`,
`conversation_participants`.`updated_at` as `pivot_updated_at`
from
`conversations`
inner join `conversation_participants` on `conversations`.`id` = `conversation_participants`.`conversation_id`
where
`conversation_participants`.`participant_id` = 1
and exists (
select
*
from
`conversation_messages`
where
`conversations`.`id` = `conversation_messages`.`conversation_id`
)
order by
`id` desc
The query above is pretty simple, it returns the conversations for a specific user. Using Laravel's Conversation::with('messages')..., it allows us to easily filter conversations that have messages (we don't want empty conversations being returned).
The problem is that we tried to filter more and prevent conversations that the user has deleted on his phone to be shown in that query. We haven't found a way.
Our first guess was to simply add tweak the exists() limiting the conversation_messages.id, like:
select
`conversations`.*,
`conversation_participants`.`participant_id` as `pivot_participant_id`,
`conversation_participants`.`conversation_id` as `pivot_conversation_id`,
`conversation_participants`.`deleted_from_id` as `pivot_deleted_from_id`,
`conversation_participants`.`created_at` as `pivot_created_at`,
`conversation_participants`.`updated_at` as `pivot_updated_at`
from
`conversations`
inner join `conversation_participants` on `conversations`.`id` = `conversation_participants`.`conversation_id`
where
`conversation_participants`.`participant_id` = 1
and exists (
select
*
from
`conversation_messages`
where
`conversations`.`id` = `conversation_messages`.`conversation_id`
and `id` > conversation_participants.deleted_from_id
)
order by
`id` desc
This will "work", but if the user has deleted a message and, let's say, there are conversations with messages older than the andid> conversation_participants.deleted_from_id, no other conversations will be returned. That's wrong, as it prevents any other conversation to shown even when they have messages and belong to the participant.
We also tried a different approach, using some joins in the exists() to try to prevent having the "deleted conversation" to show on the list:
select
`conversations`.*,
`conversation_participants`.`participant_id` as `pivot_participant_id`,
`conversation_participants`.`conversation_id` as `pivot_conversation_id`,
`conversation_participants`.`deleted_from_id` as `pivot_deleted_from_id`,
`conversation_participants`.`created_at` as `pivot_created_at`,
`conversation_participants`.`updated_at` as `pivot_updated_at`
from
`conversations`
inner join `conversation_participants` on `conversations`.`id` = `conversation_participants`.`conversation_id`
where
`conversation_participants`.`participant_id` = 1
and exists (
select
`conversation_messages`.*
from
`conversation_messages`
join
`conversations` on `conversations`.`id` = `conversation_messages`.`conversation_id`
join
`conversation_participants` on `conversations`.`id` = `conversation_participants`.`conversation_id`
where
`conversations`.`id` = `conversation_messages`.`conversation_id`
and `conversation_participants`.`participant_id` = 1
and `conversation_messages`.`id` > `conversation_participants`.`deleted_from_id`
)
order by
`id` desc
But unfortunately, that didn't work either.
To make testing more convenient, I've set up a DB Fiddle here: https://www.db-fiddle.com/f/q6S3GfZNCbvYbtvRwXJxN7/0
This fiddle has multiple users, multiple conversations and multiple messages. As you see, if you run the query right away it will return 28 conversations that belong to the given participant.
That said if you notice in the table conversation_participants, there is a row for participant_id=1 that on conversation_id=82 the delete message is the 82: (55,28,1,82,'2020-01-31 10:01:08','2020-01-31 10:01:08'), (line 166 in the fiddle). The message 82 is the last message in the conversation_id=28, therefore, it should not show up in the query as it has no messages.
In our effort to find a solution, we also thought that maybe having the row conversations.last_message_id would be helpful to prevent conversations to show up... but we aren't sure about this either as we couldn't find a solution. I've decided to leave it in the SQL in case it's useful to find a solution.
How can I get the desired results? What I'm missing out?
Thanks in advance
Here is what I would do.
Conversation contains Messages
Messages contain the participants.
I would not keep "Conversation_Participants" as seen in your case.
Instead, I would keep "Message_Participants".
This would be my table structure
CREATE TABLE `conversations` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`starter_id` bigint(20) unsigned NOT NULL,
`last_message_id` bigint(20) unsigned DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `conversations_starter_id_index` (`starter_id`),
KEY `conversations_last_message_id_index` (`last_message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `conversation_messages` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`conversation_id` bigint(20) unsigned NOT NULL,
`sender_id` bigint(20) unsigned NOT NULL,
`message` text COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `conversation_messages_conversation_id_index` (`conversation_id`),
KEY `conversation_messages_sender_id_index` (`sender_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `message_participants` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`message_id` bigint(20) unsigned NOT NULL,
`participant_id` bigint(20) unsigned NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
)
I have omitted foreign key creations, you can add as required.
Now, whenever a message arrives in a conversation which has "n" participants, "n" number of entries would be made in message_participants table.
So, when a participant deletes a message or group of messages from his chat, you can delete corresponding entries in the table "message_participants" pertaining to that message and participant.
This way, a participant can delete any message from any chat in any order. In your logic you have mentioned "last_message_id". This will limit the user to not access messages beyond a particular id, but with my logic, he can keep few messages from 2014 and then delete everything after that until 2018, then keep 2 months chat from 2018 and delete the rest.
I hope you got the point and hope it helps.

mySQL trigger fails on UPDATE statement

I have two tables and one trigger. The trigger fails on the UPDATE on table sensors. I have tested the trigger updating another table and that just works fine so I expect this to be a problem with locking on sensors. I'm certainly not an expert on mySQL and I did some searching. I have tried to add SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; before the first SELECT in the trigger but that did not make any difference.
Table measurements:
CREATE TABLE `measurements` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`sensorid` int(16) DEFAULT NULL,
`ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`distance` int(11) NOT NULL,
`temperature` float DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=26727 DEFAULT CHARSET=latin1;
Table sensors:
CREATE TABLE `sensors` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` char(32) DEFAULT '',
`zeropoint` int(11) unsigned DEFAULT NULL,
`threshold` int(11) unsigned DEFAULT NULL,
`hysteresis` int(11) DEFAULT NULL,
`status` enum('normal','alarm') DEFAULT 'normal',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
Trigger raise alarm:
CREATE TRIGGER `raise alarm` BEFORE INSERT ON `measurements`
FOR EACH ROW
begin
declare zp integer;
declare st char(32);
select zeropoint into zp from sensors where id = new.sensorid;
select status into st from sensors where id = new.sensorid;
if new.distance > zp then
if st = 'normal' then
update sensors set status = 'alarm' where id = new.sensorid;
end if;
end if;
end;
There is something in the documentation that you might be interested in:
https://dev.mysql.com/doc/refman/5.7/en/trigger-syntax.html
In a BEFORE trigger, the NEW value for an AUTO_INCREMENT column is 0,
not the sequence number that is generated automatically when the new
row actually is inserted.
It means, that all queries in your trigger are always looking for a record with id=0, since id column in measurement table is auto-increment.
In case there is no record id=0 in sensors table, then zp variable is null, and this condition: if new.distance > zp then is always false.

i get wrong results from mysql join query

I have 2 tables, that i want to join, one is rooms and another is reservations.
Basically I want to search for rooms which are not reserved (not in reservation table) and to get the details of those rooms (which are not in reservation table) from room table.
Here are my tables structure:
CREATE TABLE `room` (
`roomID` int(11) NOT NULL AUTO_INCREMENT,
`hotelID` int(11) NOT NULL,
`roomtypeID` int(11) NOT NULL,
`roomNumber` int(11) NOT NULL,
`roomName` varchar(255) NOT NULL,
`roomName_en` varchar(255) NOT NULL,
`roomDescription` text,
`roomDescription_en` text,
`roomSorder` int(11) NOT NULL,
`roomVisible` tinyint(4) NOT NULL,
PRIMARY KEY (`roomID`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8;
CREATE TABLE `reservation` (
`reservationID` int(11) NOT NULL AUTO_INCREMENT,
`customerID` int(11) NOT NULL,
`hotelID` int(11) NOT NULL,
`reservationCreatedOn` datetime NOT NULL,
`reservationCreatedFromIp` varchar(255) CHARACTER SET greek NOT NULL,
`reservationNumberOfAdults` tinyint(4) NOT NULL,
`reservationNumberOfChildrens` tinyint(4) NOT NULL,
`reservationArrivalDate` date NOT NULL,
`reservationDepartureDate` date NOT NULL,
`reservationCustomerComment` text CHARACTER SET greek,
PRIMARY KEY (`reservationID`)
) ENGINE=InnoDB AUTO_INCREMENT=47 DEFAULT CHARSET=utf8;
CREATE TABLE `reservationroom` (
`reservationroomID` int(11) NOT NULL AUTO_INCREMENT,
`reservationID` int(11) NOT NULL,
`hotelID` int(11) NOT NULL,
`roomID` int(11) NOT NULL,
PRIMARY KEY (`reservationroomID`)
) ENGINE=InnoDB AUTO_INCREMENT=47 DEFAULT CHARSET=utf8;
Here is the query that I have right now, which gives me wrong results:
SELECT * FROM room r
LEFT JOIN reservation re
ON r.hotelID = re.hotelID
WHERE re.hotelID = 13
AND NOT
(re.reservationArrivalDate >= '2014-07-07' AND re.reservationDepartureDate <= '2014-07-13')
I also have created a fiddle, with the data from both tables included:
http://sqlfiddle.com/#!2/4bb9ea/1
Any help will be deeply appreciated
Regards, John
i agree that room number was missed,
but query template should looks like
SELECT
*
FROM
room r
LEFT JOIN reservation re
ON r.hotelID = re.hotelID
WHERE r.hotelID = 2
AND NOT (
re.hotelID IS NOT NULL
AND re.reservationArrivalDate >= '2014-07-07'
AND re.reservationDepartureDate <= '2014-09-23'
) ;
You need change table in where statement from reservation to room. Also you need add re.hotelID to where statement as well, because on where statement you need check that record is not null ans only after try to check dates
Given the newly-added reservationroom table, consider using a NOT EXISTS sub-query to find rooms without reservations:
SELECT
*
FROM
room r
WHERE NOT EXISTS
(SELECT
*
FROM
reservationroom rr
WHERE
rr.reservationroomID = r.roomID
)

Mysql: Count of orders based on reason,historical status and current status

I want to calculate the count of orders and sum of revenue according to there history status and reason.
Following is my table structure.
Order Table :-
CREATE TABLE `order_item` (
`id_order_item` int(10) unsigned NOT NULL AUTO_INCREMENT,
`unit_price` decimal(17,2) DEFAULT NULL,
`fk_reason` int(11) DEFAULT NULL,
PRIMARY KEY (`id_order_item`),
KEY `fk_reason` (`fk_reason`)
) ENGINE=InnoDB;
History Table :-
CREATE TABLE `order_item_status_history` (
`id_order_item_status_history` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_order_item` int(10) unsigned NOT NULL,
`fk_order_item_status` int(10) unsigned NOT NULL COMMENT ''New status'',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_at` datetime DEFAULT NULL,
PRIMARY KEY (`id_order_item_status_history`),
KEY `fk_order_item` (`fk_order_item`),
CONSTRAINT `order_item_status_history_ibfk_1` FOREIGN KEY (`fk_order_item`) REFERENCES `order_item` (`id_order_item`) ON DELETE CASCADE ON UPDATE NO ACTION,
CONSTRAINT `order_item_status_history_ibfk_3` FOREIGN KEY (`fk_order_item_status`) REFERENCES `order_item_status` (`id_order_item_status`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB;
Status Table :-
CREATE TABLE `order_item_status` (
`id_order_item_status` int(10) unsigned NOT NULL,
`name` varchar(50) NOT NULL,
`desc` varchar(255) NOT NULL,
`deprecated` tinyint(1) DEFAULT ''0'',
PRIMARY KEY (`id_order_item_status`),
) ENGINE=InnoDB;
Reason Table :-
CREATE TABLE `reason` (
`id_reason` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`desc` varchar(255) NOT NULL,
PRIMARY KEY (`id_cancel_reason`),
) ENGINE=InnoDB ;
I need to group orders into following buckets,
Orders has status as 'Closed' and If Order was shipped before.(i.e.
previous status of order is 'shipped')
Orders has status as 'Closed' and If Order was NOT shipped before(i.e. previous status of order is NOT 'shipped')
(in this case need to check current status as well as previous status of order. )
Orders has status as 'fraud'
(in this case need to check current status only.)
......
How can I get the count or orders and there revenue according to bucket defined above.
I am facing problem while counting orders in point 3 and 4 and get all counts in single query.
To get all those into one query you can use CASE WHEN .. like this
SELECT
whateverYouAreGroupingByIfNeeded,
SUM(CASE WHEN status = 'canceled' AND reason = 1 THEN 1 ELSE 0 END) AS count_whatever
SUM(CASE WHEN whatever = true THEN whateverYouWantToSummarize ELSE NULL END) AS sum_whatever
FROM yourTable
GROUP BY whatever
When you need specific help, it's best to show what you've tried.
P.S.: If you're having trouble with joining, read this.

How can I select the current holder for each championship?

I want to select the current holders for each championship in a championships table, and return NULL for championships that have not had any winners yet.
Here are the create statements for the two tables:
CREATE TABLE `championships` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`friendly_name` varchar(255) NOT NULL,
`rank` int(2) unsigned NOT NULL DEFAULT '1',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`),
UNIQUE KEY `friendly_name` (`friendly_name`)
) ENGINE=InnoDB;
CREATE TABLE `title_history` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`championship` int(10) unsigned NOT NULL,
`winner` varchar(255) NOT NULL,
`date_from` date NOT NULL,
`location` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `championship` (`championship`)
) ENGINE=InnoDB;
ALTER TABLE `title_history` ADD CONSTRAINT `title_history_ibfk_1` FOREIGN KEY (`championship`) REFERENCES `championships` (`id`) ON UPDATE CASCADE;
What MySQL statement would return the data set I wanted?
Assuming you're storing the winner of a championship as the primary key/id of the holder, something like this should work. You might want to add in another join to get the actual name of the team from another table though.
Because LEFT join will only select rows from the 'right' table when there is a match, everything that doesn't have one should come back as NULL.
SELECT name, [holder]
FROM championships AS c
LEFT JOIN title_history AS h ON c.winner = h.id
EDITED VERSION:
With further insight into your tables and from your comment, maybe try this subselect:
SELECT friendly_name,
(SELECT winner FROM title_history WHERE championship = c.id ORDER BY date_from DESC LIMIT 1)
FROM championships AS c
ORDER BY name
If I understand your structure correctly, that ought to get the last winner of each championship?