Rails join two tables or replace numbers by the words - mysql

I want to write logs to the database and I have often repeated words (syslog, sms, voice, email), I need to instead these words insert numbers (syslog - 1, sms - 2, voice - 3, email - 4), so as not to clog the base repeating words. And then select the words instead numbers
Excuse me, i asked the question wrong. I have data already saved, and i need select it to my index.html.erb. query must be like this: "SELECT triggerid, severity, status, types.name FROM logs, types WHERE logs.type_id = types.id;" logs.type_id = (1, 2, 3, 3, 2, 2, 1), types.name = (email, sms, syslog, voice), types.id = (1, 2, 3, 4,) after query myst be: types.name = (email, sms, syslog, syslog, sms, sms, email). How can i use activerecord fo this result?

I'm not sure what field name/table your wanting to store these numbers in so I'm going to just call it 'FIELD_NAME' and 'TABLE'. Keeping it in mysql you could do this:
Retrieve:
SELECT TABLE.*, case FIELD_NAME WHEN 'syslog' THEN 1 WHEN 'sms' THEN 2 WHEN 'voice' THEN 3 WHEN 'email' THEN 4 ELSE null END as FIELD_ALIAS FROM TABLE
the result will have an attribute of FIELD_NAME with the int value, and an attribute of FIELD_ALIAS with the text version.
Inserting (STR_VALUE is the string version of the field):
INSERT INTO TABLE_NAME (FIELD_NAME) VALUES (case STR_VALUE WHEN 'syslog' THEN 1 WHEN 'sms' THEN 2 WHEN 'voice' THEN 3 WHEN 'email' THEN 4 ELSE null END)
I believe a better alternative though would be to offload this to your model. for simplicity I'm going to refer to your integer version as 'int_value'.
class Address < ActiveRecord::Base
before_save :convert_int_value
def str_value
case self.int_value
WHEN 1 THEN 'syslog'
WHEN 2 THEN 'sms'
WHEN 3 THEN 'voice'
WHEN 4 THEN 'email'
end
protected
def convert_int_value
return nil unless int_value.is_a?(String)
self.int_value = case self.int_value
WHEN 'syslog' THEN 1
WHEN 'sms' THEN 2
WHEN 'voice' THEN 3
WHEN 'email' THEN 4
end
end
Anytime you want the int version, just call int_value on your model. If you want the string call str_value. Additionally you can store the string version, in your int_value and before the record is saved it will convert it for you. This way all you have to pass mysql is an integer.
Update per comment
If your associations are set up correctly (Log belongs_to :type)
Log.includes(:type).each do |l|
l.status
l.type.name #> email, sms, or syslog
end
I realized after the fact there is another solution as well, I think this is more what you were looking for
Log.joins(:type).select('triggerid, severity, status, types.name')
Alt1
Log.joins(:type).select('log.*, types.name')
Alt2
Log.joins('JOINS types ON types.id = logs.type_id').select('log.*, types.name')

are you after one address per client?:
Client.joins(:addresses)
or every address with client info?:
Adress.join(:client)

Related

Fetch how many failure and success did each person (name) have

I have a table buggy, the dummy dataset link can be see here
https://github.com/FirzaCank/Project/blob/main/SQL/IFG%20test/Dataset%20Dummy%20no%205.sql
Which contains:
id (INT)
name (VARCHAR)
bug (INT, contains the numbers 0 and 1)
With dataset explanations on 'bug' column are:
0, it means fault / failure
1, it means success
If there is no 'fault', then the 'fault' value will be filled with '0' (null is okay too), so is 'success'
I've tried a MySQL query like this:
SELECT name,
CASE
WHEN bug = 0 THEN COUNT(bug)
END AS failure,
CASE
WHEN bug = 1 THEN COUNT(bug)
END AS success
FROM buggy
GROUP BY name;
The desire output is like This, but as far as I've tried in the above syntax it just came out like this
Thank you for the help!
You should use SUM instead of Count.
SELECT
name,
SUM(IF(bug = 0, 1, 0)) as fault,
SUM(IF(bug = 1, 1, 0)) as success
FROM buggy
GROUP BY name
This counts the number of rows satisfying the conditions inside the IF function.
this sql will give wanted result
SELECT t.name , SUM(t.failure) as failure , SUM(t.success) as success
from ( SELECT name , CASE
WHEN bug < 1 THEN COUNT(bug) ELSE 0
END AS failure,
CASE
WHEN bug = 1 THEN COUNT(bug) ELSE 0
END AS success
FROM buggy
GROUP BY name,bug ) t
GROUP BY t.name;

Splitting out a single field into multiple fields

