SQL insert string from one column into another in select statement - sql

I have a SQL table that looks something like this:
Date Object_ID Category Direction
0 2011-02-02 0H 1234 JKL/987 A N
1 2011-02-02 0H 4321 BNM/987 A N
2 2011-02-02 0H 5678+ JKL/987 A N
3 2011-02-02 0H 8765 BNM/987 A S
4 2011-02-02 0H 9021+ JKL/987 A S
5 2011-02-02 0H 1102+ JKL/987 A N
I want to be able to add the string value in the 'Direction' column (either 'N' or 'S') to the 'Object_ID' column at a specific position so that the output of the select statement returns this:
Date Object_ID Category Direction
0 2011-02-02 0H 1234 NJKL/987 A N
1 2011-02-02 0H 4321 NBNM/987 A N
2 2011-02-02 0H 5678+NJKL/987 A N
3 2011-02-02 0H 8765 SBNM/987 A S
4 2011-02-02 0H 9021+SJKL/987 A S
5 2011-02-02 0H 1102+NJKL/987 A N
I know that the spacing is odd but it's important that it is maintained. Any help would be appreciated.

Given the example, where data are exactly in 2 different formats based on something in the 8th position, then you can use a case expression with concat().
with my_data as (
select '2011-02-02' as date, '0H 1234 JKL/987' as object_id, 'A' as category, 'N' as direction union all
select '2011-02-02', '0H 4321 BNM/987', 'A', 'N' union all
select '2011-02-02', '0H 5678+ JKL/987', 'A', 'N' union all
select '2011-02-02', '0H 8765 BNM/987', 'A', 'S' union all
select '2011-02-02', '0H 9021+ JKL/987', 'A', 'S' union all
select '2011-02-02', '0H 1102+ JKL/987', 'A', 'N')
select date, object_id as orig_obj_id,
case
when substring(object_id, 8, 1) = ' '
then concat(substring(object_id, 1, 8), direction, substring(object_id, 9, 8))
else concat(substring(object_id, 1, 8), direction, substring(object_id, 10, 7))
end as mod_obj_id,
category, direction
from my_data;
date
orig_obj_id
mod_obj_id
category
direction
2011-02-02
0H 1234 JKL/987
0H 1234 NJKL/987
A
N
2011-02-02
0H 4321 BNM/987
0H 4321 NBNM/987
A
N
2011-02-02
0H 5678+ JKL/987
0H 5678+NJKL/987
A
N
2011-02-02
0H 8765 BNM/987
0H 8765 SBNM/987
A
S
2011-02-02
0H 9021+ JKL/987
0H 9021+SJKL/987
A
S
2011-02-02
0H 1102+ JKL/987
0H 1102+NJKL/987
A
N
Output easier seen as text vs table above:
mod_obj_id
0H 1234 NJKL/987
0H 4321 NBNM/987
0H 5678+NJKL/987
0H 8765 SBNM/987
0H 9021+SJKL/987
0H 1102+NJKL/987

Using regexp_replace:
select t.rnum, t.date, regexp_replace(t.object_id, '(?<=\d)\+*\s(?=[A-Z])',
case when regexp_substr(t.object_id, '(?<=\d)\+*\s(?=[A-Z])') = ' '
then ' '||t.direction else '+'||t.direction end),
t.category, t.direction
from tbl t
See fiddle.

Related

How to get the previous 10 lines and the last 10 lines at the same time by sql oracle

