Multiple row retrieval with their latest inserted value using MySQL - mysql

I have two tables, first one is 'file_details':
+---------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+-------------+------+-----+---------+-------+
| file_name | varchar(40) | YES | | NULL | |
| creation_date | date | YES | | NULL | |
+---------------+-------------+------+-----+---------+-------+
and second one is 'logs':
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| sl_no | varchar(20) | YES | | NULL | |
| file_name | varchar(40) | YES | | NULL | |
| status | varchar(100) | YES | | NULL | |
+-----------+--------------+------+-----+---------+-------+
values in the tables are:
file_details:
+-----------+---------------+
| file_name | creation_date |
+-----------+---------------+
| a1 | 2020-01-09 |
| a2 | 2020-01-08 |
+-----------+---------------+
logs:
+-------+-----------+---------+
| sl_no | file_name | status |
+-------+-----------+---------+
| 1 | a1 | created |
| 2 | a1 | step1 |
| 1 | a2 | created |
| 2 | a2 | step1 |
| 3 | a2 | step2 |
+-------+-----------+---------+
now I want to retrieve the following data:
+-----------+---------------+--------+
| file_name | creation_date | status |
+-----------+---------------+--------+
| a1 | 2020-01-09 | step1 |
| a2 | 2020-01-08 | step2 |
+-----------+---------------+--------+
using the below query:
select f.file_name,f.creation_date,
l.status
from file_details f
inner join logs l on f.file_name=l.file_name
and l.status=(select status
from logs
where sl_no=(
select max(convert(sl_no,unsigned))
from logs));
But the above query gives me the below output:
+-----------+---------------+--------+
| file_name | creation_date | status |
+-----------+---------------+--------+
| a2 | 2020-01-08 | step2 |
+-----------+---------------+--------+
which is not a required solution. So, please help me out.

So there is a couple things to discuss here, you mentioned in the comments that you are new to SQL, so I will provide some links to look at, first off being normalization, this is used to reduce data redundancy (which you have with your status descriptions).
Also what you are trying to do is essentially make the engine "guess" what status is the most up to date one, using the MAX like you have will only deal with alphabetical orders and as such is not scale-able for if you say want to add a status such as "completed", so what you would have to do is hard code the order in something like a case statement which gets really messy with multiple conditions.
And lastly here is a tutorial site on SELECT query basics with links to other data manipulation commands.
So the answer I came up with, I made a status table to store the description and then in the log table I store the status_id, doing this addresses the normalization issue I mentioned earlier. Creating this table also allows me to assign the statuses a rank to order with, which is another issue I discussed earlier.
SELECT t.file_name,
t.creation_date,
s.description
FROM status_details s
JOIN (SELECT f.file_name,
f.creation_date,
MAX(s2.rank_no) rank_no
FROM file_details f
JOIN logs l
ON l.file_name = f.file_name
JOIN status_details s2
ON s2.status_id = l.status_id
GROUP BY f.file_name,
f.creation_date) t
ON t.rank_no = s.rank_no
Now I don't want you so blindly copy this query without understanding what it is doing, so the general gist is that the inner select gets the file names and creation dates with the rank number of the status, note this only gets the status with the highest rank number, then the outer select takes the data already retrieved and joins back onto the status table to grab the status description from the rank number. Giving the output
file_name creation_date description
a1 2020-01-09 step1
a2 2020-01-08 step2
If you would like to see the query working I have created a fiddle for you to try.
These are the data scripts I used to create the environment:
create table file_details( file_name varchar(40), creation_date date)
create table logs (sl_no varchar(20), file_name varchar(40), status_id int)
create table status_details (status_id int, description varchar(100), rank_no int)
insert into file_details values ('a1', '2020-01-09')
insert into file_details values ('a2', '2020-01-08')
insert into status_details values (1, 'created', 1)
insert into status_details values (2, 'step1', 2)
insert into status_details values (3, 'step2', 3)
insert into logs values ('1', 'a1' , 1)
insert into logs values ('2', 'a1' , 2)
insert into logs values ('1', 'a2' , 1)
insert into logs values ('1', 'a2' , 2)
insert into logs values ('3', 'a2' , 3)

max(convert(sl_no,unsigned)) from logs) will return 3 in your example and therefore it only matches
+-------+-----------+---------+
| sl_no | file_name | status |
+-------+-----------+---------+
| 3 | a2 | step2 |
+-------+-----------+---------+

Related

MySQL self join return all rows

