Oracle select from table and join where date is oldest - sql

My real life problem is more comlpex, but the issue can be washed down to the following: I have one table with clients, and another table with clients' bank accounts. Now I want to do a select returning all clients that have bank accounts, joining with the bank account that was used least recently, ordered by the BANK_ID.
CLIENT:
CLIENT_ID NAME
-----------------
1 JOE
2 BEN
3 SUE
BANK_ACCOUNT:
BANK_ID CLIENT_ID LAST_USED
-----------------------------------
1 2 Jan 1 2020
2 1 Mar 15 2020
3 2 Aug 5 2020
4 1 Feb 7 2020
5 1 Oct 13 2020
So Joe has three bank accounts, Ben has two, and Sue does not have any.
The select should contain only Joe and Ben. Ben should go first because his "oldest" bank account is on BANK_ID 1 while Joe's is on BANK_ID 4.
BANK_ID CLIENT_ID NAME
---------------------------
1 2 BEN
4 1 JOE
I've been playing around with joins and subqueries, but I'm not sure what would be the best way to accomplish this query.
Thank you

A typical method is to use row_number():
select ba.bank_id, ba.client_id, c.name
from client c join
(select ba.*,
row_number() over (partition by client_id order by last_used) as seqnum
from bank_account ba
) ba
on ba.client_id = c.client_id and ba.seqnum = 1
order by ba.bank_id;
In Oracle, you can also use aggregation:
select max(ba.bank_id) keep (dense_rank over order by ba.last_used) as bank_id,
ba.client_id, c.name
from bank_account ba
on ba.client_id = c.client_id
group by ba.client_id, c.name
order by ba.bank_id;

One option uses a lateral join:
select ba.bank_id, ba.client_id, c.name
from client c
cross apply (
select ba.*
from bank_account ba
where ba.client_id = c.client_id
order by ba.last_used
fetch first row only
) ba
The upside is that you can easily bring more columns from the bank account tables (say you want the last_used date for example), by just expanding the select clause.

Related

Find the people who are login 3 consecutive dates

LoginHistory table
Date Name Login
----------------------------------------
03/20/2021 Amy 1
03/20/2021 Lily 1
03/20/2021 Nancy 1
03/21/2021 Amy 1
03/21/2021 Lily 1
03/21/2021 Leo 1
03/22/2021 Amy 1
03/22/2021 Lisa 1
03/22/2021 Leo 1
03/23/2021 Lily 1
03/23/2021 Lisa 1
03/23/2021 Leo 1
I want to find the people and their login date who was login instance 3 times in consecutive dates. For example, my output should has Amy, because she was login 3/20,3/21 and 3/22. For Lily, she shouldn't be in my output, because even she login 3 times, the date(3/20,3/21 and 3/23) is not in consecutive order.
output should be:
Date Name Login
----------------------------------------
03/20/2021 Amy 1
03/21/2021 Amy 1
03/21/2021 Leo 1
03/22/2021 Amy 1
03/22/2021 Leo 1
03/23/2021 Leo 1
Thanks.
Based on the specific sample data provided, you could use analytic min and max to get the first and last date for each name, count the difference in days and the number of logins which must be 3 with 2 days between first and last date.
You haven't specific a RDBMS so the date functions may need amending as appropriate, however all RDBMS support the same functionality.
select date, name
from (
select *,
DateDiff(day,Min(date) over(partition by name),
Max(date) over(partition by name))diff,
Count(*) over(partition by name) qty
from t
)t
where diff=2 and qty=3
order by date;
To produce a table of the consecutive logins, you can first anchor your search on the action that is the last in the sequence. Then, you can join all the preceding dates to that original result:
with vals(v) as (
select 1
union all
select 2
)
select c2.* from (
select c.* from loginhistory c where
(select count(*) from loginhistory c1 cross join vals v
where c1.name = c.name and c.dt = c1.dt + interval '1' day * v.v) = 2
) t1
join loginhistory c2 on t1.name = c2.name and c2.dt <= t1.dt and (c2.dt + interval '2' day) >= t1.dt
order by c2.dt
select * from LoginHistory where name in (
select name
from LoginHistory
where date between <start> and <end> -- must be exactly three dates in the range
group by name
having count(distinct date) = 3
)

Inner Join - special time conditions

