view contains a convert that is imprecise or non-deterministic? - sql

My query:
IF OBJECT_ID ('vw_F_GWLVL_RAW', 'V') IS NOT NULL
DROP VIEW vw_F_GWLVL_RAW ;
GO
CREATE VIEW vw_F_GWLVL_RAW WITH SCHEMABINDING
AS
SELECT S.SiteName, CAST(D.[Date] as Date) as [Date], CONVERT(CHAR(5),T.[Time]) as [Time], CAST(F.SampleValue as NUMERIC(6,3)) as ValueAsRecorded,
Q.Code as DataQualityCode, Q.QualityDesc as DataQualityDesc
FROM dbo.F_GWLVL_RAW AS F INNER JOIN
dbo.D_Site AS S ON F.D_Site_Key = S.D_Site_Key INNER JOIN
dbo.D_Date AS D ON F.D_Date_Key = D.D_Date_Key INNER JOIN
dbo.D_Time AS T ON F.D_Time_Key = T.D_Time_Key INNER JOIN
dbo.F_ODP_QC_GWLVL AS Q ON Q.[Site] collate database_default = S.[SiteName] AND
(CONVERT(datetime,CAST(D.[Date] as VARCHAR(30))+' '+CAST(T.[Time] as VARCHAR(30)),121) BETWEEN Q.StartTime AND Q.EndTime)
GO
CREATE UNIQUE CLUSTERED INDEX IX_GWLVL_RAW1 ON vw_F_GWLVL_RAW(SiteName,[Date],[Time])
Gives me this error even though my convert is using deterministic 121 type:
'Cannot create index on view "dbo.vw_F_GWLVL_RAW". The view contains a convert that is imprecise or non-deterministic.'

Create INDEXED VIEWS.
You need to use a Deterministic Style in your CONVERT clause. The default is non-deterministic
Try,CONVERT(DECIMAL(10,2)) OR CONVERT(DATETIME, '20131202', 103)

Related

create a table based on the results (output) of previous queries

how can I create a table based on the output of previous queries? I tried Create table as select distinct syntax but it doesn't work.
ps: the previous queries contain inner join SET dateformat dmy
select distinct c.peril, c.period, c.eventid, c.eventdate, c.loss_index_2016,
c.loss_index_20182019, a.eventdatetime, b.region, a.year,a.eventid
from RetroNCTJan2019v5 a
inner join retropltheader2019v5 b
on a.segmentid=b.segmentid
inner join Index2019 c
on b.region = c.peril and a.Year = c.period and
a.eventid=convert(numeric(30,0),convert(float(28),c.eventid)) and
month(eventdate) = month(eventdatetime) and day(eventdate)=day(eventdatetime)
In SQL Server you insert the results of a SELECT into a new table using INTO:
select distinct c.peril, c.period, c.eventid,c.eventdate, c.loss_index_2016, c.loss_index_20182019,
a.eventdatetime, b.region, a.year,a.eventid
into new_table
from RetroNCTJan2019v5 a join
retropltheader2019v5 b
on a.segmentid = b.segmentid join
Index2019 c
on b.region = c.peril and a.Year = c.period and
a.eventid = convert(numeric(30,0), convert(float(28),c.eventid)) and
month(eventdate) = month(eventdatetime) and
day(eventdate) = day(eventdatetime)
Most databases use create table as, which in my opinion, is a little clearer.
That said, you should learn some better coding habits:
Use table aliases that are meaningful. This usually means abbreviations of the table names. Don't use arbitrary letters.
Qualify all column references, especially when your query has more than one table reference.
Converting to a float and then a numeric is probably unnecessary. In fact, having to do a type conversion in a JOIN condition is suspicious -- and even more so when the two columns have the same name. Same name --> same type.

Left outer join with left() function

