Access Query for TopN by Group - ms-access

I've reviewed quite a bit of the sites (e.g. Allen Brown) for creating a query that produces top 5 (or N) values by group. I think I am getting hung up on the creation of a subquery because I'm referencing a previous query not a table.
I have a query started which counts by month the number of PIs (qryPICountbyMonth). Currently the below gives a data mismatch expression error:
SELECT qryPI.EventMonth, qryPI.PI_Issue, Count(qryPI.PI_Issue) AS
CountOfPI_Issue
FROM qryPI
GROUP BY qryPI.EventMonth, qryPI.PI_Issue
HAVING (((Count(qryPI.PI_Issue)) In (Select Top 5 [PI_Issue] From [qryPI]
Where [EventMonth]=[qryPI].[EventMonth] Order By [PI_Issue] Desc)))
ORDER BY qryPI.EventMonth DESC , Count(qryPI.PI_Issue) DESC;
It is built off a a separate query, qryPI
SELECT tblPI.EventDate, Format([EventDate],'yyyy-mm',1,1) AS EventMonth, tblPI.PI_Issue
FROM tblPI
WHERE (((tblPI.EventDate) >= #4/1/2016# And (tblPI.EventDate) <= #5/31/2016#))
GROUP BY tblPI.EventDate, Format([EventDate],'yyyy-mm',1,1), tblPI.PI_Issue;
I'm hoping to have it generate the top 5 counts of PI_Issue by EventMonth. If I haven't provided enough info let me know.

The problem (or at least a problem) is with [EventMonth]=[qryPI].[EventMonth]. Both your primary source and your lookup are called qryPI. You have to alias at least one of them.

You can't do this:
HAVING (((Count(qryPI.PI_Issue)) In (Select Top 5 [PI_Issue] From [qryPI]
count(field) will return an integer, not the set of values you're counting
I thought you could specify TopN in an Access query (it's in the properties), but you have to specify an order by clause, so it knows how to determine the TOP.
Have you tried:
SELECT top 5
tblPI.EventDate, Format([EventDate],'yyyy-mm',1,1) AS EventMonth, tblPI.PI_Issue
FROM tblPI
WHERE (((tblPI.EventDate) >= #4/1/2016# And (tblPI.EventDate) <= #5/31/2016#))
GROUP BY tblPI.EventDate, Format([EventDate],'yyyy-mm',1,1), tblPI.PI_Issue
order by PI_Issue
also not sure why you're using GROUP BY in your inner query as you're not returning any aggregate functions. Do you just need DISTINCT instead?
try:
SELECT distinct top 5
tblPI.EventDate, Format([EventDate],'yyyy-mm',1,1) AS EventMonth, tblPI.PI_Issue
FROM tblPI
WHERE (((tblPI.EventDate) >= #4/1/2016# And (tblPI.EventDate) <= #5/31/2016#))
order by PI_Issue
Actually, if I understand what you want, you need that GROUP BY instead of DISTINCT, but you also need to return the COUNT(*):
SELECT
Year([eventDate]) AS yr,
Month([eventDate]) AS mo,
tblPI.PI_issue,
Min(tblPI.eventDate) AS MinOfeventDate,
Max(tblPI.eventDate) AS MaxOfeventDate,
Count(tblPI.PI_issue) AS CountOfPI_issue
FROM tblPI
WHERE
(((tblPI.EventDate)>=#4/1/2016# And
(tblPI.EventDate)<#6/1/2016#))
GROUP BY
Year([eventDate]),
Month([eventDate]),
tblPI.PI_issue;
then you want to apply the TOPN function to cnt_issue in an outer query:
SELECT TOP 5 from qryInner
order by cnt_issue desc
except that TOP5 applies to all the query results, not the results grouped by yy/mm, which is what I'm assuming you want, so try this:
SELECT TOP 5
qry_inner.yr,
qry_inner.mo,
qry_inner.CountOfPI_issue,
qry_inner.PI_issue,
qry_inner.MinOfeventDate,
qry_inner.MaxOfeventDate
FROM qry_inner
ORDER BY qry_inner.CountOfPI_issue DESC;
As far as I know, Access doesn't allow you to select the top number of rows within a group, so you'll need to limit your outer query results to one month, then apply the TOP function.

Related

Why DISTINCT OF MYSQL does not remove duplication?

I am trying to make a query in MYSQL which contains compnay name, ticker and total return of the 20 stocks that performed worst from 2020-11-09 to 2020-11-10.
The firt problem DISTINCT does not help me remove duplication, I expected to remove duplication by permno, but the output is still duplicated as comnam and tickers are different for the same permno and ret, even though they are just in parent company and son company relationship.enter image description here
How should I get unique permno row?
SELECT DISTINCT permno, date, ret, ticker, comnam
FROM crsp.dsf as a
INNER JOIN crsp.dsenames as b
USING (permno)
WHERE date >='2020-11-09' AND date <='2020-11-10'
ORDER BY ret DESC
limit 20;
Second Problem should be cause by GROUP BY, when I am trying to add function to get total return of 20 stocks, always error nomatter how I tried :(
SELECT DISTINCT permno, date, ret, ticker, comnam
exp(sum(log(1+ret))) as "total return"
FROM crsp.dsf as a
INNER JOIN crsp.dsenames as b
USING (permno)
WHERE date >='2020-11-09' AND date <='2020-11-10'
group by permno
ORDER BY ret DESC
limit 20;

SQL sum(time) as time column does not sort in ASC and DESC order

I have been trying to sort the result of my SQL query but it's not working properly,
I have tried other solutions, but not worked, reference link
My Query
SELECT tournament_entry.TE_club, MAX(tournament_entry.TE_pegion) As TE_pegion,
concat(
floor(SUM(TIME_TO_SEC(tournament_entry.TE_flytime))/3600),':',
floor(SUM( TIME_TO_SEC(tournament_entry.TE_flytime))/60)%60,':',
SUM(TIME_TO_SEC(tournament_entry.TE_flytime))%60) as TE_flytime
FROM tournament_entry
WHERE tournament_entry.TE_tournament = '$Tname'
GROUP BY TE_club
ORDER BY TE_flytime DESC
Point 1, I have used CONCAT(), the reason to display the sum of time greater than 838:59:59 reference link
Step 1 followed,
ORDER BY TE_flytime ASC
results, which is not correct
Step 2 followed,
ORDER BY TE_flytime DESC
results, not correct, in this way it takes results greater than 1000 to the bottom (last),
maybe if there is any logic or way to use and it will sort it properly.
Step 3 followed,
ORDER BY 'cast(TE_flytime as float) time'
results, not correct, mixed up all results. reference link
NOTE: I want to sort it in a way that it displays the greatest result on top and so on
e.g
1034:46:0
1027:4:0
965:37:0
809:11:0
The calculated column TE_flytime is a string so if you sort by that column the sorting will be alphabetical.
You can order by the total number of seconds:
ORDER BY SUM(TIME_TO_SEC(tournament_entry.TE_flytime)) DESC

MySQL - get users who placed 25th order during period

I have users and orders tables with this structure (simplified for question):
USERS
userid
registered(date)
ORDERS
id
date (order placed date)
user_id
I need to get array of users (array of userid) who placed their 25th order during specified period (for example in May 2019), date of 25th order for each user, number of days to place 25th order (difference between registration date for user and date of 25th order placed).
For example if user registered in April 2018, then placed 20 orders in 2018, and then placed 21-30th orders in Jan-May 2019 - this user should be in this array, if he placed 25th (overall for his account) order in May 2019.
How I can do this with MySQL request?
Sample data and structure: http://www.sqlfiddle.com/#!9/998358 (for testing you can get 3rd order as ex., not 25th, to not add a lot of sample data records).
One request is not required - if this can't be done in one request, few is possible and allowed.
You can use a correlated subquery to get the count of orders placed before the current one by a user. If that's 24 the current order is the 25th. Then check if the date is in the desired range.
SELECT o1.user_id,
o1.date,
datediff(o1.date, u1.registered)
FROM orders o1
INNER JOIN users u1
ON u1.userid = o1.user_id
WHERE (SELECT count(*)
FROM orders o2
WHERE o2.user_id = o1.user_id
AND o2.date < o1.date
OR o2.date = o1.date
AND o2.id < o1.id) = 24
AND o1.date >= '2019-01-01'
AND o1.date < '2019-06-01';
The basic inefficient way of doing this would be to get the user_id for every row in ORDERS where the date is in your target range AND the count of rows in ORDERS with the same user_id and a lower date is exactly 24.
This can get very ugly, very quickly, though.
If you're calling this from code you control, can't you do it from the code?
If not, there should be a way to assign to each row an index describing its rank among orders for its specific user_id, and select from this all user_id from rows with an index of 25 and a correct date. This will give you a select from select from select, but it should be much faster. The difficulty here is to control the order of the rows, so here are the selects I envision:
Select all rows, order by user_id asc, date asc, union-ed to nothing from a table made of two vars you'll initialize at 0.
from this, select all while updating a var to know if a row's user_id is the same as the last, and adding a field that will report so (so for each user_id the first line in order will have a specific value like 0 while the other rows for the same user_id will have a 1)
from this, select all plus a field that equals itself plus one in case the first added field is 1, else 0
from this, select the user_id from the rows where the second added field is 25 and the date is in range.
The union thingy is only necessary if you need to do it all in one request (you have to initialize them in a lower select than the one they're used in).
Edit: Well if you need the date too you can just select it along with the user_id, but calculating the number of days in sql will be a pain. Just join the result table to the users table and get both the date of 25th order and their date of registration, you'll surely be able to do the difference in code.
I'll try building an actual request, however if you want to truly understand what you need to make this you gotta read up on mysql variables, unions, and conditional statements.
"Looks too complicated. I am sure that this can be done with current DB structure and 1-2 requests." Well, yeah. Use the COUNT request, it will be easy, and slow as hell.
For the complex answer, see http://www.sqlfiddle.com/#!9/998358/21
Since you can use multiple requests, you can just initialize the vars first.
It isn't actually THAT complicated, you just have to understand how to concretely express what you mean by "an user's 25th command" to a SQL engine.
See http://www.sqlfiddle.com/#!9/998358/24 for the difference in days, turns out there's a method for that.
Edit 5: seems you're going with the COUNT method. I'll pray your DB is small.
Edit 6: For posterity:
The count method will take years on very large databases. Since OP didn't come back, I'm assuming his is small enough to overlook query speed. If that's not your case and let's say it's 10 years from now and the sqlfiddle links are dead; here's the two-queries solution:
SET #PREV_USR:=0;
SELECT user_id, date_ FROM (
SELECT user_id, date_, SAME_USR AS IGNORE_SMUSR,
#RANK_USR:=(CASE SAME_USR WHEN 0 THEN 1 ELSE #RANK_USR+1 END) AS RANK FROM (
SELECT orders.*, CASE WHEN #PREV_USR = user_id THEN 1 ELSE 0 END AS SAME_USR,
#PREV_USR:=user_id AS IGNORE_USR FROM
orders
ORDER BY user_id ASC, date_ ASC, id ASC
) AS DERIVED_1
) AS DERIVED_2
WHERE RANK = 25 AND YEAR(date_) = 2019 AND MONTH(date_) = 4 ;
Just change RANK = ? and the conditions to fit your needs. If you want to fully understand it, start by the innermost SELECT then work your way high; this version fuses the points 1 & 2 of my explanation.
Now sometimes you will have to use an API or something and it wont let you keep variable values in memory unless you commit it or some other restriction, and you'll need to do it in one query. To do that, you put the initialization one step lower and make it so it does not affect the higher statements. IMO the best way to do this is in a UNION with a fake table where the only row is excluded. You'll avoid the hassle of a JOIN and it's just better overall.
SELECT user_id, date_ FROM (
SELECT user_id, date_, SAME_USR AS IGNORE_SMUSR,
#RANK_USR:=(CASE SAME_USR WHEN 0 THEN 1 ELSE #RANK_USR+1 END) AS RANK FROM (
SELECT DERIVED_4.*, CASE WHEN #PREV_USR = user_id THEN 1 ELSE 0 END AS SAME_USR,
#PREV_USR:=user_id AS IGNORE_USR FROM
(SELECT * FROM orders
UNION
SELECT * FROM (
SELECT (#PREV_USR:=0) AS INIT_PREV_USR, 0 AS COL_2, 0 AS COL_3
) AS DERIVED_3
WHERE INIT_PREV_USR <> 0
) AS DERIVED_4
ORDER BY user_id ASC, date_ ASC, id ASC
) AS DERIVED_1
) AS DERIVED_2
WHERE RANK = 25 AND YEAR(date_) = 2019 AND MONTH(date_) = 4 ;
With that method, the thing to watch for is the amount and the type of columns in your basic table. Here orders' first field is an int, so I put INIT_PREV_USR in first then there are two more fields so I just add two zeroes with names and call it a day. Most types work, since the union doesn't actually do anything, but I wouldn't try this when your first field is a blob (worst comes to worst you can use a JOIN).
You'll note this is derived from a method of pagination in mysql. If you want to apply this to other engines, just check out their best pagination calls and you should be able to work thinks out.

How to get the top records and have a different condition in sql?

I need to have the top most record of 'RouteID' who has also the most 'Economy Price' in the total.
I already tried this code:
SELECT RouteID
, Count (RouteID )
FROM [Session6].[dbo].[Schedules]
Group
by RouteID
Order
by count(RouteID ) DESC
But the result is this:
Result
The result are only the top most count of 'RouteID' not the most sum of 'EconomyPrice'
This is the table:
Table
Then why not simply sum up EconomyPrice instead counting the RouteIDs?
SELECT
RouteID,
SUM(EconomyPrice) AS TotalEconomyPrice
FROM
[Session6].[dbo].[Schedules]
GROUP BY
RouteID
ORDER BY
SUM(EconomyPrice) DESC
You cannot have the routes with the highest count and the highest total economy price at the top at the same time, since these conditions might be met by different route Ids. This is not a limitation of SQL. It is simply illogical.
You could also use the average AVG(EconomyPrice) as measure.

SQL comparison within max function

I'm trying to get a list of 20 events grouped by their Ids and sorted by whether they are in progress, pending, or already finished. The problem is that there are events with the same id that include finished, pending, and in progress events and I want to have 20 distinct Ids in the end. What I want to do is group these events together but if one of them is in progress then sort that group by that event. So basically I want to sort by the latest end time that is also before now().
What I have so far is something like this where end and start are end/start times. I'm not sure if what is inside max() is behaving how I should expect.
select * from event_schedule as t1
JOIN (
SELECT DISTINCT(event_id) as e
from event_schedule
GROUP BY event_id
order by MAX(end < unix_timestamp(now())) asc,
MIN(start >= unix_timestamp(now())) asc,
MAX(start) desc
limit 0, 20
)
as t2 on (t1.event_id = t2.e)
This results in some running / pending events to be mixed around in order when I want them to be in the order running -> pending -> Ended.
I would suggest to first create a view in order to not get an overcomplicated SELECT statement:
CREATE VIEW v_event_schedule AS
SELECT *,
CASE
WHEN end < unix_timestamp(now())
THEN 1
WHEN start > unix_timestamp(now())
THEN 2
ELSE 3
END AS category
FROM event_schedule;
This view v_event_schedule returns an extra column, in addition to the columns of event_schedule, which represents the priority of the category (running, pending, past):
running (in progress)
pending (future)
past
Then the following will do what you want:
SELECT a.*
FROM v_event_schedule a
INNER JOIN (
SELECT id,
MIN(category) category
FROM v_event_schedule b
GROUP BY id
) b
ON a.id = b.id
AND a.category = b.category
ORDER BY category,
start DESC
LIMIT 20;
The ORDER BY can be further adapted to your needs as to how you want to sort within the same category. I added start DESC as that seemed what you were doing in your attempt.
About the original ORDER BY
You had this:
order by MAX(end < unix_timestamp(now())) asc,
MIN(start >= unix_timestamp(now())) asc,
The expressions you have there evaluate to boolean values, and both elements in the ORDER BY each divide the groups into two sections, one for false and one for true, so in total 4 groups.
The first of the two will order IDs first that have no record with an end value in the past, because only then the boolean expression is always false which is the only way to make the MAX of them false as well.
Now let's say for the same ID you have both records that have an end date in the future as well as records with an end date in the past. In that case the MAX aggregates to true, and so the id will be sorted secondary. This is not intended, as this ID might have a "running" record.
I did not look into making your query work based on such aggregates on boolean expressions. It requires some time to understand what they are doing. A CASE WHEN to determine the category with a number really makes the SQL a lot easier to understand, at least to me.