WHERE-clause for last non-null value - mysql

I'm making a community database for a school project and have ran into an issue. I am attempting to integrate a log system but retrieve the latest non-null value from column in a table called logs and present that information on a different page. My current code (without any attempt at filtering by their rank) looks as follows:
SELECT m.MemberID, m.MemberName, o.OfficeID, o.OfficeDesignation, p.PositionAbbreviation, l.LogRank
FROM logs l
INNER JOIN (SELECT l.LogMember, MAX(l.LogDate) AS maxLogDate FROM logs l GROUP BY l.LogMember) l2
ON (l.LogMember = l2.LogMember AND l.LogDate = l2.maxLogDate)
INNER JOIN members m
ON (l.LogMember = m.MemberID)
INNER JOIN offices o
ON (l.LogOffice = o.OfficeID)
INNER JOIN positions p
ON (l.LogPosition = p.PositionID)
GROUP BY m.MemberID;
The above query returns the latest entry for each member in the logs table, but I can't figure out how to, when l.LogRank returns NULL, take the latest non-null value only for that column.
I have struggled with a variety of approaches to this problem over the past week to no avail. Any help/pointers would be appreciated.
EDIT: Sample data seen below:
+-------+-----------+---------+-----------+-------------+-----------+
| LogID | LogMember | LogRank | LogOffice | LogPosition | LogDate |
+-------+-----------+---------+-----------+-------------+-----------+
| 1 | 1 | 1 | 7 | 5 | TIMESTAMP |
+-------+-----------+---------+-----------+-------------+-----------+
| 2 | 1 | 1 | | 1 | TIMESTAMP |
+-------+-----------+---------+-----------+-------------+-----------+
| 3 | 1 | | 1 | | TIMESTAMP |
+-------+-----------+---------+-----------+-------------+-----------+
The various INT values reference IDs in the relevant other tables.
Desired Output:
+-------+-----------+---------+-------------------+-----------------+-----------+
| LogID | LogMember | LogRank | OfficeDesignation | PositionAbbrev. | LogDate |
+-------+-----------+---------+-------------------+-----------------+-----------+
| 1 | 1 | 1 | C-6 | CO | TIMESTAMP |
+-------+-----------+---------+-------------------+-----------------+-----------+
So basically I want to retrieve the MemberID, MemberName, OfficeID, OfficeDesignation, PositionAbbreviation, and LogRank from a variety of tables and get the latest non-null record for each column.

INNER JOIN (
SELECT l.LogMember, l.LogRank FROM logs l
WHERE l.LogDate=(
SELECT LogDate FROM logs
WHERE LogMember=l.LogMember
AND LogRank IS NOT NULL
ORDER BY LogDate DESC
LIMIT 1
)
) l3
ON (l.LogMember = l3.LogMember)

Related

Bring all data from a table with joins with where clause that may not exist in the other table

