I have started a simple auction system where each row contains the following data:
Type:= BID|WIT
ProductName := Str
User := Str
Value := Decimal
CreatedAt := datetime
*Here WIT means withdraw, aka quit from item auction *
A user can naturally do multiple bid requests, always raising the bid value (this is handled at language level)
I would like to list all top bids from all users but under a condition, only if they are not before a WITHDRAW request for the given item.
For example, given the entries
BID,Doll,John,10,2021-11-26 10:10
BID,Ball,John,12,2021-11-26 10:11
BID,Doll,Mary,12,2021-11-26 10:12
BID,Doll,Alice,13,2021-11-26 10:13
BID,Doll,Mary,14,2021-11-26 10:14
BID,Doll,Alice,17,2021-11-26 10:14
BID,Ball,Mary,14,2021-11-26 10:14
WIT,Doll,John,00,2021-11-26 10:16
BID,Doll,Mary,20,2021-11-26 10:18
BID,Ball,John,15,2021-11-26 10:20
If I magic_sql_query(Doll) I would like to get
BID,Doll,Alice,17,2021-11-26 10:14
BID,Doll,Mary,20,2021-11-26 10:18
Also If I magic_sql_query(Ball) I would like to get:
BID,Ball,Mary,14,2021-11-26 10:14
BID,Ball,John,15,2021-11-26 10:20
How can I do it in a SQL Statement?
You can
use the row_number() windowing function to rank within a group (a
group is defined by the username and the product); the latest entry
gets rank 1
get rid of all entries whose rank is > 1 (i.e. the user has a later entry for this product)
get rid of all entries of type 'WIT'
with base as (select
b.type,
b.productname,
b.username,
b.value,
b.createdat,
row_number() over (partition by productname, username
order by createdat desc) as rn
from bids b
order by productname, username, createdat
)
select * from base
where rn = 1
and type = 'BID';
SQL Fiddle
To find all bids that have no withdrawal later (by the same user for the same product) you can use a NOT EXITS condition:
select a1.*
where a1.product_name = 'Ball'
and type = 'BID'
and not exists (select *
from auction a2
where a2.product_name = a1.product_name
and a2.user_name = a1.user_name
and a2.type = 'WIT'
and a2.created_at > a1.created_at)
Now we need to filter out the highest bids per product and user. This can be done using the dense_rank() function.
select type, product_name, user_name, value, created_at
from (
select a1.*, dense_rank() over (partition by product_name, user_name order by value desc) as rnk
from auction a1
where a1.product_name = 'Ball'
and type = 'BID'
and not exists (select *
from auction a2
where a2.product_name = a1.product_name
and a2.user_name = a1.user_name
and a2.type = 'WIT'
and a2.created_at > a1.created_at)
) t
where rnk = 1
order by created_at;
Online example
A left join version
select t.Type, product_name, t.User, Value, CreatedAt
from (
select b.*
, row_number() over(partition by b.product_name, b.user order by b.createdAt desc) rn
from auction b
left join auction w
on w.type = 'WIT'
and b.product_name = w.product_name
and b.user = w.user
and w.createdAt > b.createdAt
where b.type = 'BID'
and w.product_name is null
-- and b.product_name = 'Ball'
) t
where rn=1
-- and product_name = 'Ball'
order by product_name, CreatedAt;
uncomment one of .. product_name = 'Ball' to get only this product.
db<>fidlle
You can use MAX() window function to get for each user the last entry with type = 'BID' and the last entry with type = 'WIT'.
Then filter the results:
SELECT type, productname, "user", value, createdat
FROM (
SELECT *,
MAX(CASE WHEN type = 'BID' THEN createdat END) OVER (PARTITION BY "user") last_bid,
MAX(CASE WHEN type = 'WIT' THEN createdat END) OVER (PARTITION BY "user") last_wit
FROM tablename
WHERE productname = ?
) t
WHERE (last_wit IS NULL OR last_wit < last_bid) AND createdat = last_bid;
Replace ? with the name of the product that you want.
See the demo.
Related
I have the following data represented in a table like this:
User
Type
Date
A
Mobile
2019-01-10
A
Mobile
2019-01-20
A
Desktop
2019-03-01
A
Desktop
2019-03-20
A
Email
2021-01-01
A
Email
2020-01-02
A
Desktop
2021-01-03
A
Desktop
2021-01-04
A
Desktop
2021-01-05
Using PostgreSQL - I want to achieve the following:
User
First_Type
First Type Initial Date
Last_Type
Last_Type_Initial_Date
A
Mobile
2019-01-10
Desktop
2021-01-03
So for each user, I want to capture the initial date and type but then also, on the same row (but diff columns), have their last type they "switched" to but with the first date the switch occurred and not the last record of activity on that type.
Consider using a LAG window function and conditional aggregation join via multiple CTEs and self-joins:
WITH sub AS (
SELECT "user"
, "type"
, "date"
, CASE
WHEN LAG("type") OVER(PARTITION BY "user" ORDER BY "date") = "type"
THEN 0
ELSE 1
END "shift"
FROM myTable
), agg AS (
SELECT "user"
, MIN(CASE WHEN shift = 1 THEN "date" END) AS min_shift_dt
, MAX(CASE WHEN shift = 1 THEN "date" END) AS max_shift_dt
FROM sub
GROUP BY "user"
)
SELECT agg."user"
, s1."type" AS first_type
, s1."date" AS first_type_initial_date
, s2."type" AS last_type
, s2."date" AS last_type_initial_date
FROM agg
INNER JOIN sub AS s1
ON agg."user" = s1."user"
AND agg.min_shift_dt = s1."date"
INNER JOIN sub AS s2
ON agg."user" = s2."user"
AND agg.max_shift_dt = s2."date"
Online Demo
user
first_type
first_type_initial_date
last_type
last_type_initial_date
A
Mobile
2019-01-10 00:00:00
Desktop
2021-01-03 00:00:00
Here is my solution with only windows functions and no joins:
with
prep as (
select *,
lag("Type") over(partition by "User" order by "Date") as "Lasttype"
from your_table_name
)
select distinct "User",
first_value("Type") over(partition by "User") as "First_Type",
first_value("Date") over(partition by "User") as "First_Type_Initial_Date",
last_value("Type") over(partition by "User") as "Last_Type",
last_value("Date") over(partition by "User") as "Last_Type_Initial_Date"
from prep
where "Type" <> "Lasttype" or "Lasttype" is null
;
I think this will work, but it sure feels ugly. There might be a better way to do this.
SELECT a.User, a.Type AS First_Type, a.Date AS FirstTypeInitialDate, b.Type AS Last_Type, b.LastTypeInitialDate
FROM table a
INNER JOIN table b ON a.User = b.User
WHERE a.Date = (SELECT MIN(c.Date) FROM table c WHERE c.User = a.User)
AND b.Date = (SELECT MIN(d.Date) FROM table d WHERE d.User = b.User
AND d.Type = (SELECT e.Type FROM table e WHERE e.User = d.User
AND e.Date = (SELECT MAX(f.Date) FROM table f WHERE f.User = e.User)))
I have a table like this
Test_order
Order Num Order ID Prev Order ID
987Y7OP89 919325 0
987Y7OP90 1006626 919325
987Y7OP91 1029350 1006626
987Y7OP92 1756689 0
987Y7OP93 1756690 0
987Y7OP94 1950100 1756690
987Y7OP95 1977570 1950100
987Y7OP96 2160462 1977570
987Y7OP97 2288982 2160462
Target table should be like below,
Order Num Order ID Prev Order ID
987Y7OP89 919325 0
987Y7OP90 1006626 919325
987Y7OP91 1029350 1006626
987Y7OP92 1756689 1029350
987Y7OP93 1756690 1756689
987Y7OP94 1950100 1756690
987Y7OP95 1977570 1950100
987Y7OP96 2160462 1977570
987Y7OP97 2288982 2160462
987Y7OP97 2288900 2288982
Prev Order ID should be updated with the Order ID from the previous record from the same table.
I'm trying to create a dummy data set and update..but it's not working..
WITH A AS
(SELECT ORDER_NUM, ORDER_ID, PRIOR_ORDER_ID,ROWNUM RID1 FROM TEST_ORDER),B AS (SELECT ORDER_NUM, ORDER_ID, PRIOR_ORDER_ID,ROWNUM+1 RID2 FROM TEST_ORDER)
SELECT A.ORDER_NUM,B.ORDER_ID,A.PRIOR_ORDER_ID,B.PRIOR_ORDER_ID FROM A,B WHERE RID1 = RID2
You could use Oracles Analytical Functions (also called Window functions) to pick up the value from the previous order:
UPDATE Test_Order
SET ORDERID = LAG(ORDERID, 1, 0) OVER (ORDER BY ORDERNUM ASC)
WHERE PrevOrderId = 0
See here for the documentation on LAG()
In sql-server you cannot use window function in update statement, not positive but don't think so in Oracle either. Anyway to get around that you can just update a cte as follows.
WITH cte AS (
SELECT
*
,NewPreviousOrderId = LAG(OrderId,1,0) OVER (ORDER BY OrderNum)
FROM
TableName
)
UPDATE cte
SET PrevOrderId = NewPreviousOrderId
And if you want to stick with the ROW_NUMBER route you were going this would be the way of doing it.
;WITH cte AS (
SELECT
*
,ROW_NUMBER() OVER (ORDER BY OrderNum) AS RowNum
FROM
TableName
)
UPDATE c1
SET PrevOrderId = c2.OrderId
FROM
cte c1
INNER JOIN cte c2
ON (c1.RowNum - 1) = c2.RowNum
Here's the background, I have a set of rows:
I only want to get the first ADVANCE after every RECOVERY in the type column per accountid
so I the result should be the advances with procdates
2015-09-03 09:55:12.228343
2015-09-04 23:10:42.016903
Is this possible in one query?
Try this:
WITH CTE_PrevType
AS (
SELECT accountid
,procdate
,LAG(type, 1, NULL) OVER (
ORDER BY procdate
) AS PreviousType
FROM < Table_Name >
)
SELECT
accountid
,procdate
,type
,value
FROM < Table_Name > AS TN
INNER JOIN CTE_PrevType CPT
ON TN.accountid = CPT.accountid
AND TN.procdate = CPT.procdate
WHERE TN.type = 'ADVANCE'
AND CPT.type = 'RECOVERY'
I Have found on how to get it!
SELECT * FROM
(SELECT accountid,
procdate,
type,
value,
LAG(type) OVER (ORDER BY procdate ASC) AS previousType
FROM transaction) w WHERE previousType = 'RECOVERY';
I have the following query which does not increment properly for ActionId
insert into CorrAction (AlertId, Action, ActionId)
select b.alertID, a.Action, b.ActionId = (Select isnull(max(CorrectiveActionId),0) + 1 from CorrAction where alertid = b.alertId)
FROM
(select requestDate, action, tag from #alert ) a
INNER JOIN
(select alertdate, tag, alertId from #RetroAlert ) b
on Convert(date,a.requestdate) = Convert(date,b.alertdate) and a.tag = b.tag
The problem that I am having is that ActionId does not increment properly.
It should do something like the following:
AlertId ActionId
------ --------
2344 1
2344 2
3455 1
5344 1
3432 1
Notice that if there is a duplicate entry for AlertId, it should increment by 1. Else it should be 1.
What happens in my query is that is always remains at 1
You can use the ROW_NUMBER function.
Here is a simplified query because I don't know the structure of your tables.
SELECT
AlertId,
alertdate,
(SELECT ISNULL(MAX(actionId), 0) FROM CorrAction AS c WHERE c.AlertId = t.AlertId)
+ ROW_NUMBER() OVER (PARTITION BY AlertId ORDER BY alertdate DESC) AS ActionId
FROM TempTable AS t;
With an example here: http://sqlfiddle.com/#!3/0d1d30/5
EDIT: Updated my example so that it starts counting from the highest actionId already inserted in the CorrAction table.
Use row number function so below query should work
insert into CorrAction (AlertId, Action, ActionId)
select b.alertID, a.Action, row_number() Over (partition by alertId order by CorrectiveActionId) as ActionId
FROM
(select requestDate, action, tag from #alert ) a
INNER JOIN
(select alertdate, tag, alertId from #RetroAlert ) b
on Convert(date,a.requestdate) = Convert(date,b.alertdate) and a.tag = b.tag
inner join CorrAction C
on C.alertid =b.alertId
I currently have the following query
SELECT organisation.organisationID, COUNT(organisation.organisationID)
FROM position, positionLocation, organisation
WHERE position.positionLocationID = positionLocation.positionLocationID AND
positionLocation.organisationID = organisation.organisationID AND
position.status = 'Open'
GROUP BY organisation.organisationID;
This query outputs
organisationID | countOrganisationID
1 3
3 2
5 3
I would like to display records that have max countOrganisationID. Ideally i would just like output the organisationID with its corresponding organisationName if possible.
Something along the lines of
organisationID | organisatioName
1 name1
5 name2
Any help would be appreciate
Thanks
Barrett is right, RANK() is the way to go, e.g.:
SELECT organisationID, c FROM (
SELECT organisationID
,c
,RANK() OVER (ORDER BY c DESC) r
FROM (
SELECT organisation.organisationID
,COUNT(organisation.organisationID) AS c
FROM position, positionLocation, organisation
WHERE position.positionLocationID = positionLocation.positionLocationID
AND positionLocation.organisationID = organisation.organisationID
AND position.status = 'Open'
GROUP BY organisation.organisationID
)
) WHERE r = 1;
Could just subquery it:
WITH counts AS (
SELECT organisation.organisationID
,organisation.organisationName
,COUNT(organisation.organisationID) the_count
FROM position, positionLocation, organisation
WHERE position.positionLocationID = positionLocation.positionLocationID
AND positionLocation.organisationID = organisation.organisationID
AND position.status = 'Open'
GROUP BY organisation.organisationID, organisation.organisationName
)
SELECT organisationID, organisationName
FROM counts
WHERE the_count = (SELECT MAX(the_count) FROM counts)
This should work.
SELECT organisationID, organisatioName
FROM position, positionLocation, organisation
WHERE position.positionLocationID = positionLocation.positionLocationID AND
positionLocation.organisationID = organisation.organisationID AND
position.status = 'Open'
AND COUNT(organisation.organisationID) =
SELECT MAX(cnt) AS MaxCnt FROM
SELECT organisation.organisationID, COUNT(organisation.organisationID) AS cnt
FROM organisation
WHERE position.status = 'Open'
GROUP BY organisation.organisationID
GROUP BY organisation.organisationID, organisation.organisatioName;