Select Latest 3 records - sql

Using SQL Server 2014. I have data that lists a Unique Identifier, a Sale Data and a Sale Price. I would like to extract into a VIEW the last 3 sales for each unique Id.
Example of data:
+------+-----------+------------+-------------+
| ID | UNIQUE_ID | SaleDate | SalePrice |
+------+-----------+------------+-------------+
| 8210 | 1-5 | 2015-09-29 | 0 |
| 8211 | 1-6 | 2016-11-01 | 485672 |
| 8212 | 1-7 | 1994-06-24 | 120000 |
| 8213 | 1-1 | 1996-09-06 | 170000 |
| 8214 | 1-1 | 2000-01-28 | 265000 |
| 8215 | 1-1 | 2013-10-02 | 305000 |
| 8216 | 1-1 | 2015-11-20 | 1425000 |
| 8217 | 1-3 | 1994-01-12 | 1 |
| 8218 | 1-3 | 2001-04-30 | 1 |
| 8219 | 1-3 | 2004-09-30 | 0 |
+------+-----------+------------+-------------+
The result in the view would list each Unique ID and then 6 fields:
SaleDate1
SalePrice1
SaleDate2
SalePrice2
SaleDate3
SalePrice3
Any hints appreciated.

You can use row_number() :
SELECT t.*
FROM (SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY UNIQUE_ID ORDER BY SaleDate DESC, SalePrice DESC) AS Seq
FROM table t
) t
WHERE Seq <= 3;

You can use a window function to filter data and then conditional aggregation to get the 6 columns you need:
declare #tmp table(ID int, UNIQUE_ID varchar(50), SaleDate date, SalePrice int)
insert into #tmp values
(8210, '1-5','2015-09-29', 0 )
,(8211, '1-6','2016-11-01', 485672 )
,(8212, '1-7','1994-06-24', 120000 )
,(8213, '1-1','1996-09-06', 170000 )
,(8214, '1-1','2000-01-28', 265000 )
,(8215, '1-1','2013-10-02', 305000 )
,(8216, '1-1','2015-11-20', 1425000)
,(8217, '1-3','1994-01-12', 1 )
,(8218, '1-3','2001-04-30', 1 )
,(8219, '1-3','2004-09-30', 0 )
SELECT t.UNIQUE_ID
,max(case when t.Seq = 1 then SaleDate else null end) as SaleDate1
,sum(case when t.Seq = 1 then SalePrice else null end) as SalePrice1
,max(case when t.Seq = 2 then SaleDate else null end) as SaleDate2
,sum(case when t.Seq = 2 then SalePrice else null end) as SalePrice2
,max(case when t.Seq = 3 then SaleDate else null end) as SaleDate3
,sum(case when t.Seq = 3 then SalePrice else null end) as SalePrice3
FROM (SELECT x.*,
ROW_NUMBER() OVER (PARTITION BY UNIQUE_ID
ORDER BY SaleDate DESC, SalePrice DESC) AS Seq
FROM #tmp x
) t
WHERE t.Seq < 4
group by t.UNIQUE_ID
Results:

The following query return the 3 most recent sold rows of each item
select * from
(
select UNIQUE_ID,SaleDate,SalePrice,rank() over (partition by UNIQUE_ID order by SaleDate desc) as rnk
from salestable
) where rnk<4

Related

how to join or merge as one row in SQL

I have these 2 tables;
table A
| ID | Name | S_ID |
|----|--------|------|
| 1 | mark | 1 |
| 2 | john | 2 |
table B (rows are not limited to 5 and Scores could be more than 3)
| ID | S_ID | Score |
|-------------------|
| 1 | 1 | 90% |
| 2 | 1 | 80% |
| 3 | 1 | 10% |
| 4 | 2 | 10% |
| 5 | 2 | 12% |
Normally using "GROUP_CONCAT" would work but is there any way to achieve this;
You are asking for a pivot query, either with or without a fixed number of columns. Assuming the former, we can use ROW_NUMBER here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY S_ID ORDER BY ID) rn
FROM tableB
)
SELECT
a.ID,
a.Name,
MAX(CASE WHEN rn = 1 THEN b.S_ID END) AS S_ID_1,
MAX(CASE WHEN rn = 1 THEN b.Score END) AS Score_1,
MAX(CASE WHEN rn = 2 THEN b.S_ID END) AS S_ID_2,
MAX(CASE WHEN rn = 2 THEN b.Score END) AS Score_2,
MAX(CASE WHEN rn = 3 THEN b.S_ID END) AS S_ID_3,
MAX(CASE WHEN rn = 3 THEN b.Score END) AS Score_3
FROM cte
GROUP BY
a.ID,
a.Name;

