如果一个表有一个未索引列与索引列是一对多的关系,如何优化未索引列的查询?

Posted

技术标签:

【中文标题】如果一个表有一个未索引列与索引列是一对多的关系,如何优化未索引列的查询?【英文标题】:If a table has an unindexed column with a 1 to many relationship to an indexed column, how to optimize a query for the unindexed column? 【发布时间】:2019-10-16 04:49:36 【问题描述】:

如果有一个包含足够记录的两列表 MyTable,则查询优化是相关的。

CorporationID int(未编入索引)

BatchID int(索引)

假设 CorporationID 和 BatchID 之间始终存在一对多的关系。换句话说,对于每个 BatchID,将只有一个 CorporationID,但对于每个 CorporationID,将有多个 BatchID 值。

我们需要获取所有公司 ID = 1 的 BatchID 值。

我知道最简单的解决方案可能是只为 CorporationID 添加一个索引,但假设不允许这样做,是否有其他方法可以通过查询或其他方式通知 SQL 每个 BatchID 仅对应于 1 个 CorporationID?

select distinct batchid from MyTable where corporationID = 1

好像没有效果。

select batchid from (select min(corporationid) corporationid, batchid 
from MyTable group by batchid) subselect where corporationid = 1 

这也是无效的,我认为是因为 SQL 需要不必要地遍历公司 ID 的所有值? (是否存在聚合函数来选择任何不会产生 min()、max()、sum() 或 avg() 开销的值??)

select batchid 
from (
       select corporationid, batchid 
       from (
               select *, ROW_NUMBER() OVER (PARTITION BY batchid ORDER BY(SELECT NULL)) AS RowNumber
              from mytable
            ) subselect 
       where RowNumber = 1
      ) subselect2 
where corporationid = 1 

这行得通吗?通过batchid无序分区后任意选择第1行相关的corporateid?

【问题讨论】:

你能解释一下你为什么不创建那个索引吗?这正是它的设计目的。这是一个纯粹的理论问题吗? 是的,这主要是一个理论问题,试图扩展我的 SQL 知识。实际上,我明天可能会将索引添加到表中,即使表很大,这让我有点犹豫是否要进行更改。 没有其他办法了。如果您想按没有索引的列进行选择,则必须对其进行扫描。 【参考方案1】:

“假设不允许创建索引” - 这是一个极不可能的假设。当然,你应该创建索引。

对于您问题中的备选问题,最直接的答案是“否”。没有任何功能或子查询或视图或其他“读取”操作可用于获取给定 CorpID 的批次列表。您需要访问 corpID 数据才能做到这一点……您的所有示例查询都不起作用,因为在某些时候,它们需要访问 CorpID 以了解要为 BatchID 收集哪些行。任何可能存在的摘要或“汇总”功能仍然需要访问所有数据页面才能“查看”它们。书页的阅读是无法避免的。

如果不更改您的架构,就不可能进一步优化您的查询。

但是,通过一些更改,您可以有一些选项(但我想它们比仅仅添加索引要丑得多)。例如,您可以修改 BatchID 的结构以包含 BatchID 和 CorpID 的数据。像“8888899999999”这样的东西...... 9 是 batchID,8 是 CorpID。但这并没有给你带来太多好处,你没有节省任何索引空间,但至少你不必索引 CorpID 字段:) 可以这样做,但我不会分享任何其他内容。我不希望这里真正有经验的人看到这些东西而生病。 :)

如果你想提高性能,你需要一个 CorpID 上的索引。

【讨论】:

感谢您抽出宝贵时间回复。你的解释很有道理。理想情况下,我不会首先在每条记录中存储重复值,但不幸的是我无权修改访问现有数据结构的应用程序,因此我认为添加索引将是我的最佳选择。【参考方案2】:

如果您没有大量数据,我建议在 Corporation ID 列上放置一个索引。但是如果数据太多,可以为每个Corporation ID定义一个索引

【讨论】:

我不知道“为每个公司 ID 定义索引”是什么意思?? 当你写信时:select * from mytable where corporationid = @corporationid 。您将在执行计划中看到从索引扫描(corporationid = @corporationid)直接读取速度相对较好 这意味着您应该在 CorporationId 上放置一个索引。我从您的回答中不明白为什么您有两种选择? “每个 CorporationID”相对于“CorporationID 上的索引”是什么意思?? 创建索引时,就是在主表上创建了一个表。当索引基于公司 ID 时,此表较大。如果您需要每个公司 ID,则此表较小。为每个 Corporation ID 创建索引时,您只查找包含 Corporation ID 数据的表,而不是更多。 仍然是零意义。您能否使用显示您的意思的实际命令来更新您的答案。仍然不知道“公司 ID 上的索引”与“每个公司 ID 的索引”是什么意思。【参考方案3】:

第 01 部分=>

