Select only one column using case or pivot in PLSQL - sql

I have a table or data like this
This data has a same invoice number, so I want to show table only one column using case or pivot. The result that I want is like this
Can you help me about this ?

Using CASE expressions:
WITH
tbl AS -- Sample Data
(
Select 'WSIV/H/02/22/00122' "NO_INVOICE", To_Date('04.08-2022', 'dd.mm.yyyy') "PAID_DATE", 50000 "AMOUNT_APPLY" From Dual Union All
Select 'WSIV/H/02/22/00122' "NO_INVOICE", To_Date('06.08-2022', 'dd.mm.yyyy') "PAID_DATE", 60000 "AMOUNT_APPLY" From Dual Union All
Select 'WSIV/H/02/22/00122' "NO_INVOICE", To_Date('07.08-2022', 'dd.mm.yyyy') "PAID_DATE", 70000 "AMOUNT_APPLY" From Dual
)
SELECT
NO_INVOICE,
Max(PAID_DATE_1) "PAID_DATE_1", Max(AMOUNT_APPLY_1) "AMOUNT_APPLY_1",
Max(PAID_DATE_2) "PAID_DATE_2", Max(AMOUNT_APPLY_2) "AMOUNT_APPLY_2",
Max(PAID_DATE_3) "PAID_DATE_3", Max(AMOUNT_APPLY_3) "AMOUNT_APPLY_3",
Max(PAID_DATE_4) "PAID_DATE_4", Max(AMOUNT_APPLY_4) "AMOUNT_APPLY_4",
Nvl(Max(AMOUNT_APPLY_1), 0) + Nvl(Max(AMOUNT_APPLY_2), 0) + Nvl(Max(AMOUNT_APPLY_3), 0) + Nvl(Max(AMOUNT_APPLY_4), 0) "TOTAL"
FROM
(
Select
NO_INVOICE,
CASE WHEN ROW_NUMBER() OVER(Partition By NO_INVOICE Order By PAID_DATE) = 1 THEN PAID_DATE END "PAID_DATE_1",
CASE WHEN ROW_NUMBER() OVER(Partition By NO_INVOICE Order By PAID_DATE) = 2 THEN PAID_DATE END "PAID_DATE_2",
CASE WHEN ROW_NUMBER() OVER(Partition By NO_INVOICE Order By PAID_DATE) = 3 THEN PAID_DATE END "PAID_DATE_3",
CASE WHEN ROW_NUMBER() OVER(Partition By NO_INVOICE Order By PAID_DATE) = 4 THEN PAID_DATE END "PAID_DATE_4",
CASE WHEN ROW_NUMBER() OVER(Partition By NO_INVOICE Order By PAID_DATE) = 1 THEN AMOUNT_APPLY END "AMOUNT_APPLY_1",
CASE WHEN ROW_NUMBER() OVER(Partition By NO_INVOICE Order By PAID_DATE) = 2 THEN AMOUNT_APPLY END "AMOUNT_APPLY_2",
CASE WHEN ROW_NUMBER() OVER(Partition By NO_INVOICE Order By PAID_DATE) = 3 THEN AMOUNT_APPLY END "AMOUNT_APPLY_3",
CASE WHEN ROW_NUMBER() OVER(Partition By NO_INVOICE Order By PAID_DATE) = 4 THEN AMOUNT_APPLY END "AMOUNT_APPLY_4"
From
tbl
)
GROUP BY NO_INVOICE
ORDER BY NO_INVOICE
Result:
NO_INVOICE
PAID_DATE_1
AMOUNT_APPLY_1
PAID_DATE_2
AMOUNT_APPLY_2
PAID_DATE_3
AMOUNT_APPLY_3
PAID_DATE_4
AMOUNT_APPLY_4
TOTAL
WSIV/H/02/22/00122
04-AUG-22
50000
06-AUG-22
60000
07-AUG-22
70000
180000