I'm running the below query daily (overnight) and it's taking considerable time to run (1-1.5 hours). I'm certain the "Acc.DateKey >= LEFT (LocationKey, 8)" is the reason and if this part of the join is removed the query executes in around 5 minutes. I just cannot think of a more efficient way.
Acc.DateKey is a bigint typically 20180101 etc., with the location key being a bigint typically 201801011234 etc.
So far I've considered including a new column in the LO table "AccLocationKey" which will be inserted with the LEFT (LocationKey, 8) function when loaded.
I've decided to pose the question here first - could this be improved upon without changing the LO table?
SELECT
ISNULL(MAX(L.LocationKey),(SELECT MIN(LocationKey) FROM LO WHERE Location = Acc.Location)) AS LocationKey
FROM
Acc
LEFT OUTER JOIN
(
SELECT
LocationKey
,Location
FROM
LO
)AS L
ON Acc.Location = L.Location AND Acc.DateKey >= LEFT(LocationKey,8)
Let's rewrite the query without the subquery in the SELECT:
SELECT COALESCE(MAX(L.LocationKey), MIN(L.MIN_LocationKey)) AS LocationKey
FROM Acc LEFT OUTER JOIN
(SELECT MIN(l.LocationKey) OVER (PARTITION BY l.Location) as min_location,
l.*
FROM LO l
) L
ON Acc.Location = L.Location AND Acc.DateKey >= LEFT(l.LocationKey, 8);
Probably your best chance at performance is to add a computed column and appropriate index. So:
alter table lo add locationkey_datekey as (try_convert(bigint, LEFT(l.LocationKey, 8))) persisted;
Then, the appropriate index:
create index idx_lo_location_datekey on lo(location, locationkey_datekey);
Then use this in the query:
SELECT COALESCE(MAX(L.LocationKey), MIN(L.MIN_LocationKey)) AS LocationKey
FROM Acc LEFT OUTER JOIN
(SELECT MIN(l.LocationKey) OVER (PARTITION BY l.Location) as min_location,
l.*
FROM LO l
) L
ON Acc.Location = L.Location AND Acc.DateKey >= l.LocationKey_datekey;
Happily, this index will also work for the window function.

Join different tables depending on field values