Given an hourly table A with full heart_rate records, e.g.:
User Hour Heart_rate
Joe 1 60
Joe 2 70
Joe 3 72
Joe 4 75
Joe 5 68
Joe 6 71
Joe 7 78
Joe 8 83
Joe 9 85
Joe 10 80
And a subset hours where a purchase happened, e.g.
User Hour Purchase
Joe 3 'Soda'
Joe 9 'Coke'
Joe 10 'Doughnut'
I want to keep only those records from A that are in B or at most 2hr behind the B subset, without duplication, i.e. and preserving both the heart_rate from A and the item purchased from b so the outcome is
User Hour Heart_rate Purchase
Joe 1 60 null
Joe 2 70 null
Joe 3 72 'Soda'
Joe 7 78 null
Joe 8 83 null
Joe 9 85 'Coke'
Joe 10 80 'Doughnut'
How can the result be achieved with an inner join, without duplication (in this case the hours 8&9) (This is an MWE, assume multiple users and timestamps instead of hours)
The obvious solution is to combine
Inner Join + deduplication
Left join
Can this be achieved in a more elegant way?
You could use an INNER join of the tables and conditional aggregation for the deduplication:
SELECT a.User, a.Hour, a.Heart_rate,
MAX(CASE WHEN a.Hour = b.Hour THEN b.Purchase END) Purchase
FROM a INNER JOIN b
ON b.User = a.User AND a.Hour BETWEEN b.Hour - 2 AND b.Hour
WHERE a.User = 'Joe' -- remove this line if you want results for all users
GROUP BY a.User, a.Hour, a.Heart_rate;
Or with MAX() window function:
SELECT DISTINCT a.*,
MAX(CASE WHEN a.Hour = b.Hour THEN b.Purchase END) OVER (PARTITION BY a.User, a.Hour) Purchase
FROM a INNER JOIN b
ON b.User = a.User AND a.Hour BETWEEN b.Hour - 2 AND b.Hour;
See the demo (for MySql but it is standard SQL).
Your solutiuons should work and sounds good.
There is another way, using 3 Select Statements.
The inner Select combines both tables by UNION ALL. Because only tables with the same columns can be combinded, fields which are only in one table have to be defined in the other one as well and set to null. The column hour_eat is added to see when the last purchase has occured. By sorting this table, we can archive that under each row from table B lies now the row of table A which occures next.
In the middle Select statement the lag(Purchase) gets the last Purchase. If we only think about the rows from the 1st table, the Purchase value from the 2nd table is now at the right place. This comes in handy if timestamps and not defined hours are used. The row the last_value calculates the time between the purchase and measurement of the heart_beat.
The outer Select filters the rows of interest. The last 2 hours before the purchase and only the rows of the 1st table.
With
heart_tbl as (SELECT "Joe" as USER, row_number() over() Hour, Heart_rate from unnest([60,72,72,75,68,71,78,83,85,80]) Heart_rate ),
eat_tbl as (Select "Joe" as User ,3 Hour , 'Soda' as Purchase UNION ALL SELECT "Joe", 9, 'Coke' UNION ALL SELECT "Joe", 10, 'Doughnut' )
SELECT user, hour,heart_rate,Purchase_,hours_till_Purchase
from
(
SELECT *,
lag(Purchase) over (order by hour, heart_rate is not null) as Purchase_,
hour-last_value(hour_eat ignore nulls) over (order by hour desc,heart_rate is not null) as hours_till_Purchase
From # combine both tables to one table (ordered by hours)
(
SELECT user, hour,heart_rate, null as Purchase, null as hour_eat from heart_tbl
UNION ALL
Select user, hour, null as heart_rate, Purchase, hour from eat_tbl
)
)
Where heart_rate is not null and hours_till_Purchase >= -2
order by hour

How to count by two fields and join with other table Postgres?

I have two tables, one table user and second table transactions related with the transactions done by a user. I want to do a query that give me the count by name and date, with the fields in user table. How can I do it?
Table user:
Name Id Card
-----------------
Alex 01 N
James 02 Y
Table transaction:
Name Date
-----------------
Alex 01/07/2012
Alex 01/12/2012
James 01/08/2012
Alex 01/07/2012
Alex 01/12/2012
James 01/07/2012
James 01/07/2012
I want sometihng like this:
Name Date Transactions ID Card
---------------------------------------------
Alex 01/07/2012 2 01 N
Alex 01/12/2012 2 01 N
James 01/08/2012 1 02 Y
James 01/07/2012 2 02 Y
First of all I tryed to count by two columns with something like this:
select name, date, count(name, date) from pm_transaction GROUP BY (name,date)
select count(distinct(machine, date)) from pm_transaction
But it does not work, I tried a lot of combinations but no one works
Try this
select tb1.name, tb2.date , tb2.transaction , tb1.Id, tb1.card
from tbUser as tb1
inner join
(select date,
name,
count(date) as transaction
from tbTransaction group by date)
as tb2 on tb1.name = tb2.name
This looks like simple aggregation task. Just check and correct table join condition and table names:
select u.name, t.date, count(1) as transactions, u.id, u.card
from transaction t
join user_table u on u.name = t.name
group by u.name, t.date, u.id, u.card;