Per the example data below, I need a query that returns every row, where if the 'contingent_on' field is NULL, it is returned as NULL, but if it is not NULL it is returned with the 'ticket_name' corresponding to the 'primary_key' value.
I tried self join queries but could only get them to return the not NULL rows.
example table data:
primary_key | ticket_name | contingent_on
1 | site preparation | NULL
2 | tender process | NULL
3 | construction | 1
All rows should be returned, where the in the 'construction' row return, 'site preparation' is input in place of '1' in the 'contingent_on' field.
You need a self left join:
select
t.primary_key,
t.ticket_name,
tt.ticket_name ticket_name2
from tablename t left join tablename tt
on tt.primary_key = t.contingent_on
order by t.primary_key
See the demo.
Results:
| primary_key | ticket_name | ticket_name2 |
| ----------- | ---------------- | ---------------- |
| 1 | site preparation | null |
| 2 | tender process | null |
| 3 | construction | site preparation |
It looks simple query:
select
primary_key,
ticket_name,
case when contingent_on is not null then ticket_name else contingent_on end as contingent_on
from <<your_table>>
order by primary_key

merger one row with null values to not null values of another row mysql

I want to merge two rows into one.The below format is in the database.
+----+---------+-----------------------+-------------------------+
| id | appid | photo | signature |
+====+=========+=======================+=========================+
| 1 | 10001 | 10001.photograph.jpg | NULL |
| 2 | 10001 | NULL | 10001.signature.jpg |
+----+---------+-----------------------+-------------------------+
I want a mysql query so that i can fetch data like below,
+--------+------------------------+-------------------------+
| appid | photo | signature |
+========+========================+=========================+
|10001 | 10001.photograph.jpg | 10001.signature.jpg |
+--------+------------------------+-------------------------+
Kindly suggest...
You can also use max function
select appid,
max(photo) photo,
max(signature) signature
from test
group by appid
Demo
This should do this:
select t1.appid,t1.photo,t2.signature from mytable t1 join mytable t2 on t1.appid=t2.appid where t1.id=1 and t2.id=2

mysql update and insert statements based on another table

I need some help with the mysql statements for inserting and updating rows in a new table based on the contents of another table. I am going to use this in automated perl code, but the mysql statements themselves are what I am having trouble with.
My first table named PROFILE looks something like this:
+----------+---------------------------+
| ID | NAME |
+----------+---------------------------+
| 0 | Default profile |
| 04731470 | Development profile |
| 87645420 | Core Base |
| a41401a0 | Core Test |
| ba0e3000 | Development profile child |
| e37fe780 | Test2 |
+----------+---------------------------+
The second called DEPLOYMENT has these columns (and no rows yet):
+------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| PROF_ID | char(36) | NO | PRI | NULL | |
| NAME | varchar(60) | NO | | NULL | |
| ID | tinyint(4) | NO | MUL | NULL | |
+------------+-------------+------+-----+---------+-------+
ID.PROFILE is the foreign key for PROF_ID.DEPLOYMENT and I want all of the values for ID.PROFILE to go in PROF_ID.DEPLOYMENT. Then I want the NAME.DEPLOYMENT and ID.DEPLOYMENT fields to be set based on the words found in the NAME.PROFILE field.
The following shows what I want to do as far as the insert statements goes, but these failed due to "ERROR 1242 (21000): Subquery returns more than 1 row":
INSERT INTO DEPLOYMENT(PROF_ID,NAME,ID) VALUES((select ID from PROFILE where NAME like '%core%'),'Core','2');
INSERT INTO DEPLOYMENT(PROF_ID,NAME,ID) VALUES((select ID from PROFILE where NAME like '%development%'),'Dev','3');
INSERT INTO DEPLOYMENT(PROF_ID,NAME,ID) VALUES((select ID from PROFILE where NAME not like '%development%' and not like '%core%'),'Default','1');
I'm not sure where to start on the update part of this but the ID.DEPLOYMENT and NAME.DEPLOYMENT fields should change as above if the text in the NAME.PROFILE fields changes with any of the words above.
This is the resulting DEVELOPMENT table I am looking for.
+----------+---------------+----+
| PROF_ID | NAME | ID |
+----------+---------------+----+
| 0 | Default | 1 |
| 04731470 | Dev | 3 |
| 87645420 | Core | 2 |
| a41401a0 | Core | 2 |
| ba0e3000 | Dev | 3 |
| e37fe780 | Default | 1 |
+----------+---------------+----+
Then I want statements to update if any of the NAME.PROFILE information changes.
Sorry if this is confusing, I wasn't sure how to explain and I am still learning mysql. Any help is appreciated.
Just get rid of the values keyword, basically:
INSERT INTO DEPLOYMENT(PROF_ID,NAME,ID)
select ID, 'Core','2'
from PROFILE
where NAME like '%core%';
INSERT INTO DEPLOYMENT(PROF_ID,NAME,ID)
select ID, 'Dev', '3'
from PROFILE
where NAME like '%development%';
INSERT INTO DEPLOYMENT(PROF_ID,NAME,ID)
select ID, 'Default', '1'
from PROFILE
where NAME not like '%development%' and not like '%core%';
By the way, you could combine these into one statement, using conditional expressions:
INSERT INTO DEPLOYMENT(PROF_ID,NAME,ID)
select ID,
(case when NAME like '%core%' then 'Core'
when NAME like '%development%' then 'Dev'
else 'Default'
end)
(case when NAME like '%core%' then '2'
when NAME like '%development%' then '3'
else '1'
end)
from PROFILE;

