I'm extending some legacy software that splits data up in to multiple schemas by company, for example CP1.ACCOUNTS, CP2.ACCOUNTS, CPN.ACCOUNTS. I'm attempting to create an updatable view of these tables using partitioning, but I'm getting the typical "not updatable because a partitioning column was not found" error. The column I'm trying to partition on is the primary key, and as far as I can tell, isn't any of the things it isn't allowed to be.
So, with table definitions like so:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [CP1].[ACCOUNTS](
[ACCOUNTID] [char](10) NOT NULL,
[LASTNAME] [varchar](60) NOT NULL,
[FIRSTNAME] [varchar](35) NOT NULL,
[MIDDLE] [varchar](26) NULL,
[SUFFIX] [varchar](10) NULL,
[ADDRESS1] [varchar](55) NULL,
[ADDRESS2] [varchar](55) NULL,
[SOME_FLAG] [tinyint] NULL,
CONSTRAINT [ARM_CODE_KEY] PRIMARY KEY CLUSTERED
(
[CODE_] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [CP1].[ACCOUNTS] WITH CHECK ADD CONSTRAINT [CK__ACCOUNTS__CODE___4DD705FF] CHECK ((left([ACCOUNTID],(3))='CP1'))
GO
ALTER TABLE [CP1].[ACCOUNTS] CHECK CONSTRAINT [CK__ACCOUNTS__CODE___4DD705FF]
GO
ALTER TABLE [CP1].[ACCOUNTS] ADD DEFAULT ((0)) FOR [SOME_FLAG]
GO
and the rest of the tables defined exactly as above, following the CP2, CP3, CPN pattern, and the view definition being a simple:
CREATE VIEW [ALL].[ACCOUNTS] AS
SELECT * FROM CP1.ACCOUNTS
UNION ALL
SELECT * FROM CP2.ACCOUNTS
--UNION ALL etc...
Inserts would be like:
INSERT INTO [ALL].[ACCOUNTS]
([ACCOUNTID]
,[LASTNAME]
,[FIRSTNAME]
,[MIDDLE]
,[SUFFIX]
,[ADDRESS1]
,[ADDRESS2]
,[SOME_FLAG])
VALUES
('CP1XYZ0001',
'SMITH',
'JOHN',
'Q',
'',
'123 Fake St',
'Apt 2',
0,
GO
generates an error like:
Msg 4436, level 16, State 12, Line 1
UNION ALL view 'ALL.ACCOUNTS' is not updatable because a partitioning column was not found.
Am I missing something simple? Am I just way out in left field here?
You need a constraint that defines which column is used as a partitioning column. As the error suggests, you don't have one defined. As described in the documentation:
To perform updates on a partitioned view, the partitioning column must
be a part of the primary key of the base table. If a view is not
updatable, you can create an INSTEAD OF trigger on the view that
allows updates. You should design error handling into the trigger to
make sure that no duplicate rows are inserted. For an example of an
INSTEAD OF trigger designed on a view, see Designing INSTEAD OF
Triggers.
In other words, SQL Server needs to be able to figure out which table gets the update.
You might be able to alter the tables to contain a company name column, which is then used as part of the primary key. Something like this might work:
create table . . .
CompanyName as 'CompanyA',
primary key (AccountId, CompanyName)
. . .
The alternative is to use an instead of trigger, as suggested in the documentation.
In case someone comes upon this, you can use a computed column for partitioning, just make sure to make it a persisted computed column.
In this case, the computed column should be left([ACCOUNTID],(3) and the partition constraint would be <computed column> = 'CP1'. Note: using left() in the constraint will cause it to still scan all partitions. The CHECK constraints can only use these operators: BETWEEN, AND, OR, <, <=, >, >=, =.
Also, since the question referenced enterprise edition, you'd get better performance using a partitioned table instead of a partitioned view.
Related
I'm still fairly new to postgres. I have a table named: university_table with fields: name,
nationality, abbreviation, adjective, person.
I found this sql query to insert data from: https://stackoverflow.com/a/21759321/9469766
Snippet of query below.
How can alter the query to insert these values into my university_country table
-- Create and load Nationality Table - English
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Nationality]') AND type in (N'U'))
DROP TABLE [dbo].[Nationality]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-------------------------------------------------------------------
-- TABLE: [dbo].[Nationality]
-- Creation Date: 02/12/2014
-- Created by: Dan Flynn, Sr. DBA
--
-------------------------------------------------------------------
CREATE TABLE [dbo].[Nationality]
(
[NationalityID] [int] IDENTITY(1,1) NOT NULL,
[Country] [nvarchar](50) NULL,
[Abbreviation] [nvarchar](5) NULL,
[Adjective] [nvarchar] (130) NULL,
[Person] [nvarchar] (60) NULL
) ON [PRIMARY]
GO
-------------------------------------------------------------------------------
-- INSERT VALUES
-------------------------------------------------------------------------------
INSERT INTO [dbo].[Nationality](Country, Abbreviation, Adjective, Person )
VALUES ( 'AMERICAN - USA','US','US (used attributively only, as in US aggression but not He is US)','a US citizen' ),
( 'ARGENTINA','AR','Argentinian','an Argentinian' ),
( 'AUSTRALIA','AU','Australian','an Australian' ),
( 'BAHAMAS','BS','Bahamian','a Bahamian' ),
( 'BELGIUM','BE','Belgian','a Belgian' ),
GO
-------------------------------------------------------------------------------
-- ADD CLUSTERED INDEX
-------------------------------------------------------------------------------
CREATE CLUSTERED INDEX [idxNationality] ON [dbo].[Nationality]
(
[NationalityID] ASC,
[Country] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
EXEC sys.sp_addextendedproperty #name=N'TableDiscription', #value=N'CreatedBy: Dan Flynn, Sr. SQL Server DBA
CreationDate: 02/12/2014
Nationality table contains five columns, i.e.:
1. NationalityID, 2. Country, 3. Abbreviation, 4. Adjective, 5. Person
IDs 1 to 34 are alphabetical countries that are statistically the most popular as far as interaction with the United States. IDs 35 to 248 are also alphabetical for the rest of the countries.
' , #level0type=N'SCHEMA',#level0name=N'dbo', #level1type=N'TABLE',#level1name=N'Nationality'
GO
To convert T-SQL to be compatible with Postres' SQL dialect you can use the following steps.
Remove all square brackets (they are illegal in SQL identifiers). If you have identifiers that require them use double quotes " but I would highly recommend to avoid quoted identifiers completely (so never use " in SQL)
Remove all GO statements and end the statements with ; (Something that is recommended for SQL Server as well)
Remove the [dbo]. schema prefix if you didn't create one in Postgres (you typically don't)
Remove the ON [Primary] option it's not needed in Postgres (the equivalent would be to define a tablespace, but that's hardly ever needed in Postgres)
There is no IF in SQL (or Postgres), to conditionally drop a table use DROP TABLE IF EXISTS ....
There are no clustered indexes in Postgres, so just make that a regular index and remove all the options that are introduced by the WITH keyword.
Comments on tables are defined through comment on, not by calling a stored procedure
identity(x,y) needs to be replaced with the standard SQL generated always as identity
There is no nvarchar type, just make everything varchar and make sure your database was created with an encoding that can store multi-byte characters (by default it's UTF-8, so that should be fine)
Not required, but: it's highly recommended to use snake_case identifiers, rather than CamelCase in Postgres
Putting that all together the script should be something like this:
DROP TABLE IF EXISTS Nationality CASCADE;
CREATE TABLE nationality
(
Nationality_id int generated always as IDENTITY NOT NULL,
Country varchar(50) NULL,
Abbreviation varchar(5) NULL,
Adjective varchar (130) NULL,
Person varchar (60) NULL
);
INSERT INTO Nationality (Country, Abbreviation, Adjective, Person )
VALUES ( 'AMERICAN - USA','US','US (used attributively only, as in US aggression but not He is US)','a US citizen' ),
( 'ARGENTINA','AR','Argentinian','an Argentinian' ),
( 'AUSTRALIA','AU','Australian','an Australian' ),
( 'BAHAMAS','BS','Bahamian','a Bahamian' ),
( 'BELGIUM','BE','Belgian','a Belgian' );
CREATE INDEX idx_Nationality ON Nationality
(
Nationality_ID ASC,
Country ASC
);
comment on table nationality is 'CreatedBy: Dan Flynn, Sr. SQL Server DBA
CreationDate: 02/12/2014
Nationality table contains five columns, i.e.:
1. NationalityID, 2. Country, 3. Abbreviation, 4. Adjective, 5. Person
IDs 1 to 34 are alphabetical countries that are statistically the most popular as far as interaction with the United States. IDs 35 to 248 are also alphabetical for the rest of the countries.
';
I am a bit surprised that there is no primary key defined. You probably want to add:
alter table nationality
add primary key (nationality_id);
There is an SQL standard, but nobody implements it fully. Some database systems like PostgreSQL are better at sticking to the standard, others like Microsoft SQL Server are not.
The upshot of this is that you cannot take SQL that works on one RDBMS and use it with another one. You will have to translate that to the PostgreSQL dialect.
I have a fully functional database in sql server. Around 40 tables. I have to install this schema (only the schema, not the data) on multiple other sql server instances. SSMS offers a nice way to auto generate schemas using Tasks --> Generate Scripts. It kinda works, but I am not sure if I understand it correctly:
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TableName]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[TableName](
[id] [uniqueidentifier] NOT NULL,
[history] [varchar](max) NOT NULL,
[isdeleted] [bit] NOT NULL,
CONSTRAINT [PK_RecGroupData] PRIMARY KEY CLUSTERED
(
[rid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
END
GO
IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[DF_TableName_id]') AND type = 'D')
BEGIN
ALTER TABLE [dbo].[TableName] ADD CONSTRAINT [DF_TableName_id] DEFAULT (newid()) FOR [id]
END
--Just showing one ALTER TABLE and IF NOT EXISTS. The others are generated in the same way.
What happens, if I execute the script, create a new script with the exact same content, but add a new column to it (--> id, history, isdeleted and timestamp)? Does it automatically add the new line? I think yes, of course, but I don't get, how it would know, if the column should be NOT NULL, VARCHAR, BIT, or something similar. It would just execute
ALTER TABLE [dbo].[TableName] ADD CONSTRAINT [DF_TableName_id] DEFAULT (newid()) FOR [id]
(id => new sample column)
But there isn't any information about the data type or any other modifiers.
Also, if I execute my script like this one a second time, it'll throw some errors:
Meldung 1781, Ebene 16, Status 1, Zeile 3
An die Spalte ist bereits ein DEFAULT-Wert gebunden.
Which translates to this:
Message 1781, level 16, status 1, line 3
A DEFAULT value is already bound to the column.
Why does this happen?
The error message is saying that there was a default value assigned to that column before.
Also:
ALTER TABLE [dbo].[TableName] ADD CONSTRAINT [DF_TableName_id] DEFAULT (newid()) FOR [id]
is not the syntax for adding new column - this is to add default value of NEWID() to the column [id].
To add a column you you should follow this steps (with an example inside).
Also how would the SQL Server know the setup settings for the new columns from your manually added lines? It would simply allow you to define them as you want and accept if the syntax is right or through an error if not during the script parse process (can be done by [ctrl] + [F5] in SSMS).
Im having a bit of a design issue here. Im kind of a novice in this, so I need some help.
Due to a company merge, where everything must go in one of the systems; Im supposed to map our customers with a new customerid in the other company.
When I get the new customerID's Im supposed to ensure that it is unique and the same goes for our existing customerID.
Current customerID: CurCustID
New customer ID: NewCustID
First, I would like the database to make sure that every CurCustID in column CurCustID is unique - only with one record, secondly I would like the column NewCustID to be unique - only with one record.
Third I would like that the row combination of CurCustID and NewCustID only accepts unique data.
If you can help me I would be very thankful, on the otherhand if my approach is bad practice and there is a best practice way of doing this, then please let me know.
USE [Database]
GO
/****** Object: Table [dbo].[TblMapning] Script Date: 05/30/2016 14:30:21 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[TblMapning](
[CurCustID] [varchar](255) NOT NULL,
[NewCustID] [varchar](255) NOT NULL,
PRIMARY KEY CLUSTERED
(
[CurCustID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
Seems like you'd have to create 3 separate tables to enforce all those things. How new customer ids are generated in the new and mapping tables could be automated depending on your house rules for how new ones are assigned. You would probably want to create code or SPs to make the process more user-friendly, spit out errors, etc. for duped oldcustids, but at the table level this would be one way to enforce it.
CREATE TABLE [dbo].[Tbloldcustids](
CustID [varchar](255) NOT NULL
PRIMARY KEY (CustID)
)
CREATE TABLE [dbo].[Tblnewcustids](
CustID [varchar](255) NOT NULL
PRIMARY KEY (CustID)
)
CREATE TABLE [dbo].[TblMapping](
[CurCustID] [varchar](255) NOT NULL,
[NewCustID] [varchar](255) NOT NULL
PRIMARY KEY (CurCustID,NewCustID)
)
I need write a sql view on series of tables in a database. The problem is that the table only contains one month of history. Each month a new table is created. For example dbo.LOG_2015_09 would be September's table.
I need to write a view that shows me the last 60 days of history.
SELECT * FROM dbo.LOG_2015_09
UNION ALL
SELECT * FROM dbo.LOG_2015_08
The problem is that next month this will not be valid anymore.
I am limited to using SQL Views. Stored procedures are not an option.
One thought I had was to create a Table Function to get the relevant tables but I don't think we can use dynamic SQL to generate the code.
Thank you for any help.
EDIT: This is a sample of the table definition created by the application. I do not have any access to modify the table. I can only create views or functions.:
CREATE TABLE [dbo].[CLOG201509](
[LASTUPD] [datetime] NULL,
[CREDATE] [datetime] NULL,
[SERIALNO] [int] NOT NULL,
[LSEQNO] [int] NOT NULL,
[EVENTNO] [int] NOT NULL,
[EVDATE] [datetime] NOT NULL,
[LOGDATE] [datetime] NOT NULL,
CONSTRAINT [PK__CLOG2015__D08461DA672EF3E9] PRIMARY KEY CLUSTERED
([SERIALNO] ASC,
[EVENTNO] ASC,
[LSEQNO] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
These tables contain over a million records each.
EDIT: I have attempted to create a TABLE UDF to combine the tables but without being able to use Dynamic SQL I don't know how to make it change the tables based on the date. I would want to have this months table and last months table joined together to reference. The problem is that I have to go and remember to update this UDF each month and until I do, the data is unavailable.
CREATE FUNCTION [dbo].[KS_ManitouSync_OPT_CLOG]()
RETURNS TABLE
AS
RETURN
(
SELECT *
FROM CLOG201511
UNION ALL
SELECT *
FROM CLOG201512
)
As a reminder I cannot use Stored Procs, It must be useable by a program that supports a single SQL SELECT Query only.
Thanks for any help.
You could create a partitioned view which unions all the tables, but each table must have a constraint to limit the time interval allowed in it. In this case, when you select from the partitioned view, using a WHERE clause on the partitioning column, only the relevant tables will be accessed.
See https://technet.microsoft.com/en-us/library/ms190019(v=sql.105).aspx
create your view via a stored procedure. there you can figure out which log tables are all existing and then build your create view command dynamicaly.
have a look in a quite similar question i had in the past:
Creating View with dynamic columns by stored procedure
If really must use a view (you cannot use a stored procedure) and you cannot update the view automatically (using an SQL Server Agent job or using a DDL trigger), you could use dynamic SQL in a view using OPENROWSET, but there are a lot of caveats, see http://www.sommarskog.se/share_data.html#OPENQUERY
I have a "history" table where I log each request into a Web Handler on our web site. Here is the table definition:
/****** Object: Table [dbo].[HistoryRequest] Script Date: 10/09/2009 17:18:02 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[HistoryRequest](
[HistoryRequestID] [uniqueidentifier] NOT NULL,
[CampaignID] [int] NOT NULL,
[UrlReferrer] [nvarchar](512) NOT NULL,
[UserAgent] [nvarchar](512) NOT NULL,
[UserHostAddress] [nvarchar](15) NOT NULL,
[UserHostName] [nvarchar](512) NOT NULL,
[HttpBrowserCapabilities] [xml] NOT NULL,
[Created] [datetime] NOT NULL,
[CreatedBy] [nvarchar](100) NOT NULL,
[Updated] [datetime] NULL,
[UpdatedBy] [nvarchar](100) NULL,
CONSTRAINT [PK_HistoryRequest] PRIMARY KEY CLUSTERED
(
[HistoryRequestID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[HistoryRequest] WITH CHECK ADD CONSTRAINT [FK_HistoryRequest_Campaign] FOREIGN KEY([CampaignID])
REFERENCES [dbo].[Campaign] ([CampaignId])
GO
ALTER TABLE [dbo].[HistoryRequest] CHECK CONSTRAINT [FK_HistoryRequest_Campaign]
GO
37 seconds for 1050 rows on this statement:
SELECT *
FROM HistoryRequest AS hr
WHERE Created > '10/9/2009'
ORDER BY Created DESC
Does anyone have anysuggestions for speeding this up? I have a Clustered Index on the PK and a regular Index on the CREATED column. I tried a Unique Index and it barfed complaining there is a duplicate entry somewhere - which can be expected.
Any insights are welcome!
You are requesting all columns (*) over a non-covering index (created). On a large data set you are guaranteed to hit the Index Tipping Point where the clustered index scan is more efficient than an nonclustered index range seek and bookmark lookup.
Do you need * always? If yes, and if the typical access pattern is like this, then you must organize the table accordingly and make Created the leftmost clustered key.
If not, then consider changing your query to a coverable query, eg. select only HistoryRequestID and Created, which are covered by the non clustered index. If more fields are needed, add them as included columns to the non-clustered index, but take into account that this will add extra strorage space and IO log write time.
Hey, I've seen some odd behavior when pulling XML columns in large sets. Try putting your index on Created back, then specify the columns in your select statement; but omit the XML. See how that affects the return time for results.
For a log table, you probably don't need a uniqueidentifier column. You're not likely to query on it either, so it's not a good candidate for a clustered index. Your sample query is on "Created", yet there's no index on it. If you query frequently on ranges of "Created" values then it would be a good candidate for clustering even though it's not necessarily unique.
OTOH, the foreign key suggests frequent querying by Campaign, in which case having the clustering done by that column could make sense, and would also probably do a better job of scattering the inserted keys in the indexes - both the surrogate key and the timestamp would add records in sequential order, which is net more work over time for insertions because the node sectors are filled less randomly.
If it's just a log table, why does it have update audit columns? It would normally be write-only.
Rebuild indexes. Use WITH (NOLOCK) clause after the table names where appropriate, this probably applies if you want to run long(ish) running queries against table that are heavily used in a live environment (such as a log file). It basically means your query migth miss some of teh very latest records but you also aren't holding a lock open on the table - which creates additional overhead.