Using PIVOT clause
WITH
tbl AS -- Sample Data
(
SELECT 'WSIV/H/02/22/00122' no_invoice, DATE '2022-08-04' paid_date, 50000 amount_apply FROM DUAL UNION ALL
SELECT 'WSIV/H/02/22/00122' no_invoice, DATE '2022-08-06' paid_date, 60000 amount_apply FROM DUAL UNION ALL
SELECT 'WSIV/H/02/22/00122' no_invoice, DATE '2022-08-07' paid_date, 70000 amount_apply FROM DUAL
)
SELECT
no_invoice,
"1_PAID_DATE" paid_date_1, "1_AMOUNT_APPLY" amount_apply_1,
"2_PAID_DATE" paid_date_2, "2_AMOUNT_APPLY" amount_apply_2,
"3_PAID_DATE" paid_date_3, "3_AMOUNT_APPLY" amount_apply_3,
"4_PAID_DATE" paid_date_4, "4_AMOUNT_APPLY" amount_apply_4,
NVL("1_AMOUNT_APPLY",0)+NVL("2_AMOUNT_APPLY",0)+NVL("3_AMOUNT_APPLY",0)+NVL("4_AMOUNT_APPLY",0)+NVL("5_AMOUNT_APPLY",0) total
FROM
( SELECT
no_invoice, paid_date, amount_apply,
LEAST(5, ROW_NUMBER() OVER (PARTITION BY NO_INVOICE ORDER BY paid_date)) bucket
FROM tbl
)
PIVOT
( ANY_VALUE(paid_date) paid_date, ANY_VALUE(amount_apply) amount_apply
FOR bucket IN (1,2,3,4,5)
)
Bucket 5 is used to trap all amount_apply's beyond the first 4 dates so they may be included in the total even though those amounts will not be shown in the columns (a catch all).
In Oracles prior to 19c use MAX rather than ANY_VALUE.
If you want the paid_date's to be aggregated so that all payments for an no_invoice on the same date appear just in one column, then change ROW_NUMBER to DENSE_RANK and ANY_VALUE(amount_apply) to SUM(amount_apply).
Result is as per OP. Thanks to #d-r for Sample Data.

Related

Get range of dates from dates record in MS SQL

I have dates record
with DateTable (dateItem) as
(
select '2022-07-03' union all
select '2022-07-05' union all
select '2022-07-04' union all
select '2022-07-09' union all
select '2022-07-12' union all
select '2022-07-13' union all
select '2022-07-18'
)
select dateItem
from DateTable
order by 1 asc
I want to get ranges of dates between this record like this
with DateTableRange (dateItemStart, dateItemend) as
(
select '2022-07-03','2022-07-05' union all
select '2022-07-09','2022-07-09' union all
select '2022-07-12','2022-07-13' union all
select '2022-07-18','2022-07-18'
)
select dateItemStart, dateItemend
from DateTableRange
I am able to do it in SQL with looping using while or looping by getting first one and check the next dates and if they are 1 plus then I add it in enddate and do the same in loop
But I don't know what the best or optimized way is, as there were lots of looping and temp tables involve
Edited :
as in data we have 3,4,5 and 6,7,8 is missing so range is 3-5
9 exist and 10 is missing so range is 9-9
so ranges is purely depend on the consecutive data in datetable
Any suggestion will be appreciated
With some additional clarity this requires a gaps-and-islands approach to first identify adjacent rows as groups, from which you can then use a window to identify the first and last value of each group.
I'm sure this could be refined further but should give your desired results:
with DateTable (dateItem) as
(
select '2022-07-03' union all
select '2022-07-05' union all
select '2022-07-04' union all
select '2022-07-09' union all
select '2022-07-12' union all
select '2022-07-13' union all
select '2022-07-18'
), valid as (
select *,
case when exists (
select * from DateTable d2 where Abs(DateDiff(day, d.dateitem, d2.dateitem)) = 1
) then 1 else 0 end v
from DateTable d
), grp as (
select *,
Row_Number() over(order by dateitem) - Row_Number()
over (partition by v order by dateitem) g
from Valid v
)
select distinct
Iif(v = 0, dateitem, First_Value(dateitem) over(partition by g order by dateitem)) DateItemStart,
Iif(v = 0, dateitem, First_Value(dateitem) over(partition by g order by dateitem desc)) DateItemEnd
from grp
order by dateItemStart;
See Demo Fiddle
After clarification, this is definitely a 'gaps and islands' problem.
The solution can be like this
WITH DateTable(dateItem) AS
(
SELECT * FROM (
VALUES
('2022-07-03'),
('2022-07-05'),
('2022-07-04'),
('2022-07-09'),
('2022-07-12'),
('2022-07-13'),
('2022-07-18')
) t(v)
)
SELECT
MIN(dateItem) AS range_from,
MAX(dateItem) AS range_to
FROM (
SELECT
*,
SUM(CASE WHEN DATEADD(day, 1, prev_dateItem) >= dateItem THEN 0 ELSE 1 END) OVER (ORDER BY rn) AS range_id
FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY dateItem) AS rn,
CAST(dateItem AS date) AS dateItem,
CAST(LAG(dateItem) OVER (ORDER BY dateItem) AS date) AS prev_dateItem
FROM DateTable
) groups
) islands
GROUP BY range_id
You can check a working demo

Pivot the dates based on event/status values

