selecting max record in mysql - mysql

I have a table of multiple transactions. I am trying to get the row of the last transaction.
I am using the following:
select n.AccountNumber, max(PostDate), f.TransAmt
from mp_cycle n, fintrans f
where n.AccountNumber = f.AccountNumber
and TransCode >= '50'
and TransCode <= '59'
group by n.AccountNumber
This is returning the last date for a particular account, but the TransAmt is not for the same record.
ie:
Acct # Date Amt
1 1/1 10.00
1 1/2 11.00
1 1/3 12.00
2 1/2 20.00
2 1/3 21.00
2 1/4 22.00
My select will return the last date for each account, so 1/3 for act # 1 and 1/4 for act # 2, but the Amt field is not the amt that goes with that record.
Any assistance will be greatly appreciated.

There are many ways to solve this problem, one is by joining extra subquery which separate gets the latest PostDate for every AccountNumber. The result of the subquery will then be joined on the other table provided that it should match on two columns: AccountNumber and PostDate.
SELECT a.*, b.*
FROM mp_cycle a
INNER JOIN fintrans b
ON a.AccountNumber = b.AccountNumber
INNER JOIN
(
SELECT AccountNumber, MAX(PostDate) max_date
FROM fintrans
GROUP BY AccountNumber
) c ON b.AccountNumber = c.AccountNumber AND
b.PostDate = c.max_date
-- WHERE ..your conditions here..

Related

MySQL (version lower then 8.0) : Select where date value and return a row of dates from table of milestone date