How I can convert Rows to Columns in SQL?

I have a table like this:
Phones
------------------------------------------------------
| CustomerID | PhoneID | PhoneNum |
-----------------------------------------------------
| 1 | 101 | 09811111 |
| 1 | 102 | 09822222 |
| 1 | 103 | 09833333 |
| 2 | 104 | 09844444 |
| 2 | 105 | 09855555 |
-------------------------------------------------
I want query that give me bellow result:
--------------------------------------------------------------------------
| CustomerID | PhoneNum1 | PhoneNum2 | PhoneNum3 |
--------------------------------------------------------------------------
| 1 | 09811111 | 09822222 | 09833333 |
| 2 | 09844444 | 09855555 | NULL |
---------------------------------------------------------------------------
How can I build the result?
We can handle this requirement with the help of ROW_NUMBER and a pivot query:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY PhoneID) rn
FROM Phones
)
SELECT
CustomerID,
MAX(CASE WHEN rn = 1 THEN PhoneNum END) AS PhoneNum1,
MAX(CASE WHEN rn = 2 THEN PhoneNum END) AS PhoneNum2,
MAX(CASE WHEN rn = 3 THEN PhoneNum END) AS PhoneNum3
FROM cte
GROUP BY
CustomerID
ORDER BY
CustomerID;
Demo
The query above was very useful. But when I use the Where, the result is not right
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY PhoneID) rn
FROM Phones
)
SELECT
CustomerID,
MAX(CASE WHEN rn = 1 THEN PhoneNum END) AS PhoneNum1,
MAX(CASE WHEN rn = 2 THEN PhoneNum END) AS PhoneNum2,
MAX(CASE WHEN rn = 3 THEN PhoneNum END) AS PhoneNum3
FROM cte
where PhoneNum ='09811111'
GROUP BY
CustomerID
ORDER BY
CustomerID;
Result:
--------------------------------------------------------------------------
| CustomerID | PhoneNum1 | PhoneNum2 | PhoneNum3 |
--------------------------------------------------------------------------
| 1 | 09811111 | NULL | NULL |
---------------------------------------------------------------------------
I find a way for my asked
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY PhoneID) rn
FROM Phones
)
SELECT
CustomerID,
MAX(CASE WHEN rn = 1 THEN PhoneNum END) AS PhoneNum1,
MAX(CASE WHEN rn = 2 THEN PhoneNum END) AS PhoneNum2,
MAX(CASE WHEN rn = 3 THEN PhoneNum END) AS PhoneNum3
FROM cte
where CustomerID = ( select CustomerID from cte where PhoneNum ='09811111' )
GROUP BY
CustomerID
ORDER BY
CustomerID;

One SQL query with multiple conditions

