I'm new to work with database. Describing the Problem below:
Problem: Having 'X' number of Tables with 'Y' number of partitions. Now want to have a way of finding which tables have partitions and which partitions are almost full/partitions created before D-30 is needed to be deleted as getting error in application due to such issue.
Please share guidance/queries how this problem can be resolved?
Artifacts: Tried several queries as SYSDBA to list down all the partitions but there is multiple dependencies pop up. Still trying to figuring a concrete way to make a scheduler Job.
step 1 is to create your PARTITIONS. In my test CASE I am creating 1 PARTITION t1= weekly but I'm leaving the syntax for other partitions:
t0 = daily, t2= monthly, t3=quaterly and t4=yearly.
Then I will populate PARTITION t1 with sample data. I'm using 1 date for each day but during your testing add as many dates as you like per day. I would suggest data to emulate your application.
In addition, I am using a GLOBAL index for table t1 and the rest are LOCAL indexes.
When a partition from a global index is dropped the entire index MUST be rebuilt or it will be invalid.
Based on your application you need to make your own determination whether to use global or local indexes!!
Note: I am creating all the PARTITIONS on DATE columns but you can also use TIMESTAMP columns If need be.
/* weekly PARTITION */
CREATE TABLE t1 (
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
dt DATE)
PARTITION BY RANGE (dt)
INTERVAL ( NUMTODSINTERVAL (7, 'DAY') ) (
PARTITION OLD_DATA VALUES LESS THAN (DATE '2022-04-01')
);
/
INSERT into t1 (dt)
with dt (dt, interv) as (
select date '2022-04-01', numtodsinterval(1,'DAY') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date '2022-08-31')
select dt from dt;
/
create index t1_global_ix on T1 (dt);
/
/* daily PARTITION */
CREATE TABLE t0 (
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
dt DATE)
PARTITION BY RANGE (dt)
INTERVAL ( NUMTODSINTERVAL (1, 'DAY') ) (
PARTITION OLD_DATA VALUES LESS THAN (DATE 2022-01-01')
);
/
INSERT into t0 (dt)
with dt (dt, interv) as (
select date '2022-01-01', numtodsinterval(1,'DAY') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date '2022-12-31')
select dt from dt;
/
create index t0_global_ix on T0 (dt);
/
/* MONTHLY PARTITION */
CREATE TABLE t2 (
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
dt DATE)
PARTITION BY RANGE (dt)
INTERVAL ( NUMTODSINTERVAL (1, 'MONTH') ) (
PARTITION OLD_DATA VALUES LESS THAN (DATE '2022-01-01')
);
/
INSERT into t2 (dt)
with dt (dt, interv) as (
select date '2021-01-01', numtodsinterval(1,'DAY') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date '2022-12-31')
select dt from dt;
/
create index t2_local_ix on T2 (dt) LOCAL;
/
/* QUARTERLY PARTITION */
CREATE TABLE t3 (
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
dt DATE
)
PARTITION BY RANGE (dt)
INTERVAL
(NUMTOYMINTERVAL(3,'MONTH'))
(
PARTITION OLD_DATA values LESS THAN (TO_DATE('2021-01-01','YYYY-MM-DD'))
);
/
INSERT into t3 (dt)
with dt (dt, interv) as (
select date '2021-01-01', numtodsinterval(1,'DAY') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date '2022-12-31')
select dt from dt;
/
create index t3_local_ix on T3 (dt) LOCAL;
/
/* yearly PARTITION */
CREATE TABLE t4 (
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
dt DATE
)
PARTITION BY RANGE (dt)
INTERVAL
(NUMTOYMINTERVAL(1,'YEAR'))
(
PARTITION OLD_DATA values LESS THAN (TO_DATE('2020-01-01','YYYY-MM-DD'))
);
/
INSERT into t4 (dt)
with dt (dt, interv) as (
select date '2021-01-01', numtodsinterval(90,'DAY') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv < date 2023-12-31')
select dt from dt;
/
create index t4_local_ix on T4 (dt) LOCAL;
/
Every time a new partition is created Oracle generates a system name, which is quite cryptic
Here is a list of the PARTITION names, which Oracle generated when I loaded the data above
SELECT PARTITION_NAME
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'T1'
PARTITION_NAME
OLD_DATA
SYS_P458773
SYS_P458774
SYS_P458775
SYS_P458776
SYS_P458777
SYS_P458778
SYS_P458779
SYS_P458780
SYS_P458781
SYS_P458782
SYS_P458783
SYS_P458784
SYS_P458785
SYS_P458786
SYS_P458787
SYS_P458788
SYS_P458789
SYS_P458790
SYS_P458791
SYS_P458792
SYS_P458793
SYS_P458794
Although PARTITION management will work with system GENERATED PARTITION names, I use the procedure below to rename them to something more meaningful.
Let's create and run the procedure and take a look at the names again. As you can see, since we are working with weekly partitions the name is P_ for partittion YYYY 4 digit year the PARTITION is in, W for Week of the year, and ## for the week number within the year.
I would suggest using the scheduler to run this process at least once a day. You can run it as many time as you want as it will not cause any harm.
CREATE OR REPLACE PROCEDURE MaintainPartitions IS EXPRESSION_IS_OF_WRONG_TYPE EXCEPTION;
PRAGMA EXCEPTION_INIT(EXPRESSION_IS_OF_WRONG_TYPE, -6550);
CURSOR PartTables IS
SELECT TABLE_NAME, INTERVAL
FROM USER_PART_TABLES
WHERE PARTITIONING_TYPE = 'RANGE'
ORDER BY TABLE_NAME;
CURSOR TabParts(aTableName VARCHAR2) IS
SELECT PARTITION_NAME, HIGH_VALUE
FROM USER_TAB_PARTITIONS
WHERE regexp_like(partition_name,'^SYS_P[[:digit:]]{1,10}') AND
TABLE_NAME = aTableName AND
table_name not like 'BIN$%'
and interval is not null
ORDER BY PARTITION_POSITION;
ym INTERVAL YEAR TO MONTH;
ds INTERVAL DAY TO SECOND;
newPartName VARCHAR2(30);
PERIOD TIMESTAMP;
BEGIN
FOR aTab IN PartTables LOOP
BEGIN
EXECUTE IMMEDIATE 'BEGIN :ret := '||aTab.INTERVAL||'; END;' USING OUT ds;
ym := NULL;
EXCEPTION
WHEN EXPRESSION_IS_OF_WRONG_TYPE THEN
EXECUTE IMMEDIATE 'BEGIN :ret := '||aTab.INTERVAL||'; END;' USING OUT ym;
ds := NULL;
END;
FOR aPart IN TabParts(aTab.TABLE_NAME) LOOP
EXECUTE IMMEDIATE 'BEGIN :ret := '||aPart.HIGH_VALUE||'; END;' USING OUT PERIOD;
IF ds IS NOT NULL THEN
IF ds >= INTERVAL '7' DAY THEN
-- Weekly partition
EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"IYYY"W"IW';
ELSE
-- Daily partition
EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"YYYYMMDD';
END IF;
ELSE
IF ym = INTERVAL '3' MONTH THEN
-- Quarterly partition
EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"YYYY"Q"Q';
ELSE
-- Monthly partition
EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"YYYYMM';
END IF;
END IF;
IF newPartName <> aPart.PARTITION_NAME THEN
EXECUTE IMMEDIATE 'ALTER TABLE '||aTab.TABLE_NAME||' RENAME PARTITION '||aPart.PARTITION_NAME||' TO '||newPartName;
END IF;
END LOOP;
END LOOP;
END MaintainPartitions;
/
EXEC MaintainPartitions
SELECT PARTITION_NAME
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'T1'
PARTITION_NAME
OLD_DATA
P_2022W14
P_2022W15
P_2022W16
P_2022W17
P_2022W18
P_2022W19
P_2022W20
P_2022W21
P_2022W22
P_2022W23
P_2022W24
P_2022W25
P_2022W26
P_2022W27
P_2022W28
P_2022W29
P_2022W30
P_2022W31
P_2022W32
P_2022W33
P_2022W34
SELECT COUNT(*) FROM USER_TAB_PARTITIONS
COUNT(*)
31
Next step is setting up your RETENTION table. There should be an entry for each interval range PARTITION.
The RETENTION value is for you to decide. In my example, I chose 30 days fir table T1. This means, when the high value for a PARTITION is greater than 30 days its eligible to be dropped. So chose wisely when setting up these values.
Note: I listed the names of other tables to show how each table has its own value.
CREATE TABLE PARTITION_RETENTION (
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
TABLE_NAME VARCHAR2(30),
RETENTION INTERVAL DAY(3) TO SECOND(0),
CONSTRAINT
partition_retention_pk primary key (table_name),
CONSTRAINT CHK_NON_ZERO_DAYS CHECK (
RETENTION > INTERVAL '0' DAY
),
CONSTRAINT CHK_WHOLE_DAYS CHECK (
EXTRACT(HOUR FROM RETENTION) = 0
AND EXTRACT(MINUTE FROM RETENTION) = 0
AND EXTRACT(SECOND FROM RETENTION) = 0
)
);
insert into PARTITION_RETENTION (TABLE_NAME, RETENTION)
select 'T0', interval '10' day from dual union all
select 'T1', interval '30' day from dual union all
select 'T2', interval '15' day from dual union all
select 'T3', interval '30' day from dual union all
select 'T4', 15 * interval '1' day from dual union all
select 'T5', 5 * interval '1 00:00:00' day to second from dual;
Below are 3 procedures that need to be created.
The ddl procedure is a wrapper, which shows you what is being processed and how long it takes.
The rebuild_index procedure is obvious it rebuilds any invalid indexes. As I mentioned above if you are using a global index and a PARTITION is dropped then the index needs to be rebuilt. I hardcoded parallel 4 in this example but if you have plenty if CPU power you may want to increase the number to fit your needs.
In addition, there are other ways indexes can be marked unusable so you may want to consider scheduling that task.
Lastly, is the anonymous block. Which actually drops the PARTITIONS for, which the retention PERIOD has passed. This needs to be scheduled once a day!!
If you look carefully at the anonymous block the last step is a call to the rebuild index procedure. So if an index is unusable it will be rebuilt.
Now let's run the process and see what happens.
CREATE OR REPLACE PROCEDURE ddl(p_cmd varchar2)
authid current_user
is
t1 pls_integer;
BEGIN
t1 := dbms_utility.get_time;
dbms_output.put_line(p_cmd);
execute immediate p_cmd;
dbms_output.put_line((dbms_utility.get_time - t1)/100 || ' seconds');
END;
/
CREATE OR REPLACE PROCEDURE rebuild_index
authid current_user
is
BEGIN
for i in (
select index_owner, index_name, partition_name, 'partition' ddl_type
from all_ind_partitions
where status = 'UNUSABLE'
union all
select owner, index_name, null, null
from all_indexes
where status = 'UNUSABLE'
)
loop
if i.ddl_type is null then
ddl('alter index '||i.index_owner||'.'||i.index_name||' rebuild parallel 4 online');
else
ddl('alter index '||i.index_owner||'.'||i.index_name||' modify '||i.ddl_type||' '||i.partition_name||' rebuild parallel 4 online');
end if;
end loop;
END;
/
DECLARE
CANNOT_DROP_LAST_PARTITION EXCEPTION;
PRAGMA EXCEPTION_INIT(CANNOT_DROP_LAST_PARTITION, -14758);
CANNOT_DROP_ONLY_ONE_PARTITION EXCEPTION;
PRAGMA EXCEPTION_INIT(CANNOT_DROP_ONLY_ONE_PARTITION, -14083);
ts TIMESTAMP;
CURSOR TablePartitions IS
SELECT TABLE_NAME, PARTITION_NAME, p.HIGH_VALUE, t.INTERVAL, RETENTION, DATA_TYPE
FROM USER_PART_TABLES t
JOIN USER_TAB_PARTITIONS p USING (TABLE_NAME)
JOIN USER_PART_KEY_COLUMNS pk ON pk.NAME = TABLE_NAME
JOIN USER_TAB_COLS tc USING (TABLE_NAME, COLUMN_NAME)
JOIN PARTITION_RETENTION r USING (TABLE_NAME)
WHERE pk.object_type = 'TABLE' AND
t.partitioning_type = 'RANGE' AND
REGEXP_LIKE (tc.data_type, '^DATE$|^TIMESTAMP.*');
BEGIN
FOR aPart IN TablePartitions LOOP
EXECUTE IMMEDIATE 'BEGIN :ret := '||aPart.HIGH_VALUE||'; END;' USING OUT ts;
IF ts < SYSTIMESTAMP - aPart.RETENTION THEN
BEGIN
ddl('alter table '||aPart.TABLE_NAME||' drop partition '||aPart.partition_name);
EXCEPTION
WHEN CANNOT_DROP_ONLY_ONE_PARTITION THEN
DBMS_OUTPUT.PUT_LINE('Cant drop the only partition '||aPart.PARTITION_NAME ||' from table '||aPart.TABLE_NAME);
ddl('ALTER TABLE '||aPart.TABLE_NAME||' TRUNCATE PARTITION '||aPart.PARTITION_NAME);
WHEN CANNOT_DROP_LAST_PARTITION THEN
BEGIN
DBMS_OUTPUT.PUT_LINE('Drop last partition '||aPart.PARTITION_NAME ||' from table '||aPart.TABLE_NAME);
EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' SET INTERVAL ()';
ddl('alter table '||aPart.TABLE_NAME||' drop partition '||aPart.partition_name);
EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' SET INTERVAL( '||aPart.INTERVAL||' )';
EXCEPTION
WHEN CANNOT_DROP_ONLY_ONE_PARTITION THEN
-- Depending on the order the "last" partition can be also the "only" partition at the same time
EXECUTE IMMEDIATE 'ALTER TABLE '||aPart.TABLE_NAME||' SET INTERVAL( '||aPart.INTERVAL||' )';
DBMS_OUTPUT.PUT_LINE('Cant drop the only partition '||aPart.PARTITION_NAME ||' from table '||aPart.TABLE_NAME);
ddl('ALTER TABLE '||aPart.TABLE_NAME||' TRUNCATE PARTITION '||aPart.PARTITION_NAME);
END;
END;
END IF;
END LOOP;
rebuild_index();
END;
alter table T1 drop partition OLD_DATA
.02 seconds
alter table T1 drop partition P_2022W14
.01 seconds
alter table T1 drop partition P_2022W15
.02 seconds
alter table T1 drop partition P_2022W16
.01 seconds
alter table T1 drop partition P_2022W17
.02 seconds
alter table T1 drop partition P_2022W18
.01 seconds
alter table T1 drop partition P_2022W19
.02 seconds
alter table T1 drop partition P_2022W20
.01 seconds
alter table T1 drop partition P_2022W21
.01 seconds
alter table T1 drop partition P_2022W22
.02 seconds
alter table T1 drop partition P_2022W23
.01 seconds
alter table T1 drop partition P_2022W24
.01 seconds
alter table T1 drop partition P_2022W25
.01 seconds
alter table T1 drop partition P_2022W26
.01 seconds
alter table T1 drop partition P_2022W27
.02 seconds
alter index SQL_WUKYPRGVPTOUVLCAEKUDCRCQI.T1_GLOBAL_IX rebuild parallel 4 online
.1 seconds
…
…
…
alter index SQL_WUKYPRGVPTOUVLCAEKUDCRCQI.T1_GLOBAL_IX rebuild parallel 4 online
.1 seconds
SELECT count(*) from USER_TAB_PARTITIONS
Where
table_name not like 'BIN$%'
8
SELECT PARTITION_NAME
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'T1'
AND
table_name not like 'BIN$%'
P_2022W28
P_2022W29
P_2022W30
P_2022W31
P_2022W32
P_2022W33
P_2022W34
P_2022W35
Related
So in Postgres I created a function that creates a table vehicletracks_tracks and fills in with information calculated from original table. Original table includes the following atributes: id bigint NOT NULL DEFAULT, session character varying(32), client_id character varying(32) NOT NULL, vehicle_type smallint NOT NULL, geolocation geography(Geometry,4326) NOT NULL and "timestamp" timestamp without time zone NOT NULL.
In the function I calculate tracks and borders between them by observing time and velocity between two consecutive rows in original table. A fter creating tracks I calculate distance and duration of every track.
The function takes around 6 minutes and 30 seconds and then the output table is created and filled with rows. The original table has around 5 million rows. When you look at the procedure is there any possibility to reduce its execution time?
This is my function:
CREATE OR REPLACE FUNCTION vehicletracks()
RETURNS void AS
$BODY$
DECLARE
vrstica1 RECORD;
vrstica2 RECORD;
track_number INT;
BEGIN
SET TIME ZONE 'UTC';
DROP TABLE IF EXISTS vehicletracks_tracks;
CREATE TABLE vehicletracks_tracks (
session character varying(32),
client_id character varying(32) NOT NULL,
vehicle_type smallint NOT NULL,
track_number SERIAL PRIMARY KEY,
track_distance DOUBLE PRECISION,
track_duration INTERVAL,
track_start TIMESTAMP WITH TIME ZONE,
track_stop TIMESTAMP WITH TIME ZONE
);
DROP VIEW IF EXISTS group_by_client_session_vehicle;
CREATE VIEW group_by_client_session_vehicle AS
SELECT DISTINCT client_id, session, vehicle_type
FROM roaduserspositions
GROUP BY client_id, session, vehicle_type
ORDER BY session, client_id, vehicle_type;
FOR vrstica1 IN (SELECT * FROM group_by_client_session_vehicle)
LOOP
DROP VIEW IF EXISTS velocity_difference_view;
DROP TABLE IF EXISTS difference_table;
CREATE TABLE difference_table AS
SELECT id, session, client_id, vehicle_type, geolocation, timestamp, time_difference, distance_difference
FROM (SELECT t.*, t.timestamp - LAG(t.timestamp) OVER (ORDER BY t.session, t.client_id, t.vehicle_type, t.timestamp ASC) AS time_difference,
ST_Distance(geolocation, LAG(geolocation) OVER (ORDER BY t.session, t.client_id, t.vehicle_type, t.timestamp ASC)) AS distance_difference
FROM (SELECT *
FROM timonwww_roaduserspositions
WHERE client_id = vrstica1.client_id AND session = vrstica1.session AND vehicle_type = vrstica1.vehicle_type) AS t) AS tab
ORDER BY session, client_id, vehicle_type, timestamp;
CREATE VIEW velocity_difference_view AS (
SELECT *, distance_difference / EXTRACT(EPOCH FROM time_difference) AS velocity
FROM difference_table
WHERE EXTRACT(EPOCH FROM time_difference) > 0
UNION
SELECT *, 0 AS velocity
FROM difference_table
WHERE EXTRACT(EPOCH FROM time_difference) = 0
ORDER BY session, client_id, vehicle_type, timestamp);
DROP TABLE IF EXISTS new_time_difference;
CREATE TABLE new_time_difference (
track_id INTEGER,
LIKE velocity_difference_view INCLUDING ALL);
track_number := 1;
INSERT INTO new_time_difference(track_id, id, session, client_id, vehicle_type, geolocation, timestamp, time_difference)
SELECT
CASE
WHEN EXTRACT(EPOCH FROM time_difference) < 300 AND velocity < 121 THEN track_number
ELSE track_number + 1
END AS track_id, id, session, client_id, vehicle_type, geolocation, timestamp, time_difference
FROM velocity_difference_view OFFSET 1;
track_number := 1;
FOR vrstica2 IN (SELECT * FROM velocity_difference_view OFFSET 1)
LOOP
IF EXTRACT(EPOCH FROM vrstica2.time_difference) < 300 AND vrstica2.velocity < 121 THEN
INSERT INTO new_time_difference VALUES(track_number, vrstica2.id, vrstica2.session, vrstica2.client_id, vrstica2.vehicle_type, vrstica2.geolocation, vrstica2.timestamp, vrstica2.time_difference);
ELSE
track_number := track_number + 1;
END IF;
END LOOP;
DROP TABLE IF EXISTS geolocation_difference_table;
CREATE TABLE geolocation_difference_table AS
SELECT *
FROM (SELECT *, ST_Distance(geolocation, LAG(geolocation) over (ORDER BY timestamp ASC)) as geolocation_difference
FROM new_time_difference
WHERE track_id IN (SELECT * FROM generate_series((SELECT MIN(new_time_difference.track_id) FROM new_time_difference), (SELECT MAX(track_id) FROM new_time_difference)) num)) AS tab;
DROP TABLE IF EXISTS track_distance_table;
CREATE TABLE track_distance_table AS
SELECT track_id, SUM(geolocation_difference) AS track_distance, SUM(time_difference) AS track_duration, MIN(timestamp) AS track_start, MAX(timestamp) AS track_stop
FROM geolocation_difference_table
GROUP BY track_id
ORDER BY track_id ASC;
INSERT INTO vehicletracks_tracks(session, client_id, vehicle_type, track_distance, track_duration, track_start, track_stop)
SELECT vrstica1.session, vrstica1.client_id, vrstica1.vehicle_type, track_distance, track_duration, track_start, track_stop
FROM (SELECT * FROM track_distance_table WHERE track_distance > 0 AND track_distance IS NOT NULL) AS t;
END LOOP;
DROP TABLE IF EXISTS vehicletracks_binding;
CREATE TABLE vehicletracks_binding AS
SELECT id, track_id
FROM geolocation_difference_table
ORDER BY track_id ASC;
DROP VIEW IF EXISTS group_by_client_session_vehicle;
DROP VIEW IF EXISTS velocity_difference_view;
DROP TABLE IF EXISTS difference_table;
DROP TABLE IF EXISTS new_time_difference;
DROP TABLE IF EXISTS geolocation_difference_table;
DROP TABLE IF EXISTS track_distance_table;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
I need to partition SQL table on monthly basis. So far i am able to create 12 partitions in Year-2015. But when year 2016 starts, all data started to pile up in last partition (December in my case). I need to place data of January-2016 in 1 partition (January in my case). I cannot make partitions for every year. Any suggestions?
Below is an example of how to create an incremental monthly partition for a RANGE RIGHT function, including test data.
CREATE DATABASE Test;
GO
USE Test
GO
--main table partition function (before start of next month)
CREATE PARTITION FUNCTION PF_Monthly(datetime2(0))
AS RANGE RIGHT FOR VALUES (
'2015-01-01T00:00:00'
, '2015-02-01T00:00:00'
, '2015-03-01T00:00:00'
, '2015-04-01T00:00:00'
, '2015-05-01T00:00:00'
, '2015-06-01T00:00:00'
, '2015-07-01T00:00:00'
, '2015-08-01T00:00:00'
, '2015-09-01T00:00:00'
, '2015-10-01T00:00:00'
, '2015-11-01T00:00:00'
, '2015-12-01T00:00:00'
, '2016-01-01T00:00:00' --future empty partition
)
GO
--main table partition scheme
CREATE PARTITION SCHEME PS_Monthly
AS PARTITION PF_Monthly
ALL TO ( [PRIMARY] );
GO
--main partitioned table
CREATE TABLE dbo.MontylyPartitionedTable(
PartitioningColumn datetime2(0)
, OtherKeyColumn int NOT NULL
, OtherData int NULL
, CONSTRAINT PK_MontylyPartitionedTable PRIMARY KEY
CLUSTERED (PartitioningColumn, OtherKeyColumn)
ON PS_Monthly(PartitioningColumn)
) ON PS_Monthly(PartitioningColumn);
GO
---load 12M rows test data
WITH
t4 AS (SELECT n FROM (VALUES(0),(0),(0),(0)) t(n))
,t256 AS (SELECT 0 AS n FROM t4 AS a CROSS JOIN t4 AS b CROSS JOIN t4 AS c CROSS JOIN t4 AS d)
,t16M AS (SELECT ROW_NUMBER() OVER (ORDER BY (a.n)) - 1 AS num FROM t256 AS a CROSS JOIN t256 AS b CROSS JOIN t256 AS c)
INSERT INTO dbo.MontylyPartitionedTable WITH (TABLOCKX) (PartitioningColumn, OtherKeyColumn, OtherData)
SELECT DATEADD(month, num/1000000, '20150101'), num, num
FROM t16M
WHERE num < 12000000;
GO
CREATE PROCEDURE dbo.CreateMonthlyPartition
#NewMonthStartDate datetime2(0) --partition boundary to create
/*
*/
AS
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRAN;
--acquire exclusive lock on main table to prevent deadlocking during partition maintenance
DECLARE #result int = (SELECT TOP (0) 1 FROM dbo.MontylyPartitionedTable WITH (TABLOCKX));
--add new partition for future data
ALTER PARTITION SCHEME PS_Monthly
NEXT USED [PRIMARY];
ALTER PARTITION FUNCTION PF_Monthly()
SPLIT RANGE (#NewMonthStartDate);
--this will release the exclusve table lock but the data in the staging table temporarily unavailable
COMMIT;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK;
THROW;
END CATCH;
GO
--schedule this before the start of each new month to create a new monthly partition 2 months in advance
SELECT DATEADD(day, 1, DATEADD(month, 1, EOMONTH(GETDATE())));
DECLARE #NewMonthStartDate datetime2(0) = DATEADD(day, 1, DATEADD(month, 1, EOMONTH(GETDATE())));
EXEC dbo.CreateMonthlyPartition #NewMonthStartDate;
GO
Partition Table Monthly Bases using Computed Column.
**step1 : Create FileGroup For 12 Month **
ALTER DATABASE yourDataBase ADD FILEGROUP January
ALTER DATABASE yourDataBase ADD FILE (
NAME = N'January',
FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\January.ndf'
) TO FILEGROUP January
ALTER DATABASE yourDataBase ADD FILEGROUP February
ALTER DATABASE yourDataBase ADD FILE (
NAME = N'February',
FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\February.ndf',
SIZE = 3072KB , FILEGROWTH = 1024KB
) TO FILEGROUP February
and so on Until 12 Month.
step2 : Create Function
CREATE PARTITION FUNCTION partition_ByMonth (int) AS RANGE RIGHT FOR VALUES (2,3,4,5,6,7,8,9,10,11,12);
step3: Create SCheme
CREATE PARTITION SCHEME partition_scheme_ByMonth
AS PARTITION partition_ByMonth
TO (January, February, March, April, May, June, July, August, September, October, November, December);
step4: Table Partition
ALTER TABLE PartitionTableByMonth ADD PartitionColumn as MONTH(OrderDate) PERSISTED
step5: index
CREATE NONCLUSTERED INDEX IX_PartitionedTable_Pd ON PartitionTableByMonth (PartitionColumn )
ON partition_scheme_ByMonth(PartitionColumn )
Now yourTable Partitioning By month
I need to build a datetime in a select statement based on another 2 columns (datetime).
I cannot seem to get the conversion correct. Can you spot what I am doing wrong?
It seems to me that DatePart it omits the "0" part of the day
Below script should create all the data necessary
IF EXISTS (SELECT * FROM sys.databases WHERE name='TestDB')
BEGIN
ALTER DATABASE TestDB
SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE TestDB
END
CREATE DATABASE TestDB
GO
IF OBJECT_ID(N'[dbo].[TestTable]','U') IS NOT NULL
DROP TABLE [dbo].[TestTable]
GO
CREATE TABLE [dbo].[TestTable]
(
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[DateSample1] datetime NOT NULL,
[DateSample2] datetime NOT NULL
)
GO
INSERT dbo.TestTable (DateSample1, DateSample2)
VALUES('2006-10-06 00:00:00.000', '2007-01-17 00:00:00.000')
/*
In your select statement you should return another column "DateSample3"
and this should be year from DateSample1 and month and day from dateSample2
*/
--my try1
SELECT
tt.DateSample1, tt.DateSample2,
DateSample3 = CAST(DATEPART(YYYY, tt.DateSample1) AS CHAR(4))
+ CAST(DATEPART(MM, tt.DateSample2) AS CHAR(2))
+ CAST(DATEPART(dd, tt.DateSample2) AS CHAR(2))
,WantedResultForDateSample3='2006-01-17 00:00:00.000'
FROM
dbo.TestTable tt
--mytry2 THROWS AN ERROR
--Conversion failed when converting date and/or time from character string
/*
SELECT
tt.DateSample1, tt.DateSample2,
DateSample3 = CONVERT(DATETIME,CAST(DATEPART(YYYY, tt.DateSample1) AS CHAR(4))
+ CAST(DATEPART(MM, tt.DateSample2) AS CHAR(2))
+ CAST(DATEPART(dd, tt.DateSample2) AS CHAR(2)),120),
WantedResult='2006-01-17 00:00:00.000'
FROM
dbo.TestTable tt
*/
You can use DATETIMEFROMPARTS
DATETIMEFROMPARTS(YEAR(tt.DateSample1),
MONTH(tt.DateSample2),
DAY(tt.DateSample2),
0,0,0,0)
Which is a lot cleaner than constructing a string IMO.
Whichever method you use you might have to deal with impossible dates with this requirement. One approach is below
SELECT CASE WHEN month=2
AND day = 29
AND (yr % 4 != 0 OR (yr % 100 = 0 AND yr % 400 != 0))
THEN NULL
ELSE
DATETIMEFROMPARTS(yr,
month,
day,
0,0,0,0)
END
FROM [dbo].[TestTable] tt
CROSS APPLY (VALUES (YEAR(tt.DateSample1),
MONTH(tt.DateSample2),
DAY(tt.DateSample2))) V(yr, month, day)
SQL Fiddle
Let me start with my table ;
these 3 table are main table .
club profile my customer information exists , in card transactions my customer transactions exists and cardtransationslog the transactions detail is exists .
This is my query logic :
select people total points and select dateTime(card transactions log) when Their points is more than 12000
I tried this query , but i cant select the time in 3th table
SELECT CP.ClubProfileId, FirstName , LastName ,
sum(Points)
FROM ClubProfile CP JOIN CardTransaction CT
ON CT.ClubProfileId = CP.ClubProfileId
JOIN CardTransactionLog CL
ON CL.CardTransactionLogId = CT.CardTransactionLogId
group by FirstName,LastName,cp.ClubProfileId
having sum(Points) > 12000
my main problem is that , with upper query i cant find their time when the people total points when is more than 120000
In other sentence select their time when the points is more than 12000
You can easily get a dateTime from the card transactions table if it is enclosed in an appropriate aggregate function such as MIN or MAX.
SELECT CP.ClubProfileId, FirstName , LastName , max(CT.dateTime) as MaxDateTime
sum(Points)
FROM ClubProfile CP JOIN CardTransaction CT
ON CT.ClubProfileId = CP.ClubProfileId
JOIN CardTransactionLog CL
ON CL.CardTransactionLogId = CT.CardTransactionLogId
group by FirstName,LastName,cp.ClubProfileId
having sum(Points) > 12000
If you don't want the min (earliest) or max (latest) you will need to provide some more information in your question.
Edit
The question is actually "find the dateTime when the points total exceeds 12,000". In order to find this we need some sort of running total of points. The SQLPerformance.com article Best approaches for running totals suggests that cursors are often more efficient than set-based approaches for running totals so here is a cursor based solution.
create table dbo.ClubProfile (
ClubProfileId int not null,
FirstName varchar(10) not null,
LastName varchar(10) not null
)
create table dbo.CardTransaction (
ClubProfileId int not null,
CardTransactionLogId int not null,
Points int not null
)
create table dbo.CardTransactionLog (
CardTransactionLogId int not null,
[dateTime] datetime not null
)
insert dbo.ClubProfile values (1,'Rhys','Jones')
insert dbo.CardTransaction values (1, 1, 11000)
insert dbo.CardTransaction values (1, 2, 2000)
insert dbo.CardTransaction values (1, 3, 2000)
insert dbo.CardTransactionLog values (1, '2015-01-16')
insert dbo.CardTransactionLog values (2, '2015-01-17')
insert dbo.CardTransactionLog values (3, '2015-01-18')
go
-- create a working table to hold the dateTime and running sum of points for each ClubProfile
if (object_id('tempdb..#workingTable') is not null) drop table #workingTable
select ClubProfileId, cast(null as datetime) as dateTime, cast(0 as int) as SumPoints
into #workingTable
from dbo.ClubProfile
-- declare a cursor to iterate over all of the transactions.
declare cur cursor local static forward_only read_only for
select CT.ClubProfileId, CT.Points, CL.dateTime
from dbo.CardTransaction CT
inner join dbo.CardTransactionLog CL on CL.CardTransactionLogId = CT.CardTransactionLogId
order by CL.dateTime -- make sure this is the right order for your requirements
declare #ClubProfileId int
declare #Points int
declare #dateTime datetime
open cur
fetch next from cur into #ClubProfileId, #Points, #dateTime
while (##fetch_status = 0)
begin
-- Update the working table with the current date and running sum of points
-- if the current running sum of points is less than 12,000.
update #workingTable set
[dateTime] = #dateTime,
SumPoints = SumPoints + #Points
where
ClubProfileId = #ClubProfileId and
SumPoints < 12000 -- do you want "<" or "<="?
fetch next from cur into #ClubProfileId, #Points, #dateTime
end
close cur
deallocate cur
-- select the results, note that SumPoints here is only the partial sum upto exceeding 12,000
select
CP.ClubProfileId, CP.FirstName, CP.LastName, T.dateTime, T.SumPoints
from
dbo.ClubProfile CP
inner join #workingTable T on T.ClubProfileId = CP.ClubProfileId
Hope this helps,
Rhys
I have a SQL Server database and I need to manually do an update query. There for no solutions using any programming language can be used.(stored procedures can be used)
I have 4 tables affected (/used) in the query.
[Orders]
[StatusHistoryForOrder]
[StatusHistory]
[Statuses]
I need to update the field [Orders].[OrderStatusID] which is a foreign key to [Statuses]. (So actually changing the state of the order. The table [StatusHistoryForOrder] is a linking table to [StatusHistory] and only contains 2 colums.
[StatusHistoryForOrder].[OrderId]
[StatusHistoryForOrder].[OrderStatusHistoryid]
Don't say that this is not logically cause I already know that. The company who designed the database is a complete retarded company but the database is now too large to set things straight and there is neither the time or money to do it.
The [StatusHistory] table has multiple columns:
[StatusHistory].[OrderStatusHistoryId]
[StatusHistory].[OrderStatusId]
[StatusHistory].[Date]
[StatusHistory].[Message]
The [StatusHistory].[OrderStatusId] is also a foreign key to [Statuses].
In the update query I need to update the status of the order to status 16. But only on rows that now have status 1 and are older then 60 days. I know I can check the date by using the function
DATEDIFF(DD,[StatusHistory].[Date],GETDATE()) > 60
But how to implement this query if the date field is not in the orders. And to set the new [StatusHistory] a new row has to be made for that table and the [StatusHistoryForOrder] table also needs a new row and the ID of that row needs to be set in the [Orders] table row.
Does anyone know how to do this? I am fairly new to SQL Server (or SQL for that matter) and I have absolutly no clue where to begin.
Conclusion:
I need a stored procedure that first checks every row in [Orders] if the [StatusHistory].[Date] (which is linked to the order using foreign keys) of that order is older that 60. If it is older then a new StatusHistory row must be inserted with the current date and status 16. Then in [StatusHistoryForOrder] a new row must be inserted with the new ID of the statusHistory been set in [StatusHistoryForOrder].[OrderStatusHistoryid] and the order id set in [StatusHistoryForOrder].[OrderId]. And last but not least: The [Orders].[OrderStatusID] also needs to be set to 16.
A select query to select the date and status of the order:
SELECT TOP (100) PERCENT
dbo.Orders.OrderID,
dbo.Statuses.Description AS Status,
dbo.StatusHistory.Date
FROM
dbo.Orders
INNER JOIN
dbo.Statuses
ON
dbo.Orders.OrderStatusID = dbo.Statuses.StatusId
INNER JOIN
dbo.StatusHistoryForOrder
ON
dbo.Orders.OrderID = dbo.StatusHistoryForOrder.OrderId
INNER JOIN
dbo.StatusHistory
ON
dbo.StatusHistoryForOrder.OrderStatusHistoryid = dbo.StatusHistory.OrderStatusHistoryId
WHERE
(dbo.Statuses.StatusId = 1)
AND
(DATEDIFF(DD, dbo.StatusHistory.Date, GETDATE()) > 60)
UPDATE
For #marc_s:
Can anyone help me with that?
Try this CTE (Common Table Expression) to find all those orders - does it work, are the results plausible? (this doesn't update anything just yet - just SELECTing for now):
USE (your database name here)
GO
DECLARE #OrdersToUpdate TABLE (OrderID INT, StatusHistoryID INT, StatusDate DATETIME)
;WITH RelevantOrders AS
(
SELECT
o.OrderId, sh.Date
FROM dbo.Orders o
INNER JOIN dbo.StatusHistoryForOrder ho ON ho.OrderId = o.OrderId
INNER JOIN dbo.StatusHistory sh ON ho.OrderStatusHistoryid = sh.OrderStatusHistoryid
WHERE
sh.Date <= DATEADD(D, -60, GETDATE()) -- older than 60 days back from today
AND o.OrderStatusID = 1 -- status = 1
)
INSERT INTO #OrdersToUpdate(OrderID, StatusDate)
SELECT OrderID, [Date]
FROM RelevantOrders
BEGIN TRANSACTION
BEGIN TRY
DECLARE #OrderIDToInsert INT, -- OrderID to process
#InsertedStatusHistoryID INT -- new ID of the inserted row in StatusHistory
-- grab the first OrderID that needs to be processed
SELECT TOP 1 #OrderIDToInsert = OrderID
FROM #OrdersToUpdate
WHERE StatusHistoryID IS NULL
ORDER BY OrderID
-- as long as there are still more OrderID to be processed ....
WHILE #OrderIDToInsert IS NOT NULL
BEGIN
PRINT 'Now inserting new StatusHistory entry for OrderID = ' + CAST(#OrderIDToInsert AS VARCHAR(10))
INSERT INTO dbo.StatusHistory(OrderStatusID, [Date], [Message])
VALUES(16, GETDATE(), 'Bulk Insert/Update operation') -- enter here whatever you want to store
SELECT #InsertedStatusHistoryID = SCOPE_IDENTITY(); -- grab newly inserted ID
PRINT 'New StatusHistory entry inserted with ID = ' + CAST(#InsertedStatusHistoryID AS VARCHAR(10))
UPDATE #OrdersToUpdate
SET StatusHistoryID = #InsertedStatusHistoryID
WHERE OrderID = #OrderIDToInsert
-- safety - reset #OrderIDToInsert to NULL so that we'll know when we're done
SET #OrderIDToInsert = NULL
-- read next OrderID to be processed
SELECT TOP 1 #OrderIDToInsert = OrderID
FROM #OrdersToUpdate
WHERE StatusHistoryID IS NULL
ORDER BY OrderID
END
-- insert into the StatusHistoryForOrder table
INSERT INTO dbo.StatusHistoryForOrder(OrderID, OrderStatusHistoryID)
SELECT OrderID, StatusHistoryID
FROM #OrdersToUpdate
-- update your Orders to status ID = 16
UPDATE dbo.Orders
SET OrderStatusID = 16
FROM #OrdersToUpdate upd
WHERE dbo.Orders.OrderID = upd.OrderID
COMMIT TRANSACTION
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage
ROLLBACK TRANSACTION
END CATCH
This CTE basically joins your Orders table to the StatusHistory table (via the intermediate link table) and selects the values you're interested in (hopefully!).
This particular problem seems solvable with set operations only.
DECLARE #Orders TABLE (ID int, rownum int IDENTITY);
DECLARE #StatusHistory TABLE (ID int, rownum int IDENTITY);
/* get the list of orders with expired statuses */
INSERT INTO #Orders (ID)
SELECT o.OrderID
FROM Orders o
INNER JOIN StatusHistoryForOrder shfo ON o.OrderID = shfo.OrderId
INNER JOIN StatusHistory sh ON shfo.OrderStatusHistoryid = sh.OrderStatusHistoryId
GROUP BY o.OrderID
HAVING DATEDIFF(DD, MAX(sh.Date), GETDATE()) > 60
/* add so many new rows to StatusHistory and remember the new IDs */
INSERT INTO StatusHistory (OrderStatusId, Date, Message)
OUTPUT inserted.OrderStatusHistoryId INTO #StatusHistory (ID)
SELECT
16,
GETDATE(),
'Auto-inserted as the previous status has expired'
FROM #Orders
/* join the two temp lists together and add rows to StatusHistoryForOrder */
INSERT INTO StatusHistoryForOrder (OrderId, OrderStatusHistoryid)
SELECT o.ID, sh.ID
FROM #Orders o
INNER JOIN #StatusHistory sh ON o.rownum = sh.rownum
/* finally update the statuses in Orders */
UPDATE Orders
SET OrderStatusID = 16
FROM #Orders o
WHERE Orders.OrderID = o.ID
This should be the body of a single transaction, of course.