I have data like below for a SQL query and want to convert that as below where 106 is event start and 110 is event end date.
If the sequence of records is always 106/110 for each (orders_sk_seq, order_product_sk_seq) tuple, then you can just use lead():
select *
from (
select
orders_sk_seq,
order_product_sk,
create_datetime start_date,
status_code,
lead(create_datetime) over(
partition by orders_sk_seq, order_product_sk_seq order by create_datetime
) end_date
from mytable
) t
where status_code = 106
order by start_date
WITH Order_CTE AS
(
SELECT order_Product_sk_seq,status_code,create_datetime
,ROW_NUMBER() OVER (PARTITION BY order_Product_sk_seq,status_code ORDER BY create_datetime) AS SEQUENCE
FROM atclose.order_status
where status_code IN (106,110)
)
SELECT
b1.order_Product_sk_seq
,b1.create_datetime
,b2.create_datetime
FROM Order_CTE b1
JOIN (
SELECT
order_Product_sk_seq
,create_datetime
,ROW_NUMBER() OVER (PARTITION BY order_Product_sk_seq ORDER BY create_datetime) AS SEQUENCE
FROM atclose.order_status
WHERE status_code = 110) b2
ON b1.order_Product_sk_seq = b2.order_Product_sk_seq
AND b1.Sequence = b2.Sequence
WHERE b1.status_code = 106;

How to get the validity date range of a price from individual daily prices in SQL

