SQL - 为复杂动态行选择查询

时间:2012-02-21 11:11:13

标签: sql

我需要根据搜索条件从下表中检索ListingId。请帮助以最佳方式检索以下条件的查询

注意:ListingId可以有任意数量的ExtrafieldId,因此搜索ListingId基于动态ExtrafieldId

If (ExtrafieldId = 1 and Value = 1) => OUTPUT - 20, 22
If (ExtrafieldId = 1 and Value = 1) and (ExtrafieldId = 2 and Value = 7) => OUTPUT - 21
If (ExtrafieldId =4and Value = 1999) => OUTPUT - 20, 21, 23

依旧......

ListingId   ExtraFieldId    Value      
20  1   1      
20  2   4      
20  3          
20  4   1990       
21  1   2      
21  2   7      
21  3          
21  4   1990       
22  1   1      
22  2   4      
22  3          
22  4   2000       
23  1   NULL       
23  2   NULL       
23  4   1999    

3 个答案:

答案 0 :(得分:2)

使用HAVING而不是自我加入。由于不需要连接且只有1个表扫描,因此效率更高。这也意味着如果有多个条件,它只需要HAVING条款中的附加表达式而不是额外的连接。

e.g。第二个例子:

SELECT  ListingID
FROM    [YourTable]
GROUP BY ListingID
HAVING  COUNT(CASE WHEN ExtrafieldId = 1 AND Value = 1 THEN 1 END) > 0
AND     COUNT(CASE WHEN ExtrafieldId = 2 AND Value = 7 THEN 1 END) > 0

<强>附录

以上是完全错误的。我认为它在眼睛上稍微容易一点,但下面的效率要高得多。

SELECT  t1.ListingID
FROM    Listing AS t1 
        INNER JOIN Listing AS t2
            ON t2.ListingID = t1.ListingID 
        INNER JOIN Listing AS t3
            ON t3.ListingID = t1.ListingID 
        INNER JOIN Listing AS t4
            ON t4.ListingID = t1.ListingID 
WHERE   (t1.ExtraFieldID = 1 AND t1.Value = 1)
AND     (t2.ExtraFieldID = 2 AND t2.Value = 7)
AND     (t3.ExtraFieldID = 3 AND t3.Value = '')
AND     (t4.ExtraFieldID = 4 AND t4.Value = 1999)

为了证明这一点,我运行了以下代码来测试它:

DECLARE @Iterations INT, @Listings INT
/*******************************************************************************************************
SET THE PARAMETERS FOR THE TEST HERE, @Listings IS THE NUMBER OF ListingIDs TO INSERT INTO THE SAMPLE
TABLE. EACH LISTING GETS 4 RECORDS SO 10,000 LISTINGS WILL GENERATE A SAMPLE OF 40,000 RECORDS ETC.
@Iterations IS THE NUMBER OF SELECTS TO PERFORM TO TEST THE PERFORMANCE OF EACH METHOD.
*******************************************************************************************************/
SET @Iterations = 500
SET @Listings = 1000000
/*******************************************************************************************************/
/*******************************************************************************************************/

IF EXISTS (SELECT * FROM TempDB.INFORMATION_SCHEMA.TABLES WHERE Table_Name LIKE '#Listing%')
    BEGIN
        DROP TABLE #Listing
    END

CREATE TABLE #Listing (ListingID INT NOT NULL, ExtraFieldID TINYINT NOT NULL, Value VARCHAR(4), PRIMARY KEY (ListingID, ExtraFieldID))

IF EXISTS (SELECT * FROM TempDB.INFORMATION_SCHEMA.TABLES WHERE Table_Name LIKE '#Results%')
    BEGIN
        DROP TABLE #Results
    END

CREATE TABLE #Results (GroupBy INT, SelfJoin INT)