Get distinct results from several tables

I need to implement mysql query to calculate space used by user's mailbox.
A message thread may have multiple messages (reply, follow up) by 2 parties
(sender/recipient) and is tagged with one or more tags (Inbox, Sent etc.).
The following conditions have to be met:
a) user is either recipient OR author of the message;
b) message IS TAGGED by any of the tags: 1,2,3,4;
c) distinct records only, ie if the thread, containing messages is tagged with
more than one of the 4 tags (for example 1 and 4: Inbox and Sent) the calculation
is done on one tag only
I have tried the following query but I am not able to get distinct values - the
subject/body values are duplicated:
SELECT SUM(LENGTH(subject)+LENGTH(body)) AS sum
FROM om_msg_message omm
JOIN om_msg_index omi ON omm.mid = omi.mid
JOIN om_msg_tags_index omti ON omi.thread_id = omti.thread_id AND omti.uid = user_id
WHERE (omi.recipient = user_id OR omi.author = user_id) AND omti.tag_id IN (1,2,3,4)
GROUP BY omi.mid;
Structure of the tables:
om_msg_message - fields subject and body are the ones to be calculated
+--------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+----------------+
| mid | int(10) unsigned | NO | PRI | NULL | auto_increment |
| subject | varchar(255) | NO | | NULL | |
| body | longtext | NO | | NULL | |
| timestamp | int(10) unsigned | NO | | NULL | |
| reply_to_mid | int(10) unsigned | NO | | 0 | |
+--------------+------------------+------+-----+---------+----------------+
om_msg_index
+-----+-----------+-----------+--------+--------+---------+
| mid | thread_id | recipient | author | is_new | deleted |
+-----+-----------+-----------+--------+--------+---------+
| 1 | 1 | 1392 | 1211 | 0 | 0 |
| 2 | 1 | 1211 | 1392 | 1 | 0 |
+-----+-----------+-----------+--------+--------+---------+
om_msg_tags_index
+--------+------+-----------+
| tag_id | uid | thread_id |
+--------+------+-----------+
| 1 | 1211 | 1 |
| 4 | 1211 | 1 |
| 1 | 1392 | 1 |
| 4 | 1392 | 1 |
+--------+------+-----------+
Here's another solution:
SELECT SUM(LENGTH(omm.subject) + LENGTH(omm.body)) as totalLength
FROM om_msg_message omm
JOIN om_msg_index omi
ON omi.mid = omm.mid
AND (omi.recipient = user_id OR omi.author = user_id)
JOIN (SELECT DISTINCT thread_id
FROM om_msg_tags_index
WHERE uid = user_id
AND tag_id IN (1, 2, 3, 4)) omti
ON omti.thread_id = omi.thread_id
I'm assuming that:
user_id is a parameter marker/host variable, being queried for an individual user.
You want the total of all messages per user, not the total length of each message (which is what the GROUP BY clause in your version was getting you).
That mid in both om_msg_message and om_msg_index is unique.
So, your problem is the IN clause. I'm not a MYSQL guru, but in T-SQL you could change it to have a where clause on a subquery that contained an EXISTS so your join didn't pop out two rows. You need to compensate for the fact that you have two rows with different tagID's associated with each row of your primary join data.
The way I could do it cross-platform would be with four left-joins that linked tables then demanded a non-null value for 1, 2, 3, or 4. Fairly inefficient; I'm sure there's a better way to do it in MySQL, but now that you know what the problem is you might know a better solution.

Query on two tables for one report (Advanced)