/*01Create DB*/
IF DB_ID('Test01')>0
BEGIN
    ALTER DATABASE Test01 SET SINGLE_USER WITH ROLLBACK IMMEDIATE
    DROP DATABASE Test01
END
GO
CREATE DATABASE Test01
GO
USE Test01
Go

第 02 部分=>

/*02Create table*/
CREATE TABLE Table01(
    ID INT PRIMARY KEY IDENTITY,
    Title NVARCHAR(100),
    CreationDate DATETIME,
    CorporationID INT ,
    MyID INT ,
    [GuidId1] [uniqueidentifier] NOT NULL,
    [GuidId2] [uniqueidentifier] NOT NULL,
    [Code] [nvarchar](50) NULL
)
ALTER TABLE [dbo].[Table01] ADD  DEFAULT (GETDATE()) FOR [CreationDate]
GO
ALTER TABLE [dbo].[Table01] ADD  DEFAULT (NEWSEQUENTIALID()) FOR [GuidId1]
GO
ALTER TABLE [dbo].[Table01] ADD  DEFAULT (NEWID()) FOR [GuidId2]
GO

CREATE TABLE Table02(
    ID INT PRIMARY KEY IDENTITY,
    Title NVARCHAR(100),
    CreationDate DATETIME,
    CorporationID INT ,
    MyID INT ,
    [GuidId1] [uniqueidentifier] NOT NULL,
    [GuidId2] [uniqueidentifier] NOT NULL,
    [Code] [nvarchar](50) NULL
)
ALTER TABLE [dbo].[Table02] ADD  DEFAULT (GETDATE()) FOR [CreationDate]
GO
ALTER TABLE [dbo].[Table02] ADD  DEFAULT (NEWSEQUENTIALID()) FOR [GuidId1]
GO
ALTER TABLE [dbo].[Table02] ADD  DEFAULT (NEWID()) FOR [GuidId2]
GO

第 03 部分=>

/*03Add Data*/
DECLARE @I INT = 1
WHILE @I < 1000000
BEGIN
    DECLARE @Title NVARCHAR(100) = 'TITLE '+ CAST(@I AS NVARCHAR(10)), 
            @CorporationID INT = CAST((RAND()*20) + 1 AS INT), 
            @Code NVARCHAR(50) = 'CODE '+ CAST(@I AS NVARCHAR(10)) ,
            @MyID INT  = CAST((RAND()*50) + 1 AS INT)

    INSERT INTO Table01 (Title , CorporationID , Code , MyID )
    VALUES ( @Title  , @CorporationID , 'CODE '+ @Code , @MyID)

SET @I += 1
END
INSERT INTO Table02 ([Title], [CreationDate], [CorporationID], [MyID], [GuidId1], [GuidId2], [Code])
SELECT [Title], [CreationDate], [CorporationID], [MyID], [GuidId1], [GuidId2], [Code] FROM Table01

第 04 部分=>

/*04 CREATE INDEX*/
CREATE NONCLUSTERED INDEX IX_Table01_ALL
ON Table01  (CorporationID) INCLUDE (MyID)  ;
DECLARE @QUERY  NVARCHAR(MAX) = ''
DECLARE @J INT = 1
WHILE @J < 21 
BEGIN
    SET @QUERY += ' 

    CREATE NONCLUSTERED INDEX  IX_Table02_'+CAST(@J AS NVARCHAR(5))+'
    ON Table02 (CorporationID) INCLUDE (MyID)  WHERE CorporationID = '+CAST(@J AS NVARCHAR(5))+';'
    SET @J+= 1
END
EXEC (@QUERY)

第 05 部分=>

/*05 READ DATA => PUSH Button CTRL + M ( EXECUTION PLAN) */
SET STATISTICS IO ON
SET STATISTICS TIME ON
    SELECT * FROM [dbo].[Table01] WHERE CorporationID = 10  AND MyID = 25
    SELECT * FROM [dbo].[Table01] WITH(INDEX(IX_Table01_ALL)) WHERE CorporationID = 10  AND MyID = 25
    SELECT * FROM [dbo].[Table02] WITH(INDEX(IX_Table02_10))  WHERE CorporationID = 10  AND MyID = 25
SET STATISTICS IO OFF
SET STATISTICS TIME OFF

注意 IO 、 TIME 和 EXECUTION PLAN 。 祝你好运

【讨论】:

这很有趣。 Sql Server 有一个特性,几百万年没人会用。如果你在研究大数据,你会发现你的愿景是数百万年前的。会计业务、银行业务等 用过这个功能。很抱歉浪费我的时间回复你

以上是关于如果一个表有一个未索引列与索引列是一对多的关系,如何优化未索引列的查询?的主要内容,如果未能解决你的问题,请参考以下文章

mysql外键(FOREIGNKEY)介绍及使用注意事项

分组方式不使用索引

Laravel 5.5中的一对多关系

mysql插表简单处理办法

mysql插表简单处理办法

mysql外键详解