I have 8 separate tables. Each table has id, a datetime field and some text.
I am presenting data combined from all tables on a single page in a timeline view, most recent entries are at the top, and entries from each table are mixed in this timeline.
Now the hard part - I need to add pagination to this website, so on each page I want to show exactly 10 days (except for the last page, that may have less than 10).
Each table may have variable number of rows.
I've been struggling with this for quite some time but have not yet come up with an elegant solution.
Here's an example (I'll pick only two tables for this example to make it simpler).
tableA
Apr 1 | a1
Apr 5 | a2
Apr 7 | a3
tableB
Apr 2 | b1
Apr 2 | b2
Apr 5 | b3
Apr 6 | b4
Global timeline would look like this
Apr 7 a3
Apr 6 b4
Apr 5 a2
Apr 5 b3
Apr 2 b2
Apr 2 b1
Apr 1 a1
And if each page shows only 3 days, I need for it to look like this:
--- p1 ---
Apr 7 a3
Apr 6 b4
Apr 5 a2
--- p2 ---
Apr 5 b3
Apr 2 b2
Apr 2 b1
--- p3 ---
Apr 1 a1
The problem is - I can't figure out a way to query for this data in an elegant way. Here's some live query that I've been messing with:
select date(d.entryTime) entryDate, date(wc.changeTime) wcDate
from diary_entry d
join water_change wc
on d.aquariumId = wc.aquariumId
where d.aquariumId = 2
group by entryDate
order by entryDate
limit 10, 5
so, for one table I have this query:
select date(d.entryTime) entryDate
from diary_entry d
where d.aquariumId = 2
group by entryDate
it yields 13 results
entryDate
2012-01-13
2012-01-14
2012-01-25
2012-01-26
2012-01-31
2012-02-04
2012-02-17
2012-02-20
2012-02-25
2012-03-17
2012-03-31
2012-04-01
2012-04-06
and for another:
select date(wc.changeTime) changeDate
from water_change wc
where wc.aquariumId = 2
group by changeTime
it yields 8 results
2012-01-22
2012-01-26
2012-02-17
2012-02-25
2012-03-04
2012-03-10
2012-04-04
2012-04-24
There are three common days between the two
2012-01-26
2012-02-17
2012-02-25
So the query that I need to produce would have to have
13 + 8 - 3 rows = 18 rows
And solution is found!
(select date(d.entryTime) activityDate
from diary_entry d
where d.aquariumId = 1
group by activityDate)
union
(select date(wc.changeTime) activityDate
from water_change wc
where wc.aquariumId = 1
group by activityDate
order by activityDate)
limit 10, 10
Query it using a Union.
SELECT TheDate,
TheText
FROM Table_A
WHERE [your critera]
UNION
SELECT TheDate,
TheText
FROM Table_B
WHERE [your critera]
...
If you need to only select the distinct ones, you can then wrap this as follows:
Select Distinct
TheDate,
TheText
From
(
SELECT TheDate,
TheText
FROM Table_A
WHERE [your critera]
UNION
SELECT TheDate,
TheText
FROM Table_B
WHERE [your critera]
...
);
It sounds like you've got the select and joins in place and are just missing the pageination. If that's the case, your script will have to keep track of what page you're on, and each click on "next page" will have to tell MySQL where to return the next 10 records from by appending this to your SELECT query:
SELECT...JOIN...WHERE...ORDER BY...LIMIT 20, 10
Will return the next 10 rows beginning with the 21st row in the full record set
Related
I'm trying to find admin activity within the last 30 days.
The accounts table stores the user data (username, password, etc.)
At the end of each day, if a user had logged in, it will create a new entry in the player_history table with their updated data. This is so we can track progress over time.
accounts table:
id
username
admin
1
Michael
4
2
Steve
3
3
Louise
3
4
Joe
0
5
Amy
1
player_history table:
id
user_id
created_at
playtime
0
1
2021-04-03
10
1
2
2021-04-04
10
2
3
2021-04-05
15
3
4
2021-04-10
20
4
5
2021-04-11
20
5
1
2021-05-12
40
6
2
2021-05-13
55
7
3
2021-05-17
65
8
4
2021-05-19
75
9
5
2021-05-23
30
10
1
2021-06-01
60
11
2
2021-06-02
65
12
3
2021-06-02
67
13
4
2021-06-03
90
The following query
SELECT a.`username`, SEC_TO_TIME((MAX(h.`playtime`) - MIN(h.`playtime`))*60) as 'time' FROM `player_history` h, `accounts` a WHERE h.`created_at` > '2021-05-06' AND h.`user_id` = a.`id` AND a.`admin` > 0 GROUP BY h.`user_id`
Outputs this table:
Note that this is just admin activity, so Joe is not included in this data.
from 2021-05-06 to present (yy-mm-dd):
username
time
Michael
00:20:00
Steve
00:10:00
Louise
00:02:00
Amy
00:00:00
As you can see this from data, Amy's time is shown as 0 although she has played for 10 minutes in the last month. This is because she only has 1 entry starting from 2021-05-06 so there is no data to compare to. It is 0 because 10-10 = 0.
Another flaw is that it doesn't include all activity in the last month, basically only subtracts the highest value from the lowest.
So I tried fixing this by comparing the highest value after 2021-05-06 to their most previous login before the date. So I modified the query a bit:
SELECT a.`Username`, SEC_TO_TIME((MAX(h.`playtime`) - (SELECT MAX(`playtime`) FROM `player_history` WHERE a.`id` = `user_id` AND `created_at` < '2021-05-06'))*60) as 'Time' FROM `player_history` h, `accounts` a WHERE h.`created_at` >= '2021-05-06' AND h.`user_id` = a.`id` AND a.`admin` > 0 GROUP BY h.`user_id`
So now it will output:
username
time
Michael
00:50:00
Steve
00:50:00
Louise
00:52:00
Amy
00:10:00
But I feel like this whole query is quite inefficient. Is there a better way to do this?
I think you want lag():
SELECT a.username,
SEC_TO_TIME(SUM(h.playtime - COALESCE(h.prev_playtime, 0))) as time
FROM accounts a JOIN
(SELECT h.*,
LAG(playtime) OVER (PARTITION BY u.user_id ORDER BY h.created_at) as prev_playtime
FROM player_history h
) h
ON h.user_id = a.id
WHERE h.created_at > '2021-05-06' AND
a.admin > 0
GROUP BY a.username;
In addition to the LAG() logic, note the other changes to the query:
The use of proper, explicit, standard, readable JOIN syntax.
The use of consistent columns for the SELECT and GROUP BY.
The removal of single quotes around the column alias.
The removal of backticks; they just clutter the query, making it harder to write and to read.
My query is supposed to look for and extract data for all patients who haven't had an entry for 18 months.
oID Date Crea
1 2014-09-22 153
1 2012-10-15 365
2 2014-08-02 212
2 2015-02-18 245
3 2016-03-19 438
I extract the latest entry for oID 1 because they have no entries since 2014-09-22 - but I need to omit all other data, even though oID 2 had an entry 18 months ago if they had had one since. How can I accomplish this?
Additionally I need to return any patient with no entries:
oID Date Crea
1 2014-09-22 153
1 2012-10-15 365
2 2014-08-02 212
2 2015-02-18 245
3 2016-03-19 438
4 514
This doesn't seem to work:
select ifnull(date_column , 'No visit')
You could try a double anti-pattern:
SELECT e1.* /* Select entry.. */
FROM entry e1
LEFT JOIN entry e2 /* Find later entries for same oid */
ON e2.oid = e1.oid
AND e2.date > e1.date
LEFT JOIN entry e3 /* Find entries for same oid within 18 months */
ON e3.oid = e1.oid
AND e3.date > CURDATE() - INTERVAL 18 MONTH
WHERE e2.oid IS NULL /* e2 doesn't exist */
AND e3.oid IS NULL /* e3 doesn't exist */
See SQL Fiddle
Use TIMESTAMPDIFF() for finding difference between two dates.
Try below query
SELECT oID
FROM table_name
WHERE 18 < (SELECT TIMESTAMPDIFF(MONTH, date, date)
FROM table_name a, table_name b
WHERE a.oID = b.oID);
I need to extract the required fields from a table along with relevant time stamp
SELECT * FROM Glm_Test.LicenseUsage where FeatureId='2';
Output :
VendorId,FeatureId,Total_Lic_Installed,Total_Lic_Used,Reserved,CurrentTime
1 2 106 19 67 2015-12-15 15:00:05
1 2 106 19 67 2015-12-15 15:02:02
1 2 106 19 69 2015-12-15 15:04:02
1 2 106 19 67 2015-12-15 15:06:01
1 2 106 20 67 2015-12-15 15:08:02
select VendorId,FeatureId,Total_Lic_Installed,Max(Total_Lic_Used),Reserved,CurrentTime from Glm_Test.LicenseUsage where FeatureId= '2' group by VendorId,FeatureId;
output:
1 2 106 20 69 2015-12-15 15:00:05
In the above 2 queries
1st query lists all entries from the table
and i want second query to return time stamp for the MAX value of column Total_Lic_Used but somehow it is returning me only timestamp of the first entry.
Help is much appreciated.
Selecting the columns which are not part of an aggregation function like count/max/min/sum... or not in group by clause will give unexpected results:
Other RBBMS wont allow these statements(gives error like):
sql server ==> the select list because it is not contained in either
an aggregate function or the GROUP BY clause
Oracle ==>not a GROUP BY expression
You can do this by a sub query and join
select
a.VendorId,
a.FeatureId,
a.Total_Lic_Installed,
b.max_Total_Lic_Used,
a.Reserved,
a.CurrentTime
from Glm_Test.LicenseUsage a
join (
select
VendorId,
FeatureId,
Max(Total_Lic_Used) max_Total_Lic_Used
from Glm_Test.LicenseUsage
where FeatureId = '2'
group by VendorId, FeatureId
) b
on a.VendorId = b.VendorId and
a.FeatureId = b.FeatureId and
a.Total_Lic_Used = b.max_Total_Lic_Used
sql fiddle demo
You can try this also;
select
`VendorId`,
`FeatureId`,
`Total_Lic_Installed`,
`Total_Lic_Used`,
`Reserved`,
`CurrentTime`
from Glm_Test.LicenseUsage
order by Total_Lic_Used desc
limit 1
demo
I'm trying to find the number of orders that were open for each week. An order that is open for multiple weeks should be included in each week's count that it was open. The data looks something like below
id open_dt close_dt
1 2014-01-01 07:00:00 2014-01-01 07:00:00
2 2014-01-01 07:00:00 2014-01-02 07:00:00
3 2014-01-02 07:00:00 2014-01-09 07:00:00
4 2014-01-08 07:00:00 NULL
NULL close_dt counts as still open and should appear in each week since it was opened
My query looks like below however it isn't returning the numbers I'm expecting:
SELECT YEAR(open_dt) AS year, WEEK(open_dt) AS week, count(*) 'open'
FROM table
WHERE open_dt >= week(open_dt)
OR
(
close_dt > week(open_dt)
OR close_dt IS NULL
)
GROUP BY YEAR(open_dt), WEEK(open_dt)
I'm trying to get results like below:
year week open
2014 1 3
2014 2 2
2014 3 1
...
Appreciate any tips or guidance.
This is a case where it helps to have a calendar table or list of weeks. Let me assume that you have at least one open in each week:
select yw.y, yw.w, count(t.open_dt) as "Open"
from (select distinct year(open_dt) as y, week(open_dt) as w,
year(open_dt) * 100 + week(open_dt) as yw
from table t
) yw left outer join
table t
on yw.yw >= year(open_dt)*100 + week(open_dt) and
(yw.yw <= year(close_dt)*100 + week(close_dt) or close_dt is null)
group by yw.y, yw.w
order by yw.y, yw.w;
id qid answer date answer_userKey
72 2 2 2012-07-30 00:00:00 1
71 1 4 2012-07-30 00:00:00 1
70 2 2 2012-07-30 00:00:00 2
69 1 4 2012-07-30 00:00:00 2
68 2 2 2012-07-30 00:00:00 3
67 1 3 2012-07-30 00:00:00 3
66 2 2 2012-07-31 00:00:00 4
65 1 4 2012-07-31 00:00:00 4
64 2 2 2012-07-31 00:00:00 5
Here's my sample table, I need to get all data + all distinct answer_userKeys for every date like this
date DISTINCT(answer_userKey)
2012-07-30 3
2012-07-31 2
As well as all the single values in a normal associative array like you get when you do
SELECT * FROM tbl_data
WHERE date BETWEEN '2012-07-29' AND '2012-08-01'
Tried everything here :(
Try this:
SELECT DATE(date) dte, COUNT(DISTINCT answer_userKey) cnt
FROM tbl_data
WHERE DATE(date) BETWEEN '2012-07-29' AND '2012-08-01'
GROUP BY dte;
i hope this is your requirement..this one works in oracle
select to_char(date,'yyyy-mm-dd') date
,count(distinct answer_userKey) DISTINCT(answer_userKey)
from table_name
group by to_char(date,'yyyy-mm-dd')
If you need both the detail and aggregate data, then you will likely either need to perform two queries to get these two different sets of data, or simply query the details and build the aggregation within whatever language you are using (perhaps in a multidimensional array). For example in pseudo-code:
Query: SELECT * FROM tbl_data WHERE date BETWEEN '?' AND '?'
array; // multidimensional associative array to be populated
while ([fetch next row from result set as row] ) {
array[row['date']][row['answer_userKey']] = row
}