Issue in Generation of Sequence Code using Cursor in SQL - tsql

Code seems Correct,But giving wrong Output,Please correct me if i am doing something wrong
This is table "LoadData"
Sequencement_CD
ID
Current_Year
Record_DT
Status
18AA
109754BY6
2018
2018-01-01
U
19AA
222367DY7
2019
2019-02-22
A
19AB
222367DY7
2019
2020-05-26
A
NULL
222367DY7
2019
2020-06-06
A
NULL
222367DY7
2021
2020-06-06
A
NULL
29276KAR2
2021
2020-06-01
A
NULL
29276KAR2
2021
2020-07-06
A
I need to Generate 'Sequence_Code' for only those ID whose Status is 'A'
Alphabetic 'Sequence_Code' is combination of Last Two Digit of 'Current_Year' and 2 character alphabet like AA,AB,AC....AZ,
BA,BB,BC...BZ. CA,CB,CC....
Below Function gives me next sequence code:
Create FUNCTION [dbo].[GetNextSequenceCode_TEST] (#LastSeqAlphabet varchar(100))
RETURNS varchar(100)
AS
BEGIN
DECLARE #fPart varchar(100), #lChar CHAR(1);
IF(#LastSeqAlphabet IS NULL OR #LastSeqAlphabet = '')
RETURN 'AA';
SELECT #fPart = LEFT(#LastSeqAlphabet, LEN(#LastSeqAlphabet) - 1);
SELECT #lChar = RIGHT(#LastSeqAlphabet, 1);
IF(#lChar = 'Z')
RETURN (SELECT [dbo].[GetNextSequenceCode_TEST](#fPart)) + 'A';
RETURN #fPart + CHAR(ASCII(#lChar)+1);
END
Table is having Duplicate ID,Each row has Different data(like 'Record_DT').
Sequence_CD must be generated based on ID and Current Year.
Rules:
If already ID is having Sequence_CD,then for next record which is having same ID and Tax_year,
The code should take Biggest value of Sequence_CD (Existing for same ID and CurrentYear), and it should increment it by 1 for the next row.and update it in the table.
If ID is not having Sequence_CD,Then it should generate it from 'AA' i.e CurrentYear(considering last 2 digit only)+'AA'
IF the ID is present for another Current_Year,here also Sequence_CD should be like CurrentYear(considering last 2 digit only)+'AA'
For Every Record,i need to follow the above rule
I am doing this by using Cursor
DECLARE #ID Varchar(9);
DECLARE #Current_Year Varchar(4);
DECLARE #Record_DT date;
DECLARE #Seq_CD VARCHAR(4);
DECLARE #GeneratedSeq_CD varchar(20);
DECLARE #InsertedRecordsCursor as CURSOR;
--DECLARE #FileName VARCHAR(500);
BEGIN
SET #InsertedRecordsCursor = CURSOR FOR
select ID,Current_Year,Record_DT from TestTable
where Sequencement_CD IS NULL AND Status ='A'
OPEN #InsertedRecordsCursor;
FETCH NEXT FROM #InsertedRecordsCursor INTO #ID,#Current_Year,#Record_DT
WHILE ##FETCH_STATUS = 0
BEGIN
Select top 1 #Seq_CD = Substring(Sequencement_CD,3,2) from TestTable
where Current_Year = #Current_Year And ID = #ID
AND Sequencement_CD IS NOT NULL
ORDER BY Sequencement_CD desc
IF(#Seq_CD IS NULL OR #Seq_CD = '')
Set #Seq_CD = '';
UPDATE TestTable
SET Sequencement_CD = (RIGHT(#Current_Year,2)) + dbo.GetNextSequenceCode_TEST (RTRIM(#Seq_CD))
WHERE ID = #ID And Current_Year = #Current_Year
And Record_DT = #Record_DT
FETCH NEXT FROM #InsertedRecordsCursor INTO #ID,#Current_Year,#Record_DT
END;
CLOSE #InsertedRecordsCursor ;
DEALLOCATE #InsertedRecordsCursor;
END;
Expected Output:
Sequencement_CD
ID
Current_Year
Record_DT
Status
18AA
109754BY6
2018
2018-01-01
U
19AA
222367DY7
2019
2019-02-22
A
19AB
222367DY7
2019
2020-05-26
A
19AC
222367DY7
2019
2020-06-06
A
21AA
222367DY7
2021
2020-06-06
A
21AA
29276KAR2
2021
2020-06-01
A
21AB
29276KAR2
2021
2020-07-06
A

Related

DB2 - use field as a labeled duration for time calculation

Given a table that looks like this:
id task scheduled_date reminder
-- ----------------------- ---------------- --------
1 mail january newsletter 2022-01-01 15 days
I had planned on executing a query to mimic date addition as in
SELECT TASK, SCHEDULED_DATE + 15 DAYS FROM ...
==> 2022-01-16
Unfortunately, using the REMINDER field gives an error:
SELECT TASK, (SCHEDULED_DATE + REMINDER) FROM ...
==>[Code: -182, SQL State: 42816] [SQL0182] A date, time, or timestamp expression not valid.
Is there any way to accomplish using the reminder field as a labeled duration? (I'm using IBMi DB2)
You'll need to convert the string "15 days" into an actual duration.
A date durration is a decimal(8,0) number representing YYYYMMDD
So 15 days would be 00000015
1 year, 00010000
1 year 1 month, one day '00010101`
create table testdur (
datedur decimal(8,0)
);
insert into testdur
values (15), (10000), (10101), (90), (300);
select current_date as curDate
, dateDur
,current_date + dateDur
from testdur;
Results
There is an attempt to implement the interval function available in Db2 for LUW. It supports string expression as a parameter, not just string constant as the built-in one.
The result of this function can participate in whatever allowed date arithmetic.
This works on Db2 for LUW v11.1+ and Db2 for IBM i v7.5+ at least.
create or replace function interval_d (p_interval varchar (100))
returns dec (8)
contains sql
deterministic
no external action
begin atomic
declare v_sign dec (1) default 0;
declare v_pattern varchar (100) default '([+-]? *[0-9]+) *(\w+)';
declare v_y int default 0;
declare v_m int default 0;
declare v_d int default 0;
declare v_occ int default 1;
declare v_num int;
declare v_kind varchar (10);
l1: while 1=1 DO
set v_kind =
lower
(
regexp_substr
(
p_interval
, v_pattern
, 1, v_occ, '', 2
)
);
if v_kind is null then leave l1; end if;
set v_num =
int
(
replace
(
regexp_substr
(
p_interval
, v_pattern
, 1, v_occ, '', 1
)
, ' ', ''
)
);
if sign (v_num) * v_sign < 0 then
signal sqlstate '75001' set message_text = 'Sign of all operands must be the same';
end if;
if v_sign = 0 then set v_sign = sign (v_num); end if;
if v_kind in ('d', 'day', 'days')
then set v_d = v_d + v_num;
elseif v_kind in ('mon', 'mons', 'month', 'months')
then set v_m = v_m + v_num;
elseif v_kind in ('y', 'year', 'years')
then set v_y = v_y + v_num;
else
signal sqlstate '75000' set message_text = 'wrong duration';
end if;
set v_occ = v_occ + 1;
end while l1;
if abs (v_d) > 99 then
set v_m = v_m + v_d / 30, v_d = mod (v_d, 30);
end if;
if abs (v_m) > 99 then
set v_y = v_y + v_m / 12, v_m = mod (v_m, 12);
end if;
return v_y * 10000 + v_m * 100 + v_d;
end
select interval_d (i) as d
from
(
values
('4 years 2 months 3 days')
, ('3 day 4 year 2 month')
, ('-4y -2mon -3d')
) t (i)
D
40203
40203
-40203
fiddle

postgresSql PIVOT: I want to return dynamic columns

I have stored data in DB like this:
id
week
qty
product_code
related_id
1
2201
10
X000001
24
2
2202
14
X000001
24
3
2201
15
X000002
24
4
2202
25
X000002
24
5
2210
11
X000001
25
6
2244
22
X000001
26
...
I want to make an sql query with the chosen related_id to get this result:
For example if i chose the related_id = 24
product_code
2201
2202
2203
...
X000001
10
14
...
X000002
15
25
...
I want to transform all the week values of the related_id into columns with ASC order and put the right qty in front of every (product_code, week) couple.
Thanks
I got finally a solution after a few days of sql training, i post it here, it's may help someone, special thanks for #S-Man for the motivation :)
DO
$$
DECLARE
sql text;
BEGIN
WITH latest_create_date AS (
SELECT product_code, week, MAX(create_date) AS max_create_date
FROM my_table_name
WHERE related_id = 437
GROUP BY product_code, week
)
SELECT string_agg(
DISTINCT 'SUM(CASE WHEN rop.week = ' || CAST(rop.week AS text) ||
' THEN rop.qty ELSE 0 END) AS ' || rop.week || '',
', '
) INTO sql
FROM my_table_name rop
INNER JOIN latest_create_date lcd
ON rop.product_code = lcd.product_code AND rop.week = lcd.week AND rop.create_date = lcd.max_create_date;
IF sql IS NOT NULL THEN
sql := 'SELECT product_code, ' || sql ||
' FROM my_table_name WHERE related_id = 437 GROUP BY product_code';
EXECUTE sql;
END IF;
END;
$$ LANGUAGE plpgsql;

Find total number in a specific period of time SQL

I am trying to find the total number of members in a given period. Say I have the following data:
member_id start_date end_date
1 9/1/2013 12/31/2013
2 10/1/2013 11/12/2013
3 12/1/2013 12/31/2013
4 5/1/2012 8/5/2013
5 9/1/2013 12/31/2013
6 7/1/2013 12/31/2013
7 6/6/2012 12/5/2013
8 10/1/2013 12/31/2013
9 7/8/2013 12/31/2013
10 1/1/2012 11/5/2013
In SQL I need to create a report that will list out the number of members in each month of the year. In this case something like the following:
Date Members Per Month
Jan-12 1
Feb-12 1
Mar-12 1
Apr-12 1
May-12 2
Jun-12 3
Jul-12 3
Aug-12 3
Sep-12 3
Oct-12 3
Nov-12 3
Dec-12 3
Jan-13 3
Feb-13 3
Mar-13 3
Apr-13 3
May-13 3
Jun-13 3
Jul-13 5
Aug-13 4
Sep-13 6
Oct-13 8
Nov-13 6
Dec-13 6
So there is only 1 member from Jan-12 (member id 10) until May-12 when member id 4 joins making the count 2 and so on.
The date range can be all over so I can't specify the specific dates but it is by month, meaning that even if someone ends 12-1 it is considered active for the month for Dec.
I was able to create the following stored procedure that was able to accomplish what I needed:
USE [ValueBasedSandbox]
GO
/****** Object: StoredProcedure [dbo].[sp_member_count_per_month] Script Date: 01/08/2015 12:02:37 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Create date: 2015-08-01
-- Description: Find the counts per a given date passed in
-- =============================================
CREATE PROCEDURE [dbo].[sp_member_count_per_month]
-- Add the parameters for the stored procedure here
#YEAR int
, #ENDYEAR int
AS
DECLARE #FIRSTDAYMONTH DATETIME
DECLARE #LASTDAYMONTH DATETIME
DECLARE #MONTH INT = 1;
--Drop the temporary holding table if exists
IF OBJECT_ID('tempdb.dbo.##TEMPCOUNTERTABLE', 'U') IS NOT NULL
DROP TABLE dbo.##TEMPCOUNTERTABLE
CREATE TABLE dbo.##TEMPCOUNTERTABLE (
counter INT
, start_date DATETIME2
, end_date DATETIME2
)
--Perform this loop for each year desired
WHILE #YEAR <= #ENDYEAR
BEGIN
--Perform for each month of the year
WHILE (#MONTH <= 12)
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SET #FIRSTDAYMONTH = DATEADD(MONTH, #MONTH - 1, DATEADD(YEAR, #YEAR-1900, 0))
SET #LASTDAYMONTH = DATEADD(MONTH, #MONTH, DATEADD(YEAR, #YEAR-1900, 0)-1)
INSERT INTO dbo.##TEMPCOUNTERTABLE(counter, start_date, end_date)
SELECT COUNT(*) AS counter
, #FIRSTDAYMONTH AS start_date
, #LASTDAYMONTH AS end_date
FROM dbo.member_table
WHERE start_date <= #LASTDAYMONTH
AND end_date >= #FIRSTDAYMONTH
--Increment through all the months of the year
SET #MONTH = #MONTH + 1
END -- End Monthly Loop
--Reset Month counter
SET #MONTH = 1
--Increment the desired years
SET #YEAR = #YEAR + 1
END -- End Yearly Loop
--Display the results
SELECT *
FROM dbo.##TEMPCOUNTERTABLE
-- Drop the temp table
IF OBJECT_ID('tempdb.dbo.##TEMPCOUNTERTABLE', 'U') IS NOT NULL
DROP TABLE dbo.##TEMPCOUNTERTABLE
GO
This should do the trick
with datesCte(monthStart,monthEnd) as
(
select cast('20120101' as date) as monthStart, cast('20120131' as date) as monthEnd
union all
select DATEADD(MONTH, 1, d.monthStart), dateadd(day, -1, dateadd(month, 1, d.monthStart))
from datesCte as d
where d.monthStart < '20140101'
)
select *
from datesCte as d
cross apply
(
select count(*) as cnt
from dbo.MemberDates as m
where m.startDate <= d.monthEnd and m.endDate > d.monthStart
) as x
order by d.monthStart

Generate Multiple rows from single row depending on date difference

I want to generate multiple rows from 1 rows depending on dates difference in dtStart and dtEnd
-- This is demo table to show the issue
create table #temp(Id int,hTenant int , dtStart datetime,dtEnd datetime)
Insert into #temp values(1,8,'2013-01-08 00:00:00.000','2014-01-01 00:00:00.000')
And data should be returned by query as :-
**Id** **Tenant** **Month** **Year**
1 8 Aug 2013
1 8 Sep 2013
1 8 Oct 2013
1 8 Nov 2013
1 8 Dec 2013
1 8 Jan 2014
How i can achieve this
I have created one table valued function which returns month and year but not able to join it with the table to fetch id and tenant
Create FUNCTION vw_emg_common_GetYearMonthDiffList ( #startdt datetime,#enddt datetime )
RETURNS #Months TABLE
(
Months int,
Years int,
StartDate datetime,
EndDate datetime
)
AS
BEGIN
WHILE (#startdt< #enddt)
BEGIN
INSERT INTO #Months(Months,Years,StartDate,EndDate) VALUES (MONTH(#startdt),Year(#startdt),#startdt,#enddt)
SET #startdt = DATEADD(MONTH,1,#startdt)
end
INSERT #Months
Select Months,Years,StartDate,EndDate from #Months
RETURN
end
Function call :-
select * FROM vw_emg_common_GetYearMonthDiffList('2013-01-01 00:00:00.000','2013-12-01 00:00:00.000' )
Try this method using Cross Join and CTE. You may need to create a function (or slightly modify). Fiddle demo is here
DECLARE #sd DATE = '20130801', #ed DATE = '20140101',
#id INT = 1, #hTenant INT = 8
;WITH CTE AS
(
SELECT DATEDIFF(MONTH, #sd, #ed) Months
)
SELECT DISTINCT #id Id, #hTenant Tenant,
DATENAME(MONTH, DATEADD(MONTH, number, #sd)) [Month],
YEAR(DATEADD(MONTH, number, #sd)) [Year]
FROM master..spt_values x
CROSS JOIN CTE
WHERE x.number BETWEEN 0 AND Months
Results
| ID | TENANT | MONTH | YEAR |
|----|--------|-----------|------|
| 1 | 8 | August | 2013 |
| 1 | 8 | September | 2013 |
| 1 | 8 | October | 2013 |
| 1 | 8 | November | 2013 |
| 1 | 8 | December | 2013 |
| 1 | 8 | January | 2014 |
Hope this helps you.
DECLARE #TEMP TABLE(ID INT,TENANT INT , DTSTART DATETIME,DTEND DATETIME)
INSERT INTO #TEMP VALUES(1,8,'2013-01-08 00:00:00.000','2014-01-01 00:00:00.000')
--You can create a function with the given logic
--It receives STARTDATE and ENDDATE
DECLARE #STARTDATE DATETIME = (SELECT DTSTART FROM #TEMP),
#ENDDATE DATETIME = (SELECT DTEND FROM #TEMP)
DECLARE #S INT = CAST(#STARTDATE AS INT)
DECLARE #E INT = CAST(#ENDDATE AS INT)
DECLARE #TAB TABLE (ID INT, DT DATETIME)
WHILE #S <= #E
BEGIN
INSERT INTO #TAB VALUES (#S,CAST(#S AS DATETIME))
SET #S = #S + 1
END
SELECT TEMP.ID,TEMP.TENANT,[Mname] MONTH,[Y] YEAR FROM (
SELECT DATEPART(YEAR,DT) [Y],DATEPART(MONTH,DT) [Mnum],DATENAME(MONTH,DT) [Mname] FROM #TAB
GROUP BY DATEPART(YEAR,DT),DATEPART(MONTH,DT),DATENAME(MONTH,DT)) LU,#TEMP TEMP
ORDER BY [Y],[Mnum]
Result:

PostgreSql - Adding YEar and Month to Table

I am creating a Customer table and i want one of the attributes to be Expiry Date of credit card.I want the format to be 'Month Year'. What data type should i use? i want to use date but the format is year/month/day. Is there any other way to restrict format to only Month and year?
You can constrain the date to the first day of the month:
create table customer (
cc_expire date check (cc_expire = date_trunc('month', cc_expire))
);
Now this fails:
insert into customer (cc_expire) values ('2014-12-02');
ERROR: new row for relation "customer" violates check constraint "customer_cc_expire_check"
DETAIL: Failing row contains (2014-12-02).
And this works:
insert into customer (cc_expire) values ('2014-12-01');
INSERT 0 1
But it does not matter what day is entered. You will only check the month:
select
date_trunc('month', cc_expire) > current_date as valid
from customer;
valid
-------
t
Extract year and month separately:
select extract(year from cc_expire) "year", extract(month from cc_expire) "month"
from customer
;
year | month
------+-------
2014 | 12
Or concatenated:
select to_char(cc_expire, 'YYYYMM') "month"
from customer
;
month
--------
201412
Use either
char(5) for two-digit years, or
char(7) for four-digit years.
Code below assumes two-digit years, which is the form that matches all my credit cards. First, let's create a table of valid expiration dates.
create table valid_expiration_dates (
exp_date char(5) primary key
);
Now let's populate it. This code is just for 2013. You can easily adjust the range by changing the starting date (currently '2013-01-01'), and the "number" of months (currently 11, which lets you get all of 2013 by adding from 0 to 11 months to the starting date).
with all_months as (
select '2013-01-01'::date + (n || ' months')::interval months
from generate_series(0, 11) n
)
insert into valid_expiration_dates
select to_char(months, 'MM') || '/' || to_char(months, 'YY') exp_date
from all_months;
Now, in your data table, create a char(5) column, and set a foreign key reference from it to valid_expiration_dates.exp_date.
While you're busy with this, think hard about whether "exp_month" might be a better name for that column than "exp_date". (I think it would.)
As another idea you could essentially create some brief utilities to do this for you using int[]:
CREATE OR REPLACE FUNCTION exp_valid(int[]) returns bool LANGUAGE SQL IMMUTABLE as
$$
SELECT $1[1] <= 12 AND (select count(*) = 2 FROM unnest($1));
$$;
CREATE OR REPLACE FUNCTION first_invalid_day(int[]) RETURNS date LANGUAGE SQL IMMUTABLE AS
$$
SELECT (to_date($1[2]::text || $1[1]::text, CASE WHEN $1[2] < 100 THEN 'YYMM' ELSE 'YYYYMM' END) + '1 month'::interval)::date;
$$;
These work:
postgres=# select exp_valid('{04,13}');
exp_valid
-----------
t
(1 row)
postgres=# select exp_valid('{13,04}');
exp_valid
-----------
f
(1 row)
postgres=# select exp_valid('{04,13,12}');
exp_valid
-----------
f
(1 row)
Then we can convert these into a date:
postgres=# select first_invalid_day('{04,13}');
first_invalid_day
-------------------
2013-05-01
(1 row)
This use of arrays does not violate any normalization rules because the array as a whole represents a single value in its domain. We are storing two integers representing a single date. '{12,2}' is December of 2002, while '{2,12}' is Feb of 2012. Each represents a single value of the domain and is therefore perfectly atomic.