DECLARE @i INT, @Time DATETIME, @Time2 DATETIME, @t INT
SET @i = ISNULL((SELECT MAX(ListingID) + 1 FROM #Listing), 0)
-- FILL LISTING TABLE WITH RANDOM VALUES
WHILE @i < @Listings
    BEGIN
        INSERT #Listing VALUES (@i, 1, ROUND(RAND() * 4, 0))
        INSERT #Listing VALUES (@i, 2, ROUND(RAND() * 20, 0))
        INSERT #Listing VALUES (@i, 3, CASE WHEN ROUND(RAND(), 0) = 0 THEN '' ELSE CONVERT(VARCHAR(4), ROUND(RAND(), 3) * 1000) END)
        INSERT #Listing VALUES (@i, 4, DATEPART(YEAR, DATEADD(YEAR, (RAND()-1) * 100, GETDATE())))

        SET @i = @i + 1
    END

CREATE NONCLUSTERED INDEX #IX_Listing_Value ON #Listing (Value) WITH FILLFACTOR = 100

SET @i = 0
-- PERFORM BOTH METHODS X NUMBER OF TIMES TO GET AN AVERAGE EXECUTION TIME
WHILE @i < @Iterations
    BEGIN
        SET @Time = GETDATE()

        SELECT  @t = COUNT(*)
        FROM    (   SELECT  ListingID
                    FROM    #Listing
                    GROUP BY ListingID
                    HAVING  COUNT(CASE WHEN ExtrafieldId = 1 AND Value = 1 THEN 1 END) > 0
                    AND     COUNT(CASE WHEN ExtrafieldId = 2 AND Value = 7 THEN 1 END) > 0
                    AND     COUNT(CASE WHEN ExtrafieldId = 3 AND Value = '' THEN 1 END) > 0
                    AND     COUNT(CASE WHEN ExtrafieldId = 4 AND Value = 1999 THEN 1 END) > 0
                ) D

        SET @Time2 = GETDATE()

        SELECT  @t = COUNT(*)
        FROM    (   SELECT  t1.ListingID
                    FROM    #Listing AS t1 
                            JOIN #Listing AS t2
                                ON t2.ListingID = t1.ListingID 
                            JOIN #Listing AS t3
                                ON t3.ListingID = t1.ListingID 
                            JOIN #Listing AS t4
                                ON t4.ListingID = t1.ListingID 
                    WHERE   (t1.ExtraFieldID = 1 AND t1.Value = 1)
                    AND     (t2.ExtraFieldID = 2 AND t2.Value = 7)
                    AND     (t3.ExtraFieldID = 3 AND t3.Value = '')
                    AND     (t4.ExtraFieldID = 4 AND t4.Value = 1999)
                ) D

        INSERT INTO #Results
        SELECT  DATEDIFF(MICROSECOND, @Time, @Time2) [GroupBy],
                DATEDIFF(MICROSECOND, @Time2, GETDATE()) [SelfJoin]

        SET @i = @i + 1
    END

IF NOT EXISTS (SELECT 1 FROM TempDB.INFORMATION_SCHEMA.TABLES WHERE Table_Name LIKE '#OverallResults%')
    BEGIN
        CREATE TABLE #OverallResults (GroupBy INT NOT NULL, SelfJoin INT NOT NULL, Iterations INT NOT NULL, Listings INT NOT NULL)
    END
INSERT INTO #OverallResults
SELECT  AVG(GroupBy) [Group By],
        AVG(SelfJoin) [Self Join],
        COUNT(*) [Iterations],
        @Listings
FROM    #Results

SELECT  AVG(GroupBy) [Group By],
        AVG(SelfJoin) [Self Join],
        COUNT(*) [Iterations],
        CONVERT(DECIMAL(5, 4), (AVG(GroupBy) - AVG(SelfJoin)) / 1000000.0) [Difference (Seconds)],
        CONVERT(DECIMAL(4, 2), 100 * (1 - (1.0 * AVG(SelfJoin) / AVG(GroupBy)))) [Percent Faster]
FROM    #Results

DROP TABLE #Listing
DROP TABLE #results

SELECT  Records,    
        Iterations,
        GroupBy [Group By],
        SelfJoin [Self Join],
        CONVERT(DECIMAL(5, 4), (GroupBy - SelfJoin) / 1000000.0) [Difference (Seconds)],
        CONVERT(DECIMAL(4, 2), 100 * (1 - (1.0 * SelfJoin / GroupBy))) [Percent Faster]
FROM    (   SELECT  Listings * 4 [Records], 
                    SUM(Iterations) [Iterations],
                    SUM(GroupBy * Iterations) / SUM(Iterations) [GroupBy],
                    SUM(SelfJoin * Iterations) / SUM(Iterations) [SelfJoin]
            FROM    #OverallResults
            GROUP BY Listings
        ) a

这可以使用不同的变量反复运行。我为100,1000,10000,100000和1000000个列表运行了这个列表,每个列表都有500个选择语句,以获得平均执行时间,这表明自加入平均快约60%,直到1,000,000个列表,当它变得快95%时。自我加入方法显然是性能优胜者。

答案 1 :(得分:1)

SELECT
      t1.ListingID
FROM 
      TableX AS t1 

  JOIN                                --- 2nd JOIN
      TableX AS t2
    ON 
      t2.ListingID = t1.ListingID 

  JOIN                                --- 3rd JOIN
      TableX AS t3
    ON 
      t3.ListingID = t1.ListingID 

WHERE 
      (t1.ExtraFieldID, t1.Value) = (@ExtraFieldID_search1, @Value_search1)

                        --- 2nd condition
  AND 
      (t2.ExtraFieldID, t2.Value) = (@ExtraFieldID_search2, @Value_search2)

                        --- 3rd condition
  AND 
      (t3.ExtraFieldID, t3.Value) = (@ExtraFieldID_search3, @Value_search3)

如果你需要3个条件,你需要再次将表连接到自己(所以总共3次)

答案 2 :(得分:0)

你可以很容易地使用union和distinct。如果您使用ListingId作为使用IN子句的另一个查询的输入,则不必考虑重复项,否则您可以添加

SELECT DISTINCT ListingId FROM (
  SELECT
    ListingId
  ... -- the rest from below
) AS Data

这是获取列表的查询(可能有重复项!):

SELECT
  ListingID
FROM
  TABLE_NAME
WHERE
  ExtrafieldId = 1 and Value = 1
UNION ALL
SELECT
  ListingID
FROM
  TABLE_NAME
WHERE
  ExtrafieldId = 1 AND Value = 1 AND ExtrafieldId = 2 and Value = 7
UNION ALL
SELECT
  ListingID
FROM
  TABLE_NAME
WHERE
  ExtrafieldId = 4 AND Value = 1999