I'm having some trouble with an advanced SQL query, and it's been a long time since I've worked with SQL databases. We use MySQL.
Background:
We will be working with two tables:
"Transactions Table"
table: expire_history
+---------------+-----------------------------+------+-----+-------------------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+-----------------------------+------+-----+-------------------+-------+
| m_id | int(11) | NO | PRI | 0 | |
| m_a_ordinal | int(11) | NO | PRI | 0 | |
| a_expired_date| datetime | NO | PRI | | |
| a_state | enum('EXPIRED','UNEXPIRED') | YES | | NULL | |
| t_note | text | YES | | NULL | |
| t_updated_by | varchar(40) | NO | | | |
| t_last_update | timestamp | NO | | CURRENT_TIMESTAMP | |
+---------------+-----------------------------+------+-----+-------------------+-------+
"Information Table"
table: information
+---------------------+---------------+------+-----+---------------------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+---------------+------+-----+---------------------+-------+
| m_id | int(11) | NO | PRI | 0 | |
| m_a_ordinal | int(11) | NO | PRI | 0 | |
| a_type | varchar(15) | YES | MUL | NULL | |
| a_class | varchar(15) | YES | MUL | NULL | |
| a_state | varchar(15) | YES | MUL | NULL | |
| a_publish_date | datetime | YES | | NULL | |
| a_expire_date | date | YES | | NULL | |
| a_updated_by | varchar(20) | NO | | | |
| a_last_update | timestamp | NO | | CURRENT_TIMESTAMP | |
+---------------------+---------------+------+-----+---------------------+-------+
We have a set of fields in one table that describe the record. Each record is comprised of a m_id (the person) and an ordinal (a person can have multiple records). So for instance, my m_id could be 1, and i could have multiple ordinals, (1, 2, 3, 4, etc), each with their own individual set of data. The m_id and the m_a_ordinal comprise a composite key in the "information" table, and the m_id, m_a_ordinal, and a_expired_date fields in the "transactions" table comprises a composite key as well.
Essentially when we expire a record, the a_state field in the information table is updated to expired. At the same time, a record is created in the transactions table with the m_id, m_a_ordinal, and a_expired_date. We've found in the past that people get impatient and can click a button twice, so through some previous help I've managed to narrow down the most recent transaction for each expired record using the following query:
SELECT e1.m_id, e1.m_a_ordinal, e1.a_expired_date, e1.t_note, e1.t_updated_by
FROM expire_history e1
INNER JOIN (SELECT m_id, m_a_ordinal, MAX(a_expired_date) AS a_expired_date
FROM expire_history GROUP BY m_id, m_a_ordinal) e2
ON (e2.m_id = e1.m_id AND e2.m_a_ordinal = e1.m_a_ordinal AND e2.a_expired_date = e1.a_expired_date)
WHERE e2.a_expired_date > '2008-05-15 00:00:00' ORDER BY a_date_expired;
Seems simple enough, right?
Let's add some complexity. Each record in the "information" table has a "natural expiration date" as well. The original developer of our software, however, didn't code it to change the state of the record to "expired" once it's reached it's natural expiration date. It also does not write a transaction to the transaction table once it's expired (which I understand because this is only to keep records of ones that were expired by a person, as opposed to automagically). Also, when a record is expired manually, the original expiration date does not change. This is why this is so complicated :P~~.
Essentially I need to build a report that shows all aspects of expiration, whether it was expired manually, or naturally.
This report should take the data from the query above, and combines it with another query on the "information table" that says if a_expire_date <= CURDATE show record, except if record exisits in (query above from expire_history), then show record from (query on expire_history).
a rough structure of the raw logic is as follows:
for x in record_total
if (m_id m_a_ordinal) exists in expire_history
display m_id, m_a_ordinal, a_expired_date, a_state)
else if (m_id_a_ordinal) exists in information AND a_expire_date <= CURDATE
display (m_id, m_a_ordinal, a_expire_date, a_state)
end if
x++
I hope that this is concise enough.
Thanks for any help you can provide!
SELECT i.m_id, I.m_a_ordinal,
coalesce(e1.a_expired_date, I.A_Expire_Date) as Expire_DT,
coalesce(e1.t_note,'insert related item column'),
coalesce(e1.t_updated_by, I.A_Updated_by) as Updated_By
FROM Information I
LEFT JOIN expire_history e1
ON E1.M_ID = I.M_ID
AND I.m_a_ordinal=e1.M_a_ordinal
INNER JOIN
(SELECT m_id, m_a_ordinal, MAX(a_expired_date) AS a_expired_date
FROM expire_history GROUP BY m_id, m_a_ordinal) e2
ON (e2.m_id = e1.m_id
AND e2.m_a_ordinal = e1.m_a_ordinal
AND e2.a_expired_date = e1.a_expired_date)
WHERE coalesce(e2.a_expired_date,i.A_Expire_Date) > '2008-05-15 00:00:00'
ORDER BY a_date_expired;
Syntax may be off a bit don't ahve time to test; but you can get the gist of it from this I hope:
Again what coalesce does is simply return the first NON-null value in a series of values. If you're only dealing with two NULLIF may work as well.