I want to write an oracle sql query
I have data table like this:
Table A
no ID Time
1 A001 9/27/2021 3:22:42 PM
2 A002 9/27/2021 3:25:58 PM
3 A003 9/27/2021 2:40:48 PM
4 A004 9/27/2021 2:40:44 PM
5 A005 9/27/2021 2:40:46 PM
6 A006 9/27/2021 2:40:51 PM
........................................
1000 A1000 9/27/2021 2:44:38 PM
1001 A1001 9/27/2021 2:44:47 PM
1002 A1002 9/27/2021 2:44:36 PM
1003 A1003 9/27/2021 2:44:40 PM
1004 A1004 9/27/2021 2:44:43 PM
1005 A1005 9/27/2021 2:43:57 PM
............................................
A99999999999................................
and 1 more table like this:
Table B
No ID Time
1 A03 9/27/2021 2:40:51 PM
2 A05 9/27/2021 2:44:36 PM
............................................
A999........................................
know that table B is definitely in table A.How can we get 10 rows above and 10 rows down from table A for each row in table B?.
currently I just use rank(), lag(), lead() and then join the 2 tables together but no desired result yet
WITH JOINED_TABLES AS (
SELECT "A".*, B."no" AS B_no
, ROW_NUMBER() OVER (ORDER BY "A"."Time") AS RN
FROM "A"
LEFT JOIN B ON "A"."ID"=B."ID"
)
, CENTRE_RNS AS (
SELECT RN FROM JOINED_TABLES WHERE B_no IS NOT NULL
)
SELECT J."no", J."ID", J."Time", C.RN FROM JOINED_TABLES J
LEFT JOIN CENTRE_RNS C ON J.RN BETWEEN C.RN-1 AND C.RN+1
ORDER BY "no"
SQL Fiddle here
You can edit the second last line to be -10 and +10
From Oracle 12, you can use a LATERAL join and analytic functions.
For example, if you wanted the window of 2 rows either side:
SELECT *
FROM TableB b
INNER JOIN LATERAL (
SELECT no,
id,
time,
rn - MAX(match) OVER () AS window
FROM (
SELECT no,
id,
time,
ROW_NUMBER() OVER (ORDER BY time) AS rn,
CASE
WHEN a.time = b.time
THEN ROW_NUMBER() OVER (ORDER BY time)
END AS match
FROM TableA a
)
) a
ON (a.window BETWEEN -2 AND 2)
Then, for the sample data:
CREATE TABLE TableA (no, ID, Time) AS
SELECT level,
'A' || TO_CHAR(level, '000'),
DATE '2021-09-27' + LEVEL * INTERVAL '3' SECOND
FROM DUAL
CONNECT BY LEVEL <= 50;
CREATE TABLE TableB (no, ID, Time) AS
SELECT ROWNUM,
ID,
time
FROM TableA
WHERE no IN (6, 32);
The query outputs:
NO
ID
TIME
NO
ID
TIME
WINDOW
1
A 006
2021-09-27 00:00:18
4
A 004
2021-09-27 00:00:12
-2
1
A 006
2021-09-27 00:00:18
5
A 005
2021-09-27 00:00:15
-1
1
A 006
2021-09-27 00:00:18
6
A 006
2021-09-27 00:00:18
0
1
A 006
2021-09-27 00:00:18
7
A 007
2021-09-27 00:00:21
1
1
A 006
2021-09-27 00:00:18
8
A 008
2021-09-27 00:00:24
2
2
A 032
2021-09-27 00:01:36
30
A 030
2021-09-27 00:01:30
-2
2
A 032
2021-09-27 00:01:36
31
A 031
2021-09-27 00:01:33
-1
2
A 032
2021-09-27 00:01:36
32
A 032
2021-09-27 00:01:36
0
2
A 032
2021-09-27 00:01:36
33
A 033
2021-09-27 00:01:39
1
2
A 032
2021-09-27 00:01:36
34
A 034
2021-09-27 00:01:42
2
db<>fiddle here

convert data wide to long with make sequential date in postgresql