Table a (milestone) :
Pk_id
emp_id
milestone_date
1
2
2022-01-17
2
2
2021-03-23
3
2
2018-06-29
4
3
2018-01-15
5
3
2016-02-17
...
...
...
Table b :
PK_id
Emp_id
ins_date
1
2
2022-01-20
2
2
2019-03-30
3
3
2017-06-29
My problem is I want to select the floor date row of Table A.but I don't know how to use the command.
The method is, in each row of Table B. Determine the column of ins_date and emp_id. Then use that value to select the date in Table A that is the floor date (I don't know how to explain it. But try to understand in the example below). and then show the results in the list.
for example
Row 1: Table B. The ins_date value is '2022-01-20' and emp_id is 2. Consider emp_id = 2 and the floor date of '2022-01-20' (Because Greater than 2022-01-20 ), so select the item PK_ID = 1.
Row 2: Table B. The ins_date value is '2019-03-30' and emp_id is 2. Consider emp_id = 2 and the floor date of '2018-06-29' (Because Greater than 2018-06-29 but less than 2021-03-23 ), so select item PK_ID = 3.
Row 3: Table B. The ins_date value is '2017-06-29' and emp_id is 3. Consider emp_id = 3 and the floor date of '2016-02-17' (Because Greater than 2016-02-17 but less than 2018-01-15 ), so select the item PK_ID = 5.
Thank you and Sorry my English.
P.S. I don't know how I can explain the question and details. But if you edit the text to make it easier to understand. I strongly allow editing.
You are looking for the maximum milestone_date that is less or equal to an ins_date.
Two ways to get that date:
option #1
select b.*, max(a.milestone_date) as floor_milestone_date
from b
join a on a.emp_id = b.emp_id and a.milestone_date <= b.ins_date
group by b.pk_id
order by b.pk_id;
option #2
select
b.*,
(
select max(a.milestone_date)
from a
where a.emp_id = b.emp_id and a.milestone_date <= b.ins_date
) as floor_milestone_date
from b;
The difference between the two queries above: When there is no floor milestone date for an ins_date, then the first query doesn't return the b row, while the second query returns the b row with a null milestone date.
Now, if you want more information from table a, then join the table to one of the queries above.
The final query
select ab.*, a.pk_id
from
(
select b.*, max(a.milestone_date) as floor_milestone_date
from b
join a on a.emp_id = b.emp_id and a.milestone_date <= b.ins_date
group by b.pk_id
) ab
join a on a.emp_id = ab.emp_id and a.milestone_date = ab.floor_milestone_date
order by ab.pk_id;

MySQL How to get the row with max date when joining multiple tables?

My goal is the get a list of current prices and prices at the time of whatever date is given. The price as of today is always product.price. Each time a new price is set, an entry is added to product_audit and revinfo.
If we are looking for what the prices were on 2020-11-31, it would return:
num CurrentPrice OldPrice
--------------------------------------
1001 100 175
1030 110 100
2010 150 130
EDIT FOR CLARIFICATION: My intention is to get what the price was on a specific day. So OldPrice is actually the newest entry in Product_aud/revinfo that is before or on the set date (in this case, 2020-11-31). Looking specifically at code 1001, the price was changed on 2020-08-02, 2020-09-26, and 2020-01-08. If we are looking at 2020-11-31, that means it should grab 2020-09-26 because it is the soonest date before then. This means the price of 1001 on 2020-11-31 was 175.
There are three tables: Product, product_audit, revinfo
Everytime the price is changed, an entry is added to product_audit with the new price and a reference to a new entry in revinfo that has the date/time. Revinfo contains entries for other audit tables mixed in.
product.id = product_audit.id
product_audit.rev = revinfo.id
product
id num price
------------------------
1 1001 100
2 1030 110
3 2010 150
product_audit
id rev price
------------------------
1 1 200
1 3 175
1 6 100
2 2 100
2 7 110
3 4 130
3 5 120
3 8 150
revinfo
id timestamp
-------------------
1 2020-08-02
2 2020-09-25
3 2020-09-26
4 2020-11-12
5 2020-12-20
6 2021-01-08
7 2021-01-09
8 2021-01-23
Of course this just returns the oldest price from product_audit:
SELECT product.num, product.price AS CurrentPrice, product_audit.price AS OldPrice
FROM product
LEFT JOIN product_audit ON product_audit.id = product.id
LEFT JOIN revinfo ON revinfo.id = product_audit.rev
WHERE rev.timestamp <= "2020-11-31"
GROUP BY product.id
I tried nesting joins like this based on some stuff I was reading, but quickly realized it still wasn't going to get the right price:
SELECT product.id, product.num, product.price AS CurrentPrice, revisions.price AS OldPrice
FROM product
LEFT JOIN (SELECT product_audit.id AS id, product_audit.price AS price, MAX(revinfo.timestamp) AS timestamp
FROM product_audit
LEFT JOIN revinfo ON product_audit.rev = revinfo.id
WHERE revinfo.timestamp <= $DATE{Date}
GROUP BY product_aud.id) AS revisions ON revisions.id = product.id
I can't seem to think of how to get to that last step. Some sort of WHERE timestamp = (SELECT...) maybe? But I haven't been able to figure that out.
Also, just a heads up, I'm limited to statements that start with SELECT because of permissions. I can't add functions or anything like that.
I had to assume how we were getting the "old" price, and my assumption was that you wanted the "earliest" revision record, so I used Row_number and a derived table to get that record and then use it in the join constraint for the revision table... not exactly sure what your logic is, but here is a fiddle with the resultset that matches your "desired results"
SELECT product.num, product.price AS CurrentPrice, product_audit.price AS OldPrice
FROM product
LEFT JOIN (select p.price, p.id, p.rev,
ROW_NUMBER() over (partition by p.id order by p.rev asc) as rn
From product_audit p
) AS product_audit ON product_audit.id = product.id
and product_audit.rn = 1
LEFT JOIN revinfo ON revinfo.id = product_audit.rev
WHERE revinfo.timestamp <= '2020-11-31';
https://www.db-fiddle.com/f/fbvrgo2gRLoBPhgwQnuvY9/3
WITH cte AS ( SELECT product.num,
product.price CurrentPrice,
product_audit.price OldPrice,
ROW_NUMBER() OVER (PARTITION BY product.num
ORDER BY revinfo.`timestamp` DESC) rn
FROM product
JOIN product_audit ON product_audit.id = product.id
JOIN revinfo ON revinfo.id = product_audit.rev
WHERE revinfo.`timestamp` <= #date
)
SELECT num, CurrentPrice, OldPrice
FROM cte
WHERE rn = 1;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=a276ec8ad89e3c2f3aaeee411072fa3e

SQL : Show date when product was present in 6 months of data

I am pretty new to sql but have a problem I can't seem to find an answer to yet.
I need to find the month where the number of months a product in a certain segment showed up in a report is equal to 6. However, a product may not show up in sequential months, shows up multiple times a month, and some products have not beensold in 6 months yet.
I have a database with the following attributes:
Entry_Id Product_Name Sold_Date Payment_Amount Segment
======================================================================
112341 ABC Product 2017/12/20 10.50 Segment 1
112342 123 Product 2016/08/21 11.20 Segment 1
112343 ABC Product 2017/12/20 11.50 Segment 1
112344 123 Product 2017/08/21 11.20 Segment 1
112345 123 Product 2017/06/12 11.20 Segment 1
112346 123 Product 2016/06/21 11.20 Segment 1
112347 123 Product 2016/05/02 11.20 Segment 1
112348 123 Product 2015/04/01 11.20 Segment 1
112348 123 Product 2018/01/05 11.20 Segment 1
I would like to get something to the following effect
Product_Name Date where N = 6 segment
=================================================
ABC Product N/A Segment 1
123 Product 2018/01/05 Segment 1
The Day of month does not matter, just the month where the the number of months it has shown up in is equal to 6.
This is my first question and I will be as active as possible, please ask any clarifying questions.
Thank you!
Use GROUP BY and COUNT() to count the number of months that each product was sold in, and HAVING to filter the results.
SELECT t1.product_name, max_date, segment
FROM yourTable AS t1
JOIN (
SELECT product_name, MAX(sold_date) AS max_date
FROM yourTable
GROUP BY product_name
HAVING COUNT(DISTINCT YEAR(sold_date), MONTH(sold_date)) >= 6
) AS t2 ON t1.product_name = t2.product_name AND t1.sold_date = t2.max_date
The t2 subquery finds all the products that were sold in at least 6 different months. Joining that with the table finds the row with the last date, so its segment column can be selected.
If you want to include the products that weren't sold in at least 6 months, with N/A in that column, you can move the test out of the subquery.
SELECT product_name, IF(month_count >= 6, max_date, "N/A") AS last_date, segment
FROM yourTable AS t1
JOIN (
SELECT product_name, MAX(sold_date) AS max_date, COUNT(DISTINCT YEAR(sold_date), MONTH(sold_date)) as month_count
FROM yourTable
GROUP BY product_name
) AS t2 ON t1.product_name = t2.product_name AND t1.sold_date = t2.max_date
You can do this with a correlated subquery:
select t.*
from t
where 6 = (select count(distinct year(t2.sold_date), month(t2.sold_date))
from t t2
where t2.segment = t.segment and and t2.product = t.product and
t2.sold_date <= t.sold_date
);
Because of the count(distinct), this will show all records from the 6th month. Also, this is not particularly efficient, but it does use just standard SQL.
You can summarize this to one row per segment/product:
select t.segment, t.product, min(t.sold_date)
from t
where 6 = (select count(distinct year(t2.sold_date), month(t2.sold_date))
from t t2
where t2.segment = t.segment and and t2.product = t.product and
t2.sold_date <= t.sold_date
)
group by t.segment, t.product;

MySQL join with a subquery

I have three tables and am trying to get info from two and then perform a calculation on the third and display all the results in one query.
The (simplified) tables are:
table: employee_work
employee_id name
1 Joe
2 Bob
3 Jane
4 Michelle
table: carryover
employee_id days
1 5
2 10
3 3
table: timeoff
employee_id time_off_type days
1 Carryover 2
1 Leave 3
1 Carryover 1
2 Sick 4
2 Carryover 4
3 Leave 1
4 Sickness 4
The results I would like are:
employee_id, carryover.days, timeoff.days
1 5 3
2 10 4
3 3 0
However when I run the query, whilst I get the correct values in columns 1 and 2, I get the same number repeated in the third column for all entries.
Here is my query:
Select
employee_work.employee_id,
carryover.carryover,
(SELECT SUM(days) FROM timeoff WHERE timeoff.time_off_type = 'Carryover'
AND timeoff.start_date>='2013-01-01') AS taken
From
carryover Left Join
employee_work On employee_work.employee_id = carryover.employee_id Left Join
timeoff On employee_work.employee_id = timeoff.employee_id Left Join
Where
carryover.carryover > 0
Group By
employee_work.employee_id
I have tried to group by in the sub query but I then get told "Subquery returns more than one row" - how can I ensure that the sub query is respecting the join so it only looks at each employee at a time so I get my desired results?
The answer to your question is to use a correlated subquery. You don't need to mention the timeoff table twice in this case:
Select
employee_work.employee_id,
carryover.carryover,
(SELECT SUM(days)
FROM timeoff
WHERE timeoff.time_off_type = 'Carryover' and
timeoff.start_date>='2013-01-01' and
timeoff.employee_id = employee_work.employee_id
) AS taken
From
carryover Left Join
employee_work On employee_work.employee_id = carryover.employee_id
Where
carryover.carryover > 0
Group By
employee_work.employee_id;
An alternative structure is to do the grouping for all employees in the from clause. You can also remove the employee_work table, because it does not seem to be being used. (You can use carryover.employee_id for the id.)
Select co.employee_id, co.carryover, et.taken
From carryover c Left Join
(SELECT employee_id, SUM(days) as taken
FROM timeoff
WHERE timeoff.time_off_type = 'Carryover' and
timeoff.start_date>='2013-01-01'
) et
on co.employee_id = et.employee_id
Where c.carryover > 0;
I don't think the group by is necessary. If it is, then you should probably have an aggregation function in the original query.

Need help transforming a dataset from multiple rows to a single row

I have a database (MySQL) that holds cost records for some of our customers (each customer has a different cost for items). Any given cost record has an effective date range (ie, the cost is valid for a given time frame). Here's some sample data:
Customer ItemNumber EffectDate Counter Cost
ABC123 ITEM101 2011-12-01 0 $1.00
ABC123 ITEM101 2011-12-10 0 $1.20
ABC123 ITEM101 2011-12-10 1 $1.25
DEF456 ITEM101 2011-11-15 0 $1.10
DEF456 ITEM101 2011-12-01 0 $1.12
To make some sense of that data, the "Counter" variable is the number of times it's been changed (this is from an antiquated accounting platform), and the date is the "Beginning Date" of the cost. So, ITEM101 for customer ABC123 will have a cost of $1.00 on 12/1/2011 and 12/8/2011, but starting 12/10/2011, will be $1.25 (would be $1.20, but the Counter has gone up - ie the cost is $1.25 now). I hope that makes sense.
What I'm trying to do is to get the data to come out as follows:
Customer ItemNumber EffectiveBegin EffectiveEnd Cost
ABC123 ITEM101 2011-12-01 2011-12-09 $1.00
ABC123 ITEM101 2011-12-10 2099-12-31 $1.25
DEF456 ITEM101 2011-11-15 2011-11-30 $1.10
DEF456 ITEM101 2011-12-01 2099-12-31 $1.12
Where 12/31/2099 is just a placeholder date for "Current".
Any help would be HUGELY appreciated!!
UNTESTED: (read as: I likely have at least one logic error and 2-3 syntax errors)
Areas where there might be problems:
1) B.effectiveDate-1 - Hoping B.EffectDate-1 when B.EffectDate is null
will retrun null so coalesce will return 12/31/2099.
2) '12/31/2099' may need to be cast to date data type.
3) Hoping mySQL supports date-1 to subtract 1 day.
4) Replace "TABLE" with your table name
What it does:
1) Joins to self in order to get begin & End for Effective dates. Excludes same
date entries.
2) subtracts 1 from B.effectdate when customer item has multiple entries to
get "End"
3) Only joins records where customer, item match AND B.Counter is the MAX counter.
(Key if multiple entires)
4) Uses cost from B if exists, otherwise uses A.
Without further ado:
Select A.Customer, A.ItemNumber, A.EffectDate as EffectiveBegin,
coalesce(B.effectDate-1,'12/31/2099') as EffectiveEnd,
coalesce(B.cost, A.cost) as Cost
FROM Table A
LEFT JOIN Table B
ON A.Customer=B.Customer
and A.ItemNumber = B.ItemNumber
and A.EffectDate < B.EffectDate
and B.Counter = (SELECT max(C.Counter)
FROM Table C
WHERE C.Customer = A.customer
and C.ItemNumber = A.ItemNumber)
Basic overview: Give me cost and date-1 of highest counter affiliated B record. If no b record, use A record info for cost and 12/31/2099 for end date.
I tested the query proposed by #xQbert, but it returns 5 rows for your sample data, not four as you requested.
Here is a tested query which returns four rows for your testing data:
select p.Customer, p.ItemNumber, p.EffectDate, coalesce(e.effectdate - interval 1 day, curdate()) EffectiveEnd, p.Cost
from (
select customer, itemnumber, effectdate, max(counter) counter
from prices
group by customer, itemnumber, effectdate
) c
left outer join prices p
on p.customer = c.customer
and p.itemnumber = c.itemnumber
and p.effectdate = c.effectdate
and p.counter = c.counter
left outer join prices e
on e.customer = p.customer
and e.itemnumber = p.itemnumber
and e.effectdate > p.effectdate
and e.counter = p.counter