The next code makes a join between users and payment, to get the last payment.
The query should work if the payment table did not contain duplicated rows with the same max_date as the following one.
Something to notice, is that the row is not completely duplicated, sometimes contains little changes. But we do not care if we select the 'right' one, we only need it to be one, no matter which one of those is.
user_ID | Payment | date | product | credit_card
1 300 1/1/2020 A No
1 300 1/1/2020 Null | No
1 300 1/1/2021 A Yes
1 300 1/1/2021 Null | Yes
This causes the second inner join to duplicate rows because it makes a match twice with the maxDate which is 1/1/2021
SELECT a.*, c.*
FROM users a
INNER JOIN payments c
ON a.id = c.user_ID
INNER JOIN
(
SELECT user_ID, MAX(date) maxDate
FROM payments
GROUP BY user_ID
) b ON c.user_ID = b.user_ID AND
c.date = b.maxDate
I'm looking for a way to select only the first match of the maxDate. Any clue is welcome, thank in advance for any help.
You should be using window functions for this. That would be:
SELECT u.*, p.*
FROM users u JOIN
(SELECT p.*,
ROW_NUMBER() OVER (PARTITION BY p.user_id ORDER BY p.date DESC) as seqnum
FROM payments p
) p
ON p.user_ID = u.id AND p.seqnum = 1;
This returns one row, but which row is arbitrary.
Note the use of meaningful table aliases in the query -- u for users and p for `payments. Don't use meaningless letters. They just make the query hard to read -- and to maintain.
Related
I have a database based on a bank, each branch having a Unique branch ID.
I am trying to create a query showing the top balance from the savings accounts at each branch. Only showing ONE row per branch ID.
I have this query which shows the top balances of savings accounts but I cannot figure out how to have this unique per branch ID.
select max(a.balance) as balance, b.bid, c.pname.firstname, c.pname.middleinitial, c.pname.lastname
from brancht b
join accountt a on b.bid=a.bid
join customeraccountt ca on ca.accnum = a.accnum
join customert c on c.custid = ca.custid
where acctype='Savings'
group by b.bid, c.pname.firstname, c.pname.middleinitial, c.pname.lastname
order by balance desc, b.bid
/
The results for this query returns this:
BALANCE BID PNAME.FIRSTNAME PNAME.MIDDLEINITIAL PNAME.LASTNAME
14050 101 Kornelia J Oconnor
13000 101 Myra D Johnson
9850 105 Lucie M Crosby
9050 105 Simon R Patrick
Another part of the query would be to also show their limit of overdraft on their current accounts (if they have one) if it's possible to have that in the same query?
Hopefully this is enough information.
Could you please try using ROW_NUMBER function as below. In the sub-query a ROW_NUMBER is assigned to each row based on balance partitioned by Branch. Row with maximum balance for a particular branch will be assigned 1 and so on ; Then you filter only those records having row_number -1 ; Each branch will have only one record (maximum balance) with ROW_NUMBER=1
select * from
(select balance, b.bid, c.pname.firstname, c.pname.middleinitial, c.pname.lastname,
row_number() over (partion by b.bid order by balance desc) as rn
from brancht b
join accountt a on b.bid=a.bid
join customeraccountt ca on ca.accnum = a.accnum
join customert c on c.custid = ca.custid
where acctype='Savings'
group by b.bid, c.pname.firstname, c.pname.middleinitial, c.pname.lastname
) where rn=1
I have a table named StatementSummary.
SELECT *
FROM StatementSummary
WHERE AccountID = 1234
Results
StatementId StatementDate AccountId AmountDue
-------------------------------------------------
100 2017-10-16 1234 600
99 2017-09-16 1234 500
98 2017-08-16 1234 400
I have another table that has a list of Accounts. I am trying to give results that show the last AmountDue for each account
My code:
SELECT
AccountID,
(SELECT MAX(StatementDate)
FROM StatementSummary
GROUP BY AccountID) LastStatementDate,
AmountDue
FROM
Accounts A
INNER JOIN
StatementSummary S ON A.AccountId = S.AccountId
Basically, I want to show all the details of the last statement for every AccountId.
You can use the SQL Server Windowing functions in cases like this.
SELECT DISTINCT
a.AccountId,
FIRST_VALUE(s.StatementDate) OVER (PARTITION BY s.AccountId ORDER BY s.StatementDate DESC) As LastStatementDate,
FIRST_VALUE(s.AmountDue) OVER (PARTITION BY s.AccountId ORDER BY s.StatementDate DESC) As LastAmountDue
FROM Accounts a
INNER JOIN StatementSummary s
ON a.AccountId = s.AccountId
Basically what happens is the OVER clause creates partitons in your data, in this case, by the account number (these partitions are the windows). We then tell SQL Server to sort the data within each partition by the statement date in descending order, so the last statement will be at the top of the partition, and then the FIRST_VALUE function is used to just grab the first row.
Finally, since you perform this operation for every account/statement combo between the two tables, you need the DISTINCT to say you just want one copy of each row for each account.
There are quite a bit of useful things you can do with the windowing functions in SQL Server. This article gives a good introduction to them: https://www.red-gate.com/simple-talk/sql/learn-sql-server/window-functions-in-sql-server/
Derived Table over row numberand left join - to display all accounts regardless if there is a statement
select *
from
(select row_number() over (partition by accountid order by statementdate desc) rn,
accountid, statementdate,amount
from statementtable
) l
left outer join accountstable a
on l.accountid = a.accountid
And l.rn = 1
That's sounds like a job for me sings lateral join aka cross apply in T-SQL.
SELECT a.*, last_ss.*
FROM Accounts A
cross apply (
select top 1 *
from StatementSummary S ON A.AccountId = S.AccountId
order by StatementDate desc
) last_ss
Alternatively you can use CTE to get last date for account:
; with l as (
select accountid, max(StatementDate)
from StatementSummary
group by accountid
)
select ...
from Accounts a
inner join l on l.accountid = a.accountid
inner join StatementSummary ss on ss.accountid = a.accountid
and l.StatementDate = ss.StatementDate
Good morning,
I am writing a SQL query for the latest metal prices with the latest date they were put into the database. Example table below:
ID Date Created
1 01/01/01 01:01
2 01/01/01 01:02
3 01/01/01 01:03
4 01/01/01 01:04
1 02/01/01 01:01
2 02/01/01 01:02
So from this I want the following result:
ID Date Created
1 02/01/01 01:01
2 02/01/01 01:02
When I run the below query it is just giving me the last one entered into the date base so from the above example it would be ID 2 DateCreated 02/01/01 01:02. The query I am using is below:
SELECT mp.MetalSourceID, ROUND(mp.PriceInPounds,2),
mp.UnitPrice, mp.HighUnitPrice, mp.PreviousUnitPrice,
mp.PreviousHighUnitPrice, ms.MetalSourceName,
ms.UnitBasis, cu.Currency
FROM tblMetalPrice AS mp
INNER JOIN tblMetalSource AS ms
ON tblMetalPrice.MetalSourceID = tblMetalSource.MetalSourceID
INNER JOIN tblCurrency AS cu
ON tblMetalSource.CurrencyID = tblCurrency.CurrencyID
WHERE DateCreated = (SELECT MAX (DateCreated) FROM tblMetalPrice)
GROUP BY mp.MetalSourceID;
Could anyone please help its driving me crazy not knowing and my brain is dead this friday morning.
Thanks
Use a correlated subquery for the where clause:
WHERE DateCreated = (SELECT MAX(DateCreated) FROM tblMetalPrice mp2 WHERE mp2.id = mp.id)
You can join on a subquery, and I don't think you'll need the group by, or indeed the where clause (because that's handled by the join).
SELECT mp.MetalSourceID,
ROUND(mp.PriceInPounds,2),
mp.UnitPrice,
mp.HighUnitPrice,
mp.PreviousUnitPrice,
mp.PreviousHighUnitPrice,
ms.MetalSourceName,
ms.UnitBasis,
cu.Currency
FROM tblMetalPrice AS mp
INNER JOIN tblMetalSource AS ms
ON tblMetalPrice.MetalSourceID = tblMetalSource.MetalSourceID
INNER JOIN tblCurrency AS cu
ON tblMetalSource.CurrencyID = tblCurrency.CurrencyID
INNER JOIN (SELECT ID,MAX(DateCreated) AS maxdate FROM tblMetalPrice GROUP BY ID) AS md
ON md.ID = mp.ID
AND md.maxdate = mp.DateCreated
with maxDates as
(select max(datecreated) maxd, ids grp , count(1) members from s_tableA group by ids having count(1) > 1)
select ids, datecreated from s_tableA,maxDates
where maxd = datecreated and ids = grp;
this query will give your desired result. Correlated sub queries tend to consume lot of processing time, because for each row of the outer query it has to process all the rows in the inner query.
I've seen many posts about this subject but none of the solutions solved my problem.
In a nutshell, I have a users table and a user_history table. Each user can have 0 or more user_history entries. The user_history table has a status column. All I want to do is get a list of users and the value of the status column for their most recent user_history entry. And I can't get it to work. I've tried:
select u.id, u.name,
(select status from (select status, rownum as rn from user_history uh where uh.user_id = u.id
order by created_date desc) where rn = 1) status
from users u;
This gives me a "ORA-00904: invalid identifier, u.id" error. From what I've read, Oracle does not allow you to access the outer-select 'u.id' from within a sub-sub-select (the one with rownum). From the first sub-select it works fine but as I said, I can have n entries in user_history, I only need the most recent.
I've also tried using an inner join:
select u.id, u.name, h.status
from users u
inner join (select user_id, status, rownum as rn from user_history where user_id = u.id order by created_date desc) h on u.id = h.user_id where h.rn = 1;
This gives me the dreaded "ORA-06553: wrong number or types of arguments in call to u" ... which I tried fixing by using distinct but to no avail.
I've also tried using row_number(), over and partition ... other types of inner joins with select ... nothing gets me the data I need.
Can someone give me a hand with this (seemingly) simple query?
In old days query would look something like this
select
u.id,
u.name,
uh.status
from
users u
inner join
(select
user_id,
status
from
user_history h
where
created_date = (select
max(created_date)
from
user_history d
where
h.user_id = d.user_id)
) uh
on u.id = uh.user_id;
What you have here is a correlated subquery that will get you latest date in history for the user. It is going to execute for each row so it is a bit slow performer. And you Join it with your user table to get your status.
I haven't tested it but it looks right.
Would something like this work? It would also eliminate the scalar within your query and be a little easier to debug, since you can run the inner query (uh) independently and evaluate its results.
with uh as (
select
u.id, u.name, uh.status, uh.created_date,
max (uh.created_date) over (partition by uh.user_id) as max_date
from
users u,
user_history uh
where
u.id = uh.user_id
)
select
id, name, status
from uh
where created_date = max_date
-- Edit --
For what it's worth, I loaded some sample data:
Users
1 Bilbo
2 Fatty
3 Pippin
4 Balin
User History
1 one 1/1/2014
1 two 1/2/2014
1 three 1/3/2014
2 four 1/4/2014
2 five 1/5/2014
2 six 1/6/2014
3 seven 1/7/2014
3 eight 1/8/2014
3 nine 1/9/2014
This was the output:
1 Bilbo three
2 Fatty six
3 Pippin nine
Here is the row_number alternative if you have multiple history records with the exact same "date" field.
with uh as (
select
u.id, u.name, uh.status,
row_number() over
(partition by u.id order by uh.created_date desc) as rn
from
users u,
user_history uh
where
u.id = uh.user_id
)
select
id, name, status
from uh
where rn = 1
I've got three tables; Auctions, Auction Bids and Users. The table structure looks something like this:
Auctions:
id title
-- -----
1 Auction 1
2 Auction 2
Auction Bids:
id user_id auction_id bid_amt
-- ------- ---------- -------
1 1 1 200.00
2 2 1 202.00
3 1 2 100.00
Users is just a standard table, with id and user name.
My aim is to join these tables so I can get the highest values of these bids, as well as get the usernames related to those bids; so I have a result set like so:
auction_id auction_title auctionbid_amt user_username
---------- ------------- -------------- -------------
1 Auction 1 202.00 Bidder2
2 Auction 2 100.00 Bidder1
So far my query is as follows:
SELECT a.id, a.title, ab.bid_amt, u.display_name FROM auction a
LEFT JOIN auctionbid ab ON a.id = ab.auction_id
LEFT JOIN users u ON u.id = ab.user_id
GROUP BY a.id
This gets the single rows I am after, but it seems to display the lowest bid_amt, not the highest.
You can use the MAX-Function and a sub-select to get the maximum bid for each auction. If you join this subselect with your other tables and set the where clause as follows you should get what you are looking for.
SELECT a.id, a.title, ab.bid_points, u.display_name
FROM Auction AS a
INNER JOIN (SELECT auction_id, MAX(bid_points) AS maxAmount FROM auction_bids GROUP BY auction_id) AS maxBids ON maxBids.auction_id = a.id
INNER JOIN auction_bids AS ab ON a.id = ab.auction_id
INNER JOIN users AS u ON u.id = ab.user_id
WHERE ab.auction_id = maxBids.auction_id AND ab.bid_amount = maxBids.maxAmount
Hope that helps.
This is a typical within-group aggregate problem. You can solve it using a so called left self exclusion join
Try the following:
SELECT a.id, a.title, ab.bid_points, u.displayname
FROM auction a
INNER JOIN auction_bids ab ON ab.auction_id = a.id
LEFT JOIN auction_bids b1 ON ab.auction_id = b1.auction_id
AND ab.bid_points < b1.bid_points
LEFT JOIN users u ON u.id = ab.user_id
WHERE b1.auction_id IS NULL
It basically builds a join between the left and right side, until it doesn't find one for the left side anymore, and thats the highest element then.
Another solution would be using multiple querys (of course) or a temporary aggregate table.
Try this:
SELECT a.id, a.title, ab.bid_points, u.display_name FROM auction a
LEFT JOIN auctionbid ab ON a.id = ab.auction_id
LEFT JOIN users u ON u.id = ab.user_id
GROUP BY a.id
ORDER BY ab.bid_points DESC
If that doesn't work, try using a subselect on auctionbids containing something like
SELECT id, user_id, auction_id, MAX(bid_amt) FROM action_bids GROUP BY auction_id
Try adding the following clause; not sure about performance.
WHERE NOT EXISTS
(SELECT * FROM auctionbid abhigher
WHERE abhigher.auction_id = ab.auction_id
AND abhigher.auctionbid_amt > ab.auctionbid_amt)
Excludes auction bids from the query that have a higher bid for the same auction.
The only problem is that if you have 2 equal bids and both will list. One way to get rid of them - but it is a relatively arbitrary choice of winner, is to use the bid id:
WHERE NOT EXISTS
(SELECT * FROM auctionbid abhigher
WHERE abhigher.auction_id = ab.auction_id
AND abhigher.auctionbid_amt >= ab.auctionbid_amt
AND abhigher.id > ab.id)
Here is what you can try..like old school..nothing new..no need to go for left join or anything else..rest depends on your exact requirement
select A.id,A.title,max(AB.bid_amt),name
from Auction A,AuctionBids AB,Users U
where U.ID=AB.USER_ID AND A.ID=AB.ID
group by A.ID,A.title,name