【问题标题】:SQL - Get value for a specific element in arraySQL - 获取数组中特定元素的值
【发布时间】:2018-08-16 17:28:52
【问题描述】:

我几天前问过这个问题,但它涉及更深层次的答案,所以建议我创建一个全新的,所以就这样......

免责声明:我无法创建任何自定义 DB 对象(函数、SP、视图等),因此所有内容都需要内嵌在 SQL 查询中。

我正在查询审核表,为简单起见,该表具有以下字段:

AttributeMask
ChangedData
CreatedOn
ObjectId

数据库中的每条记录可能有多个与之关联的审计记录。每次对 DB 记录进行更改时,它都会在 Audit 表中创建一个记录,其中包含特定的 ObjectID 指向源记录 CreatedOn 将具有更改的 DateTime AttributeMask执行 SAVE 时已更改的 AttributeId 列表(注意,可能同时更改了多个字段),ChangedData 实际上将包含已更改的数据(预更改的值)。一个字段当然可以多次更改,如果是这种情况,该字段的多个审核记录将存在(不同的CreatedOn 值)。我需要查找源记录中的某些(不是全部)字段在特定日期的样子。

我可以在下面运行查询:

select a1.ChangeData as ChangedData1, a1.AttributeMask as AttributeMask2, a2.ChangeData as ChangedData2, a2.AttributeMask as AttributeMask2
from Table1 t
join audit a1 on a1.AuditId = 
(select top 1 a.auditid from audit a where a.objecttypecode = 3
and a.objectid = T.ObjectId
and a.AttributeMask like '%,10192,%'
and a.CreatedOn <= '8-16-2018'
order by a1.CreatedOn desc)
join audit a2 on a2.AuditId = 
(select top 1 a.auditid from audit a where a.objecttypecode = 3
and a.objectid = T.ObjectId
and a.AttributeMask like '%,10501,%'
and a.CreatedOn <= '8-16-2018'
order by a1.CreatedOn desc)
where t.ObjectID = SomeGuidValue

此查询正在查找在 8-16-2018 之前发生的 2 个字段(1019210501)的最新更改。它返回以下数据(我添加了第三条记录来说明所有可能的情况):

ChangeData1 AttributeMask1  ChangeData2 AttributeMask2
NULL    NULL    True~~True~1904~~~15.8700000000~4760~30000~590~12000~0~390~1904~False~200~  ,10499,10604,10501,10436,10491,10490,10459,10099,10319,10253,10433,10031,10091,10020,10265,10008,10509,
~True~5.56~~House~~200000~  ,10030,10432,10435,197,10099,10192,198, False~1170~600~0~Complete~True~1770~    ,10501,10091,10008,10020,10570,10499,10253,10715,
~~~~200001~ ,10432,10435,197,10099,10192,198,   True~2~True~~0~~~100.0000000000~1~business,96838c4f-e63c-e011-9a14-78e7d1644f78~~0~~~~0~False~~1~   ,10499,10509,10501,10203,10436,10491,10490,10459,10099,10157,10253,10433,10715,10031,10091,10020,10265,10008,10319,10699,

这意味着第一条记录仅更改为10501 字段,第二条记录仅更改为10192,第三条记录更改1019210501 字段。

AttributeMask 字段具有所有已更改的 FieldID 的逗号分隔列表(请注意,它以逗号开头和结尾)。 ChangedData 字段具有tilde (~) 分隔的已更改数据列表。 AttributeMask 中的每个条目对应于 ChangedData 中的条目。例如,如果我想查看第一条记录的 10501 字段中有哪些数据,我需要确定 AttributeMask 字段中的条目 #10501 (列表中的 #3),然后我需要找到找出ChangedData 字段中条目#3 中的数据(它是TRUE),如果我想查看字段10192 的第二条记录中的内容,我会看看它在AttributeMask 中有什么索引(它是# 6) 其对应的ChangedData字段值为2000000。 我需要以某种方式在同一个查询中提取这些数据。我得到了一些samples 的帮助,了解了如何做到这一点,但我一开始没有提出正确的问题(认为这比解释所有这些更简单)。

我需要这个查询返回是这样的:

ChangeData1 AttributeMask1  ChangeData2 AttributeMask2
NULL    NULL    TRUE    10501
200000  10192   FALSE   10501
200001  10192   TRUE    10501

我希望现在清楚了。

