How can I select all 2nd to the last transaction - sql

Hi all how can I select all 2nd last transaction.
Transaction log
------------------------------
ID|MemberName|TransactionDate|
------------------------------
1 |Member 1 |1/1/2017 |
------------------------------
2 |Member 1 |1/2/2017 | <- I want to select this transaction
------------------------------
3 |Member 1 |1/3/2017 |This the last transaction of this client
------------------------------
4 |Member 2 |2/1/2017 |
------------------------------
5 |Member 2 |2/2/2017 |<- I want to select this transaction
------------------------------
6 |Member 2 |2/3/2017 |This the last transaction of this client
I want to select all 2nd to the last transaction of all of my clients.

Try ROW_NUMBER() functionality(unless you are using MySQL).
SELECT A.ID, A.MEMBERNAME, A.TRANSACTIONDATE
FROM
(
SELECT ID, MEMBERNAME, TRANSACTIONDATE, ROW_NUMBER() OVER (PARTITION BY MEMBERNAME ORDER BY TRANSACTIONDATE DESC) AS RNUM
FROM TRANSACTION_TABLE
) A
WHERE A.RNUM = 2;
It creates indexing for all entries within each type of Membername and orders the numbering in descending order(meaning the first index represents the latest transactiondate). From this index column, you can give a filter of rnum = 2 for the second last transactiondate.

Select top(1) a.Id,a.MemberName, a.TransactionDate from (select top(2) Id,MemberName,TransactionDate from Transaction_log order by Id desc) as a order by a.Id asc;
Assumed table name to be Transaction_Log.
I hope this worked for you.

I just made use on aggregation and join as shown in the query below:
SELECT A.* FROM TRANSACTION_LOG A INNER JOIN
(SELECT MEMBER_NAME, MAX(ID) ID FROM TRANSACTION_LOG
GROUP BY MEMBER_NAME) B
ON A.MEMBER_NAME=B.MEMBER_NAME AND A.ID=B.ID-1;
Here is an SQL Fiddle DEMO

Related

Get the only the last row in a sequence. SQL Server

I have a table like this:
ID Seq Prod
-----------------
1 001 1
2 002 1
3 001 2
4 002 2
5 003 2
I want to make a query that only gets the last "Seq" of each product, so the expected output will be something like this:
ID Seq Prod
-----------------
2 002 1
5 003 2
Any help?
A simple way is a correlated subquery:
select t.*
from t
where t.seq = (select max(t2.seq) from t t2 where t2.prod = t.prod);
For performance, you want an index on (prod, seq).
The above often has the best performance. But another way to write the query is to use window fucntions:
select t.*
from (select t.*, row_number() over (partition by prod order by seq desc) as seqnum
from t
) t
where seqnum = 1;
Yet another option is using WITH TIES
Select top 1 * with ties
From YourTable
Order By row_number() over (partition by prod order by seq desc)
Full Disclosure:
Gordon's answer is a nudge more performant (+1), but WITH TIES does not generate an extra column.
You could use a sub-query that finds the maximum ID by Prod. In the following example, replace 'myTable' with your table name:
SELECT t.*
FROM myTable t
INNER JOIN (
SELECT MAX(ID) AS ID,
Prod
FROM myTable
GROUP BY Prod
) a ON a.ID = t.ID
Output:
ID Seq Prod
2 002 1
5 003 2
Here is a quick, working fiddle.
You can write a correlated subquery as:
select T.ID,T.Seq,T.Prod
from #T1 T
where T.ID = (select max(T_Inner.ID)
from #T1 T_Inner
where T_Inner.Prod = T.Prod
group by T_Inner.Prod
)

Select count of total records and also distinct records