Specific Ordering in SQL

I have a SQL Server 2008 database. In this database, I have a result set that looks like the following:
ID Name Department LastOrderDate
-- ---- ---------- -------------
1 Golf Balls Sports 01/01/2015
2 Compact Disc Electronics 02/01/2015
3 Tires Automotive 01/15/2015
4 T-Shirt Clothing 01/10/2015
5 DVD Electronics 01/07/2015
6 Tennis Balls Sports 01/09/2015
7 Sweatshirt Clothing 01/04/2015
...
For some reason, my users want to get the results ordered by department, then last order date. However, not by department name. Instead, the departments will be in a specific order. For example, they want to see the results ordered by Electronics, Automotive, Sports, then Clothing. To throw another kink in works, I cannot update the table schema.
Is there a way to do this with a SQL Query? If so, how? Currently, I'm stuck at
SELECT *
FROM
vOrders o
ORDER BY
o.LastOrderDate
Thank you!
You can use case expression ;
order by case when department = 'Electronics' then 1
when department = 'Automotive' then 2
when department = 'Sports' then 3
when department = 'Clothing' then 4
else 5 end
create a table for the departments that has the name (or better id) of the department and the display order. then join to that table and order by the display order column.
alternatively you can do a order by case:
ORDER BY CASE WHEN Department = 'Electronics' THEN 1
WHEN Department = 'Automotive' THEN 2
...
END
(that is not recommended for larger tables)
Here solution with CTE
with c (iOrder, dept)
as (
Select 1, 'Electronics'
Union
Select 2, 'Automotive'
Union
Select 3, 'Sports'
Union
Select 4, 'Clothing'
)
Select * from c
SELECT o.*
FROM
vOrders o join c
on c.dept = o.Department
ORDER BY
c.iOrder

SQL Get count of users who participated in a survey

I want to know how I can write a SQL query to get the count of users who participated in each month survey
The table structures are like below
SurveyTable
SurveyId, SurveyName, Datetime
SurveyInfo
Id, SurveyId, First name, Last name DateTime
Sample Data
SurveyTable
1 Survey-ABC 1 Jan 2011
2 Survey-AXC 1 Feb 2011
3 Survey-AEW 1 Mar 2011
4 Survey-AVD 1 Apr 2011
....
SurveyInfo
1 1 Peter James 1 Jan 2011
2 1 John Tom 1 Jan 2011
3 1 Harry Kin 1 Jan 2011
4 2 Amber Wills 1 Feb 2011
5 2 Tom Kin 1 Feb 2011
I want a result like
3 users participated in Survey-ABC,
2 users participated in Survey-AXC
Thanks
SELECT
Count(*) AS "Number of participants",
SurveyTable.surveyName
FROM
surveyinfo
INNER JOIN surveytable
ON surveytable.surveyid = surveyinfo.surveyid
GROUP BY
Year(DateTime),
Month(Date_Time)
SELECT stab.Name, COUNT(si.ID)
FROM SurveyTable as stab INNER JOIN SurveyInfo si ON si.SurveyId = stab.SurveyId
GROUP BY stab.Name
Because you want to see the number or participant for each month, so you need to extract the month and group by this.
SELECT SurveyTable.SurveyId, SurveyTable.SurveyName, Month(SurveyInfo.DateTime) As M, COUNT(SurveyInfo.*) As Cnt
FROM SurveyTable
LEFT JOIN SurveyInfo ON SurveyInfo.SurveyId = SurveyTable.SurveyId
GROUP By SurveyTable.SurveyId, SurveyTable.SurveyName, Month(SurveyInfo.DateTime)
The following select statement
SELECT st.Name, COUNT(si.ID)
FROM SurveyTable.st INNER JOIN SurveyInfo si ON si.SurveyId = st.SurveyId
GROUP BY st.Name
should provide you with the users who participated in the surveys.