I have a table with the following fields:
id, type, date, changelog.
The changelog field has 10 useful pieces of information I would like to split out into their own fields. both new and old: name, month, year, zipcode, status
So I would like to create a table with the following fields:
id, type, date, old_name, new_name, old_month, new_month, old_year, new_year, old_zipcode, new_zipcode, old_status, new_status.
When all 5 pieces of information exist it is easy but when some are missing I can’t get it to work. Any help is appreciated.
a typical changelog field doesn't have all of these pieces of information, just what is being updated.
for example:
id type date changelog
101 upd 1/1/2019 ---!hash:ActiveSupport
name:
- Adam
- Chris
month:
- 7
- 12
status:
- 1
- 3
Which would translate to:
id type date old_name new_name old_month new_month old_year new_year old_zipcode new_zipcode old_status new_status
101 upd 1/1/19 Adam Chris 7 12 1 3
This is not a complete solution (it assumes you can already parse out the values when you know they are present), but it addresses how to handle when those values are missing:
INSERT INTO tableV2 (id, type, date, old_name, new_name, and so on....)
SELECT id, type, date
, CASE WHEN INSTR(changelog, 'name:') = 0 THEN NULL
ELSE (parse the value out here)
END AS old_name
, CASE WHEN INSTR(changelog, 'name:') = 0 THEN NULL
ELSE (parse the value out here)
END AS new_name
, and so on....
FROM tableV1
;
The parsing, while not trivial, probably won't be too difficult other than the tediousness of it. You'll need to take the found "tag" location, find the 3 newlines following it (first for the tag, latter for each value), and then use those along with other string functions such as SUBSTR, LEFT... and maybe some CHAR_LENGTH(tag string) like CHAR_LENGTH('name:') to make the parsing repeatable for each tag with minor modification.

Updating multiple columns depending on 1 CASE-condition