I have data frame with date like below :
id start_date end_date product supply_per_day
1 2020-03-01 2020-03-01 A 10
1 2020-03-01 2020-03-01 B 10
1 2020-03-01 2020-03-02 A 5
2 2020-02-28 2020-03-02 A 10
2 2020-03-01 2020-03-03 B 4
2 2020-03-02 2020-03-05 A 5
I want make this data wide to long like :
id date product supply_per_day
1 2020-03-01 A 10
1 2020-03-01 B 10
1 2020-03-01 A 5
1 2020-03-02 A 5
2 2020-02-28 A 10
2 2020-03-01 A 10
2 2020-03-02 A 10
2 2020-03-01 B 4
2 2020-03-02 B 4
2 2020-03-03 B 4
2 2020-03-02 B 5
2 2020-03-03 B 5
2 2020-03-04 B 5
2 2020-03-05 B 5
give me some idea please
For Oracle 12c and later, you can use:
SELECT t.id,
d.dt,
t.product,
t.supply_per_day
FROM table_name t
OUTER APPLY(
SELECT start_date + LEVEL - 1 AS dt
FROM DUAL
CONNECT BY start_date + LEVEL - 1 <= end_date
) d
Which, for the sample data:
CREATE TABLE table_name ( id, start_date, end_date, product, supply_per_day ) AS
SELECT 1, DATE '2020-03-01', DATE '2020-03-01', 'A', 10 FROM DUAL UNION ALL
SELECT 1, DATE '2020-03-01', DATE '2020-03-01', 'B', 10 FROM DUAL UNION ALL
SELECT 1, DATE '2020-03-01', DATE '2020-03-02', 'A', 5 FROM DUAL UNION ALL
SELECT 2, DATE '2020-02-28', DATE '2020-03-02', 'A', 10 FROM DUAL UNION ALL
SELECT 2, DATE '2020-03-01', DATE '2020-03-03', 'B', 4 FROM DUAL UNION ALL
SELECT 2, DATE '2020-03-02', DATE '2020-03-05', 'A', 5 FROM DUAL;
Outputs:
ID
DT
PRODUCT
SUPPLY_PER_DAY
1
2020-03-01 00:00:00
A
10
1
2020-03-01 00:00:00
B
10
1
2020-03-01 00:00:00
A
5
1
2020-03-02 00:00:00
A
5
2
2020-02-28 00:00:00
A
10
2
2020-02-29 00:00:00
A
10
2
2020-03-01 00:00:00
A
10
2
2020-03-02 00:00:00
A
10
2
2020-03-01 00:00:00
B
4
2
2020-03-02 00:00:00
B
4
2
2020-03-03 00:00:00
B
4
2
2020-03-02 00:00:00
A
5
2
2020-03-03 00:00:00
A
5
2
2020-03-04 00:00:00
A
5
2
2020-03-05 00:00:00
A
5
db<>fiddle here
In Postgres you can use generate_series() for this:
select t.id, g.day::date as date, t.product, t.supply_per_day
from the_table t
cross join generate_series(t.start_date, t.end_date, interval '1 day') as g(day)
order by t.id, g.day

Get all rows from one table stream and the row before in time from an other table

Suppose I have one table (table_1) and one table stream (stream_1) that gets changes made to table_1, in my case only inserts of new rows. And once I have acted on these changes, the rowes will be removed from stream_1 but remain in table_1.
From that I would like to calculate delta values for var1 (var1 - lag(var1) as delta_var1) partitioned on a customer and just leave var2 as it is. So the data in table_1 could look something like this:
timemessage
customerid
var1
var2
2021-04-01 06:00:00
1
10
5
2021-04-01 07:00:00
2
100
7
2021-04-01 08:00:00
1
20
10
2021-04-01 09:00:00
1
40
3
2021-04-01 15:00:00
2
150
5
2021-04-01 23:00:00
1
50
6
2021-04-02 06:00:00
2
180
2
2021-04-02 07:00:00
1
55
9
2021-04-02 08:00:00
2
200
4
And the data in stream_1 that I want to act on could looks like this:
timemessage
customerid
var1
var2
2021-04-01 23:00:00
1
50
6
2021-04-02 06:00:00
2
180
2
2021-04-02 07:00:00
1
55
9
2021-04-02 08:00:00
2
200
4
But to be able to calculate delta_var1 for all customers I would need the previous row in time for each customer before the ones in stream_1.
For example: To be able to calculate how much var1 has increased for customerid = 1 between 2021-04-01 09:00:00 and 2021-04-01 23:00:00 I want to include the 2021-04-01 09:00:00 row for customerid = 1 in my output.
So I would like to create a select containing all rows in stream_1 + the previous row in time for each customerid from table_1: The wanted output is the following in regard to the mentioned table_1 and stream_1.
timemessage
customerid
var1
var2
2021-04-01 09:00:00
1
40
3
2021-04-01 15:00:00
2
150
5
2021-04-01 23:00:00
1
50
6
2021-04-02 06:00:00
2
180
2
2021-04-02 07:00:00
1
55
9
2021-04-02 08:00:00
2
200
4
So given you have the "last value per day" in your wanted output, you are want a QUALIFY to keep only the wanted rows and using ROW_NUMBER partitioned by customerid and timemessage. Assuming the accumulator it positive only you can order by accumulatedvalue thus:
WITH data(timemessage, customerid, accumulatedvalue) AS (
SELECT * FROM VALUES
('2021-04-01', 1, 10)
,('2021-04-01', 2, 100)
,('2021-04-02', 1, 20)
,('2021-04-03', 1, 40)
,('2021-04-03', 2, 150)
,('2021-04-04', 1, 50)
,('2021-04-04', 2, 180)
,('2021-04-05', 1, 55)
,('2021-04-05', 2, 200)
)
SELECT * FROM data
QUALIFY ROW_NUMBER() OVER (PARTITION BY customerid,timemessage ORDER BY accumulatedvalue DESC) = 1
ORDER BY 1,2;
gives:
TIMEMESSAGE CUSTOMERID ACCUMULATEDVALUE
2021-04-01 1 10
2021-04-01 2 100
2021-04-02 1 20
2021-04-03 1 40
2021-04-03 2 150
2021-04-04 1 50
2021-04-04 2 180
2021-04-05 1 55
2021-04-05 2 200
if you can trust your data and data in table2 starts right after data in table1 then you can just get the last records for each customer from table1 and union with table2:
select * from table1
qualify row_number() over (partitioned by customerid order by timemessage desc) = 1
union all
select * from table2
if not
select a.* from table1 a
join table2 b
on a.customerid = b.customerid
and a.timemessage < b.timemessage
qualify row_number() over (partitioned by a.customerid order by a.timemessage desc) = 1
union all
select * from table2
also you can add a condition to not look to data for more than 1 day (or 1 hour or whatever safe interval is to look at) for better performance

