I have a statement I use to add or update a row of data. I'm observing the SQL updating a row when no apparent changes are present. I'm trying to work out why it's insisting on updating the row when the SQL should prevent it.
I'm updating about 1,000 rows this way (one after another) and only 2 rows seem to update when the mask I've created suggests they shouldn't.
Here's the relevant SQL snippet:
...
ELSE IF NOT EXISTS (
SELECT PERMIT_STATUS, PERMIT_LOCATION, PERMIT_COMMODITY, PERMIT_TYPE_CODE, PERMIT_TYPE_DESCRIPTION, PERMIT_ALLOCATION_METHOD, PERMIT_OPERATION_NAME, OWNERS, SHARE_PERCENTAGE, OPERATOR, MINERALS, PERMIT_DURATION_YEARS, PERMIT_DURATION_MONTHS, PERMIT_AREA, PERMIT_AREA_UNIT, PERMIT_OFFSHORE_ONSHORE, PERMIT_STATUS_DATE, PERMIT_MINERAL_GROUP, PERMIT_MP, SUBSEQUENT_TO_PERMIT, PERMIT_COMMENCEMENT_DATE, PERMIT_EXPIRY_DATE, PERMIT_GRANT_DATE, PERMIT_NONEXCLUSIVE_YN, ShapeGeoJson, Removed
FROM MyTable
WHERE PERMIT_NUMBER = #PERMIT_NUMBER
INTERSECT
SELECT #PERMIT_STATUS, #PERMIT_LOCATION, #PERMIT_COMMODITY, #PERMIT_TYPE_CODE, #PERMIT_TYPE_DESCRIPTION, #PERMIT_ALLOCATION_METHOD, #PERMIT_OPERATION_NAME, #OWNERS, #SHARE_PERCENTAGE, #OPERATOR, #MINERALS, #PERMIT_DURATION_YEARS, #PERMIT_DURATION_MONTHS, #PERMIT_AREA, #PERMIT_AREA_UNIT, #PERMIT_OFFSHORE_ONSHORE, #PERMIT_STATUS_DATE, #PERMIT_MINERAL_GROUP, #PERMIT_MP, #SUBSEQUENT_TO_PERMIT, #PERMIT_COMMENCEMENT_DATE, #PERMIT_EXPIRY_DATE, #PERMIT_GRANT_DATE, #PERMIT_NONEXCLUSIVE_YN, #ShapeGeoJson, #Removed
)
BEGIN
UPDATE MyTable
SET PERMIT_STATUS = #PERMIT_STATUS
,PERMIT_LOCATION = #PERMIT_LOCATION
,PERMIT_COMMODITY = #PERMIT_COMMODITY
,PERMIT_TYPE_CODE = #PERMIT_TYPE_CODE
,PERMIT_TYPE_DESCRIPTION = #PERMIT_TYPE_DESCRIPTION
,PERMIT_ALLOCATION_METHOD = #PERMIT_ALLOCATION_METHOD
,PERMIT_OPERATION_NAME = #PERMIT_OPERATION_NAME
,OWNERS = #OWNERS
,SHARE_PERCENTAGE = #SHARE_PERCENTAGE
,OPERATOR = #OPERATOR
,MINERALS = #MINERALS
,PERMIT_DURATION_YEARS = #PERMIT_DURATION_YEARS
,PERMIT_DURATION_MONTHS = #PERMIT_DURATION_MONTHS
,PERMIT_AREA = #PERMIT_AREA
,PERMIT_AREA_UNIT = #PERMIT_AREA_UNIT
,PERMIT_OFFSHORE_ONSHORE = #PERMIT_OFFSHORE_ONSHORE
,PERMIT_STATUS_DATE = #PERMIT_STATUS_DATE
,PERMIT_MINERAL_GROUP = #PERMIT_MINERAL_GROUP
,PERMIT_MP = #PERMIT_MP
,SUBSEQUENT_TO_PERMIT = #SUBSEQUENT_TO_PERMIT
,PERMIT_COMMENCEMENT_DATE = #PERMIT_COMMENCEMENT_DATE
,PERMIT_EXPIRY_DATE = #PERMIT_EXPIRY_DATE
,PERMIT_GRANT_DATE = #PERMIT_GRANT_DATE
,PERMIT_NONEXCLUSIVE_YN = #PERMIT_NONEXCLUSIVE_YN
,ShapeGeoJson = #ShapeGeoJson
,UpdatedUtc = GETUTCDATE()
,Removed = #Removed
,UpdatedMask = (
IIF ( PERMIT_STATUS != #PERMIT_STATUS, 4096, 0 ) +
IIF ( PERMIT_LOCATION != #PERMIT_LOCATION, 256, 0 ) +
IIF ( PERMIT_COMMODITY != #PERMIT_COMMODITY, 512, 0 ) +
IIF ( PERMIT_TYPE_CODE != #PERMIT_TYPE_CODE, 64, 0 ) +
IIF ( PERMIT_TYPE_DESCRIPTION != #PERMIT_TYPE_DESCRIPTION, 128, 0 ) +
IIF ( PERMIT_ALLOCATION_METHOD != #PERMIT_ALLOCATION_METHOD, 1024, 0 ) +
IIF ( PERMIT_OPERATION_NAME != #PERMIT_OPERATION_NAME, 2048, 0 ) +
IIF ( OWNERS != #OWNERS, 1048576, 0 ) +
IIF ( SHARE_PERCENTAGE != #SHARE_PERCENTAGE, 2097152, 0 ) +
IIF ( OPERATOR != #OPERATOR, 4194304, 0 ) +
IIF ( MINERALS != #MINERALS, 8388608, 0 ) +
IIF ( PERMIT_DURATION_YEARS != #PERMIT_DURATION_YEARS, 32768, 0 ) +
IIF ( PERMIT_DURATION_MONTHS != #PERMIT_DURATION_MONTHS, 65536, 0 ) +
IIF ( PERMIT_AREA != #PERMIT_AREA, 67108864, 0 ) +
IIF ( PERMIT_AREA_UNIT != #PERMIT_AREA_UNIT, 134217728, 0 ) +
IIF ( PERMIT_OFFSHORE_ONSHORE != #PERMIT_OFFSHORE_ONSHORE, 262144, 0 ) +
IIF ( PERMIT_STATUS_DATE != #PERMIT_STATUS_DATE, 8192, 0 ) +
IIF ( PERMIT_MINERAL_GROUP != #PERMIT_MINERAL_GROUP, 131072, 0 ) +
IIF ( PERMIT_MP != #PERMIT_MP, 524288, 0 ) +
IIF ( SUBSEQUENT_TO_PERMIT != #SUBSEQUENT_TO_PERMIT, 33554432, 0 ) +
IIF ( PERMIT_COMMENCEMENT_DATE != #PERMIT_COMMENCEMENT_DATE, 16, 0 ) +
IIF ( PERMIT_EXPIRY_DATE != #PERMIT_EXPIRY_DATE, 32, 0 ) +
IIF ( PERMIT_GRANT_DATE != #PERMIT_GRANT_DATE, 8, 0 ) +
IIF ( PERMIT_NONEXCLUSIVE_YN != #PERMIT_NONEXCLUSIVE_YN, 16384, 0 ) +
IIF ( ShapeGeoJson != #ShapeGeoJson, 137438953472, 0 ) +
IIF ( Removed != #Removed, 274877906944, 0 )
)
WHERE PERMIT_NUMBER = #PERMIT_NUMBER;
END
...
I'm using the INTERSECT method at the top to detect a change in any of the columns of row data. This is because it's easier to compare NULL values in columns.
And you can see the UpdatedMask is being updated with a value to indicate which columns have been updated. When the problem occurs, this is being set to 0 (and when working as expected, I see a correct UpdatedMask).
I can't see any typos or obvious logic errors. So I wonder if it might be due to load on SQL Azure somehow from updating so many records one after another with a sizable ShapeGeoJson column?
Here's the table definition in case it's useful:
CREATE TABLE [dbo].[MyTable](
[PERMIT_NUMBER] [varchar](30) NOT NULL,
[PERMIT_STATUS] [varchar](30) NULL,
[PERMIT_LOCATION] [varchar](4000) NULL,
[PERMIT_COMMODITY] [varchar](8) NULL,
[PERMIT_TYPE_CODE] [varchar](4000) NULL,
[PERMIT_TYPE_DESCRIPTION] [varchar](255) NULL,
[PERMIT_ALLOCATION_METHOD] [varchar](4000) NULL,
[PERMIT_OPERATION_NAME] [varchar](4000) NULL,
[OWNERS] [varchar](4000) NULL,
[SHARE_PERCENTAGE] [varchar](4000) NULL,
[OPERATOR] [varchar](220) NULL,
[MINERALS] [varchar](4000) NULL,
[PERMIT_DURATION_YEARS] [varchar](4000) NULL,
[PERMIT_DURATION_MONTHS] [varchar](4000) NULL,
[PERMIT_AREA] [varchar](4000) NULL,
[PERMIT_AREA_UNIT] [varchar](4) NULL,
[PERMIT_OFFSHORE_ONSHORE] [varchar](4000) NULL,
[PERMIT_STATUS_DATE] [date] NULL,
[PERMIT_MINERAL_GROUP] [varchar](4000) NULL,
[PERMIT_MP] [varchar](4000) NULL,
[SUBSEQUENT_TO_PERMIT] [varchar](4000) NULL,
[PERMIT_COMMENCEMENT_DATE] [date] NULL,
[PERMIT_EXPIRY_DATE] [date] NULL,
[PERMIT_GRANT_DATE] [date] NULL,
[PERMIT_NONEXCLUSIVE_YN] [char](1) NULL,
[ShapeGeoJson] [varchar](max) NOT NULL,
[CreatedUtc] [datetime] NOT NULL,
[UpdatedUtc] [datetime] NOT NULL,
[Removed] [bit] NOT NULL,
[UpdatedMask] [bigint] NULL,
CONSTRAINT [PK_MyTable_1] PRIMARY KEY CLUSTERED
(
[PERMIT_NUMBER] 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]
Any ideas?
The update process runs nightly so I'll keep my eye on the rows that seem to break my rules to see if there's a pattern there. I've seen this a few times now, but neglected to preserve the data for investigating previously.
In your code you have code duplication. You have the INTERSECT/EXISTS part where you check whether something has been updated or not and then you have the code calculating UpdatedMask where the logic gets repeated.
I would suggest to first remove the duplication of logic. Then it will be much easier to find the problem and fix it on one place.
Below code shows you an alternative way of updating the records and only relying on the code to calculate UpdatedMask.
-- We will update MyTable from data in the NewData alias that contains
-- all of the new values and UpdatedMask
UPDATE U
SET U.PERMIT_STATUS = NewData.PERMIT_STATUS
,U.PERMIT_LOCATION = NewData.PERMIT_LOCATION
.... all other columns ....
,U.UpdatedMask = NewData.UpdatedMask
FROM MyTable U
INNER JOIN (
SELECT T.PERMIT_STATUS = #PERMIT_STATUS
,T.PERMIT_LOCATION = #PERMIT_LOCATION
... all other columns ...
,UpdatedMask = (
IIF ( T.PERMIT_STATUS != #PERMIT_STATUS, 4096, 0 ) +
IIF ( T.PERMIT_LOCATION != #PERMIT_LOCATION, 256, 0 ) +
... all other columns ....
)
FROM MyTable T
WHERE T.PERMIT_NUMBER = #PERMIT_NUMBER
-- In the join clause we only take rows that are actually updated
) AS NewData ON NewData.UpdatedMask<>0
WHERE U.PERMIT_NUMBER = #PERMIT_NUMBER;
I managed to drill down to the issue. The logic for calculating the UpdatedMask value does not take into consideration NULL values when comparing.
So the update is valid. It's the calculated UpdatedMask that's invalid.
So now I've uglified my SQL even further:
UpdatedMask = (
IIF ( ISNULL(PERMIT_STATUS, '') != ISNULL(#PERMIT_STATUS, ''), 4096, 0 ) +
IIF ( ISNULL(PERMIT_LOCATION, '') != ISNULL(#PERMIT_LOCATION, ''), 256, 0 ) +
...
)
Related
I have this complex query that i want to turn into a view.
This query comes from https://snippets.cacher.io/snippet/3e84b01b7d52b4ca7807 and i want to save it in a view or even as a table if possible.
`
/*##=============================================*/
/*## QUERY BODY */
/*##=============================================*/
/* #region QueryBody */
/* Testing variables !! Need to be commented for Production !! */
-- DECLARE #UserSIDs AS NVARCHAR(10) = 'Disabled';
-- DECLARE #CollectionID AS NVARCHAR(10) = 'SMS00001';
-- DECLARE #Locale AS INT = 2;
-- DECLARE #Categories AS NVARCHAR(250) = 'Tools';
-- DECLARE #Compliant AS INT = 0;
-- DECLARE #Targeted AS INT = 1;
-- DECLARE #Superseded AS INT = 0;
-- DECLARE #ArticleID AS NVARCHAR(10) = '';
-- DECLARE #ExcludeArticleIDs AS NVARCHAR(250) = '';
/* Variable declaration */
DECLARE #LCID AS INT = dbo.fn_LShortNameToLCID(#Locale);
DECLARE #HelperFunctionExists AS INT = 0;
/* Perform cleanup */
IF OBJECT_ID('tempdb..#MaintenanceInfo', 'U') IS NOT NULL
DROP TABLE #MaintenanceInfo;
/* Check for helper function */
IF OBJECT_ID('[dbo].[ufn_CM_GetNextMaintenanceWindow]') IS NOT NULL
SET #HelperFunctionExists = 1;
/* Initialize HealthState descriptor table */
DECLARE #HealthState TABLE (
BitMask INT
, StateName NVARCHAR(250)
)
/* Populate HealthState table */
INSERT INTO #HealthState (BitMask, StateName)
VALUES
('0', 'Healthy')
, ('1', 'Unmanaged')
, ('2', 'Inactive')
, ('4', 'Health Evaluation Failed')
, ('8', 'Pending Restart')
, ('16', 'Update Scan Failed')
, ('32', 'Update Scan Late')
, ('64', 'No Maintenance Window')
, ('128', 'Distant Maintenance Window')
, ('256', 'Expired Maintenance Window')
/* Initialize ClientState descriptor table */
DECLARE #ClientState TABLE (
BitMask INT
, StateName NVARCHAR(100)
)
/* Populate ClientState table */
INSERT INTO #ClientState (BitMask, StateName)
VALUES
('0', 'No Reboot')
, ('1', 'Configuration Manager')
, ('2', 'File Rename')
, ('4', 'Windows Update')
, ('8', 'Add or Remove Feature')
CREATE TABLE #MaintenanceInfo (
ResourceID INT
, NextServiceWindow DATETIME
)
/* Get maintenance data */
IF #HelperFunctionExists = 1
BEGIN
WITH Maintenance_CTE AS (
SELECT
CollectionMembers.ResourceID
, NextServiceWindow.Duration
, NextServiceWindow.NextServiceWindow
, RowNumber = DENSE_RANK() OVER (PARTITION BY ResourceID ORDER BY NextServiceWindow.NextServiceWindow)
, ServiceWindowType
, ServiceWindow.Enabled
FROM vSMS_ServiceWindow AS ServiceWindow
JOIN fn_rbac_FullCollectionMembership(#UserSIDs) AS CollectionMembers ON CollectionMembers.CollectionID = ServiceWindow.SiteID
JOIN fn_rbac_Collection(#UserSIDs) AS Collections ON Collections.CollectionID = CollectionMembers.CollectionID
AND Collections.CollectionType = 2 -- Device Collections
CROSS APPLY ufn_CM_GetNextMaintenanceWindow(ServiceWindow.Schedules, ServiceWindow.RecurrenceType) AS NextServiceWindow
WHERE NextServiceWindow.NextServiceWindow IS NOT NULL
AND ServiceWindowType <> 5 -- OSD Service
)
/* Populate MaintenanceInfo table and remove duplicates */
INSERT INTO #MaintenanceInfo(ResourceID, NextServiceWindow)
SELECT
ResourceID
, NextServiceWindow
FROM Maintenance_CTE
WHERE RowNumber = 1
END
/* Get update data */
;
WITH UpdateInfo_CTE
AS (
SELECT
ResourceID = Systems.ResourceID
, Missing = COUNT(*)
FROM fn_rbac_R_System(#UserSIDs) AS Systems
JOIN fn_rbac_UpdateComplianceStatus(#UserSIDs) AS ComplianceStatus ON ComplianceStatus.ResourceID = Systems.ResourceID
AND ComplianceStatus.Status = 2 -- Filter on 'Required' (0 = Unknown, 1 = NotRequired, 2 = Required, 3 = Installed)
JOIN fn_rbac_ClientCollectionMembers(#UserSIDs) AS CollectionMembers ON CollectionMembers.ResourceID = ComplianceStatus.ResourceID
JOIN fn_rbac_UpdateInfo(#LCID, #UserSIDs) AS UpdateCIs ON UpdateCIs.CI_ID = ComplianceStatus.CI_ID
AND UpdateCIs.IsSuperseded IN (#Superseded)
AND UpdateCIs.CIType_ID IN (1, 8) -- Filter on 1 Software Updates, 8 Software Update Bundle (v_CITypes)
AND UpdateCIs.ArticleID NOT IN ( -- Filter on ArticleID csv list
SELECT VALUE FROM STRING_SPLIT(#ExcludeArticleIDs, ',')
)
AND UpdateCIs.Title NOT LIKE ( -- Filter Preview updates
'[1-9][0-9][0-9][0-9]-[0-9][0-9]_Preview_of_%'
)
JOIN fn_rbac_CICategoryInfo_All(#LCID, #UserSIDs) AS CICategory ON CICategory.CI_ID = ComplianceStatus.CI_ID
AND CICategory.CategoryTypeName = 'UpdateClassification'
AND CICategory.CategoryInstanceName IN (#Categories) -- Filter on Selected Update Classification Categories
LEFT JOIN fn_rbac_CITargetedMachines(#UserSIDs) AS Targeted ON Targeted.ResourceID = ComplianceStatus.ResourceID
AND Targeted.CI_ID = ComplianceStatus.CI_ID
WHERE CollectionMembers.CollectionID = #CollectionID
AND IIF(Targeted.ResourceID IS NULL, 0, 1) IN (#Targeted) -- Filter on 'Targeted' or 'NotTargeted'
AND IIF(UpdateCIs.ArticleID = #ArticleID, 1, 0) = IIF(#ArticleID <> '', 1, 0)
GROUP BY
Systems.ResourceID
)
/* Get device info */
SELECT
Systems.ResourceID
/* Set Health states. You can find the coresponding values in the HealthState table above */
, HealthStates = (
IIF(CombinedResources.IsClient != 1, POWER(1, 1), 0)
+
IIF(
ClientSummary.ClientStateDescription = 'Inactive/Pass'
OR
ClientSummary.ClientStateDescription = 'Inactive/Fail'
OR
ClientSummary.ClientStateDescription = 'Inactive/Unknown'
, POWER(2, 1), 0)
+
IIF(
ClientSummary.ClientStateDescription = 'Active/Fail'
OR
ClientSummary.ClientStateDescription = 'Inactive/Fail'
, POWER(4, 1), 0
)
+
IIF(CombinedResources.ClientState != 0, POWER(8, 1), 0)
+
IIF(UpdateScan.LastErrorCode != 0, POWER(16, 1), 0)
+
IIF(UpdateScan.LastScanTime < (SELECT DATEADD(dd, -14, CURRENT_TIMESTAMP)), POWER(32, 1), 0)
+
IIF(ISNULL(NextServiceWindow, 0) = 0 AND #HelperFunctionExists = 1, POWER(64, 1), 0)
+
IIF(NextServiceWindow > (SELECT DATEADD(dd, 30, CURRENT_TIMESTAMP)), POWER(128, 1), 0)
+
IIF(NextServiceWindow < (CURRENT_TIMESTAMP), POWER(256, 1), 0)
)
, Missing = ISNULL(Missing, (IIF(CombinedResources.IsClient = 1, 0, NULL)))
, Device = (
IIF(
SystemNames.Resource_Names0 IS NOT NULL, UPPER(SystemNames.Resource_Names0)
, IIF(Systems.Full_Domain_Name0 IS NOT NULL, Systems.Name0 + '.' + Systems.Full_Domain_Name0, Systems.Name0)
)
)
, OperatingSystem = (
CASE
WHEN OperatingSystem.Caption0 != '' THEN
CONCAT(
REPLACE(OperatingSystem.Caption0, 'Microsoft ', ''), -- Remove 'Microsoft ' from OperatingSystem
REPLACE(OperatingSystem.CSDVersion0, 'Service Pack ', ' SP') -- Replace 'Service Pack ' with ' SP' in OperatingSystem
)
ELSE (
/* Workaround for systems not in GS_OPERATING_SYSTEM table */
CASE
WHEN CombinedResources.DeviceOS LIKE '%Workstation 6.1%' THEN 'Windows 7'
WHEN CombinedResources.DeviceOS LIKE '%Workstation 6.2%' THEN 'Windows 8'
WHEN CombinedResources.DeviceOS LIKE '%Workstation 6.3%' THEN 'Windows 8.1'
WHEN CombinedResources.DeviceOS LIKE '%Workstation 10.0%' THEN 'Windows 10'
WHEN CombinedResources.DeviceOS LIKE '%Server 6.0' THEN 'Windows Server 2008'
WHEN CombinedResources.DeviceOS LIKE '%Server 6.1' THEN 'Windows Server 2008R2'
WHEN CombinedResources.DeviceOS LIKE '%Server 6.2' THEN 'Windows Server 2012'
WHEN CombinedResources.DeviceOS LIKE '%Server 6.3' THEN 'Windows Server 2012 R2'
WHEN Systems.Operating_System_Name_And0 LIKE '%Server 10%' THEN (
CASE
WHEN CAST(REPLACE(Build01, '.', '') AS INTEGER) > 10017763 THEN 'Windows Server 2019'
ELSE 'Windows Server 2016'
END
)
ELSE Systems.Operating_System_Name_And0
END
)
END
)
, LastBootTime = (
CONVERT(NVARCHAR(16), OperatingSystem.LastBootUpTime0, 120)
)
, PendingRestart = (
CASE
WHEN CombinedResources.IsClient = 0
OR CombinedResources.ClientState = 0
THEN NULL
ELSE (
STUFF(
REPLACE(
(
SELECT '#!' + LTRIM(RTRIM(StateName)) AS [data()]
FROM #ClientState
WHERE BitMask & CombinedResources.ClientState <> 0
FOR XML PATH('')
),
' #!',', '
),
1, 2, ''
)
)
END
)
, ClientState = (
CASE CombinedResources.IsClient
WHEN 1 THEN ClientSummary.ClientStateDescription
ELSE 'Unmanaged'
END
)
, ClientVersion = CombinedResources.ClientVersion
, LastUpdateScan = (
CONVERT(NVARCHAR(16), UpdateScan.LastScanTime, 120)
)
, LastScanLocation = NULLIF(UpdateScan.LastScanPackageLocation, '')
, LastScanError = NULLIF(UpdateScan.LastErrorCode, 0)
, NextServiceWindow = IIF(CombinedResources.IsClient != 1, NULL, CONVERT(NVARCHAR(16), NextServiceWindow, 120))
FROM fn_rbac_R_System(#UserSIDs) AS Systems
JOIN fn_rbac_CombinedDeviceResources(#UserSIDs) AS CombinedResources ON CombinedResources.MachineID = Systems.ResourceID
LEFT JOIN fn_rbac_RA_System_ResourceNames(#UserSIDs) AS SystemNames ON SystemNames.ResourceID = Systems.ResourceID
LEFT JOIN fn_rbac_GS_OPERATING_SYSTEM(#UserSIDs) AS OperatingSystem ON OperatingSystem.ResourceID = Systems.ResourceID
LEFT JOIN fn_rbac_CH_ClientSummary(#UserSIDs) AS ClientSummary ON ClientSummary.ResourceID = Systems.ResourceID
LEFT JOIN fn_rbac_UpdateScanStatus(#UserSIDs) AS UpdateScan ON UpdateScan.ResourceID = Systems.ResourceID
LEFT JOIN #MaintenanceInfo AS Maintenance ON Maintenance.ResourceID = Systems.ResourceID
LEFT JOIN UpdateInfo_CTE AS UpdateInfo ON UpdateInfo.ResourceID = Systems.ResourceID
JOIN fn_rbac_FullCollectionMembership(#UserSIDs) AS CollectionMembers ON CollectionMembers.ResourceID = Systems.ResourceID
WHERE CollectionMembers.CollectionID = #CollectionID
AND (
CASE -- Compliant (0 = No, 1 = Yes, 2 = Unknown)
WHEN Missing = 0 OR (Missing IS NULL AND Systems.Client0 = 1) THEN 1 -- Yes
WHEN Missing > 0 AND Missing IS NOT NULL THEN 0 -- No
ELSE 2 -- Unknown
END
) IN (#Compliant)
/* Perform cleanup */
IF OBJECT_ID('tempdb..#MaintenanceInfo', 'U') IS NOT NULL
DROP TABLE #MaintenanceInfo;
/* #endregion */
/*##=============================================*/
/*## END QUERY BODY */
/*##=============================================*/
`
Is there an easy way to achieve this?
I have tried to look at the official Microsoft documentation but are still not able to convert the query to a view. https://learn.microsoft.com/en-us/sql/t-sql/statements/create-view-transact-sql?view=sql-server-ver16
As I am new to SQL language I am not sure where to start.
So I agree with Larnu, that this probably doesn't make a ton of sense. But there are cases where one might want to be able to run multiple batches of queries / procedural code from an object that's as consumable as a view. I've done this once in a case where I needed to maximize my options for performance tuning without losing the consumability of the object. So for the sake of when it does make sense, this is something you could do:
Wrap your code in a stored procedure.
Use OPENQUERY() to call your procedure.
Wrap the OPENQUERY() call in a view.
Limitations with this methodology is it's rather static:
You can't pass parameters to your stored procedure
If you use temp tables in your stored procedure, then you need to use the WITH RESULT SETS clause to explicitly define the shape of your result set
The procedure can only return one result set
The SQL Server Engine puts a hard-coded cardinality estimate of 10,000 against OPENQUERY(). So in cases where your procedure returns a lot more rows (typically an order of magnitude or more) than 10,000, e.g. 1 million rows, then you may experience some performance issues with joining the wrapper view to other objects.
Example:
-- Step 1: Wrap the procedural code in a stored procedure
CREATE PROCEDURE dbo.RunSomeCode
AS
BEGIN
CREATE TABLE #Results (ID INT, SomeValue VARCHAR(100));
DECLARE #SomeVariable INT = 0;
WHILE (#SomeVariable < 5)
BEGIN
SET #SomeVariable = #SomeVariable + 1;
INSERT INTO #Results (ID, SomeValue)
SELECT ID, SomeValue
FROM SomeTable
WHERE ID = #SomeVariable
END
SELECT ID, SomeValue
FROM #Results;
END
GO
-- Step 2 & 3: Wrap an OPENQUERY() call to the procedure in a view.
CREATE VIEW dbo.SomeView
AS
SELECT ID, SomeValue
FROM OPENQUERY
(
'
LocalServerName,
EXEC dbo.RunSomeCode
WITH RESULT SETS
((
ID INT,
SomeValue VARCHAR(100)
))
'
);
Voila, you can now execute the procedure by SELECTing from the view:
SELECT ID, SomeValue
FROM dbo.SomeView;
I have the following SQL code below that is returning an error message stating that the AvgPYs column is not valid. This is also impacting my Auth column. Is there an issue with my formula? Any help is appreciated.
SELECT [tblCeiling].[Proj Code], [tblCeiling].[Act Code], [tblCeiling].[Cost Ctr], [tblCeiling].[Date], [tblCeiling].[Ref2], [tblCeiling].[Analyst], [tblCeiling].[Type], [tblCeiling].[B or O], [tblCeiling].[Jul], [tblCeiling].[Aug],
[tblCeiling].[Sep], [tblCeiling].[Oct], [tblCeiling].[Nov], [tblCeiling].[Dec], [tblCeiling].[Jan], [tblCeiling].[Feb], [tblCeiling].[Mar], [tblCeiling].[Apr], [tblCeiling].[May], [tblCeiling].[Jun], [tblCeiling].[Perm], [tblCeiling].[Temp], [tblCeiling].[LimitedTerm],
[tblCeiling].[LTDate], [tblCeiling].[Sal_Rate], [tblCeiling].[New], [Perm] + [Temp] + [LimitedTerm] AS Monthly, Format(([tblCeiling].[Jul] + [tblCeiling].[Aug] + [tblCeiling].[Sep] + [tblCeiling].[Oct] + [tblCeiling].[Nov] + [tblCeiling].[Dec] + [tblCeiling].[Jan] +
[tblCeiling].[Feb] + [tblCeiling].[Mar] + [tblCeiling].[Apr] + [tblCeiling].[May] + [tblCeiling].[Jun]) / 12, '0.0####') AS AvgPYs,
((([Sal_Rate] * [AvgPYs]) * 1000) / 1000) AS Auth, [Dollar Adj] + [Auth] AS Budget, [tblCeiling].[Import], [tblCeiling].[Dollar Adj], [tblCeiling].[OngoingOrOneTime], [tblCeiling].[OneTimeEndingDate]
FROM (SELECT DISTINCT *
FROM [tblactcode]) AS [tblactcode] RIGHT JOIN
(SELECT DISTINCT *
FROM [tblCeiling]) AS [tblCeiling] ON [tblactcode].[Act Code] = [tblCeiling].[Act Code]
WHERE ((([tblCeiling].[Jul]) = iif([jul] IS NULL, 0, [jul])) AND (([tblCeiling].[Aug]) = iif([aug] IS NULL, 0, [aug])) AND (([tblCeiling].[Sep]) = iif([sep] IS NULL, 0, [sep])) AND (([tblCeiling].[Oct]) = iif([oct] IS NULL, 0, [oct])) AND (([tblCeiling].[Nov]) = iif([nov] IS NULL,
0, [nov])) AND (([tblCeiling].[Dec]) = iif([dec] IS NULL, 0, [dec])) AND (([tblCeiling].[Jan]) = iif([jan] IS NULL, 0, [jan])) AND (([tblCeiling].[Feb]) = iif([feb] IS NULL, 0, [feb])) AND (([tblCeiling].[Mar]) = iif([mar] IS NULL, 0, [mar])) AND (([tblCeiling].[Apr])
= iif([apr] IS NULL, 0, [apr])) AND (([tblCeiling].[May]) = iif([may] IS NULL, 0, [may])) AND (([tblCeiling].[Jun]) = iif([jun] IS NULL, 0, [jun])) AND (([tblCeiling].[Import]) = 0))
ORDER BY [tblCeiling].[Proj Code], [tblCeiling].[Cost Ctr], [tblCeiling].[Date]
Here is the specific line I'm referring to:
Format(([tblCeiling].[Jul] + [tblCeiling].[Aug] + [tblCeiling].[Sep] + [tblCeiling].[Oct] + [tblCeiling].[Nov] + [tblCeiling].[Dec] + [tblCeiling].[Jan] +
[tblCeiling].[Feb] + [tblCeiling].[Mar] + [tblCeiling].[Apr] + [tblCeiling].[May] + [tblCeiling].[Jun]) / 12, '0.0####') AS AvgPYs,
The specific issue you have run into is attempting to use a calculation in the same scope you are calculating it - which isn't possible. You can only access a calculated value in an outer query.
Or a neat solution is to use CROSS APPLY which allows you to reuse a calculation as follows. In general this is done as:
select -- existing columns before AvgPYs
, AvgPYs
-- , some formula which depends on AvgPYs
from (
-- existing query
) C -- C is an acceptable short alias for Ceiling
cross apply (
values (formula)
) X (AvgPYs)
In your case I think the following is correct:
SELECT C.[Proj Code], C.[Act Code], C.[Cost Ctr], C.[Date], C.[Ref2], C.[Analyst], C.[Type], C.[B or O], C.[Jul], C.[Aug]
, C.[Sep], C.[Oct], C.[Nov], C.[Dec], C.[Jan], C.[Feb], C.[Mar], C.[Apr], C.[May], C.[Jun], C.[Perm], C.[Temp], C.[LimitedTerm]
, C.[LTDate], C.[Sal_Rate], C.[New], [Perm] + [Temp] + [LimitedTerm] AS Monthly
, X.AvgPYs
, Y.Auth
, [Dollar Adj] + Y.Auth AS Budget, C.[Import], C.[Dollar Adj], C.[OngoingOrOneTime], C.[OneTimeEndingDate]
FROM (
SELECT DISTINCT *
FROM [tblactcode]
) AS AC
RIGHT JOIN (
SELECT DISTINCT *
FROM [tblCeiling]
) AS C ON AC.[Act Code] = C.[Act Code]
CROSS APPLY (
VALUES (Format((C.[Jul] + C.[Aug] + C.[Sep] + C.[Oct] + C.[Nov] + C.[Dec] + C.[Jan] +
C.[Feb] + C.[Mar] + C.[Apr] + C.[May] + C.[Jun]) / 12, '0.0####'))
) AS X (AvgPYs)
CROSS APPLY (
VALUES (((([Sal_Rate] * X.AvgPYs) * 1000) / 1000))
) Y (Auth)
WHERE (((C.[Jul]) = iif([jul] IS NULL, 0, [jul])) AND ((C.[Aug]) = iif([aug] IS NULL, 0, [aug])) AND ((C.[Sep]) = iif([sep] IS NULL, 0, [sep])) AND ((C.[Oct]) = iif([oct] IS NULL, 0, [oct])) AND ((C.[Nov]) = iif([nov] IS NULL,
0, [nov])) AND ((C.[Dec]) = iif([dec] IS NULL, 0, [dec])) AND ((C.[Jan]) = iif([jan] IS NULL, 0, [jan])) AND ((C.[Feb]) = iif([feb] IS NULL, 0, [feb])) AND ((C.[Mar]) = iif([mar] IS NULL, 0, [mar])) AND ((C.[Apr])
= iif([apr] IS NULL, 0, [apr])) AND ((C.[May]) = iif([may] IS NULL, 0, [may])) AND ((C.[Jun]) = iif([jun] IS NULL, 0, [jun])) AND ((C.[Import]) = 0)
)
ORDER BY C.[Proj Code], C.[Cost Ctr], C.[Date];
Note: A key purpose of a table alias is to have a short reference to the table. See how much easier it is to read with shorter aliases.
I can't work out why my value is being ignored.
In my stored procedure it has these two AND statements
AND (#Current IS NULL OR (o.[OrderStatusId] <> 40) AND (o.[OrderStatusId] <> 30) AND ( NOT ((o.[ShippingStatusId] IN (30, 40)) AND (o.[PaymentStatusId] IN (30, 35, 40)))))
AND (#Printed IS NULL OR o.[Printed] = #Printed)
The #Printed value is a bit same as #Current, and is passed through to the stored procedure just the same as #Current
But the printed value doesn't pull results, it's like it's doing nothing although I know the results should vary.
I'm converting a linq query to the stored procedure, so I know the results should differ.
Here is the part of the linq query which is used.
if (current.HasValue && current.Value)
query = query.Where(o => o.OrderStatusId != (int)OrderStatus.Cancelled && o.OrderStatusId != (int)OrderStatus.Complete && !((o.ShippingStatusId == (int)ShippingStatus.Shipped || o.ShippingStatusId == (int)ShippingStatus.Delivered) && o.Printed && (o.PaymentStatusId == (int)PaymentStatus.Paid || o.PaymentStatusId == (int)PaymentStatus.PartiallyRefunded || o.PaymentStatusId == (int)PaymentStatus.Refunded)));
if (printed.HasValue)
query = query.Where(o => printed.Value == o.Printed);
Any ideas
UPDATE:
Looking at the query from linq in vs debugger I can see the following query, but I' no db expert so I'm a little lost looking at this one.
{SELECT
[Project2].[Id] AS [Id],
[Project2].[OrderGuid] AS [OrderGuid],
[Project2].[StoreId] AS [StoreId],
[Project2].[CustomerId] AS [CustomerId],
[Project2].[BillingAddressId] AS [BillingAddressId],
[Project2].[ShippingAddressId] AS [ShippingAddressId],
[Project2].[PickUpInStore] AS [PickUpInStore],
[Project2].[OrderStatusId] AS [OrderStatusId],
[Project2].[ShippingStatusId] AS [ShippingStatusId],
[Project2].[PaymentStatusId] AS [PaymentStatusId],
[Project2].[PaymentMethodSystemName] AS [PaymentMethodSystemName],
[Project2].[CustomerCurrencyCode] AS [CustomerCurrencyCode],
[Project2].[CurrencyRate] AS [CurrencyRate],
[Project2].[CustomerTaxDisplayTypeId] AS [CustomerTaxDisplayTypeId],
[Project2].[VatNumber] AS [VatNumber],
[Project2].[OrderSubtotalInclTax] AS [OrderSubtotalInclTax],
[Project2].[OrderSubtotalExclTax] AS [OrderSubtotalExclTax],
[Project2].[OrderSubTotalDiscountInclTax] AS [OrderSubTotalDiscountInclTax],
[Project2].[OrderSubTotalDiscountExclTax] AS [OrderSubTotalDiscountExclTax],
[Project2].[OrderShippingInclTax] AS [OrderShippingInclTax],
[Project2].[OrderShippingExclTax] AS [OrderShippingExclTax],
[Project2].[PaymentMethodAdditionalFeeInclTax] AS [PaymentMethodAdditionalFeeInclTax],
[Project2].[PaymentMethodAdditionalFeeExclTax] AS [PaymentMethodAdditionalFeeExclTax],
[Project2].[TaxRates] AS [TaxRates],
[Project2].[OrderTax] AS [OrderTax],
[Project2].[OrderDiscount] AS [OrderDiscount],
[Project2].[OrderTotal] AS [OrderTotal],
[Project2].[RefundedAmount] AS [RefundedAmount],
[Project2].[RewardPointsWereAdded] AS [RewardPointsWereAdded],
[Project2].[CheckoutAttributeDescription] AS [CheckoutAttributeDescription],
[Project2].[CheckoutAttributesXml] AS [CheckoutAttributesXml],
[Project2].[CustomerLanguageId] AS [CustomerLanguageId],
[Project2].[AffiliateId] AS [AffiliateId],
[Project2].[CustomerIp] AS [CustomerIp],
[Project2].[AllowStoringCreditCardNumber] AS [AllowStoringCreditCardNumber],
[Project2].[CardType] AS [CardType],
[Project2].[CardName] AS [CardName],
[Project2].[CardNumber] AS [CardNumber],
[Project2].[MaskedCreditCardNumber] AS [MaskedCreditCardNumber],
[Project2].[CardCvv2] AS [CardCvv2],
[Project2].[CardExpirationMonth] AS [CardExpirationMonth],
[Project2].[CardExpirationYear] AS [CardExpirationYear],
[Project2].[AuthorizationTransactionId] AS [AuthorizationTransactionId],
[Project2].[AuthorizationTransactionCode] AS [AuthorizationTransactionCode],
[Project2].[AuthorizationTransactionResult] AS [AuthorizationTransactionResult],
[Project2].[CaptureTransactionId] AS [CaptureTransactionId],
[Project2].[CaptureTransactionResult] AS [CaptureTransactionResult],
[Project2].[SubscriptionTransactionId] AS [SubscriptionTransactionId],
[Project2].[PaidDateUtc] AS [PaidDateUtc],
[Project2].[ShippingMethod] AS [ShippingMethod],
[Project2].[Printed] AS [Printed],
[Project2].[PrintedOnUtc] AS [PrintedOnUtc],
[Project2].[IsSupport] AS [IsSupport],
[Project2].[EditedStatusId] AS [EditedStatusId],
[Project2].[Deleted] AS [Deleted],
[Project2].[Id1] AS [Id1]
FROM ( SELECT
CASE WHEN (([Extent1].[Printed] <> 1) AND (N'Express Delivery' = [Extent1].[ShippingMethod])) THEN N'1' ELSE N'0' END AS [C1],
CASE WHEN ([Extent1].[Printed] <> 1) THEN CASE WHEN (([Extent1].[ShippingMethod] IN (N'Fast Delivery',N'My Point Pickup')) AND ( NOT EXISTS (SELECT
1 AS [C1]
FROM [dbo].[GenericAttribute] AS [Extent3]
WHERE (N'WarehouseOverride' = [Extent3].[Key]) AND (N'Order' = [Extent3].[KeyGroup]) AND ([Extent3].[EntityId] = [Extent1].[Id])
))) THEN N'0' ELSE N'1' END ELSE N'0' END AS [C2],
[Extent1].[Id] AS [Id],
[Extent1].[OrderGuid] AS [OrderGuid],
[Extent1].[StoreId] AS [StoreId],
[Extent1].[CustomerId] AS [CustomerId],
[Extent1].[BillingAddressId] AS [BillingAddressId],
[Extent1].[ShippingAddressId] AS [ShippingAddressId],
[Extent1].[PickUpInStore] AS [PickUpInStore],
[Extent1].[OrderStatusId] AS [OrderStatusId],
[Extent1].[ShippingStatusId] AS [ShippingStatusId],
[Extent1].[PaymentStatusId] AS [PaymentStatusId],
[Extent1].[PaymentMethodSystemName] AS [PaymentMethodSystemName],
[Extent1].[CustomerCurrencyCode] AS [CustomerCurrencyCode],
[Extent1].[CurrencyRate] AS [CurrencyRate],
[Extent1].[CustomerTaxDisplayTypeId] AS [CustomerTaxDisplayTypeId],
[Extent1].[VatNumber] AS [VatNumber],
[Extent1].[OrderSubtotalInclTax] AS [OrderSubtotalInclTax],
[Extent1].[OrderSubtotalExclTax] AS [OrderSubtotalExclTax],
[Extent1].[OrderSubTotalDiscountInclTax] AS [OrderSubTotalDiscountInclTax],
[Extent1].[OrderSubTotalDiscountExclTax] AS [OrderSubTotalDiscountExclTax],
[Extent1].[OrderShippingInclTax] AS [OrderShippingInclTax],
[Extent1].[OrderShippingExclTax] AS [OrderShippingExclTax],
[Extent1].[PaymentMethodAdditionalFeeInclTax] AS [PaymentMethodAdditionalFeeInclTax],
[Extent1].[PaymentMethodAdditionalFeeExclTax] AS [PaymentMethodAdditionalFeeExclTax],
[Extent1].[TaxRates] AS [TaxRates],
[Extent1].[OrderTax] AS [OrderTax],
[Extent1].[OrderDiscount] AS [OrderDiscount],
[Extent1].[OrderTotal] AS [OrderTotal],
[Extent1].[RefundedAmount] AS [RefundedAmount],
[Extent1].[RewardPointsWereAdded] AS [RewardPointsWereAdded],
[Extent1].[CheckoutAttributeDescription] AS [CheckoutAttributeDescription],
[Extent1].[CheckoutAttributesXml] AS [CheckoutAttributesXml],
[Extent1].[CustomerLanguageId] AS [CustomerLanguageId],
[Extent1].[AffiliateId] AS [AffiliateId],
[Extent1].[CustomerIp] AS [CustomerIp],
[Extent1].[AllowStoringCreditCardNumber] AS [AllowStoringCreditCardNumber],
[Extent1].[CardType] AS [CardType],
[Extent1].[CardName] AS [CardName],
[Extent1].[CardNumber] AS [CardNumber],
[Extent1].[MaskedCreditCardNumber] AS [MaskedCreditCardNumber],
[Extent1].[CardCvv2] AS [CardCvv2],
[Extent1].[CardExpirationMonth] AS [CardExpirationMonth],
[Extent1].[CardExpirationYear] AS [CardExpirationYear],
[Extent1].[AuthorizationTransactionId] AS [AuthorizationTransactionId],
[Extent1].[AuthorizationTransactionCode] AS [AuthorizationTransactionCode],
[Extent1].[AuthorizationTransactionResult] AS [AuthorizationTransactionResult],
[Extent1].[CaptureTransactionId] AS [CaptureTransactionId],
[Extent1].[CaptureTransactionResult] AS [CaptureTransactionResult],
[Extent1].[SubscriptionTransactionId] AS [SubscriptionTransactionId],
[Extent1].[PaidDateUtc] AS [PaidDateUtc],
[Extent1].[ShippingMethod] AS [ShippingMethod],
[Extent1].[Printed] AS [Printed],
[Extent1].[PrintedOnUtc] AS [PrintedOnUtc],
[Extent1].[IsSupport] AS [IsSupport],
[Extent1].[EditedStatusId] AS [EditedStatusId],
[Extent1].[Deleted] AS [Deleted],
[Extent2].[Id] AS [Id1]
FROM [dbo].[Order] AS [Extent1]
LEFT OUTER JOIN [dbo].[RewardPointsHistory] AS [Extent2] ON ([Extent2].[UsedWithOrder_Id] IS NOT NULL) AND ([Extent1].[Id] = [Extent2].[UsedWithOrder_Id])
WHERE ([Extent1].[Deleted] <> 1) AND (40 <> [Extent1].[OrderStatusId]) AND (30 <> [Extent1].[OrderStatusId]) AND ( NOT (([Extent1].[ShippingStatusId] IN (30,40)) AND ([Extent1].[Printed] = 1) AND ([Extent1].[PaymentStatusId] IN (30,35,40))))
) AS [Project2]
ORDER BY [Project2].[C1] DESC, [Project2].[Printed] ASC, [Project2].[C2] DESC, [Project2].[CreatedOnUtc] DESC}
Here is my stored procedure for you to look at:
USE [Test]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[OrderLoad]
-- Add the parameters for the stored procedure here
#OrderId int = null,
#CustomerId int = null,
#WarehouseId int = null,
#BillingCountryId int = null,
#PaymentMethodSystemName nvarchar(max) = null,
#OrderStatusId int = null,
#PaymentStatusId int = null,
#ShippingStatusId int = null,
#BillingEmail nvarchar(max) = null,
#BillingFirstName nvarchar(max) = null,
#BillingLastName nvarchar(max) = null,
#PurchasedPreviously bit = null,
#Printed bit = null,
#OrderNotes nvarchar(max) = null,
#ZendeskId nvarchar(max) = null,
#RexCode nvarchar(max) = null,
#Current bit = null,
#Challenge bit = null,
#ShippingMethod nvarchar(max) = null,
#EditedStatus int = null,
#OrderGuid nvarchar(max) = null,
#SupportReason int = null,
#CreatedFromUtc datetime = null,
#CreatedToUtc datetime = null,
#IsSupport bit = null,
#PageIndex int = 0,
#PageSize int = 2147483644,
#TotalRecords int = null OUTPUT
AS
BEGIN
DECLARE
#sql nvarchar(max)
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
create table #TempTotal (RowNum int identity(1,1), id int);
--select all
INSERT INTO #TempTotal ([id])
SELECT o.[Id]
FROM [Test].[dbo].[Order] o with (NOLOCK)
-- Another alternative to bypass left join, which works with pagination
LEFT join [Test].[dbo].[Address] a on a.Id = o.BillingAddressId and (
coalesce(#BillingEmail,'') <> ''
or coalesce(#BillingFirstName,'') <> ''
or coalesce(#BillingLastName,'') <> ''
or coalesce(#BillingCountryId,'') <> ''
)
WHERE
o.[Deleted] = 0
/*AND (#BillingEmail IS null OR a.[Email] LIKE '%' + #BillingEmail + '%')
AND (#BillingFirstName IS null OR a.[FirstName] LIKE '%' + #BillingFirstName + '%')
AND (#BillingLastName IS null OR a.[LastName] LIKE '%' + #BillingLastName + '%')
AND (#BillingCountryId IS null OR a.[CountryId] = #BillingCountryId)
*/
AND (#Printed IS NULL OR o.[Printed] = #Printed)
AND (#Current IS NULL OR (o.[OrderStatusId] <> 40) AND (o.[OrderStatusId] <> 30) AND ( NOT ((o.[ShippingStatusId] IN (30, 40)) /*AND (o.[Printed] = '1')*/ AND (o.[PaymentStatusId] IN (30, 35, 40)))))
AND (#IsSupport IS null OR o.[IsSupport] = #IsSupport)
--paging
DECLARE #PageLowerBound int
SET #PageLowerBound = #PageSize * #PageIndex
-- Return the paged records
select [Id] --note select * can produce unexpected results
,[OrderGuid]
,[RexOrderId]
,[StoreId]
,[CustomerId]
,[BillingAddressId]
,[ShippingAddressId]
,[PickUpInStore]
,[OrderStatusId]
,[ShippingStatusId]
,[PaymentStatusId]
,[PaymentMethodSystemName]
,[CustomerCurrencyCode]
,[CurrencyRate]
,[CustomerTaxDisplayTypeId]
,[VatNumber]
,[OrderSubtotalInclTax]
,[OrderSubtotalExclTax]
,[OrderSubTotalDiscountInclTax]
,[OrderSubTotalDiscountExclTax]
,[OrderShippingInclTax]
,[OrderShippingExclTax]
,[PaymentMethodAdditionalFeeInclTax]
,[PaymentMethodAdditionalFeeExclTax]
,[TaxRates]
,[OrderTax]
,[OrderDiscount]
,[OrderTotal]
,[RefundedAmount]
,[RewardPointsWereAdded]
,[CheckoutAttributeDescription]
,[CheckoutAttributesXml]
,[CustomerLanguageId]
,[AffiliateId]
,[CustomerIp]
,[AllowStoringCreditCardNumber]
,[CardType]
,[CardName]
,[CardNumber]
,[MaskedCreditCardNumber]
,[CardCvv2]
,[CardExpirationMonth]
,[CardExpirationYear]
,[AuthorizationTransactionId]
,[AuthorizationTransactionCode]
,[AuthorizationTransactionResult]
,[CaptureTransactionId]
,[CaptureTransactionResult]
,[SubscriptionTransactionId]
,[PaidDateUtc]
,[ShippingMethod]
,[Printed]
,[Deleted]
,[CreatedOnUtc]
,[EditedStatusId]
,[IsSupport]
,[PrintedOnUtc]
from [Test].[dbo].[Order] ord with (NOLOCK)
where ord.[Id] in (
select id
from #TempTotal tt
)
ORDER BY
(CASE #IsSupport
WHEN 0 THEN ord.[Printed]
END) ASC
, (CASE #IsSupport
WHEN 0 THEN ord.[CreatedOnUtc]
END) DESC
, (CASE #IsSupport
WHEN 1 THEN ord.[CreatedOnUtc]
END) DESC
--ORDER BY ord.[CreatedOnUtc] DESC
OFFSET #PageLowerBound ROWS FETCH NEXT #PageSize ROWS ONLY;
--total records
select #TotalRecords = count(*) from #TempTotal;
DROP TABLE #TempTotal
END
I have this schema:
Lists ( ListId, Name, DateCreated, ... )
ListItems( ListId, Text, Foo, Baz, Qux, ... )
I have an IQueryable<List> which represents another Linq query which returns some List entities.
I want to JOIN it with some aggregate data of ListItems, but this proving difficult as Linq is generating inefficient SQL - but I also want to make the query composable.
Here is something similar to the SQL I want Linq to generate:
SELECT
*
FROM
(
-- This part represents the IQueryable:
SELECT
ListId,
Name,
...
FROM
Lists
ORDER BY
Lists.DateCreated
OFFSET
0 ROWS FETCH NEXT 25 ROWS ONLY -- Linq's .Skip(0).Take(25)
) AS ListsResult
LEFT JOIN
(
-- This is the aggregate data query I want Linq to generate:
SELECT
ListId,
COUNT(1) AS [Count],
COUNT( CASE WHEN Foo = 'bar' THEN 1 ELSE NULL END ) AS CountFooBar,
COUNT( CASE WHEN Baz > 5 THEN 1 ELSE NULL END ) AS CountBaz5
FROM
ListItems
WHERE
Qux IS NOT NULL
GROUP BY
ListId
) AS ItemsStats ON ListResults.ListId = ItemsStats.ListId
This is the Linq I have - I prefer the Extension Method syntax:
IQueryable lists = GetLists( 0, 25 );
var stats = this.dbContext.ListItems
.Where( (ListItem li) => li.Qux != null )
.GroupBy( (ListItem li) => li.ListId )
.Select( grp => new
{
grp.Key,
Count = grp.Count(),
CountFooBar = grp.Count( (ListItem li) => li.Foo == "bar" )
CountBaz5 = grp.Count( (ListItem li) => li.Baz > 5 )
} )
return lists
.Join(
inner: stats,
outerKeySelector: (List l) => l.ListId,
innerKeySelector: grp => grp.Key,
resultSelector: (list, itemStats) => new { list, itemsStats }
)
However this generates SQL looking like this (this query shows my real table and column names, which is a bit more complicated than the schema I posted earlier:)
SELECT
[Project13].[C2] AS [C1],
[Project13].[ListId] AS [ListId],
[Project13].[C1] AS [C2],
[Project13].[C3] AS [C3],
[Project13].[C4] AS [C4],
[Project13].[C5] AS [C5],
[Project13].[C6] AS [C6],
[Project13].[C7] AS [C7]
FROM ( SELECT
[Project11].[C1] AS [C1],
[Project11].[ListId] AS [ListId],
[Project11].[C2] AS [C2],
[Project11].[C3] AS [C3],
[Project11].[C4] AS [C4],
[Project11].[C5] AS [C5],
[Project11].[C6] AS [C6],
(SELECT
COUNT(1) AS [A1]
FROM (SELECT [Project12].[ListId] AS [ListId]
FROM ( SELECT
[Extent11].[ListId] AS [ListId],
[Extent11].[Created] AS [Created]
FROM [dbo].[Lists] AS [Extent11]
WHERE ([Extent11].[TenantId] = 8) AND ([Extent11].[BlarghId] = 8)
) AS [Project12]
ORDER BY [Project12].[Created] DESC
OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY ) AS [Limit6]
INNER JOIN [dbo].[ListItems] AS [Extent12] ON ([Limit6].[TenantId] = [Extent12].[TenantId]) AND ([Limit6].[BlarghId] = [Extent12].[BlarghId]) AND ([Limit6].[ListId] = [Extent12].[ListId])
WHERE (([Extent12].[Baz] > 0) OR ((LEN([Extent12].[Notes])) > 0) OR ((LEN([Extent12].[Value])) > 0)) AND ([Project11].[TenantId] = [Extent12].[TenantId]) AND ([Project11].[BlarghId] = [Extent12].[BlarghId]) AND ([Project11].[ListId] = [Extent12].[ListId]) AND ([Extent12].[RecommendationRevision] IS NOT NULL)) AS [C7]
FROM ( SELECT
[Project9].[C1] AS [C1],
[Project9].[TenantId] AS [TenantId],
[Project9].[BlarghId] AS [BlarghId],
[Project9].[ListId] AS [ListId],
[Project9].[C2] AS [C2],
[Project9].[C3] AS [C3],
[Project9].[C4] AS [C4],
[Project9].[C5] AS [C5],
(SELECT
COUNT(1) AS [A1]
FROM (SELECT [Project10].[TenantId] AS [TenantId], [Project10].[BlarghId] AS [BlarghId], [Project10].[ListId] AS [ListId]
FROM ( SELECT
[Extent9].[TenantId] AS [TenantId],
[Extent9].[BlarghId] AS [BlarghId],
[Extent9].[ListId] AS [ListId],
[Extent9].[Created] AS [Created]
FROM [dbo].[Lists] AS [Extent9]
WHERE ([Extent9].[TenantId] = 8) AND ([Extent9].[BlarghId] = 8)
) AS [Project10]
ORDER BY [Project10].[Created] DESC
OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY ) AS [Limit5]
INNER JOIN [dbo].[ListItems] AS [Extent10] ON ([Limit5].[TenantId] = [Extent10].[TenantId]) AND ([Limit5].[BlarghId] = [Extent10].[BlarghId]) AND ([Limit5].[ListId] = [Extent10].[ListId])
WHERE (([Extent10].[Baz] > 0) OR ((LEN([Extent10].[Notes])) > 0) OR ((LEN([Extent10].[Value])) > 0)) AND ([Project9].[TenantId] = [Extent10].[TenantId]) AND ([Project9].[BlarghId] = [Extent10].[BlarghId]) AND ([Project9].[ListId] = [Extent10].[ListId]) AND (3 = [Extent10].[Baz])) AS [C6]
FROM ( SELECT
[Project7].[C1] AS [C1],
[Project7].[TenantId] AS [TenantId],
[Project7].[BlarghId] AS [BlarghId],
[Project7].[ListId] AS [ListId],
[Project7].[C2] AS [C2],
[Project7].[C3] AS [C3],
[Project7].[C4] AS [C4],
(SELECT
COUNT(1) AS [A1]
FROM (SELECT [Project8].[TenantId] AS [TenantId], [Project8].[BlarghId] AS [BlarghId], [Project8].[ListId] AS [ListId]
FROM ( SELECT
[Extent7].[TenantId] AS [TenantId],
[Extent7].[BlarghId] AS [BlarghId],
[Extent7].[ListId] AS [ListId],
[Extent7].[Created] AS [Created]
FROM [dbo].[Lists] AS [Extent7]
WHERE ([Extent7].[TenantId] = 8) AND ([Extent7].[BlarghId] = 8)
) AS [Project8]
ORDER BY [Project8].[Created] DESC
OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY ) AS [Limit4]
INNER JOIN [dbo].[ListItems] AS [Extent8] ON ([Limit4].[TenantId] = [Extent8].[TenantId]) AND ([Limit4].[BlarghId] = [Extent8].[BlarghId]) AND ([Limit4].[ListId] = [Extent8].[ListId])
WHERE (([Extent8].[Baz] > 0) OR ((LEN([Extent8].[Notes])) > 0) OR ((LEN([Extent8].[Value])) > 0)) AND ([Project7].[TenantId] = [Extent8].[TenantId]) AND ([Project7].[BlarghId] = [Extent8].[BlarghId]) AND ([Project7].[ListId] = [Extent8].[ListId]) AND (2 = [Extent8].[Baz])) AS [C5]
FROM ( SELECT
[Project5].[C1] AS [C1],
[Project5].[TenantId] AS [TenantId],
[Project5].[BlarghId] AS [BlarghId],
[Project5].[ListId] AS [ListId],
[Project5].[C2] AS [C2],
[Project5].[C3] AS [C3],
(SELECT
COUNT(1) AS [A1]
FROM (SELECT [Project6].[TenantId] AS [TenantId], [Project6].[BlarghId] AS [BlarghId], [Project6].[ListId] AS [ListId]
FROM ( SELECT
[Extent5].[TenantId] AS [TenantId],
[Extent5].[BlarghId] AS [BlarghId],
[Extent5].[ListId] AS [ListId],
[Extent5].[Created] AS [Created]
FROM [dbo].[Lists] AS [Extent5]
WHERE ([Extent5].[TenantId] = 8) AND ([Extent5].[BlarghId] = 8)
) AS [Project6]
ORDER BY [Project6].[Created] DESC
OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY ) AS [Limit3]
INNER JOIN [dbo].[ListItems] AS [Extent6] ON ([Limit3].[TenantId] = [Extent6].[TenantId]) AND ([Limit3].[BlarghId] = [Extent6].[BlarghId]) AND ([Limit3].[ListId] = [Extent6].[ListId])
WHERE (([Extent6].[Baz] > 0) OR ((LEN([Extent6].[Notes])) > 0) OR ((LEN([Extent6].[Value])) > 0)) AND ([Project5].[TenantId] = [Extent6].[TenantId]) AND ([Project5].[BlarghId] = [Extent6].[BlarghId]) AND ([Project5].[ListId] = [Extent6].[ListId]) AND (1 = [Extent6].[Baz])) AS [C4]
FROM ( SELECT
[Project3].[C1] AS [C1],
[Project3].[TenantId] AS [TenantId],
[Project3].[BlarghId] AS [BlarghId],
[Project3].[ListId] AS [ListId],
[Project3].[C2] AS [C2],
(SELECT
COUNT(1) AS [A1]
FROM (SELECT [Project4].[TenantId] AS [TenantId], [Project4].[BlarghId] AS [BlarghId], [Project4].[ListId] AS [ListId]
FROM ( SELECT
[Extent3].[TenantId] AS [TenantId],
[Extent3].[BlarghId] AS [BlarghId],
[Extent3].[ListId] AS [ListId],
[Extent3].[Created] AS [Created]
FROM [dbo].[Lists] AS [Extent3]
WHERE ([Extent3].[TenantId] = 8) AND ([Extent3].[BlarghId] = 8)
) AS [Project4]
ORDER BY [Project4].[Created] DESC
OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY ) AS [Limit2]
INNER JOIN [dbo].[ListItems] AS [Extent4] ON ([Limit2].[TenantId] = [Extent4].[TenantId]) AND ([Limit2].[BlarghId] = [Extent4].[BlarghId]) AND ([Limit2].[ListId] = [Extent4].[ListId])
WHERE (([Extent4].[Baz] > 0) OR ((LEN([Extent4].[Notes])) > 0) OR ((LEN([Extent4].[Value])) > 0)) AND ([Project3].[TenantId] = [Extent4].[TenantId]) AND ([Project3].[BlarghId] = [Extent4].[BlarghId]) AND ([Project3].[ListId] = [Extent4].[ListId]) AND ([Extent4].[Foo] = 1)) AS [C3]
FROM ( SELECT
[GroupBy1].[A1] AS [C1],
[GroupBy1].[K1] AS [TenantId],
[GroupBy1].[K2] AS [BlarghId],
[GroupBy1].[K3] AS [ListId],
[GroupBy1].[K4] AS [C2]
FROM ( SELECT
[Project2].[K1] AS [K1],
[Project2].[K2] AS [K2],
[Project2].[K3] AS [K3],
[Project2].[K4] AS [K4],
COUNT([Project2].[A1]) AS [A1]
FROM ( SELECT
[Project2].[TenantId] AS [K1],
[Project2].[BlarghId] AS [K2],
[Project2].[ListId] AS [K3],
1 AS [K4],
1 AS [A1]
FROM ( SELECT
[Extent2].[ListId] AS [ListId]
FROM (SELECT [Project1].[ListId] AS [ListId]
FROM ( SELECT
[Extent1].[ListId] AS [ListId],
[Extent1].[Created] AS [Created]
FROM [dbo].[Lists] AS [Extent1]
WHERE ([Extent1].[TenantId] = 8) AND ([Extent1].[BlarghId] = 8)
) AS [Project1]
ORDER BY [Project1].[Created] DESC
OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY ) AS [Limit1]
INNER JOIN [dbo].[ListItems] AS [Extent2] ON (([Limit1].[ListId] = [Extent2].[ListId])
WHERE ([Extent2].[Baz] > 0) OR ((LEN([Extent2].[Notes])) > 0) OR ((LEN([Extent2].[Value])) > 0)
) AS [Project2]
) AS [Project2]
GROUP BY [K1], [K2], [K3], [K4]
) AS [GroupBy1]
) AS [Project3]
) AS [Project5]
) AS [Project7]
) AS [Project9]
) AS [Project11]
) AS [Project13]
It doesn't compose the COUNT() statements together at all, and it moves the COUNT predicates to separate WHERE clauses. Also note the repeated paged subqueries (where OFFSET 0 ROW FETCH NEXT 25 is used) whereas my hand-written query only executes it once.
Here's a semi-workaround I have:
I realised the best short-term solution is to have the SQL in the database (as a UDF FUNCTION or VIEW), this would mean that some data code would therefore have to be in the database (rather than using the DB as a "dumb store").
I first created a table-valued UDF which would accept a table-valued parameter, reasoning that would allow for composition, at the cost of needing to generate the input parameter table (an array of ListId values from the paged query). However in doing so, I realised that Linq-to-Entities (version 6) does not support table-valued parameters in Function Imports yet.
I then reasoned a better approach would be to move the COUNT operation to a VIEW (which represents one half of the LEFT JOIN I had in my hand-written query), and then I can get Linq to JOIN that against the existing IQueryable, thus retaining composability and generating an efficient runtime query (indeed, when I run it, the query takes 34ms to execute according to SQL Server Profiler, whereas the old Linq-generated inefficient query took 830ms).
Here's what I used:
CREATE VIEW ListItemStatistics AS
SELECT
ListId,
COUNT(*) AS [CountAll],
COUNT( CASE WHEN ... ) AS Count...
FROM
ListItems
WHERE
Foo = 'bar'
GROUP BY
ListId
And then from within Linq:
IQueryable lists = GetListsQuery( 0, 25 );
var listsWithItemsStats = lists.
.Join(
inner: this.dbContext.ListItemStatistics,
outerKeySelector: list => list.ListId,
innerKeySelector: row => row.ListId,
resultSelector: (list,row) => new { list, row }
);
However because this does use database-side logic (in the VIEW) it is not ideal.
In my experience EF generates such queries when you use aggregate functions that apply filtering like Count(predicate) in your case. You'll get much better SQL query if you replace Count(condition) construct with conditional sum (Sum(condition ? 1 : 0)) like this:
var stats = db.ListItems
.Where(li => li.Qux != null)
.GroupBy(li => li.ListId)
.Select(grp => new
{
grp.Key,
Count = grp.Count(),
CountFooBar = grp.Sum(li => li.Foo == "bar" ? 1 : 0),
CountBaz5 = grp.Sum(li => li.Baz > 5 ? 1 : 0)
});
The other parts stay the same. Before the mod I was getting similar SQL to the posted, and here is what I'm getting after this little mod:
SELECT
[Limit1].[ListId] AS [ListId],
[Limit1].[Name] AS [Name],
[Limit1].[DateCreated] AS [DateCreated],
[GroupBy1].[K1] AS [ListId1],
[GroupBy1].[A1] AS [C1],
[GroupBy1].[A2] AS [C2],
[GroupBy1].[A3] AS [C3]
FROM (SELECT [Extent1].[ListId] AS [ListId], [Extent1].[Name] AS [Name], [Extent1].[DateCreated] AS [DateCreated]
FROM [dbo].[List] AS [Extent1]
ORDER BY [Extent1].[DateCreated] ASC
OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY ) AS [Limit1]
INNER JOIN (SELECT
[Filter1].[K1] AS [K1],
COUNT([Filter1].[A1]) AS [A1],
SUM([Filter1].[A2]) AS [A2],
SUM([Filter1].[A3]) AS [A3]
FROM ( SELECT
[Extent2].[ListId] AS [K1],
1 AS [A1],
CASE WHEN (N'bar' = [Extent2].[Foo]) THEN 1 ELSE 0 END AS [A2],
CASE WHEN ([Extent2].[Baz] > 5) THEN 1 ELSE 0 END AS [A3]
FROM [dbo].[ListItem] AS [Extent2]
WHERE [Extent2].[Qux] IS NOT NULL
) AS [Filter1]
GROUP BY [K1] ) AS [GroupBy1] ON [Limit1].[ListId] = [GroupBy1].[K1]
UPDATE: The above is just the LINQ equivalent of your manual SQL query (when you adjust the join code to produce LEFT OUTER JOIN). However, taking into account the master data paging, OUTER APPLY SQL query might perform better. You can let LINQ to Entities generate such type of query like this:
var listsWithItemsStats = lists
.SelectMany(l => db.ListItems
.Where(li => li.ListId == l.ListId && li.Qux != null)
.GroupBy(li => li.ListId)
.Select(grp => new
{
grp.Key,
Count = grp.Count(),
CountFooBar = grp.Sum(li => li.Foo == "bar" ? 1 : 0),
CountBaz5 = grp.Sum(li => li.Baz > 5 ? 1 : 0)
})
.DefaultIfEmpty(),
(list, itemsStats) => new { list, itemsStats });
Please help to check why below SQL statement got error of "ORA-00936: missing expression" when executed on Oracle 11g R2.
select part.*,
decode(pv.dspuom, null, part.untqty, part.untqty / prtftp_dtl.untqty) dsp_untqty,
decode(pv.dspuom, null, pv.stkuom, pv.dspuom) untqty_uom,
decode(pv.dspuom, null, cast(null as int), pv.stkuom, cast(null as int), mod(part.untqty, prtftp_dtl.untqty)) rem_untqty,
decode(pv.dspuom, null, null, pv.stkuom, null, pv.stkuom) rem_untqty_uom
from (select #select_on:raw sum(untqty) untqty,
sum(catch_qty) catch_qty,
invadj.prtnum fixed_prtnum,
invadj.prt_client_id fixed_prt_client_id,
invadj.wh_id fixed_wh_id,
invadj.play_prc_id
from invadj
where exists(select 'x'
from prtmst_view
where prtmst_view.prtnum = invadj.prtnum
and prtmst_view.prt_client_id = invadj.prt_client_id
and prtmst_view.wh_id = invadj.wh_id
and prtmst_view.prdflg = 1)
and #+moddte:date
and #+invadj.play_prc_id
and #+invadj.prtnum
and #+invadj.prt_client_id
and wh_id = #wh_id
and #*
group by #result_string:raw,
invadj.prtnum,
invadj.prt_client_id,
invadj.wh_id,
invadj.play_prc_id
having sum(untqty) <> 0
or sum(catch_qty) <> 0) part left
join prtmst_view pv
on pv.prtnum = part.fixed_prtnum
and pv.prt_client_id = part.fixed_prt_client_id
and pv.wh_id = part.fixed_wh_id left
join prtftp
on prtftp.prtnum = pv.prtnum
and prtftp.prt_client_id = pv.prt_client_id
and prtftp.wh_id = pv.wh_id
and prtftp.defftp_flg = 1 left
join prtftp_dtl
on prtftp.prtnum = prtftp_dtl.prtnum
and prtftp.ftpcod = prtftp_dtl.ftpcod
and prtftp.prt_client_id = prtftp_dtl.prt_client_id
and prtftp.wh_id = prtftp_dtl.wh_id
and prtftp_dtl.uomcod = nvl(pv.dspuom, pv.stkuom)
I think the syntax may be off a bit.
Look at this line below
FROM (SELECT # select_on:raw sum(untqty) untqty,
The # character seems odd