I have the following sql view result which contains the audited changes for every field for every table in the system:
In this case the above image tell us that both read and write permissions were revoked for the user Lucas for the subscription called MySubscription.
I need to display that info in a grid however that is not what I want to display, I mean, I donĀ“t want to display IDs. I need to display "Read" instead of 50, "Write" instead of 51, "Lucas" instead of 1 and "MySubscription" instead of 6.
To do that I would like to improve the sql view to get the values instead of their IDs as I mention above. The result that I am looking for is this one:
The database contains the tables Subscriptions, ProductPermissions and TenantUsers to get the needed info using joins.
Could you please give me some clues about how could I achieve what I need? Thank you.
You could do this with a series of LEFT JOINs, some casting might be required to get the datatype of the joining column the same as NewValue (I've assumed a column called Name in all your joining tables, this may need changing):
SELECT a.AuditLogId,
a.Operation,
a.[Table],
a.RowId,
a.Name,
[OldValue] = COALESCE(s_Old.Name, pp_old.Name, t_Old.Name),
[NewValue] = COALESCE(s_New.Name, pp_New.Name, t_New.Name)
FROM AuditLog a
LEFT JOIN Subscriptions s_Old
ON a.OldValue = CAST(s_Old.SubscriptionID AS VARCHAR)
AND a.Name = 'SubscriptionID'
LEFT JOIN ProductPermissions pp_Old
ON a.OldValue = CAST(p_Old.ProductPermissionID AS VARCHAR)
AND a.Name = 'ProductPermissionId'
LEFT JOIN TenantUsers t_Old
ON a.OldValue = CAST(t_Old.TenantUserId AS VARCHAR)
AND a.Name = 'TenantUsers'
LEFT JOIN Subscriptions s_New
ON a.NewValue = CAST(s_New.SubscriptionID AS VARCHAR)
AND a.Name = 'SubscriptionID'
LEFT JOIN ProductPermissions pp_New
ON a.NewValue = CAST(p_New.ProductPermissionID AS VARCHAR)
AND a.Name = 'ProductPermissionId'
LEFT JOIN TenantUsers t_New
ON a.NewValue = CAST(t_New.TenantUserId AS VARCHAR)
AND a.Name = 'TenantUsers'
If required you could then PIVOT this into one row per transaction:
SELECT a.AuditLogId,
a.Operation,
a.[Table],
a.RowId,
[OldSubscriptionValue] = MAX(s_old.Name),
[OldProductPermissionValue] = MAX(pp_old.Name),
[OldTennantUserValue] = MAX(t_old.Name),
[NewSubscriptionValue] = MAX(s_New.Name),
[NewProductPermissionValue] = MAX(pp_New.Name),
[NewTennantUserValue] = MAX(t_New.Name)
FROM AuditLog a
LEFT JOIN Subscriptions s_Old
ON a.OldValue = CAST(s_Old.SubscriptionID AS VARCHAR)
AND a.Name = 'SubscriptionID'
LEFT JOIN ProductPermissions pp_Old
ON a.OldValue = CAST(p_Old.ProductPermissionID AS VARCHAR)
AND a.Name = 'ProductPermissionId'
LEFT JOIN TenantUsers t_Old
ON a.OldValue = CAST(t_Old.TenantUserId AS VARCHAR)
AND a.Name = 'TenantUsers'
LEFT JOIN Subscriptions s_New
ON a.NewValue = CAST(s_New.SubscriptionID AS VARCHAR)
AND a.Name = 'SubscriptionID'
LEFT JOIN ProductPermissions pp_New
ON a.NewValue = CAST(p_New.ProductPermissionID AS VARCHAR)
AND a.Name = 'ProductPermissionId'
LEFT JOIN TenantUsers t_New
ON a.NewValue = CAST(t_New.TenantUserId AS VARCHAR)
AND a.Name = 'TenantUsers'
GROUP BY a.AuditLogId, a.Operation, a.[Table], a.RowId;
It is a pretty dirty solution, I would be inclined to store this data in the format you want to select it in i.e. instead of 50/51, store read/write directly in the NewValue column.
Alternatively, if you did want the second format with one row per transaction then I'd be inclined to store it in this way. It could be worth reading about the Entity-Attribute-Value Antipattern.
You could PIVOT the data and LEFT JOIN to the lookup tables. Then UNPIVOT if necessary.
If you simply need to display words instead of numbers, create a lookup table that maps between the number and the word you want to display:
create table NewValueText (
NewValue integer,
Description char(20)
);
insert into NewValueText values
(50, 'Read'),
(51, 'Write'); --etc.
--MyTable is your table that contains the NewValue column.
select Description from MyTable
inner join NewValueText on MyTable.NewValue = NewValueText.NewValue;
You could try joining in your tables where your values are specific. You'll need to create a look-up table for other values other than Really it's a dirty solution because it means hard coding your NewValue fields into the SQL:
...
FROM
audit LEFT JOIN
mysubscription ON audit.rowid = mysubscription.tenantuserid
AND audit.newvalue = 1
LEFT JOIN
lookup_list ON audit.rowid = lookup_list.lookup_id
AND audit.newvalue <> 1
...
That may work for you.

SQL select from table - only including data in specific filegroup

I followed this article:
http://www.mssqltips.com/sqlservertip/1796/creating-a-table-with-horizontal-partitioning-in-sql-server/
Which in essence does the following:
Creates a database with three filegroups, call them A, B, and C
Creates a partition scheme, mapping to the three filegroups
Creates table - SalesArchival, using the partition scheme
Inserts a few rows into the table, split over the filegroups.
I'd like to perform a query like this (excuse my pseudo-code)
select * from SalesArchival
where data in filegroup('A')
Is there a way of doing this, or if not, how do I go about it.
What I want to accomplish is to have a batch run every day that moves data older than 90 days to a different file group, and perform my front end queries only on the 'current' file group.
To get at a specific filegroup, you'll always want to utilize partition elimination in your predicates to ensure minimal records get read. This is very important if you are to get any benefits from partitioning.
For archival, I think you're looking for how to split and merge ranges. You should always keep the first and last partitions empty, but this should give you an idea of how to use partitions for archiving. FYI, moving data from 1 filegroup to another is very resource intensive. Additionally, results will be slightly different if you use a range right pf. Since you are doing partitioning, hopefully you've read up on best practices.
DO NOT RUN ON PRODUCTION. THIS IS ONLY AN EXAMPLE TO LEARN FROM.
This example assumes you have 4 filegroups (FG1,FG2,FG3, & [PRIMARY]) defined.
IF EXISTS(SELECT NULL FROM sys.tables WHERE name = 'PartitionTest')
DROP TABLE PartitionTest;
IF EXISTS(SELECT NULL FROM sys.partition_schemes WHERE name = 'PS')
DROP PARTITION SCHEME PS;
IF EXISTS(SELECT NULL FROM sys.partition_functions WHERE name = 'PF')
DROP PARTITION FUNCTION PF;
CREATE PARTITION FUNCTION PF (datetime) AS RANGE LEFT FOR VALUES ('2012-02-05', '2012-05-10','2013-01-01');
CREATE PARTITION SCHEME PS AS PARTITION PF TO (FG1,FG2,FG3,[PRIMARY]);
CREATE TABLE PartitionTest( Id int identity(1,1), DT datetime) ON PS(DT);
INSERT PartitionTest (DT)
SELECT '2012-02-05' --FG1
UNION ALL
SELECT '2012-02-06' --FG2(This is the one 90 days old to archive into FG1)
UNION ALL
SELECT '2012-02-07' --FG2
UNION ALL
SELECT '2012-05-05' --FG2 (This represents a record entered recently)
Check the filegroup associated with each record:
SELECT O.name TableName, fg.name FileGroup, ps.name PartitionScheme,pf.name PartitionFunction, ISNULL(prv.value,'Undefined') RangeValue,p.rows
FROM sys.objects O
INNER JOIN sys.partitions p on P.object_id = O.object_id
INNER JOIN sys.indexes i on p.object_id = i.object_id and p.index_id = i.index_id
INNER JOIN sys.data_spaces ds on i.data_space_id = ds.data_space_id
INNER JOIN sys.partition_schemes ps on ds.data_space_id = ps.data_space_id
INNER JOIN sys.partition_functions pf on ps.function_id = pf.function_id
LEFT OUTER JOIN sys.partition_range_values prv on prv.function_id = ps.function_id and p.partition_number = prv.boundary_id
INNER JOIN sys.allocation_units au on p.hobt_id = au.container_id
INNER JOIN sys.filegroups fg ON au.data_space_id = fg.data_space_id
WHERE o.name = 'PartitionTest' AND i.type IN (0,1) --Remove nonclustereds. 0 for heap, 1 for BTree
ORDER BY O.name, fg.name, prv.value
This proves that 2012-02-05 is in FG1 while the rest are in FG2.
In order to archive, your' first instinct is to move the data. When partitioning though, you actually have to slide the partition function range value.
Now let's move 2012-02-06 (90 days or older in your case) into FG1:--Move 2012-02-06 from FG2 to FG1
ALTER PARTITION SCHEME PS NEXT USED FG1;
ALTER PARTITION FUNCTION PF() SPLIT RANGE ('2012-02-06');
Rerun the filegroup query to verify that 2012-02-06 got moved into FG1.
$PARTITION (Transact-SQL) should have what you want to do.
Run the following to know the size of your partitions and ID:
USE AdventureWorks2012;
GO
SELECT $PARTITION.TransactionRangePF1(TransactionDate) AS Partition,
COUNT(*) AS [COUNT] FROM Production.TransactionHistory
GROUP BY $PARTITION.TransactionRangePF1(TransactionDate)
ORDER BY Partition ;
GO
and the following should give you data from given partition id:
SELECT * FROM Production.TransactionHistory
WHERE $PARTITION.TransactionRangePF1(TransactionDate) = 5 ;
No. You need to use the exact condition that you use in your partition function. Which is probably like
where keyCol between 3 and 7

Error adding an index to a view

I have created a view using the following code
CREATE VIEW dbo.two_weeks_performance WITH SCHEMABINDING
AS
SELECT dbo.day_dim.date_time AS Date,
dbo.order_dim.quantity AS Target_Acheived
FROM dbo.day_dim
JOIN dbo.order_fact ON dbo.day_dim.day_id = dbo.order_fact.day_id
JOIN dbo.branch_dim ON dbo.order_fact.branch_id = dbo.branch_dim.branch_id
JOIN dbo.order_dim ON dbo.order_fact.order_id = dbo.order_dim.order_id
GROUP BY dbo.order_dim.quantity, dbo.day_dim.date_time`
Now when I use:
CREATE UNIQUE CLUSTERED INDEX two_weeks_performance_I ON two_weeks_performance (Date)
I am getting an error:
Cannot create index because its select list does not use the correct usage of COUNT_BIG(). Consider adding COUNT_BIG(*) to the select.
Please help me solve this issue.
The error tells you exactly what you have to do - add COUNT_BIG(*) to your select list.
From Creating Indexed Views:
If GROUP BY is specified, the view
select list must contain a
COUNT_BIG(*) expression, and the view
definition cannot specify HAVING,
ROLLUP, CUBE, or GROUPING SETS.
CREATE VIEW dbo.two_weeks_performance WITH SCHEMABINDING
AS
SELECT dbo.day_dim.date_time AS Date,
dbo.order_dim.quantity AS Target_Acheived,
COUNT_BIG(*) as Cnt
FROM dbo.day_dim
JOIN dbo.order_fact ON dbo.day_dim.day_id = dbo.order_fact.day_id
JOIN dbo.branch_dim ON dbo.order_fact.branch_id = dbo.branch_dim.branch_id
JOIN dbo.order_dim ON dbo.order_fact.order_id = dbo.order_dim.order_id
GROUP BY dbo.order_dim.quantity, dbo.day_dim.date_time
GO
CREATE UNIQUE CLUSTERED INDEX two_weeks_performance_I ON two_weeks_performance (Date)