Oracle SQL query to get sales by date range

I am looking to write an SQL query that will provide me sales broken into date ranges, but it is a bit above my SQL knowledge.
I have a table of date ranges by customers as follows:
Cust Product startdate enddate
-----------------------------------
A 123 2011-01-01 2011-12-31
A 124 2011-01-01 2011-05-01
A 125 2011-01-01 2011-05-01
B 123 2011-01-01 2011-03-01
B 124 2011-01-01 2011-03-01
C 125 2011-02-02 2011-05-01
and sales stored as follows:
Cust Product date qty
-----------------------------------
A 123 2011-04-08 1
A 124 2011-01-01 12
A 125 2011-05-01 2
B 123 2011-01-04 3
B 124 2011-02-01 5
C 125 2011-03-01 80
The results should look something like:
Cust Product startdate enddate qty
-----------------------------------------
A 124 2011-01-01 2011-02-01 12
B 123 2011-01-01 2011-02-01 3
B 124 2011-02-02 2011-03-01 5
A 123 2011-03-02 2011-05-01 1
C 125 2011-03-02 2011-05-01 80
A 125 2011-05-02 2011-12-31 2
Any advice gratefully received.
I made the example in MySQL because Oracle server was down. But query is the same.
SQL Fiddle Demo
SELECT R.*, S.*
FROM dRanges R
JOIN Sales S
ON S.`date` >= R.`startdate`
AND S.`date` <= R.`enddate`
AND S.`Cust` = R.`Cust`
AND S.`Product` = R.`Product`
But you have to be carefull ranges doesnt overlap, otherwise you can have same Sales value appear on two ranges
EDIT Please explain the logic here

SQL Server : how to group values by month interval with offset