【问题讨论】:

  • 看起来您在最后 2 段中写作真的很无聊。为什么需要将#3 解码为 ChangedData 中的#6?即使在那种情况下,您为什么还要将#5作为#6?无论如何,这看起来像一个糟糕的设计,我会尝试在客户端处理它而不是尝试使用 T-SQL(可能是一个 CLR 函数,但你说你不能在那里做任何事情——尽管它也是通过T-SQL)
  • 嗨@Cetin Basoz,你是对的(不是我无聊)。我写错了。我纠正了它。设计并不是那么糟糕,只是不是为了由 T-SQL 解析而设计的。用任何计算机语言(例如 C#)解析都很容易。我只能创建一个查询。我无法在此数据库中创建任何其他内容(函数、SP、视图)。
  • 如果AttributeMask1 包含两个值(2 相同)会发生什么?最大连接数是多少? 名称编号总是暗示设计不佳。为什么不将DataMask 与列表中的行计数器一起返回?如果这个列表可能越来越广泛,那就太奇怪了......
  • 嗨@Shnugo,如果 AttributeMask1 包含这两个值,那很好,因为我一次只关心 1 个连接,所以我只为该特定连接提取值,另一个值将是另一个值的一部分加入。因此,可以通过不同的联接多次提取相同的审计记录。我不确定最大连接数,但我会说大约 15 个。我不会在真正的查询中使用名称编号。我打算让这些名称更具解释性。我不确定返回行计数器将如何解决我的问题?

标签: sql tsql


【解决方案1】:

正如我的 cmets 中所说,您最好处理一组,然后使用带有 name-numbered 列的更广泛的列表。

尝试以如下样机表的格式提供您的初始输入集:

有一个正在运行的 ID、您的 ObjectID、您要查找的代码以及这两个字符串。我插入了你提供的数据,但不是并排

DECLARE @tbl TABLE(ID INT IDENTITY, CodeId INT,ObjectId INT,  ChangeData VARCHAR(1000), AttributeMask VARCHAR(1000));
INSERT INTO @tbl VALUES
 (10192,1,NULL,NULL)
    ,(10501,1,'True~~True~1904~~~15.8700000000~4760~30000~590~12000~0~390~1904~False~200~',',10499,10604,10501,10436,10491,10490,10459,10099,10319,10253,10433,10031,10091,10020,10265,10008,10509,')
,(10192,2,'~True~5.56~~House~~200000~',',10030,10432,10435,197,10099,10192,198,')
   ,(10501,2,'False~1170~600~0~Complete~True~1770~',',10501,10091,10008,10020,10570,10499,10253,10715,')
,(10192,3, '~~~~200001~',',10432,10435,197,10099,10192,198,')
   ,(10501,3,'True~2~True~~0~~~100.0000000000~1~business,96838c4f-e63c-e011-9a14-78e7d1644f78~~0~~~~0~False~~1~',',10499,10509,10501,10203,10436,10491,10490,10459,10099,10157,10253,10433,10715,10031,10091,10020,10265,10008,10319,10699,');

--查询会将字符串转换为 XML,以便通过它们的位置索引抓取它
--然后将所有代码作为派生列表进行编号。
--根据找到的位置取对应的值

SELECT t.ID 
      ,t.ObjectId
      ,t.CodeId
      ,t.ChangeData
      ,t.AttributeMask
      ,Casted.ValueXml.value('/x[sql:column("PartIndex")][1]','nvarchar(max)') ValueAtCode  
FROM @tbl t
CROSS APPLY
(
    SELECT CAST('<x>' + REPLACE(t.AttributeMask,',','</x><x>') + '</x>' AS XML).query('/x[text()]') AS CodeXml
          ,CAST('<x>' + REPLACE(t.ChangeData,'~','</x><x>') + '</x>' AS XML) AS ValueXml
) Casted
CROSS APPLY(
    SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS PartIndex
          ,x.value('text()[1]','nvarchar(max)') AS CodePart
    FROM Casted.CodeXml.nodes('/x') A(x)
) CodeDerived
WHERE CodeDerived.CodePart=t.CodeId;

结果

ID  ObjectId    CodeId  ValueAtCode
2   1           10501   True
3   2           10192   
4   2           10501   False
5   3           10192   200001
6   3           10501   True

但这会很慢......

更新

您的整个方法不是基于集合的。以下是完全未经测试的,我没有你的数据库,但会指向一个基于集合的解决方案。

DECLARE @Codes TABLE(CodeID INT);
INSERT INTO @Codes VALUES(10192),(10501);

select t.SomeIdOfYourMainTable
      ,c.CodeID
      ,a1.ChangeData
      ,a1.AttributeMask
from Table1 t
CROSS JOIN @Codes c --will repeat the result for each value in @Codes
CROSS APPLY
(
    select top 1 a.ChangeData
                ,a.AttributeMask             
    from [audit] a 
    where a.objecttypecode = 3
    and a.objectid = t.ObjectId
    and a.AttributeMask like CONCAT('%,',c.CodeID,',%')
    and a.CreatedOn <= '20180816' --use culture independant format!!!
    order by a.CreatedOn desc
) a1;

这允许您根据需要插入任意数量的代码(无需重复任何连接),它将返回类似于我上面示例的集合。

如果您需要这方面的进一步帮助:请关闭此问题并使用完全正常工作的独立 MCVE 开始一个新问题,以重现您的案例。

【讨论】:

  • 嗨@Shnugo,这似乎可行,但我不知道如何:-) 你到底在哪里指出要查找的字段ID?它只返回 10501 和 10192,但您的查询中究竟在哪里?我问是因为我需要再寻找大约 10 个字段 ID。你是对的,它可能很慢,但我需要在更大的数据集上测试它的性能。我的意思是让一些东西慢下来比什么都快:-) 真的很感谢你的帮助!
  • @Tony,只需执行一个简单的SELECT * FROM @tbl。您在CodeId 列中看到的代码就是被采用的代码。每一行的所有代码都会有一个编号列表(APPLY 工作 row-wise),而最后的 WHERE 将此列表减少到我们正在寻找的一行。该行带有一个位置,用于从另一个字符串中选择正确的元素。
  • 嗨@Shnugo,这很好,但对我来说太复杂了。我看到您将 CodeId 列添加到数据集中,并且我了解查询如何查找正确的值,但我的数据集没有此列。我需要在查询中对 CodeId(实际上是 FieldId)的值进行硬编码。这可能吗?我意识到我可能需要进行多次连接(针对每个 FieldId)。
  • @Tony 如果我理解正确,您的方法实际上要复杂得多。查看我的更新...
  • 嗨@Shnugo,您的第二个示例有效,但它没有从我所追求的 ChangedData 列返回单个值。如果我们能以某种方式将您的第一个样本与第二个样本结合起来,那将是完美的。我想我可以将您的第二个样本的输出转储到一个临时表中,然后将您的第一种方法应用于它。您在以前的方法中定义的临时表几乎就是我所拥有的(没有 CodeID 列加上 CreatedOn 列)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多