I have some prices for the month of January.
Date,Price
1,100
2,100
3,115
4,120
5,120
6,100
7,100
8,120
9,120
10,120
Now, the o/p I need is a non-overlapping date range for each price.
price,from,To
100,1,2
115,3,3
120,4,5
100,6,7
120,8,10
I need to do this using SQL only.
For now, if I simply group by and take min and max dates, I get the below, which is an overlapping range:
price,from,to
100,1,7
115,3,3
120,4,10
This is a gaps-and-islands problem. The simplest solution is the difference of row numbers:
select price, min(date), max(date)
from (select t.*,
row_number() over (order by date) as seqnum,
row_number() over (partition by price, order by date) as seqnum2
from t
) t
group by price, (seqnum - seqnum2)
order by min(date);
Why this works is a little hard to explain. But if you look at the results of the subquery, you will see how the adjacent rows are identified by the difference in the two values.
SELECT Lag.price,Lag.[date] AS [From], MIN(Lead.[date]-Lag.[date])+Lag.[date] AS [to]
FROM
(
SELECT [date],[Price]
FROM
(
SELECT [date],[Price],LAG(Price) OVER (ORDER BY DATE,Price) AS LagID FROM #table1 A
)B
WHERE CASE WHEN Price <> ISNULL(LagID,1) THEN 1 ELSE 0 END = 1
)Lag
JOIN
(
SELECT [date],[Price]
FROM
(
SELECT [date],Price,LEAD(Price) OVER (ORDER BY DATE,Price) AS LeadID FROM [#table1] A
)B
WHERE CASE WHEN Price <> ISNULL(LeadID,1) THEN 1 ELSE 0 END = 1
)Lead
ON Lag.[Price] = Lead.[Price]
WHERE Lead.[date]-Lag.[date] >= 0
GROUP BY Lag.[date],Lag.[price]
ORDER BY Lag.[date]
Another method using ROWS UNBOUNDED PRECEDING
SELECT price, MIN([date]) AS [from], [end_date] AS [To]
FROM
(
SELECT *, MIN([abc]) OVER (ORDER BY DATE DESC ROWS UNBOUNDED PRECEDING ) end_date
FROM
(
SELECT *, CASE WHEN price = next_price THEN NULL ELSE DATE END AS abc
FROM
(
SELECT a.* , b.[date] AS next_date, b.price AS next_price
FROM #table1 a
LEFT JOIN #table1 b
ON a.[date] = b.[date]-1
)AA
)BB
)CC
GROUP BY price, end_date

Group Consecutive Records

I am trying to group some records to find the first one for a particular site for a given client. The problem is that the records go back and forth between sites, so I need to keep non-consecutive site date ranges separate.
Given the sample data, I want to end up with 3 records - one for site 1 starting 7/3/18, a second for site 2 starting on 9/3/18 and the third for site 1 again starting 11/3/18.
SELECT 9999 AS CLIENT_ID, 1 AS SITE_NUM, '2018-07-03' AS START_DATE, '2018-08-05' AS CREATED_DATE, 1 AS RECORD_ID
INTO #TEMP
UNION
SELECT 9999 AS MEMBER_ID, 1 AS SITE_NUM, '2018-08-01' AS CONSENT_SIGN_DATE, '2018-10-05' AS CREATED_DATE, 2
UNION
SELECT 9999 AS MEMBER_ID, 1 AS SITE_NUM, '2018-07-03' AS CONSENT_SIGN_DATE, '2018-09-22' AS CREATED_DATE, 3
UNION
SELECT 9999 AS MEMBER_ID, 2 AS SITE_NUM, '2018-09-03' AS CONSENT_SIGN_DATE, '2018-09-05' AS CREATED_DATE, 4
UNION
SELECT 9999 AS MEMBER_ID, 2 AS SITE_NUM, '2018-10-03' AS CONSENT_SIGN_DATE, '2018-10-05' AS CREATED_DATE, 5
UNION
SELECT 9999 AS MEMBER_ID, 1 AS SITE_NUM, '2018-11-03' AS CONSENT_SIGN_DATE, '2018-11-05' AS CREATED_DATE, 6
UNION
SELECT 9999 AS MEMBER_ID, 1 AS SITE_NUM, '2018-12-01' AS CONSENT_SIGN_DATE, '2018-12-05' AS CREATED_DATE, 7
I've been playing with ROW_NUM but haven't been able to figure out how to separate the two sets of dates for Site 1.
SELECT *, ROW_NUMBER()OVER(PARTITION BY T.CLIENT_ID, T.SITE_NUM ORDER BY T.START_DATE, T.RECORD_ID)
FROM #TEMP T
LEFT JOIN #TEMP T2 ON T2.CLIENT_ID = T.CLIENT_ID AND T2.RECORD_ID = T.RECORD_ID - 1
ORDER BY T.RECORD_ID
How can I group the results by client and consecutive dates for a single site?
This is a gaps-and-islands problem. For this, the difference of row numbers is the best approach:
select t.client_id, t.site_num, min(t.start_date), max(t.start_date)
from (select t.*,
row_number() over (partition by t.client_id order by T.START_DATE, T.RECORD_ID) as seqnum_c,
row_number() over (partition by t.client_id, t.site_num order by T.START_DATE, T.RECORD_ID) as seqnum_cs
from #temp t
) t
group by client_id, site_num, (seqnum_c - seqnum_cs)
WHat you want is consecutive rows should not have the same SITE_NUM value. All you need to do is add a where clause at the end of your query.
SELECT *, ROW_NUMBER()OVER(PARTITION BY T.CLIENT_ID, T.SITE_NUM ORDER BY T.START_DATE, T.RECORD_ID)
FROM #TEMP T
LEFT JOIN #TEMP T2 ON T2.CLIENT_ID = T.CLIENT_ID AND T2.RECORD_ID = T.RECORD_ID - 1
ORDER BY T.RECORD_ID
WHERE T.SITE_NUM <> T2.SITE_NUM OR T2.SITE_NUM IS NULL
EDIT As suggested by #SteveB to add T2.SITE_NUM IS NULL to show last record too.

Grouping duplicate rows and calculating effective and end dates

As per the attached sample, I have data of repeated rows with different date values. I would like to combine the duplicate records to reduce the number of rows and at the same time would like to calculate the end date of record.
“CountryCode” column should be used to combine the records and value changes in “CountryRiskLevel” or “RegionRiskLevel” columns should be used to define the start and end date ranges.
Database - SQL Server.
Try this query, I used slightly different sample data, but query will work for you as well:
;with SampleData as(
select 1 CountryCode,
1 RegionCode,
5 CountryRiskLevel,
5 RegionRiskLevel,
CONVERT(date, '2018-01-01') EffectiveDate
union all
select 1,1,5,5,CONVERT(date, '2018-01-02')
union all
select 1,1,5,5,CONVERT(date, '2018-01-03')
union all
select 1,1,5,5,CONVERT(date, '2018-01-04')
union all
select 1,1,2,2,CONVERT(date, '2018-01-05')
union all
select 1,1,5,5,CONVERT(date, '2018-01-06')
union all
select 1,1,5,5,CONVERT(date, '2018-01-07')
union all
select 1,1,5,3,CONVERT(date, '2018-01-08')
union all
select 1,1,5,5,CONVERT(date, '2018-01-09')
union all
select 1,1,5,5,CONVERT(date, '2018-01-10')
union all
select 1,1,5,5,CONVERT(date, '2018-01-11')
)
select CountryCode,
RegionCode,
CountryRiskLevel,
RegionRiskLevel,
MIN(effectiveDate) EffecticeStartDate,
case when MAX(effectiveDate) = MIN(effectiveDate) then MAX(dt) else MAX(effectiveDate) end EffectiveEndDate
from (
select *,
ROW_NUMBER() over (partition by CountryCode, RegionCode, CountryRiskLevel, RegionRiskLevel order by EffectiveDate) rn1,
ROW_NUMBER() over (order by EffectiveDate) rn2,
case when COUNT(*) over (partition by countrycode, RegionCode, CountryRiskLevel, RegionRiskLevel) = 1
then LEAD(effectivedate) over (order by effectivedate) end dt
from SampleData
) a group by CountryCode, RegionCode, CountryRiskLevel, RegionRiskLevel, rn2 - rn1