MySQL Trigger Function (between 3 different tables) - mysql

I have 3 tables (written as important parts, not all data):
tableDeviceStatus:
device_id status
--------- ------
device01 15
device02 20
tableDeviceTrigger:
device_id device_operator device_param triggered_device command
--------- --------------- ------------ ---------------- -------
device01 > 22 device80 1
device01 < 18 device87 0
device02 = 1 device89 1
tableDeviceCommand:
device_id command
--------- -------
device80
device87
device89
Flow:
When tableDeviceStatus updated I will create trigger (AFTER)
If a device updated, check tableDeviceTrigger and compare by operators and update tableDeviceCommand
For example:
device01 updated as 25.
there is 2 comparison. Check both and since 25>22, update tableDeviceCommand.device80 as 1.
I have tried trigger but couldn't solve search, compare and execute. (because a device can take more than one comparison. I nearly created for one row but could't formulize for like for each rows and execute)

Welcome to Stack Overflow! If I understand correctly, for each new row entered in tableDeviceStatus, you want to test each row with a matching #device_id in tableDeviceTrigger.
For each matching row in tableDeviceTrigger, you test if it meets the requirements (#status #device_operator #device_param is TRUE). For each row that meets these requirements, you add a row in tableDeviceCommand with the #triggered_device and #command.
Does something like this work?
INSERT INTO tableDeviceCommand (device_id, command)
-- maybe REPLACE INTO or UPDATE depending on your requirements
(
SELECT tdt.triggered_device, tdt.command
FROM tableDeviceStatus tds
INNER JOIN tableDeviceTrigger tdt on tds.device_id = tdt.device_id
WHERE
(tdt.device_operator = ">" and tds.status > tdt.device_param) OR
(tdt.device_operator = "<" and tds.status < tdt.device_param) OR
(tdt.device_operator = "=" and tds.status = tdt.device_param)
-- add additional ORs to test the comparison specified by device_operator
)

Related

How to Find First Valid Row in SQL Based on Difference of Column Values

I am trying to find a reliable query which returns the first instance of an acceptable insert range.
Research:
some of the below links adress similar questions, but I could get none of them to work for me.
Find first available date, given a date range in SQL
Find closest date in SQL Server
MySQL difference between two rows of a SELECT Statement
How to find a gap in range in SQL
and more...
Objective Query Function:
InsertRange(1) = (StartRange(i) - EndRange(i-1)) > NewValue
Where InsertRange(1) is the value the query should return. In other words, this would be the first instance where the above condition is satisfied.
Table Structure:
Primary Key: StartRange
StartRange(i-1) < StartRange(i)
StartRange(i-1) + EndRange(i-1) < StartRange(i)
Example Dataset
Below is an example User table (3 columns), with a set range distribution. StartRanges are always ordered in a strictly ascending way, UserID are arbitrary strings, only the sequences of StartRange and EndRange matters:
StartRange EndRange UserID
312 6896 user0
7134 16268 user1
16877 22451 user2
23137 25142 user3
25955 28272 user4
28313 35172 user5
35593 38007 user6
38319 38495 user7
38565 45200 user8
46136 48007 user9
My current Query
I am trying to use this query at the moment:
SELECT t2.StartRange, t2.EndRange
FROM user AS t1, user AS t2
WHERE (t1.StartRange - t2.StartRange+1) > NewValue
ORDER BY t1.EndRange
LIMIT 1
Example Case
Given the table, if NewValue = 800, then the returned answer should be 23137. This means, the first available slot would be between user3 and user4 (with an actual slot size = 813):
InsertRange(1) = (StartRange(i) - EndRange(i-1)) > NewValue
InsertRange = (StartRange(6) - EndRange(5)) > NewValue
23137 = 25955 - 25142 > 800
More Comments
My query above seemed to be working for the special case where StartRanges where tightly packed (i.e. StartRange(i) = StartRange(i-1) + EndRange(i-1) + 1). This no longer works with a less tightly packed set of StartRanges
Keep in mind that SQL tables have no implicit row order. It seems fair to order your table by StartRange value, though.
We can start to solve this by writing a query to obtain each row paired with the row preceding it. In MySQL, it's hard to do this beautifully because it lacks the row numbering function.
This works (http://sqlfiddle.com/#!9/4437c0/7/0). It may have nasty performance because it generates O(n^2) intermediate rows. There's no row for user0; it can't be paired with any preceding row because there is none.
select MAX(a.StartRange) SA, MAX(a.EndRange) EA,
b.StartRange SB, b.EndRange EB , b.UserID
from user a
join user b ON a.EndRange <= b.StartRange
group by b.StartRange, b.EndRange, b.UserID
Then, you can use that as a subquery, and apply your conditions, which are
gap >= 800
first matching row (lowest StartRange value) ORDER BY SB
just one LIMIT 1
Here's the query (http://sqlfiddle.com/#!9/4437c0/11/0)
SELECT SB-EA Gap,
EA+1 Beginning_of_gap, SB-1 Ending_of_gap,
UserId UserID_after_gap
FROM (
select MAX(a.StartRange) SA, MAX(a.EndRange) EA,
b.StartRange SB, b.EndRange EB , b.UserID
from user a
join user b ON a.EndRange <= b.StartRange
group by b.StartRange, b.EndRange, b.UserID
) pairs
WHERE SB-EA >= 800
ORDER BY SB
LIMIT 1
Notice that you may actually want the smallest matching gap instead of the first matching gap. That's called best fit, rather than first fit. To get that you use ORDER BY SB-EA instead.
Edit: There is another way to use MySQL to join adjacent rows, that doesn't have the O(n^2) performance issue. It involves employing user variables to simulate a row_number() function. The query involved is a hairball (that's a technical term). It's described in the third alternative of the answer to this question. How do I pair rows together in MYSQL?

MySQL update with data from two tables

I have a MySQL database with the two tables that I need modified.
The first table holds notes
id type note
1 1 24 months warranty
2 1 12 months warranty
3 2 Garage in Denver
4 3 Pre sales maintenance done
....
And then a vehicle table that holds many vehicle tables and a field that hold notes with their text instead of a pointer
id licence_plate ... sales_notes ...
1 XH34DN ... <warranty>24 months warranty</warranty><garage>Garage in Denver</garage><maintenance>Pre sales maintenance done</maintenance> ...
2 K4B3C6 ... <warranty>12 months warranty</warranty><garage>Garage in Sacramento</garage><maintenance>Pre sales maintenance not done</maintenance> ...
As you can imagine this is higly inneficient and I want to modify to pointers that hold the id of the note.
id licence_plate ... warranty_note garage_note maintenace_note ...
1 XH34DN ... 1 3 4 ...
2 K4B3C6 ... 2 7 12 ...
I can do it manual updates but I would like to build one that makes it automatically by type.
So for notes.type=1 if the notes.note text is found in vehicle.sales_notes it updates the vehicle.warranty_note.
Any idea how to build something like that?
I have something like this in mind, but id doesn't work. No results are updated
UPDATE tx_vehicle v, tx_note n
SET v.garage_note = n.uid
WHERE v.sales_notes LIKE ('%'+n.note+'%')
MySQL has special XML parsing functions.
insert into your_new_notes_table (id, licence_plate, warranty_note, garage_note, maintenace_note)
select
sn.id,
sn.license_plate,
(select nt.id from notes as nt where nt.type = 1 and nt.note = ExtractValue(sn.sales_note, "/warranty")) as warranty_note,
(select nt.id from notes as nt where nt.type = 1 and nt.note = ExtractValue(sn.sales_note, "/garage")) as garage_note,
(select nt.id from notes as nt where nt.type = 1 and nt.note = ExtractValue(sn.sales_note, "/maintenance")) as maintenance_note
from note_types as nt
Although, it will fail if exact note from sales is not found in note_types table. You can adjust note text comparison, replacing it with like operator, regex checking or another functions; you can replace abbreviations with full words or invent your own MySQL function. Moreover, you can invent your own MySQL function that would encompass internal selects and return null if note is not found in note_types table.
If you want to update existing table, you need to perform update query with join to same select as I provided.

How to index a wide table of booleans

My question is on building indexes when your client is using a lot of little fields.
Consider a search of the following:
(can't change it, this is what the client is providing)
SKU zone1 zone2 zone3 zone4 zone5 zone6 zone7 zone8 zone9 zone10 zone11
A123 1 1 1 1 1 1 1 1
B234 1 1 1 1 1 1 1
C345 1 1 1 1 1 1
But it is much wider, and there are many more categories than just Zone.
The user will be looking for skus that match at least one of the selected zones. I intend to query this with (if the user checked "zone2, zone4, zone6")
select SKU from TABLE1 where (1 IN (zone2,zone4,zone6))
Is there any advantage to indexing with a multi tiered index like so:
create index zones on table1 (zone1,zone2,zone3,zone4,zone5,zone6,zone7,zone8,zone9,zone10,zone11)
Or will that only be beneficial when the user checked zone1?
Thanks,
Rob
You should structure the data as:
create table SKuZones (
Sku int not null,
zone varchar(255)
)
It would be populated with the places where a SKU has a 1. This can then take great advantage of an index on SKUZones(zone) for an index. A query such as:
select SKU
from SKUZones
where zone in ('zone2', 'zone4', 'zone6');
will readily take advantage of an index. However, if the data is not structured in a way appropriate for a relational database, then it is much harder to make queries efficient.
One approach you could take if you can add a column to the table is the following:
Add a new column called zones or something similar.
Use a trigger to populate it with values for each "1" in the columns (so "zone3 zone4 zone5 . . ." for the first row in your data).
Build a full text index on the column.
Run your query using match against
Indexing boolean values is almost always useless.
What if you use a SET datatype? Or BIGINT UNSIGNED?
Let's talk through how to do it with some sized INT, named zones
zone1 is the bottom bit (1<<0 = 1)
zone2 is the next bit (1<<1 = 2)
zone3 is the next bit (1<<2 = 4)
zone4 is the next bit (1<<3 = 8)
etc.
where (1 IN (zone2,zone4,zone6)) becomes
where (zones & 42) != 0.
To check for all 3 zones being set: where (zones & 42) = 42.
As for indexing, no index will help this design; there will still be a table scan.
If there are 11 zones, then SMALLINT UNSIGNED (2 bytes) will suffice. This will be considerably more compact than other designs, hence possibly faster.
For this query, you can have a "covering" index, which helps some:
select SKU from TABLE1 where (zones & 42) != 0 .. INDEX(zones, SKU)
(Edit)
42 = 32 | 8 | 2 = zone6 | zone4 | zone2 -- where | is the bitwise OR operator.
& is the bitwise AND operator. See http://dev.mysql.com/doc/refman/5.6/en/non-typed-operators.html
(zones & 42) = 42 effectively checks that all 3 of those bits are "on".
(zones & 42) = 0 effectively checks that all 3 of those bits are "off".
In both cases, it is ignoring the other bits.
42 could be represented as ((1<<5) | (1<<3) | (1<<1)). Because of precedence rules, I recommend using more parentheses than you might think necessary.
1 << 5 means "shift" 1 by 5 bits.

update value in comma separated and replace with value in query

iam having table like this
id name sol_id
1 abc 2,5,8
2 dt 5,9,10
here i want to add some value(10) to id=1 of sol_id,so value 10 will be added with id=1 and at the same time value 10 of id=2 replace with some empty value i want output like this
id name sol_id
1 abc 2,5,8,10(here updating)
2 dt 5,9 (10 removing)
i wrote query like this but its performs one operation not both
UPDATE my_table SET sol_id=REPLACE(sol_id,',10,',',')
and sol_id = Concat(sol_id, ',', 10) where id = 1
is it possible? Thanks in advance
It is possible, but clunky.
Basically you would do
UPDATE table SET SET field = CASE id
WHEN 1 THEN <formula for the case of id=1>
WHEN 2 THEN <formula for the case of id=2>
END
WHERE ID IN (1, 2);
There's no great advantage over running several queries inside a TRANSACTION and, if necessary, a suitable lock on the table.

Use MySQL to calculate difference between two entries in the same table

I have a series of meter reading stored in a table, each is identified by a building ID, a meter ID and the time at which it was recorded.
For each entry I would like to search for the entry that has the same ID numbers and the closest previous time, I would then like to use the previous time and the previous reading to calculate the length of the time step and the differential between the two readings.
so, I currently have:
BuildingID | MeterID | Date_and_Time | Reading
and I would like to produce:
BuildingID | MeterID | Date_and_Time | Time_Since_Previous_Read | Accumulation_Since previous_Read
two typical entries might look like this:
1 | 1 | 2010-10-09 17:56:20 | 119.6
1 | 1 | 2010-10-09 18:01:08 | 157.4
and I would like to produce:
1 | 1 | 2010-10-09 18:01:08 | 00:04:48 | 37.8
If no previous entry exists (i.e. for the first reading) i woudl like to rerun zeros for the time elapsed and the accumulation.
I would appreciate very much any help that could be offered on this, I made a concerted effort to find the answer in previous posts but to no avail, feel free to direct me to a good source if this has already been solved elsewhere.
thank you
Maybe like this?
SELECT a.BuildingID, a.MeterID, a.Date_and_Time,
a.Date_and_Time-b.Date_and_Time AS `Time_Since_Previous_Read`,
a.Reading-b.Reading AS `Accumulation`,
MAX(b.Date_and_Time) AS `otherdateandtime`
FROM `TABLENAME` AS `a`, `TABLENAME` AS `b`
WHERE a.BuildingID = b.BuildingID AND a.MeterID = b.MeterID
AND a.Date_and_Time>b.Date_and_Time
GROUP BY `a.Date_and_Time`
Try this:
/* 1*/SELECT
/* 2*/ r_B.BuildingID,
/* 3*/ r_B.MeterID,
/* 4*/ r_B.Date_and_Time,
/* 5*/ COALESCE(DATEDIFF(hh, r_A.Date_and_Time, r_B.Date_and_Time), 0) AS Time_Since_Previous_Read,
/* 6*/ COALESCE(r_B.Reading-r_A.Reading, 0) AS Accumulation_Since_Previous_Read
/* 7*/ FROM meterdata r_B
/* 8*/ LEFT OUTER JOIN meterdata r_A
/* 9*/ ON r_B.BuildingID = r_A.BuildingID AND r_B.MeterID = r_A.MeterID AND r_B.Date_and_Time > r_A.Date_and_Time
/*10*/ WHERE NOT EXISTS (SELECT nonelater.Date_and_Time FROM meterdata nonelater WHERE nonelater.BuildingID = r_B.BuildingID AND nonelater.MeterID = r_B.MeterID AND nonelater.Date_and_Time > r_A.Date_and_Time AND nonelater.Date_and_Time < r_B.Date_and_Time)
/*11*/ORDER BY r_B.BuildingID, r_B.MeterID, r_B.Date_and_Time
Here's how the design works:
Lines 7 and 8: the core of the query is a self-JOIN on the meterdata table. You need that to be able to find the difference between values in one row and values in another row. r_B is the later one, r_B the earlier one.
Line 8: Making it a LEFT OUTER JOIN means that it works even if there isn't an earlier r_A row; that part of the join will return NULLs.
Line 9: this constrains the JOIN to only join rows for the same building and meter, and makes sure the two rows are the right way around.
Line 10: If you didn't change the join any more, you'd have each r_B row joining to every single past row. To make sure that r_B matches only to the most recent past row, this line checks that there isn't another row more recent than r_A.
Line 6: this calculates the difference between the readings; if there isn't an earlier r_A row, this calculation will return NULL, so you need the COALESCE function to change that to zero.
At line 5, it does the same thing to find the time interval. For this demo I've used the SQL Server DATEDIFF function which won't give you exactly what you want, because on MySQL it only has one option, to calculate the difference in days; you may be able to use the INTERVAL function instead. Again, if there isn't a row r_A then COALESCE will change the NULL to zero.
Everything's there except for getting a time interval out in days, hours and minutes and formatting it nicely. Good luck with that.