I'm having a hard time setting up a query(select). Database is not my specialty, so I'm turning to the experts. Let me show what I need.
----companies--- ----company_server----- -----servers---- -----print------------------------
| id | name | | company | server | | id | name | | id |page|copy | date |server
|----|-------- | |---------|----------| |----|-------- | |----|----|-----|-------------
| 1 | Company1 |1--N| 1 | 1 |N*--1| 1 | Server1 |1--N| 1 | 2 | 3 | 2020-1-11 | 1
| 2 | Company2 | | 2 | 1 | | 2 | Server2 | | 2 | 1 | 6 | 2020-1-12 | 3
| 3 | Company3 | | 3 | 2 | | 3 | Server3 | | 3 | 4 | 5 | 2020-1-13 | 4
| 3 | 3 | | 4 | Server4 | | 4 | 5 | 3 | 2020-1-15 | 2
| 5 | 3 | 4 | 2020-1-15 | 4
| 6 | 1 | 2 | 2020-1-16 | 3
| 7 | 2 | 2 | 2020-1-16 | 4
What I need?
Example where date between CAST(2020-1-12 AS DATE) AND CAST(2020-1-15 AS DATE) group by servers.id
| companies | server | sum | percent
------------------------------------------------------------------------------------
| company1,company2 | server1 | sum(page*copy) = 0 or null | 0 or NULL
| company3 | server2 | sum(page*copy) = 15 | 28.30
| company3 | server3 | sum(page*copy) = 6 | 11.32
| NULL | server4 | sum(page*copy) = 32 | 60.38
Few notes:
I need this query for MYSQL;
Every Company is linked to at least one server.
I need result grouped by server. So, every company linked to that server must be concatenated by a comma.
If the company has not yet been registered, the value null should be presented.
The sum (page * copie) must be presented as zero or null (I don't care) in the case that there was no printing in the date range.
The percentage should be calculated according to the date range entered and not with all records in the database.
The field date is stored as MYSQL DATE.
Experts, I thank you in advance for your help. I currently solve this problem with at least 03 queries to the database, but I have a conviction that I could do it with just one query.
Added a fiddle. Sorry. Im still learing how to use this.
https://www.db-fiddle.com/f/dXej7QCPe9iDopfYd1SfVh/2
Follows the query that more or less represents how far I had arrived. Notice that in the middle of the way 'server4' disappeared because there are no values ​​for it in print in the period searched for him and I am in possession of the total of the period but I cannot calculate the percentage.
i'm stuck
select
*
from
(select
sum(p.copy * p.page) as sum1,
s.name as s_name,
s.id as s_id
from
print p
join servers s on s.id = p.server
where p.date between cast('2020-1-12' as date) and cast('2020-1-15' as date)
group by s.id) as t1
join company_server cs on cs.server = t1.s_id
right join companies c on c.id = cs.company
cross join(
select
sum(p1.copy * p1.page) sum2
from
print p1
where p1.date between cast('2020-1-12' as date) and cast('2020-1-15' as date)
) as c;
I did this query before you add fiddle, so may be name of column of mine is not same as you. Anyway, this is my solution, hope it help you.
select group_concat(c.name separator ',') as name_company,
ss.name,
sum_print as sum,
(sum_print/total) *100 as percentage
from companies c
inner join company_server cs on c.id = cs.company
right join servers ss on ss.id = cs.id
left join
(
select server,sum(page*copy) as sum_print, date from print
where date between CAST('2020-1-12' AS DATE) AND CAST('2020-1-15' AS DATE)
group by server
) tmp on tmp.server = ss.id
cross join
(select sum(page*copy) as total from print where date between CAST('2020-1-12' AS DATE) AND CAST('2020-1-15' AS DATE)) tmp2
group by id
Group and concat by comma, using GROUP_CONCAT .
You can reference this image for JOIN clause.
https://i.stack.imgur.com/6cioZ.png

How to count users with where condition previous row

please help me i have no idea for this...
I have table like this (create_at YYYY-MM-DD). ID is auto increment
-----------------------------------------------------------------
| ID | id_user | activity | create_at |
-----------------------------------------------------------------
| 1 | 10 | A | 2017-10-11 |
| 2 | 52 | A | 2017-10-11 |
| 3 | 41 | A | 2017-10-12 |
| 4 | 52 | A | 2017-10-12 |
| 5 | 41 | B | 2017-10-12 |
| 6 | 52 | B | 2017-10-13 |
| 7 | 10 | B | 2017-10-14 |
-----------------------------------------------------------------
How to get count (mysql) user who doing activity "B" after activity "A" in sameday create_at.. In this case, the result is 1 (IDUser 41).. How can i do this in mysql? thankyou
We could use a semi-join or a correlated subquery.
we start like this, users that are doing activity B
SELECT t.id_user
FROM table_like_this t
WHERE t.activity = 'B'
we can match those rows to users that are doing activity A on the "same day" with JOIN operation back to the same table...
SELECT t.id_user
FROM table_like_this t
JOIN table_like_this r
ON r.id_user = t.id_user
AND r.create_at = t.create_at
AND r.activity = 'A'
WHERE t.activity = 'B'
As far as whether activity B is occurring "after" activity A, I don't see any information in the table that can tell us that (we can't tell what time each activity A and B occurred, and can't determine which one was "after" the other.)
For testing, we can include other columns in the SELECT list, to verify which rows from t and r are being returned, if the matching is being done properly.
Once we are satisfied, we can replace the SELECT list, to get a count of distinct id_user
SELECT COUNT(DISTINCT t.id_user)
FROM ...
Note that this will collapse occurrences of id_user that performed activity A and B on several different days so that the id_user will be counted only once.
If we want to count the number of days for each id_user, and include each of those days in the count, the query would need to be changed.

How can I get a history like query on MySQL?

I'd like a little help here.
I'm building a database in MySQL where I will have a bunch of different activities. Each activity is part of a list.
So, I have the following tables on my database.
List
id
name
Activity
id
name
idList (FK to List)
I also want to know when each activity is finished (you can finish the same activity many times). To accomplish that, I have another table:
History
date
idActivity (FK to activity)
When the user finishes an activity, I add the id of this activity and the current time the activity was finished, to the History table.
I want to get the entire list with the date it was finished. When an activity has not been finished, I want it to show the date as null.
But, getting the list just once is easy. A simple Left Outer Join will do the trick. My issue here is that I want to get the ENTIRE list everytime a date appears on the history table.
This is what I'm looking for:
List:
id | name
1 | list1
Activity:
id | name | idList
1 | Activity1 | 1
2 | Activity2 | 1
3 | Activity3 | 1
4 | Activity4 | 1
5 | Activity5 | 1
6 | Activity6 | 1
History:
date | idActivity
17/07/14 | 1
17/07/14 | 3
17/07/14 | 4
17/07/14 | 6
16/07/14 | 2
16/07/14 | 3
16/07/14 | 5
Expected Result:
idActivity | idList | activityName | date
1 | 1 | Activity1 | 17/07/14
2 | 1 | Activity2 | NULL
3 | 1 | Activity3 | 17/07/14
4 | 1 | Activity4 | 17/07/14
5 | 1 | Activity5 | NULL
6 | 1 | Activity6 | 17/07/14
1 | 1 | Activity1 | NULL
2 | 1 | Activity2 | 16/07/14
3 | 1 | Activity3 | 16/07/14
4 | 1 | Activity4 | NULL
5 | 1 | Activity5 | 16/07/14
6 | 1 | Activity6 | NULL
The "trick" is to use a CROSS JOIN (or semi-cross join) operation with a distinct list of dates from the history table, to produce the set of rows you want to return.
Then a LEFT JOIN (outer join) to the history table to find the matching history rows.
Something like this:
SELECT a.id AS idActivity
, a.idList AS idList
, a.name AS activityName
, h.date AS `date`
FROM activity a
CROSS
JOIN ( SELECT s.date
FROM history s
GROUP BY s.date
) r
LEFT
JOIN history h
ON h.idActivity = a.id
AND h.date = r.date
ORDER
BY r.date
, a.id
That query gets the six rows from activity, and two rows (distinct values of date) from history (inline view aliased as r). The CROSS JOIN operation matches each of the six rows with each of the two rows, to produce a Cartesian product of 12 rows.
To get the rows returned in the specified order, we order by date, and then by activity.id.

How to query last records

so, if i have this table:
| ID | Date | Status | Value |
| 1 | 2-2-2012 | A | 5 |
| 2 | 3-4-2012 | B | 3 |
| 1 | 5-6-2012 | C | 1 |
| 2 | 1-1-2012 | D | 4 |
and I need to get total value and "most recent" status for every IDs, how to do the query? i tried using group by , but the somehow only oldest status shown in the query result.
I need to get the data became like this:
| ID | Date | Status |sum(Value)|
| 2 | 3-4-2012 | B | 7 |
| 1 | 5-6-2012 | C | 6 |
i'm a total newbie in this SQL thing, not an IT person, just because my boss ask to extract some data from our database....
thanks in advance...
Since you have not mentioned any RDBMs, the query below works on almost all RDBMS.
This uses a subquery which separately gets the latest date (assuming that the data type of date is really stored as DATE and not as a string) for every ID. The result of the subquery is then joined back on the table itself in order to get the other columns.
SELECT a.ID, a.Date, a.Status, b.TotalSum
FROM tableName a
INNER JOIN
(
SELECT ID, MAX(date) max_date, SUM(Value) totalSum
FROM tableName
GROUP BY ID
) b ON a.ID = b.ID AND
a.date = b.max_date
SQLFiddle Demo
If you are using mysql then this will work
SELECT id,date,status,sum(value)
FROM (select * from yourTable order by DATE desc ) t
group by ID
order by ID desc

Mysql include column with no rows returned for specific dates

I would like to ask a quick question regarding a mysql query.
I have a table named trans :
+----+---------------------+------+-------+----------+----------+
| ID | Date | User | PCNum | Customer | trans_In |
+----+---------------------+------+-------+----------+----------+
| 8 | 2013-01-23 16:24:10 | test | PC2 | George | 10 |
| 9 | 2013-01-23 16:27:22 | test | PC2 | Nick | 0 |
| 10 | 2013-01-24 16:28:48 | test | PC2 | Ted | 10 |
| 11 | 2013-01-25 16:36:40 | test | PC2 | Danny | 10 |
+----+---------------------+------+-------+----------+----------+
and another named customers :
+----+---------+-----------+
| ID | Name | Surname |
+----+---------+-----------+
| 1 | George | |
| 2 | Nick | |
| 3 | Ted | |
| 4 | Danny | |
| 5 | Alex | |
| 6 | Mike | |
.
.
.
.
+----+---------+-----------+
I want to view the sum of trans_in column for specific customers in a date range BUT ALSO include in the result set, those customers that haven't got any records in the selected date range. Their sum of trans_in could appear as NULL or 0 it doesn't matter...
I have the following query :
SELECT
`Date`,
Customer,
SUM(trans_in) AS 'input'
FROM trans
WHERE Customer IN('George','Nick','Ted','Danny')
AND `Date` >= '2013-01-24'
GROUP BY Customer
ORDER BY input DESC;
But this will only return the sum for 'Ted' and 'Danny' because they only have transactions after the 24th of January...
How can i include all the customers that are inside the WHERE IN (...) function, even those who have no transactions in the selected date range??
I suppose i'll have to join them somehow with the customers table but i cannot figure out how.
Thanks in advance!!
:)
In order to include all records from one table without matching records in another, you have to use a LEFT JOIN.
SELECT
t.`Date`,
c.name,
SUM(t.trans_in) AS 'input'
FROM customers c LEFT JOIN trans t ON (c.name = t.Customer AND t.`Date` >= '2013-01-24')
WHERE c.name IN('George','Nick','Ted','Danny')
GROUP BY c.name
ORDER BY input DESC;
Of course, I would mention that you should be referencing customer by ID, and not by name in your related table. Your current setup leads to information duplication. If the customer changes their name, you now have to update all related records in the trans table instead of just in the customer table.
try this
SELECT
`Date`,
Customer,
SUM(trans_in) AS 'input'
FROM trans
inner join customers
on customers.Name = trans.Customer
WHERE Customer IN('George','Nick','Ted','Danny')
GROUP BY Customer
ORDER BY input DESC;