I am running an Oracle database and have two tables below.
#account
+----------------------------------+
| acc_id | date | acc_type |
+--------+------------+------------+
| 1 | 11-07-2018 | customer |
| 2 | 01-11-2018 | customer |
| 3 | 02-09-2018 | employee |
| 4 | 01-09-2018 | customer |
+--------+------------+------------+
#credit_request
+-----------------------------------------------------------------+
| credit_id | date | credit_type | acc_id | credit_amount |
+------------+-------------+---------- +--------+
| 1112 | 01-08-2018 | failed | 1 | 2200 |
| 1214 | 02-12-2018 | success | 2 | 1500 |
| 1312 | 03-11-2018 | success | 4 | 8750 |
| 1468 | 01-12-2018 | failed | 2 | 3500 |
+------------+-------------+-------------+--------+---------------+
Want to have followings for each customer:
the last successful credit_request
sum of credit_amount of all failed credit_requests
Here is one method:
select a.acct_id, acr.num_fails,
acr.num_successes / nullif(acr.num_fails) as ratio, -- seems weird. Why not just the failure rate?
last_cr.credit_id, last_cr.date, last_cr.credit_amount
from account a left join
(select acc_id,
sum(case when credit_type = 'failed' then 1 else 0 end) as num_fails,
sum(case when credit_type = 'failed' then credit_amount else 0 end) as num_fails,
sum(case when credit_type = 'success' then 1 else 0 end) as num_successes
max(case when credit_type = 'success' then date else 0 end) as max_success_date
from credit_request
group by acct_id
) acr left join
credit_request last_cr
on last_cr.acct_id = acr.acct_id and last_cr.date = acr.date;
The following query should do the trick.
SELECT
acc_id,
MAX(CASE WHEN credit_type = 'success' AND rn = 1 THEN credit_id END) as last_successfull_credit_id,
MAX(CASE WHEN credit_type = 'success' AND rn = 1 THEN cdate END) as last_successfull_credit_date,
MAX(CASE WHEN credit_type = 'success' AND rn = 1 THEN credit_amount END) as last_successfull_credit_amount,
SUM(CASE WHEN credit_type = 'failed' THEN credit_amount ELSE 0 END) total_amount_of_failed_credit,
SUM(CASE WHEN credit_type = 'failed' THEN 1 ELSE 0 END) / COUNT(*) ratio_success_request
FROM (
SELECT
a.acc_id,
a.cdate adate,
a.acc_type,
c.credit_id,
c.cdate,
c.credit_type,
c.credit_amount,
ROW_NUMBER() OVER(PARTITION BY c.acc_id, c.credit_type ORDER BY c.cdate DESC) rn
FROM
account a
LEFT JOIN credit_request c ON c.acc_id = a.acc_id
) x
GROUP BY acc_id
ORDER BY acc_id
The subquery assigns a sequence to each record, within groups of accounts and credit types, using ROW_NUMBR(). The outer query does conditional aggrgation to compute the different computation you asked for.
This Db Fiddle demo with your test data returns :
ACC_ID | LAST_SUCCESSFULL_CREDIT_ID | LAST_SUCCESSFULL_CREDIT_DATE | LAST_SUCCESSFULL_CREDIT_AMOUNT | TOTAL_AMOUNT_OF_FAILED_CREDIT | RATIO_SUCCESS_REQUEST
-----: | -------------------------: | :--------------------------- | -----------------------------: | ----------------------------: | --------------------:
1 | null | null | null | 2200 | 1
2 | 1214 | 02-DEC-18 | 1500 | 3500 | .5
3 | null | null | null | 0 | 0
4 | 1312 | 03-NOV-18 | 8750 | 0 | 0
This might be what you are looking for... Since you did not show expected results, this might not be 100% accurate, feel free to adapt this.
I guess the below query is easy to understand and implement. Also, to avoid more and more terms in the CASE statements you can just make use of WITH clause and use it in the CASE statements to reduce the query size.
SELECT a.acc_id,
c.credit_type,
(distinct c.credit_id),
CASE WHEN
c.credit_type='success'
THEN max(date)
END CASE,
CASE WHEN
c.credit_type='failure'
THEN sum(credit_amount)
END CASE,
(CASE WHEN
c.credit_type='success'
THEN count(*)
END CASE )/
( CASE WHEN
c.credit_type='failure'
THEN count(*)
END CASE)
from accounts a LEFT JOIN
credit_request c on
a.acc_id=c.acc_id
where a.acc_type= 'customer'
group by c.credit_type

Customized Pivot in sql 2014

