从表中检索数据,其中一个表有多个列指向另一个中的同一个主键?

时间:2009-08-26 13:53:51

标签: sql sql-server

我不负责这个设计,但是我必须从这个类似于此的模式中提取数据(SQL Server 2000):

CREATE TABLE contract
(
    contract_id int,
    account_id int,             /* account table */
    responsible_id int,         /* account table */
    holding_id int,             /* account table */
    billingaddress_id int,      /* address table */
    deliveryaddress_id int,     /* address table */
)

CREATE TABLE address
(
    address_id int,
    postalcode char(4),
)

CREATE TABLE account
(
    account_id int,
    firstname varchar(40),
    billingaddress_id int,      /* address table */
    deliveryaddress_id int,     /* address table */
)

合约表上的account_id,responsible_id和holding_id可以为null,共享值或具有不同的值。它可能有也可能没有结算和/或送货地址。 帐户实体始终具有开票送达地址,两者都可以相同。

我必须找到与合同相关的所有帐户(即合同具有相同的帐户,负责或持有ID作为帐户ID),并且与具有特定邮政编码的地址相关联(直接或通过合同)。

问题似乎是2倍:
a)检索与合同相关的帐户 b)过滤(a)的结果以获得与某个邮政编码相关的帐户

这不起作用,因为如果account_id与相关的邮政编码没有关联但是holding_id是,那么它将不会被返回:

FROM account
INNER JOIN contract
    ON account.account_id = 
        CASE WHEN NOT IsNull(contract.account_id) THEN contract.account_id 
        WHEN NOT IsNull(contract.responsible_id) THEN contract.responsible_id 
        ELSE contract.holding_id END

由于某些原因,这太慢了(FK没有编入索引 - 等待30分钟而且没有返回):

FROM account
INNER JOIN contract
    ON account.account_id = contract.account_id
    OR account.account_id = contract.responsible_id
    OR account.account_id = contract.holding_id

唯一似乎有效的是UNION,但是我仍然遇到了按地址类型过滤结果的问题。

返回所需结果的最短方法是什么?目前,我倾向于创建一个临时表来存储中间数据。

2 个答案:

答案 0 :(得分:3)

SELECT  *
FROM    contract
WHERE   EXISTS
        (
        SELECT  NULL
        FROM    account
        JOIN    address
        ON      address_id IN (billingaddress_id, deliveryaddress_id)
        WHERE   account_id IN (account_id, responsible_id, holding_id)
                AND postalcode = @mycode
        )

要选择帐户,请使用:

SELECT  *
FROM    account ao
WHERE   EXISTS
        (
        SELECT  NULL
        FROM    (
                SELECT  account_id, responsible_id, holding_id
                FROM    contract c
                WHERE   c.account_id = ao.account_id
                UNION ALL
                SELECT  account_id, responsible_id, holding_id
                FROM    contract c
                WHERE   c.responsible_id = ao.account_id
                UNION ALL
                SELECT  account_id, responsible_id, holding_id
                FROM    contract c
                WHERE   c.holding_id = ao.account_id
                ) co
        JOIN    account ai
        ON      ai.account_id IN (co.account_id, co.responsible_id, co.holding_id)
        JOIN    address
        ON      address_id IN (billingaddress_id, deliveryaddress_id)
        WHERE   postalcode = @mycode
        )

<强>更新

由于您的列未编入索引,因此EXISTS在这种情况下效率不高,因为它不能作为IN重写。

您应该将所有连接条件重写为equijoins,以便HASH JOIN方法可用。

试试这个:

SELECT  a.account_id
FROM    (
        SELECT  account_id
        FROM    contract
        UNION
        SELECT  responsible_id
        FROM    contract
        UNION
        SELECT  holding_id
        FROM    contract
        ) c
JOIN    (
        SELECT  account_id, billingaddress_id AS address_id
        FROM    account
        UNION
        SELECT  account_id, deliveryaddress_id
        FROM    account
        ) a
ON      a.account_id = c.account_id
JOIN    address ad
ON      ad.address_id = a.address_id
WHERE   ad.postalcode = @mycode

答案 1 :(得分:0)

最后,我选择了这样的事情:

FROM (
SELECT Account.*
FROM   (SELECT Contract.Account_Id          AS ForeignKey_Id,
               Contract.DeliveryAddress_Id AS Address_Id
        FROM   Contract
        UNION
        SELECT Contract.Account_Id          AS ForeignKey_Id,
               Contract.DeliveryAddress_Id AS Address_Id
        FROM   Contract
        UNION
        SELECT Contract.Account_Id          AS ForeignKey_Id,
               Contract.BillingAddress_Id AS Address_Id
        FROM   Contract) ContractInfo
       JOIN Account Account
         ON Account.Name_Id = ForeignKey_Id
       JOIN Address
         ON Address.Address_Id = ContractInfo.Address_Id
            AND Address.PostalCode = 'ABCDE'
UNION
SELECT Account.*
FROM   (SELECT Contract.Responsible_Id        AS ForeignKey_Id,
               Contract.DeliveryAddress_Id AS Address_Id
        FROM   Contract
        UNION
        SELECT Contract.Responsible_Id        AS ForeignKey_Id,
               Contract.DeliveryAddress_Id AS Address_Id
        FROM   Contract
        UNION
        SELECT Contract.Responsible_Id        AS ForeignKey_Id,
               Contract.BillingAddress_Id AS Address_Id
        FROM   Contract) ContractInfo
       JOIN Account Account
         ON Account.Name_Id = ForeignKey_Id
       JOIN Address
         ON Address.Address_Id = ContractInfo.Address_Id
            AND Address.PostalCode = 'ABCDE'
UNION
SELECT Account.*
FROM   (SELECT Contract.Holding_Id     AS ForeignKey_Id,
               Contract.DeliveryAddress_Id AS Address_Id
        FROM   Contract
        UNION
        SELECT Contract.Holding_Id     AS ForeignKey_Id,
               Contract.DeliveryAddress_Id AS Address_Id
        FROM   Contract
        UNION
        SELECT Contract.Holding_Id    AS ForeignKey_Id,
               Contract.BillingAddress_Id AS Address_Id
        FROM   Contract) ContractInfo
       JOIN Account Account
         ON Account.Name_Id = ForeignKey_Id
       JOIN Address
         ON Address.Address_Id = ContractInfo.Address_Id
            AND Address.PostalCode = 'ABCDE'
) Account

它比使用每行子选择或IN子句表现更好。