I have a program that needs to synchronize it's frequently changing values (in temporary memory) with a database. The critical key (not primary!) in that table is the column id. My program changes the id but keeps the old id in memory, too.
Now, I would like to update several specified columns for multiple records/rows in one single statement. Furthermore, it should be reasonably fast for 5 up to 10 of such statements in 1 second with 4 GB RAM and ~ 50 MBit/s connection that is not only used for these sql-calls.
My sql-specifications
Server: 127.0.0.1 via
TCP/IP
Software: MySQL
Software version: 5.5.27 - MySQL Community Server (GPL)
Protocol version: 10
Server charset: UTF-8 Unicode (utf8)
I tried to use brackets...
UPDATE someTable
SET (id, name) = CASE id
WHEN 1 THEN (111, "Dr. Frankenstein")
WHEN 2 THEN (222, "the Monster")
WHEN 3 THEN (333, "Mr. X")
ELSE (id, name) END
WHERE id IN (1, 2, 3)
...which simply results in the following error:
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(id, name) = CASE id WHEN 1 THEN (111, "Dr. Frankenstein") WHEN 2 THEN (222,' at line 2
Know I wonder: Is there a way to do it in just one statement with the current syntax? Would it be feasable that way or should I just split it into multiple statement which is ugly in terms of the program that makes the sql-calls.
Answers and suggestions are welcome!
A case statement only returns one value:
UPDATE someTable
SET id = (CASE id WHEN 1 THEN 111 WHEN 2 THEN 222 WHEN 3 THEN 333 ELSE id END),
name = (CASE id WHEN 1 THEN 'Dr. Frankenstein'
WHEN 2 THEN 'the Monster'
WHEN 3 THEN 'Mr. X'
ELSE name
END)
WHERE id IN (1, 2, 3);
For performance, be sure you an an index on id. This will help with finding the records to update. Do note that changing the id value requires updating the index, which can be a bit longer than a normal update. However, expecting 5-10 transactions a second is reasonable.
Hope this works:
UPDATE someTable
SET id = CASE
id
WHEN 1 THEN 111
WHEN 2 THEN 222
WHEN 3 THEN 333
ELSE id END
,
name = CASE
id
WHEN 1 THEN "Dr. Frankenstein"
WHEN 2 THEN "the Monster"
WHEN 3 THEN "Mr. X"
ELSE name END
WHERE id IN (1, 2, 3)

Easier way to map record names for a column?

I am currently using the REPLACE function to change a couple record names (ints to their corresponding human readable name) like this:
SELECT SUM(COUNT) AS Actions, replace(replace(replace(replace(replace(replace(EVENT, 2, 'iOS'), 1, 'Web'), 3, 'Android'), 4, 'Windows'), 5, 'Mac'), 6, 'Unknown') AS Platform
FROM `METRICS_WEEKLY`
WHERE EVENT IN (1, 2, 3, 4 ,5 ,6)
GROUP BY EVENT
I would like to build a query which is using more than 30 numbers in the the EVENT column and map them to friendlier names. I am unable to alter the current table (I am not the creator of the table schema). Is there an easier way to map a larger group of field names?
What you are describing is the ENUM data type. Create the column as an ENUM of your list, and you avoid all the string replacements. The values will be stored in their integer formats and must be one of the available choices, but will display as their string equivalents.
CREATE TABLE `METRICS_WEEKLY` (
`COUNT` INT,
`EVENT` ENUM (
'Web',
'iOs',
'Android',
'Windows'
'Mac',
'Unknown'
)
);
Alternatively if the list is short, create a table to hold these values according to proper normalization.
It would be easier to use a mapping table.
ReplaceValue
---------------
value number
iOS 2
Web 1
Android 3
Windows 4
Then JOIN on the value and use number. You may want to consider creating a relationship so that if you update the values to be replaced, they will cascade to the child table.
You really, really, really want an EventType reference table. In the meantime, something like this:
select EventName, COUNT(*) as Actions
from Metrics_Weekly mw left outer join
(select 'iOS' as EventName, 2 as EventId union all
select 'Web', 1 union all
select 'Android', 3 union all
select 'Window', 4 union all
select 'Mac', 5 union all
select 'Unknown', 6
) eventType
on mw.Event = evenType.eventId
group by eventName
Try this:
SELECT SUM(COUNT) AS Actions,
(CASE event
WHEN 1 THEN 'Web'
WHEN 2 THEN 'iOS'
WHEN 3 THEN 'Android'
WHEN 4 THEN 'Windows'
WHEN 5 THEN 'Mac'
ELSE 'Unknown' END ) AS Platform
FROM METRICS_WEEKLY
WHERE EVENT IN (1, 2, 3, 4 ,5 ,6)
GROUP BY EVENT

MySQL - self join optimization

I have a table of phone events by HomeId. Each row has an EventId (on hook, off hook, ring, DTMF, etc), TimeStamp, Sequence (auto increment) and HomeId. Im working on a query to find specific types of occurrences(IE inbound or outbound calls) and duration.
I had planned on doing this using a multiple self-join on this table to pick out the sequences of events that usually indicate one type of occurrence or the other. EG inbound calls would be a period of inactivity followed by no DTMF, then ringing and caller id (possibly) then an off hook. I would find the next on-hook and thus have the duration.
My table is indexed by HomeId, EventId and Sequence and has ~60K records. When I do an 'explain' of my query it shows indexing and 75, 75, 1, 1, 748 for the row counts. Seems pretty doable. But when I run the query its taking more than 10 minutes (at which point the MySQL query browser times out).
Query for outbound calls:
select pe0.HomeId, pe1.Stamp, pe1.mSec, timediff( pe4.Stamp, pe0.Stamp ) from Phone_Events pe0
join Phone_Events pe1 on pe0.HomeId = pe1.HomeId and pe1.Sequence = pe0.Sequence - 1 and abs(timediff( pe0.Stamp, pe1.Stamp )) > 10
join Phone_Events pe2 on pe0.HomeId = pe2.HomeId and pe2.Sequence = pe0.Sequence + 1 and pe2.EventId = 22
join Phone_Events pe4 on pe4.HomeId = pe0.HomeId and pe4.EventId = 30 and pe4.Stamp > pe0.Stamp
where pe0.eventId = 12 and pe0.HomeId = 111
AND
NOT EXISTS(SELECT * FROM Phone_Events pe3
WHERE pe3.HomeId = pe0.HomeId
AND pe3.EventId not in( 13, 22 )
AND pe3.Stamp > pe0.Stamp and pe3.Stamp < pe4.Stamp );
Is there something specific to self joining that makes this slow? Is there a better way to optimize this? The killer seems to be the 'not exists' portion - this part is there to make sure there are no events between the last 'on hook' and the current 'off hook'.
EDIT: EventId's as follows:
'1', 'device connection'
'2', 'device disconnection'
'3', 'device alarm'
'11', 'ring start'
'12', 'off hook'
'13', 'hang up(other end)'
'15', 'missed call'
'21', 'caller id'
'22', 'dtmf'
'24', 'device error'
'30', 'on hook'
'31', 'ring stop'
Complete rewrite based on new information. How I approached this was to start with an inner-most query to get all records we care about based exclusively on HomeID = 111 and make sure they came back pre-sorted by the sequence ID (have index on HomeID, Sequence). As we all know, a phone call starts by picking up the phone -- eventID = 12, getting dial tone -- eventid = 22, dialing out, and someone answering, until the phone is back on the hook -- eventid = 30). If its a hangup (eventid=13), we want to ignore it.
I don't know why you are looking at the sequence # PRIOR to the current call, don't know if it really has any bearing. It looks like you are just trying to get completed calls and how long the duration. That said, I would remove the portion of the LEFT JOIN Phone_Event and the corresponding WHERE clause. It may have been there while you were just trying to figure this out.
Anyhow, back to the logic. The inner most guarantees the call sequences in order. You won't have two calls simultaneous. So by getting them in order first, I then join to the SQLVars (which creates inline variable #NextCall for the query). The purpose for this is to identify every time a new call is about to begin (EventID = 12). If so, take whatever the sequence number is, and save it. This will remain the same until the next call, so all the other "event IDs" will have the same "starting sequence ID". In addition, I'm looking for the other events... an event = 22 based on the starting sequence +1 and setting it as a flag. Then, the max time based on the start of the call (only set when eventid = 12), and end of the call (eventid = 30), and finally a flag based on your check for a hang up (eventid = 13) ie: don't consider the call if it was a hangup and no connection through.
By doing a group by, I've in essence, rolled-up each call to its own line... grouped by the home ID, and the sequence number used to initiate the actual phone call. Once THAT is done, I can then query the data and compute the call duration since the start/end time are on the same row, no self-self-self joins involved.
Finally, the where clause... Kick out any phone calls that HAD a HANG UP. Again, I don't know if you still need the element of what the starting call's time was of the last ending event.
SELECT
PreGroupedCalls.*,
timediff( PreGroupedCalls.CallEndTime, PreGroupedCalls.CallStartTime ) CallDuration
from
( SELECT
Calls.HomeID,
#NextCall := #NextCall + if( Calls.EventID = 12, Calls.Sequence, #NextCall ) as NextNewCall,
MAX( if( Calls.EventID = 12, Calls.Stamp, 0 )) as CallStartTime,
MAX( if( Calls.EventID = 30, Calls.Stamp, 0 )) as CallEndTime,
MAX( if( Calls.EventID = 22 and Calls.Sequence = #NewCallFirstSeq +1, 1, 0 )) as HadDTMFEntry,
MAX( if( Calls.EventID = 13 and Calls.Sequence = #NewCallFirstSeq +1, 1, 0 )) as WasAHangUp
from
( select pe.HomeId,
pe.Sequence,
pe.EventID,
pe.Stamp
from
Phone_Events pe
where
pe.HomeID = 111
order by
pe.Sequence ) Calls,
( select #NextCall := 0 ) SQLVars
group by
Calls.HomeID,
NextNewCall ) PreGroupedCalls
LEFT JOIN Phone_Event PriorCallEvent
ON PreGroupCalls.NextNewCall = PriorCallEvent.Sequence -1
where
PreGroupedCalls.WasHangUp = 0
AND ( PriorCallEvent.Sequence IS NULL
OR abs(timediff( PriorCallEvent.Stamp, PreGroupedCalls.CallStartTime )) > 10 )
COMMENT FROM FEEDBACK / ERROR reported
To try and fix the DOUBLE error, you obviously will need to make a slight change in the SQLVars select.. try the following
( select #NextCall := CAST( 0 as INT ) ) SQLVars
Now, what the IF() is doing... Lets take a look.
#NextCall + if(Calls.EventID = 12,Calls.Sequence, #NextCall)
means take a look at the Event ID. If it is a 12 (ie: off-hook), grab whatever the sequence number is for that entry. This will become the new "Starting Sequence" of another call. If not, just keep whatever the last value set was, as its a continuation of a call in progress. Now, lets look at some simulated data to help better illustrate all the columns
Original data Values that will ultimately be built into...
HomeID Sequence EventID Stamp #NextCall
111 1 12 8:00:00 1 beginning of a new call
111 2 22 8:00:01 1 not a new "12" event, keep last value
111 3 30 8:05:00 1 call ended, phone back on hook
111 4 12 8:09:00 4 new call, use the sequence of THIS entry
111 5 22 8:09:01 4 same call
111 6 13 8:09:15 4 same call, but a hang up
111 7 30 8:09:16 4 same call, phone back on hook
111 8 12 8:15:30 8 new call, get sequence ID
111 9 22 8:15:31 8 same call...
111 10 30 8:37:15 8 same call ending...
Now, the query SHOULD create something like this
HomeID NextNewCall CallStartTime CallEndTime HadDTMFEntry WasAHangUp
111 1 8:00:00 8:05:00 1 0
111 4 8:09:00 8:09:16 1 1
111 8 8:15:30 8:37:15 1 0
As you can see, the #NextCall keeps all the sequential entries for a given call "Grouped" together so you don't have to just use greater than span information or less than... It is always going to follow a certain path of "events", so whatever is the one that started the call is the basis for the rest of the events until the next call is started, then THAT sequence is grabbed for THAT group call.
Yup, its a lot to grasp.. but hopefully now more digestible for you :)