I am trying to do PIVOT on sql, where two columns value has to be aggreagated for each year.
The below code gives perfect result.
DECLARE #TABLE TABLE
(
SKU VARCHAR(10),
YYMM VARCHAR(50),
BRAND VARCHAR(50),
AMT DECIMAL,
QTY INT
)
INSERT INTO #TABLE
SELECT '104591168', '2015-January', 'abott',200, 2 UNION ALL
SELECT '104580709', '2016-January', 'GSK',159 , 2 UNION ALL
SELECT '104720038', '2017-January', 'RANBAXCY',169, 2 UNION ALL
SELECT '10467011A', '2018-January', 'abott',185, 2 UNION ALL
SELECT '104590691', '2019-January', 'abott',256 , 10
SELECT *
FROM(
SELECT BRAND, sku, QTY, YYMM,AMT/QTY AS AVGPR
FROM #TABLE
) AS src
PIVOT(
sum(QTY)
for [YYMM] IN( [2015-January], [2016-January], [2017-January] /* add other moneths here */ )
) AS Pivoted
and result look like
But how can i see AVGPR in same pivot way as like sum(qty).
when i tried like
code:
SELECT *
FROM(
SELECT BRAND, sku, QTY, YYMM
FROM #TABLE
) AS src
PIVOT(
sum(QTY),
SUM(AVG)
for [YYMM] IN( [2015-January], [2016-January], [2017-January] /* add other moneths here */ )
) AS Pivoted
i am getting Incorrect syntax error.
Please help
I have a data like this.
SKU YYMM BRAND Sales Cost QTY AVGPRICE
101110028 1/1/2017 ABOTT 15.7 5.73 1 15.7
101110028 2/1/2017 ABOTT 16.33 5.66 1 16.33
101110028 3/1/2017 ABOTT 31.2 11.34 2 15.6
and
I AM TRYING TO DISPLAY LIKE THIS
Sum of QTY Sum of Avg Price
BRAND PNO 1/1/2017 2/1/2017 3/1/2017 1/1/2017 2/1/2017 3/1/2017
PAGID 101110028 0 2 1 15.7 16.33 15.6
Pivot on Quantity and then sum of avg for a same YYMM In a row
I would just use conditional aggregation:
SELECT brand, sku,
SUM(CASE WHEN YYMM = '2015-January' THEN QTY END) as [2015-January-QTY],
SUM(CASE WHEN YYMM = '2015-January' THEN QTY END) as [2015-January-AVG],
SUM(CASE WHEN YYMM = '2015-February' THEN QTY END) as [2015-February-QTY],
SUM(CASE WHEN YYMM = '2015-February' THEN QTY END) as [2015-February-AVG],
. . .
FROM #TABLE t
GROUP BY brand, sku;
Using two pivots and union all for the result set in your comment
select *, Remarks = 'QTY'
from (select brand, sku, qty, yymm from #table) src
pivot(sum(qty) for [yymm] in (
[2015-January], [2016-January], [2017-January] /* add other months here */
) ) as Pivoted
union all
select *, Remarks = 'AVG'
from (select brand, sku, yymm,amt/qty as avgpr from #table) src
pivot(sum(avgpr) for [yymm] in (
[2015-January], [2016-January], [2017-January] /* add other months here */
) ) as Pivoted
order by brand, sku, remarks desc;
rextester demo: http://rextester.com/ORPF1616
returns:
+----------+-----------+-------------------+------------------+------------------+---------+
| brand | sku | 2015-January | 2016-January | 2017-January | Remarks |
+----------+-----------+-------------------+------------------+------------------+---------+
| abott | 104590691 | NULL | NULL | NULL | QTY |
| abott | 104590691 | NULL | NULL | NULL | AVG |
| abott | 104591168 | 2,0000000000000 | NULL | NULL | QTY |
| abott | 104591168 | 100,0000000000000 | NULL | NULL | AVG |
| abott | 10467011A | NULL | NULL | NULL | QTY |
| abott | 10467011A | NULL | NULL | NULL | AVG |
| gsk | 104580709 | NULL | 2,0000000000000 | NULL | QTY |
| gsk | 104580709 | NULL | 79,5000000000000 | NULL | AVG |
| ranbaxcy | 104720038 | NULL | NULL | 2,0000000000000 | QTY |
| ranbaxcy | 104720038 | NULL | NULL | 84,5000000000000 | AVG |
+----------+-----------+-------------------+------------------+------------------+---------+

How to JOIN a table with itself and display it as a single row

There is a table named PRODUCT_PRICE:
CREATE TABLE [TEST].[PRODUCT_PRICE]
(
[PRICE_ID] [bigint] NOT NULL,
[PRODUCT_ID] [bigint] NOT NULL,
[PRICE_DATE] [date] NOT NULL,
[IS_SALE_PRICE] [bit] NOT NULL,
[UNIT_PRICE] [decimal](18, 2) NOT NULL
)
It has the following records:
PRICE_ID PRODUCT_ID PRICE_DATE IS_SALE_PRICE UNIT_PRICE
-------- ---------- ---------- ------------- ----------
1 15 2015-05-12 False 0,05
2 15 2015-05-12 True 0,04
3 25 2015-05-12 False 1,45
4 35 2015-05-12 True 2,65
Edit: There can only be two prices - a purchase price and a sale price. There can't be 3 or more rows with same PRODUCT_ID and PRICE_DATE.
I want to write a SELECT statement that results in the following:
PRICE_ID PRODUCT_ID PRICE_DATE IS_SALE_PRICE UNIT_PRICE PRICE_ID_2 IS_SALE_PRICE_2 UNIT_PRICE_2
-------- ---------- ---------- ------------- ---------- ---------- --------------- ------------
1 15 2015-05-12 False 0,05 2 True 0,04
3 25 2015-05-12 False 1,45 NULL NULL NULL
4 35 2015-05-12 True 2,65 NULL NULL NULL
I tried FULL OUTER JOIN but it results in 4 rows rather than 3 which is correct but not what I am looking for:
SELECT
PR1.*,
PR2.PRICE_ID AS PRICE_ID_2,
PR2.IS_SALE_PRICE AS IS_SALE_PRICE_2,
PR2.UNIT_PRICE AS UNIT_PRICE_2
FROM PRODUCT_PRICE AS PR1
FULL OUTER JOIN PRODUCT_PRICE AS PR2
ON PR1.PRODUCT_ID = PR2.PRODUCT_ID
AND PR1.PRICE_DATE = PR2.PRICE_DATE
AND PR1.PRICE_ID <> PR2.PRICE_ID
AND PR1.IS_SALE_PRICE <> PR2.IS_SALE_PRICE
WHERE
PR1.PRICE_DATE = '20150512'
ORDER BY PR1.PRICE_ID
Result of the above query:
| PRICE_ID | PRODUCT_ID | PRICE_DATE | IS_SALE_PRICE | UNIT_PRICE | PRICE_ID_2 | IS_SALE_PRICE_2 | UNIT_PRICE_2 |
|----------|------------|------------|---------------|------------|------------|-----------------|--------------|
| 1 | 15 | 2015-05-12 | false | 0.05 | 2 | true | 0.04 |
| 2 | 15 | 2015-05-12 | true | 0.04 | 1 | false | 0.05 |
| 3 | 25 | 2015-05-12 | false | 1.45 | (null) | (null) | (null) |
| 4 | 35 | 2015-05-12 | true | 2.65 | (null) | (null) | (null) |
Basically I want to JOIN a table with itself and remove duplicates.
Note: PRICE_ID is an identity field (primary key). But natural key is the PRODUCT_ID, PRICE_DATE pair. I want a row for each unique PRODUCT_ID and PRICE_DATE.
SQL Fiddle
If you're sure that there will only be a maximum of 2 rows for each PRODUCT_ID - PRICE_DATE combination, you can use conditional aggregation instead of JOIN:
SQL Fiddle
SELECT
PRICE_ID = MAX(CASE WHEN RN = 1 THEN PRICE_ID END),
PRODUCT_ID,
PRICE_DATE,
IS_SALE_PRICE = MAX(CASE WHEN RN = 1 THEN CAST(IS_SALE_PRICE AS INT) END),
UNIT_PRICE = MAX(CASE WHEN RN = 1 THEN UNIT_PRICE END),
PRICE_ID2 = MAX(CASE WHEN RN = 2 THEN PRICE_ID END),
IS_SALE_PRICE2 = MAX(CASE WHEN RN = 2 THEN CAST(IS_SALE_PRICE AS INT) END),
UNIT_PRICE2 = MAX(CASE WHEN RN = 2 THEN UNIT_PRICE END)
FROM (
SELECT *,
RN = ROW_NUMBER() OVER(PARTITION BY PRODUCT_ID, PRICE_DATE ORDER BY IS_SALE_PRICE)
FROM PRODUCT_PRICE
)t
GROUP BY PRODUCT_ID, PRICE_DATE
ORDER BY PRODUCT_ID, PRICE_DATE
Result
| PRICE_ID | PRODUCT_ID | PRICE_DATE | IS_SALE_PRICE | UNIT_PRICE | PRICE_ID2 | IS_SALE_PRICE2 | UNIT_PRICE2 |
|----------|------------|------------|---------------|------------|-----------|----------------|-------------|
| 1 | 15 | 2015-05-12 | 0 | 0.05 | 2 | 1 | 0.04 |
| 3 | 25 | 2015-05-12 | 0 | 1.45 | (null) | (null) | (null) |
| 4 | 35 | 2015-05-12 | 1 | 2.65 | (null) | (null) | (null) |
If you insist on using JOIN, you can use FULL JOIN:
SQL Fiddle
SELECT
PRICE_ID = CASE WHEN PP.PRICE_ID IS NOT NULL THEN PP.PRICE_ID ELSE SP.PRICE_ID END,
PRODUCT_ID = CASE WHEN PP.PRICE_ID IS NOT NULL THEN PP.PRODUCT_ID ELSE SP.PRODUCT_ID END,
PRICE_DATE = CASE WHEN PP.PRICE_ID IS NOT NULL THEN PP.PRICE_DATE ELSE SP.PRICE_DATE END,
IS_SALE_PRICE = CASE WHEN PP.PRICE_ID IS NOT NULL THEN PP.IS_SALE_PRICE ELSE SP.IS_SALE_PRICE END,
UNIT_PRICE = CASE WHEN PP.PRICE_ID IS NOT NULL THEN PP.UNIT_PRICE ELSE SP.UNIT_PRICE END,
PRICE_ID2 = CASE WHEN PP.PRICE_ID IS NOT NULL THEN SP.PRICE_ID END,
IS_SALE_PRICE2 = CASE WHEN PP.PRICE_ID IS NOT NULL THEN SP.IS_SALE_PRICE END,
UNIT_PRICE2 = CASE WHEN PP.PRICE_ID IS NOT NULL THEN SP.UNIT_PRICE END
FROM (
SELECT *
FROM PRODUCT_PRICE
WHERE IS_SALE_PRICE = 0
)AS PP
FULL JOIN(
SELECT *
FROM PRODUCT_PRICE
WHERE IS_SALE_PRICE = 1
)AS SP
ON PP.PRODUCT_ID = SP.PRODUCT_ID
AND PP.PRICE_DATE = SP.PRICE_DATE
ORDER BY PRODUCT_ID, PRICE_DATE
You are getting PRICE_ID=1 vs PRICE_ID =2 and PRICE_ID=2 vs PRICE_ID=1
So you have a repeated row.
In the ON CLAUSE, you should force to only join when PRICE_ID1 < PRICE_ID2
Add this to the ON CLAUSE:
AND PR1.PRICE_ID < PR2.PRICE_ID
And use LEFT JOIN
With that changes u will get 4 rows, u need also to avoid the the row 2, because its already "inside" the row 1. So u only have to filter registers with that in the where clause:
AND PR1.PRICE_ID in (select min(PRICE_ID) from PRODUCT_PRICE group by PRODUCT_ID)
Do a FULL OUTER JOIN.
select ...
from PRODUCT_PRICE p1
FULL OUTER JOIN PRODUCT_PRICE p2
ON p1.PRODUCT_ID = p2.PRODUCT_ID
AND p1.PRICE_DATE = p2.PRICE_DATE
AND p1.IS_SALE_PRICE = 'true
AND p2.IS_SALE_PRICE = 'false'
Write select list columns. And adjust IS_SALE_PRICE comparisons.
Alternative solution, derived tables FULL OUTER JOIN:
select ...
from (select * from PRODUCT_PRICE
where IS_SALE_PRICE = 'true') as p1
FULL OUTER JOIN
(select * from PRODUCT_PRICE
where IS_SALE_PRICE = 'false') as p2
ON p1.PRODUCT_ID = p2.PRODUCT_ID
AND p1.PRICE_DATE = p2.PRICE_DATE