I have a query that groups aggregated sum values by month.
This is the query:
Declare #IsByStatus bit
Set #IsByStatus = 0
SELECT
CAST((DATEDIFF(month, '2012-01-01T06:00:00', datTimeStamp)) AS int) AS [Index] ,
Min(datTimeStamp) as [From],
Max(datTimeStamp) as [To],
Sum(CASE CAST(intIO_ID AS nvarchar(100))
WHEN N'284' THEN Value ELSE NULL END) AS [286]
FROM
[IOValuesFn](#IsByStatus) IOValues
WHERE
datTimeStamp >= '2012-01-01T06:00:00'
AND datTimeStamp < '2013-01-01T05:59:59'
AND intIO_ID IN (284)
GROUP BY
CAST ((DATEDIFF(Month,'2012-01-01T06:00:00', datTimeStamp)) AS int)
ORDER BY
[From]
And this is the result:
Index From To 286
0 2012-01-07 07:00:00.000 2012-01-31 23:00:00.000 142579.898864746
1 2012-02-01 00:00:00.000 2012-02-29 23:00:00.000 139486.498001099
2 2012-03-01 00:00:00.000 2012-03-31 23:00:00.000 99516.3022232056
3 2012-04-01 00:00:00.000 2012-04-30 23:00:00.000 84597.599899292
4 2012-05-01 00:00:00.000 2012-05-31 23:00:00.000 67085.2983112335
5 2012-06-01 00:00:00.000 2012-06-30 23:00:00.000 67768.9982643127
6 2012-07-01 00:00:00.000 2012-07-31 23:00:00.000 121100.264842987
7 2012-08-01 00:00:00.000 2012-08-31 23:00:00.000 165768.90776825
8 2012-09-01 00:00:00.000 2012-09-30 23:00:00.000 97441.7333068848
9 2012-10-01 00:00:00.000 2012-10-31 23:00:00.000 153764.736312866
10 2012-11-01 00:00:00.000 2012-11-30 23:00:00.000 153601.413961411
11 2012-12-01 00:00:00.000 2012-12-31 23:00:00.000 142521.07028389
12 2013-01-01 00:00:00.000 2013-01-01 05:00:00.000 1192.32000732422
Now I want to do the similar logic, that will also insert an offset in the month start-end time.
e.g. the first period will start on january 1'st on 11:00 AM and will end at february 1 10:59:59 AM.
Same goes for each subsequent month.
Thanks in advance for your help, Omer
Have a look at the example below. The trick is to add the negative amount of offset such that any hour prior to 11am on the first day of the month is "pushed" into the prior month.
Schema Setup:
create function iovaluesfn(#isbystatus bit) returns table as return
select datTimeStamp = '20130101 10:50', intIO_ID = 284, Value = 1 union all
select '20130101 11:00', 284, 1 union all
select '20130102 11:00', 284, 2 union all
select '20130301 11:00', 284, 3 union all
select '20130401 11:00', 284, 4 union all
select '20120501 11:00', 284, 5 union all
select '20120601 11:00', 284, 6 union all
select '20120101 11:00', 284, 7 union all
select '20120102 11:00', 284, 8 union all
select '20120101 11:01', 284, 9 union all
select '20120101 10:59', 284,10 union all -- ** this value is counted in Dec 2011
select '20120101 11:00', 284,11 union all
select '20120101 11:01', 281,12 union all
select '20120101 10:59', 281,13 union all
select '20120101 11:00', 281,14
GO
Query:
Declare #IsByStatus bit;
Set #IsByStatus = 0;
;with IOValues as (
select DATEADD(hour, -11, datTimeStamp) datTimeStamp, intIO_ID, Value
FROM [IOValuesFn](#IsByStatus)
WHERE datTimeStamp >= '2012-01-01T06:00:00' AND datTimeStamp < '2013-01-01T05:59:59'
AND intIO_ID IN (284)
)
SELECT CAST((DATEDIFF(month,'2012-01-01T06:00:00',datTimeStamp)) AS int) AS [Index],
Min(datTimeStamp) as [From],
Max(datTimeStamp) as [To],
Sum(CASE CAST(intIO_ID AS nvarchar(100))
WHEN N'284' THEN Value ELSE NULL END) AS [286]
FROM IOValues
GROUP BY CAST ((DATEDIFF(Month,'2012-01-01T06:00:00',datTimeStamp))AS int)
order by [From];
Results:
| INDEX | FROM | TO | 286 |
----------------------------------------------------------
| -1 | December, 31 2011 | December, 31 2011 | 10*** |
| 0 | January, 01 2012 | January, 02 2012 | 35 |
| 4 | May, 01 2012 | May, 01 2012 | 5 |
| 5 | June, 01 2012 | June, 01 2012 | 6 |
SQL Fiddle Demo