I have a table such as this:
PalmId | UserId | CreatedDate
1 | 1 | 2018-03-08 14:18:27.077
1 | 2 | 2018-03-08 14:18:27.077
1 | 3 | 2018-03-08 14:18:27.077
1 | 1 | 2018-03-08 14:18:27.077
I wish to know how many dates were created for Palm 1 and I also wish to know how many users have created those dates for Palm 1. So the outcome for first is 4 and outcome for second is 3
I am wondering if I can do that in a single query as oppose to having to do a subquery and a join on itself as in example below.
SELECT MT.[PalmId], COUNT(*) AS TotalDates, T1.[TotalUsers]
FROM [MyTable] MT
LEFT OUTER JOIN (
SELECT MT2.[PalmId], COUNT(*) AS TotalUsers
FROM [MyTable] MT2
GROUP BY MT2.[UserId]
) T1 ON T1.[PalmId] = MT.[PalmId]
GROUP BY MT.[PalmId], T1.[TotalUsers]
According to first table you could do something like this:
select count(distinct uerid) as N_Users,
count(created_date) as created_date, -- if you use count(*) you consider also rows with 'NULL'
palmid
from your_table
group by palmid
If you want "4" and "3", then I think you want:
SELECT MT.PalmId, COUNT(*) AS NumRows, COUNT(DISTINCT mt.UserId) as NumUsers
FROM MyTable MT
GROUP BY MT.PalmId

find all rows after the recent update using oracle

I tried below query to bring all rows after last Action="UNLOCKED", but ORDER BY is not allowed in subquery it seems.
SELECT *
FROM TABLE
WHERE id >= (SELECT MAX(id)
FROM TABLE
WHERE ACTION='UNLOCKED' AND action_id=123
ORDER BY CREATE_DATE DESC);
Sample data
Id action_id Action ... CREATE_DATE
1 123 ADD 03/18/2018
2 123 Unlocked 03/19/2018
3 123 Updated1 03/19/2018
4 123 Updated2 03/19/2018
5 123 Unlocked 03/20/2018
6 123 Updated3 03/20/2018
7 123 Updated4 03/20/2018
Output should be rows with id 5,6,7. What should i use to get this output
you could use an inner join on subselect for max create_date
select * from TABLE
INNER JOIN (
select max(CREATE_DATE) max_date
from TABLE
where Action = 'Unlocked' ) T on t.max_date = TABLE.CREATE_DATE
You need not order the inner query because it will return only one value. You can do it as follows
SELECT * FROM TABLE WHERE id >= (select max(id) from TABLE where ACTION='UNLOCKED' and action_id=123);

How to select all records of n groups?

