Add Fuchs_Database SSDT project (schema source of truth)
Adds the SQL Server Data Tools project for the fuchs_fds database — tables, table types, functions and stored procedures that the backend calls (e.g. fds__getInvoice, fds__merge_bankingtransactions, fds__tt__bankingtransactions, fds__admin_getReportCatalog, fis_* auth). Build/model caches (bin, obj, *.dbmdl, *.jfm, *.user) are git-ignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
|
||||
CREATE FUNCTION [dbo].[json_compareObjectArrays] (@SourceJSON NVARCHAR(MAX), @TargetJSON NVARCHAR(MAX), @uniquekey varchar(50) )
|
||||
/**
|
||||
Summary:
|
||||
This function 'diffs' a source JSON document with a target JSON document and produces an
|
||||
analysis of which properties are missing in either the source or target, or the values
|
||||
of these properties that are different. It reports on the properties and values for
|
||||
both source and target as well as the path that references that scalar value. The
|
||||
path reference to the object's parent is exposed in the result to enable a query to
|
||||
reference the value of any other object in the parent that is needed.
|
||||
Author: Phil Factor
|
||||
based on Author: Phil Factor (06/07/2020)
|
||||
Date: 06/30/2023
|
||||
|
||||
Returns: >
|
||||
equal: 1 = equal, 0 not equal
|
||||
SideIndicator: ( == equal, <- not in target, -> not in source, <> not equal )
|
||||
uid:
|
||||
parent:
|
||||
path: the JSON path used by the SQL JSON functions
|
||||
key: the key field without the path
|
||||
SourceValue: the value IN the SOURCE JSON document
|
||||
TargetValue: the value IN the TARGET JSON document
|
||||
|
||||
**/
|
||||
RETURNS @returntable TABLE
|
||||
(
|
||||
[equal] [bit],
|
||||
[SideIndicator] CHAR(2), -- == means equal, <- means not in target, -> means not in source, <> means not equal
|
||||
[UID] varchar(100),
|
||||
[parent] VARCHAR(2000), -- the parent object
|
||||
[path] VARCHAR(2000), -- the JSON path used by the SQL JSON functions
|
||||
[key] VARCHAR(255), -- the key field without the path
|
||||
[SourceValue] NVARCHAR(max), -- the value IN the SOURCE JSON document
|
||||
[TargetValue] NVARCHAR(max) -- the value IN the TARGET JSON document
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
|
||||
IF (IsJson(ISNULL(@SourceJSON, '{}')) = 1 AND IsJson(ISNULL(@TargetJSON, '{}')) = 1) --don't try anything if either json is invalid
|
||||
BEGIN
|
||||
DECLARE @map TABLE --these contain all properties or array elements with scalar values
|
||||
(
|
||||
iteration INT, --the number of times that more arrays or objects were found
|
||||
SourceOrTarget CHAR(1), --is this the source 's' OR the target 't'
|
||||
[UID] varchar(100),
|
||||
[mparent] VARCHAR(2000), --the parent object
|
||||
[mPath] VARCHAR(2000), -- the JSON path to the key/value pair or array element
|
||||
[mOPath] NVARCHAR(2000),
|
||||
[mKey] VARCHAR(255), --the key to the property
|
||||
[mValue] NVARCHAR(MAX),-- the value
|
||||
[mType] INT --the type of value it is
|
||||
);
|
||||
DECLARE @objects TABLE --this contains all the properties with arrays and objects
|
||||
(
|
||||
iteration INT,
|
||||
SourceOrTarget CHAR(1),
|
||||
[UID] varchar(100),
|
||||
[oParent] VARCHAR(2000),
|
||||
[oPath] VARCHAR(2000),
|
||||
[oOPath] VARCHAR(2000),
|
||||
[oKey] NVARCHAR(2000),
|
||||
[oValue] NVARCHAR(MAX),
|
||||
[oType] INT
|
||||
);
|
||||
DECLARE @depth INT = 1; --we start in shallow water
|
||||
DECLARE @HowManyObjectsNext INT = 1, @SourceType INT, @TargetType INT;
|
||||
|
||||
SELECT --firstly, we try to work out if the source is an array or object
|
||||
@SourceType =
|
||||
CASE IsNumeric((SELECT TOP 1 [key] FROM OpenJson(@SourceJSON)))
|
||||
WHEN 1 THEN 4 ELSE 5 END,
|
||||
@TargetType= --and if the target is an array or object
|
||||
CASE IsNumeric((SELECT TOP 1 [key] FROM OpenJson(@TargetJSON)))
|
||||
WHEN 1 THEN 4 ELSE 5 END
|
||||
--now we insert the base objects or arrays into the object table
|
||||
INSERT INTO @objects (iteration, SourceOrTarget, [oParent], [oPath], [oOPath], [oKey], [oValue], [oType])
|
||||
SELECT 0, 's' AS SourceOrTarget,'' AS [oParent], [oPath] = '$', [oOPath] = '$', [oKey] = '', @SourceJSON, @SourceType;
|
||||
INSERT INTO @objects (iteration, SourceOrTarget, [oParent], [oPath], [oOPath], [oKey], [oValue], [oType])
|
||||
SELECT 0, 't' AS SourceOrTarget, '' AS [oParent], [oPath] = '$', [oOPath] = '$', [oKey] = '', @TargetJSON, @TargetType;
|
||||
|
||||
--we now set the depth and how many objects are in the next iteration
|
||||
SELECT @depth = 0, @HowManyObjectsNext = 2;
|
||||
|
||||
WHILE @HowManyObjectsNext > 0 AND @depth < 2
|
||||
BEGIN
|
||||
INSERT INTO @map --get the scalar values into the @map table
|
||||
(iteration, SourceOrTarget, [UID], [mParent], [mPath], [mOPath], [mKey], [mValue], [mType])
|
||||
SELECT --
|
||||
[iteration] = o.[iteration] + 1
|
||||
, SourceOrTarget
|
||||
, [UID] = [UID]
|
||||
, [mParent] = [oPath]
|
||||
, [mPath] = [oPath] + CASE [otype] WHEN 4 THEN '[' + [Key] + ']' ELSE '.' + [key] END
|
||||
, [mOPath] = [oOPath] + CASE [otype] WHEN 4 THEN '[' + [Key] + ']' ELSE '.' + [key] END
|
||||
, [mkey] = [key]
|
||||
, [mvalue] = [value]
|
||||
, [mtype] = [type]
|
||||
FROM @objects AS o
|
||||
CROSS APPLY OpenJson([oValue]) as j
|
||||
WHERE j.[type] IN (0, 1, 2, 3) AND o.[iteration] = @depth;
|
||||
|
||||
--now we do the same for the objects and arrays
|
||||
INSERT INTO @objects (iteration, SourceOrTarget, [UID], [oParent], [oPath], [oOPath], [oKey], [oValue], [oType])
|
||||
SELECT
|
||||
[iteration] = o.[iteration] + 1
|
||||
, [SourceOrTarget] = SourceOrTarget
|
||||
, [UID] = JSON_VALUE(j.[Value], '$.' + @uniquekey)
|
||||
, [oParent] = [oPath]
|
||||
, [oPath] = [oPath] + CASE [oType] WHEN 4 THEN '[' + JSON_VALUE(j.[Value], '$.' + @uniquekey) + ']' ELSE '.' + [key] END
|
||||
, [oOPath] = [oOPath] + CASE [oType] WHEN 4 THEN '[' + j.[key] + ']' ELSE '.' + j.[key] END
|
||||
, [oKey] = [key]
|
||||
, [oValue] = [value]
|
||||
, [oType] = [type]
|
||||
FROM @objects o
|
||||
CROSS APPLY OpenJson([oValue]) as j
|
||||
WHERE j.[type] IN (4,5) AND o.[iteration] = @depth;
|
||||
|
||||
SELECT @HowManyObjectsNext = @@RowCount --how many objects or arrays?
|
||||
SELECT @depth = @depth + 1; --and so to the next depth maybe
|
||||
|
||||
END; --while
|
||||
|
||||
--now we just do a full join on the columns we are comparing and work out the comparison
|
||||
INSERT INTO @returntable
|
||||
SELECT
|
||||
--first we work out the side-indicator that summarises the comparison
|
||||
[equal] = CASE WHEN src.[UID] is null OR tgt.[UID] is null THEN 0
|
||||
WHEN src.[mValue] is NULL AND tgt.[mValue] is null THEN 1
|
||||
WHEN src.[mValue] = tgt.[mValue] THEN 1
|
||||
ELSE 0
|
||||
END
|
||||
, [Sideindicator] = CASE WHEN src.[UID] is null AND tgt.[UID] IS NOT null THEN '->'
|
||||
WHEN src.[UID] is NOT null AND tgt.[UID] IS null THEN '<-'
|
||||
WHEN src.[mValue] is NULL AND tgt.[mValue] is null THEN '=='
|
||||
WHEN src.[mValue] = tgt.[mValue] THEN '=='
|
||||
ELSE IIF(src.[mPath] IS NULL, '-', '<') + IIF(tgt.[mPath] IS NULL, '-', '>')
|
||||
END
|
||||
--these columns could be in either table
|
||||
, [UID] = Coalesce(src.[UID], tgt.[UID])
|
||||
, [parent] = Coalesce(src.[mParent], tgt.[mParent])
|
||||
, [path] = Coalesce(src.[mOPath], tgt.[mOPath])
|
||||
, [key] = Coalesce(src.[mKey], tgt.[mKey])
|
||||
, [sourceValue] = src.[mValue]
|
||||
, [targetValue] = tgt.[mValue]
|
||||
FROM
|
||||
(SELECT [UID], [mParent], [mPath], [mOPath], [mKey], [mValue] FROM @map WHERE SourceOrTarget = 's') AS src -- the source scalar literals
|
||||
FULL OUTER JOIN
|
||||
(SELECT [UID], [mParent], [mPath], [mOPath], [mKey], [mValue] FROM @map WHERE SourceOrTarget = 't') AS tgt --the target scalar literals
|
||||
ON src.[mPath] = tgt.[mPath]
|
||||
ORDER BY [path];
|
||||
END;
|
||||
RETURN;
|
||||
END;
|
||||
Reference in New Issue
Block a user