I want to select the records of the top n groups. My data looks like this:
Table 'runner':
id gid status rtime
---------------------------
100 5550 1 2016-08-19
200 5550 2 2016-08-22
300 5550 1 2016-08-30
100 6050 3 2016-09-01
200 6050 1 2016-09-02
100 6250 1 2016-09-11
200 6250 1 2016-09-15
300 6250 3 2016-09-19
Table 'static'
id description env
-------------------------------
100 something 1 somewhere 1
200 something 2 somewhere 2
300 something 3 somewhere 3
The unit id (id) is unique within the group but not unique in its column, because an instance of the group is generated regularly. The group id (gid) is assigned to every unit but will not generate on more than one instance.
Now, combining the tables and selecting everything or filter by a specific value is easy, but how do I select all records of, for example, the first two groups without directly refering to the group ids?
Expected result would be:
id gid description status rtime
--------------------------------------
300 6250 something 2 3 2016-09-19
200 6250 something 1 1 2016-09-15
100 6250 something 3 1 2016-09-11
200 6050 something 2 1 2016-09-02
100 6050 something 1 3 2016-09-01
Extra Question: When I filter for a timeframe like this:
[...]
WHERE runner.rtime BETWEEN '2016-08-25' AND '2016-09-16'
Is there a simple way of ensuring, that groups are not cut off but either appear with all their records or not at all?
You can use a ROW_NUMBER() to do this. First, create a query to rank groups:
SELECT gid, ROW_NUMBER() over (order by gid desc) as RN
FROM Runner
GROUP BY gid
Then use this as a derived table to get your other info, and use a where clause to filter to the number of groups you want to see. For instance, the below would return the top 5 groups RN <= 5:
SELECT id, R.gid, description, status, rtime
FROM (SELECT gid, ROW_NUMBER() over (order by gid desc) as RN
FROM Runner
GROUP BY gid) G
INNER JOIN Runner R on R.gid = G.gid
INNER JOIN Statis S on S.id = R.id
WHERE RN <= 5 --Change this to see more or less groups
For your second question about dates, you can do this with a subquery like so:
SELECT *
FROM Runner
WHERE gid IN (SELECT gid
FROM Runner
WHERE rtime BETWEEN '2016-08-25' AND '2016-09-16')
Hmmm. I suspect this might do what you want:
select top (1) with ties r.*
from runner r
order by min(rtime) over (partition by gid), gid;
At least, this will get the complete first group.
In any case, the idea is to include gid as a key in the order by and to use top with ties.
you can do the following
with report as(
select n.id,n.gid,m.description,n.status,n.rtime, dense_rank() over(order by gid desc) as RowNum
from #table1 n
inner join #table2 m on n.id = m.id )
select id,gid,description,status,rtime
from report
where RowNum<=2 -- <-- here n=2
order by gid desc,rtime desc
here a working demo
DENSE_RANK looks like a ideal solution here
Select * From
(
select DENSE_RANK() over (order by gid desc) as D_RN, r.*
from runner r
) A
Where D_RN = 1
No need to use ranking functions (ROW_NUMBER, DENSE_RANK etc).
SELECT r.id, gid, [description], [status], rtime
FROM runner r
INNER JOIN static s ON r.id = s.id
WHERE gid IN (
SELECT TOP 2 gid FROM runner GROUP BY gid ORDER BY gid DESC
)
ORDER BY rtime DESC;
The same using CTE:
WITH grouped
AS
(
SELECT TOP 2 gid
FROM runner GROUP BY gid ORDER BY gid DESC
)
SELECT r.id, grouped.gid, [description], [status], rtime
FROM runner r
INNER JOIN static s ON r.id = s.id
INNER JOIN grouped ON r.gid = grouped.gid
ORDER BY rtime DESC;

Join a dynamic number of rows in postgres

Let's say I have the following tables:
Batch Items
---+----- ---+----------+--------
id | size id | batch_id | quality
---+----- ---+----------+--------
1 | 10 1 | 1 | 9
2 | 2 2 | 1 | 10
3 | 2 | 1
4 | 2 | 2
5 | 2 | 1
6 | 2 | 9
I have batches of items. They are sent by batches of size batch.size. An item is broken if it's quality is <= 3.
I want to know the number of broken items in the last batches sent:
batch_id | broken_item_count
---------+---------------------
1 | 0
2 | 2 (and not 3)
My idea is the following:
SELECT batch.id as batch_id, COUNT(broken_items.*) as broken_item_count
FROM batch
INNER JOIN (
SELECT id
FROM items
WHERE items.quality <= 3
ORDER BY items.id asc
LIMIT batch.size -- invalid reference to FROM-clause entry for table "batch"
) broken_items ON broken_items.batch_id = batch.id
(I would ORDER BY items.shipped_at. But for simplicity, I order by items.id)
But this query shows me the error I put as the comment.
How can I limit the number of joined items based on the batch.size that is different for each row ?
Is there any other way to achieve what I want ?
SELECT b.id AS batch_id
, count(i.quality < 4 OR NULL) AS broken_item_count
FROM batch b
LEFT JOIN (
SELECT batch_id, quality
, row_number() OVER (PARTITION BY batch_id ORDER BY id DESC) AS rn
FROM items
) i ON i.batch_id = b.id
AND i.rn <= b.size
GROUP BY 1
ORDER BY 1;
SQL Fiddle with added examples.
This is much like #Clodoaldos's answer, but with a couple of differences. Most importantly:
You want to count the broken items in the last batches sent, so we have to ORDER BY id DESC
If there can be batches without items at all you need to use LEFT JOIN instead of a plain JOIN or those batches are excluded.
Consequently, the check i.rn <= b.size needs to move from the WHERE clause to the JOIN clause.
SQL Fiddle
select
b.id as batch_id,
count(quality <= 3 or null) as broken_item_count
from
batch b
inner join (
select
id, quality, batch_id,
row_number() over (partition by batch_id order by id) as rn
from items
) i on i.batch_id = b.id
where rn <= b.size
group by b.id
order by b.id
From what I understand the count of defective items cannot be greater than the batch size.
EDIT: After reading your comments, I think using the RANK() function, and then join by rank and size should work for you. The following query attempts that.
SELECT b.id,
SUM(CASE WHEN i1.quality <= 3 THEN 1 ELSE 0END) as broken_item_count
FROM BATCH as b
LEFT JOIN (SELECT i.id, i.batch_id, i.quality,
RANK() OVER(PARTITION BY i.batch_id ORDER BY i.id) as RANK
FROM ITEMS as i) as i1 ON b.id = i1.batch_id AND i1.RANK <= b.size
GROUP BY b.id
EDIT2: Updated the query with a LEFT JOIN to cover the case where there are no samples in some batch.