SQL - 如何在没有循环或联合的语句中进行多个查询

时间:2014-02-19 20:35:36

标签: sql sql-server-2008

我没有空间让标题更具描述性,抱歉,但这正是我想要实现的目标:

我正在为网页上的Google直方图制作数据表。我正在查看客户和发票数据,我想获得上一时段(周,月,季度,年)每周的客户状态快照。我知道我的SELECT语句一周应该是什么,我可以使用UNION ALL来做,但是如果我想要在过去三年中每周都需要这样的话,这似乎要维护很多代码。我将为此示例删除其他过滤器(MS SQL 2008):

    SELECT COUNT(distinct customer.AccountNumber)
    FROM Datahub..InvoiceHold invoice 
        INNER JOIN Datahub..CustomerBase as customer ON invoice.AccountNumber = customer.AccountNumber
        INNER JOIN Datahub..OrderHold as orders ON invoice.AccountNumber = orders.AccountNumber
    WHERE customer.CoNo = '003' 
        AND customer.CustomerStartDate <= GETDATE() - 7
        AND invoice.InvoiceDate > GETDATE() - 7
        AND orders.OrderDate > GETDATE() - 7
    UNION ALL
    SELECT COUNT(distinct customer.AccountNumber)
    FROM Datahub..InvoiceHold invoice 
        INNER JOIN Datahub..CustomerBase as customer ON invoice.AccountNumber = customer.AccountNumber
        INNER JOIN Datahub..OrderHold as orders ON invoice.AccountNumber = orders.AccountNumber
    WHERE customer.CoNo = '003' 
        AND customer.CustomerStartDate <= GETDATE() - 14
        AND invoice.InvoiceDate > GETDATE() - 14
        AND orders.OrderDate > GETDATE() - 14
    UNION ALL........And so on

我已经考虑过在我的调用代码中动态编写SQL字符串,我可以轻松地在那里执行for循环并连接我发送到MS SQL 2008的查询字符串。但是如果我想在存储过程中执行此操作,我如何将其缩小到单一陈述?我已经读过在SQL中使用循环是一个禁忌,除了一小部分情况,我不明白使用游标。看起来这应该很简单,但我不是一个SQL人员。

修改 我尝试了所提出的解决方案,并没有得到我想要的那种性能,奇怪的是。这需要7分钟或更长时间。我不知道为什么。无论如何,我最终对数据进行了一些过滤并将其拉入内存,然后在dataTable上使用LINQ并从那里开始。它可能不漂亮,但只需要大约20秒。

非常感谢您的回复!

3 个答案:

答案 0 :(得分:0)

您可以使用以下内容创建一个临时表,无论您想要返回多少周,并使用分组来对数据进行分区。

这将使临时表中包含周数(在本例中为第1周 - 第150周)

declare @digits table 
(
    digit int
)
insert into @digits values (1)
insert into @digits values (2)
insert into @digits values (3)
insert into @digits values (4)
insert into @digits values (5)
insert into @digits values (6)
insert into @digits values (7)
insert into @digits values (8)
insert into @digits values (9)
insert into @digits values (0)

declare @weeks table
(
    weekNumber int
)
insert into @weeks
select a.digit * 100 + b.digit * 10 + c.digit
from @digits a
cross join @digits b
cross join @digits c
where a.digit * 100 + b.digit * 10 + c.digit <= 150
and a.digit * 100 + b.digit * 10 + c.digit > 0

然后你会在你的查询中做这样的事情:

SELECT w.weekNumber, COUNT(distinct customer.AccountNumber)
FROM Datahub..InvoiceHold invoice 
    INNER JOIN Datahub..CustomerBase as customer ON invoice.AccountNumber = customer.AccountNumber
    INNER JOIN Datahub..OrderHold as orders ON invoice.AccountNumber = orders.AccountNumber
    INNER JOIN @weeks w ON customer.CustomerStartDate <= DATEADD(dd, -(w.weekNumber * 7), GetDate())
                AND invoice.InvoiceDate > DATEADD(dd, -(w.weekNumber * 7), GetDate())
                AND orders.OrderDate > DATEADD(dd, -(w.weekNumber * 7), GetDate())
WHERE customer.CoNo = '003' 
GROUP BY w.weekNumber
ORDER BY w.weekNumber

答案 1 :(得分:0)

我认为你需要的是一张带有神奇数字的桌子。在Postgres中,您可以使用generate_series生成伪表。

 SELECT gs, COUNT(distinct customer.AccountNumber)
 FROM Datahub..InvoiceHold invoice 
    INNER JOIN Datahub..CustomerBase as customer 
    ON invoice.AccountNumber = customer.AccountNumber
    INNER JOIN Datahub..OrderHold as orders 
    ON invoice.AccountNumber = orders.AccountNumber
    JOIN generate_series(1, 10) AS gs /* 10?? no on clause, cross join */
WHERE customer.CoNo = '003' 
    AND customer.CustomerStartDate <= GETDATE() - 7*gs
    AND invoice.InvoiceDate > GETDATE() - 7*gs
    AND orders.OrderDate > GETDATE() - 7*gs
ORDER BY 1,2 /* column numbers */;

如果SQL Server没有这个,只需创建一个1到10(或其他)的表并交叉连接到那个。

答案 2 :(得分:0)

我尝试了所提出的解决方案,并没有得到我想要的那种性能,奇怪的是。这需要7分钟或更长时间。我不知道为什么。无论如何,我最终对数据进行了一些过滤并将其拉入内存,然后在dataTable上使用LINQ并从那里开始。它可能不漂亮,但只需要大约20秒。如果有人想要做类似的事情,这是我的代码:

public IList<object[]> GetBarChartData(string timeframe, string channel, string unit, string division)
    {
        DataTable dt = new DataTable();
        List<CustomerClass> myList = new List<CustomerClass>();
        List<object[]> retVal = new List<object[]>();
        CustomerClass myClass = new CustomerClass();

        string marketChannel = (channel == "ALL MARKET CHANNELS" ? string.Empty : "AND customer.MarketChannelDescription = '" + channel + "' ");
        string businessUnit = (unit == "ALL BUSINESS UNITS" ? string.Empty : "AND customer.BusinessUnitDescription = '" + unit + "' ");

        using (SqlConnection conn = ConnectionManager.DataHubConnection())
        {
            string sql = "SELECT customer.AccountNumber "
                    + ", customer.CustomerStartDate "
                    + ", MAX(invoice.InvoiceDate) as LastInvoice "
                    + ", MAX(orders.OrderDate) AS LastOrder "
                + "FROM Datahub..InvoiceHold as invoice "
                    + "INNER JOIN Datahub..CustomerBase as customer ON invoice.AccountNumber = customer.AccountNumber "
                    + "INNER JOIN Datahub..OrderHold as orders ON invoice.AccountNumber = orders.AccountNumber "
                + "WHERE customer.CoNo = '003' "
                    + "AND customer.CustomerStartDate BETWEEN GETDATE() - 1095 AND GETDATE() "
                    + marketChannel
                    + businessUnit
                + "GROUP BY customer.AccountNumber, customer.CustomerStartDate "
                + "ORDER BY LastInvoice ";

            using (SqlDataAdapter da = new SqlDataAdapter(sql, conn))
            {
                da.Fill(dt);
            }
        }

        string foo = string.Empty;
        foreach (DataRow row in dt.Rows)
        {
            myClass = new CustomerClass();
            myClass.AccountNumber = row["AccountNumber"].ToString();
            myClass.CustomerStartDate = row["CustomerStartDate"] == DBNull.Value ? new DateTime(1900,1,1) : Convert.ToDateTime(row["CustomerStartDate"]);
            myClass.LastInvoiceDate = row["LastInvoice"] == DBNull.Value ? new DateTime(1900, 1, 1) : Convert.ToDateTime(row["LastInvoice"]);
            myClass.LastOrderDate = row["LastOrder"] == DBNull.Value ? "1-1-1900" : Convert.ToDateTime(row["LastOrder"]).ToString("d");
            myList.Add(myClass);
        }

        // Use LINQ to break this data into series and then write string

        // How many weeks do we need
        int weeks = 1;
        switch (timeframe)
        {
            case "Last Year":
                weeks = 104;
                break;
            case "This Year":
                weeks = 52;
                break;
            case "Last Quarter":
                weeks = 26;
                break;
            case "This Quarter":
                weeks = 13;
                break;
            case "Last Month":
                weeks = 8;
                break;
            case "This Month":
                weeks = 4;
                break;
            case "Last Week":
                weeks = 2;
                break;
            case "This Week":
                weeks = 1;
                break;
        }


        object[] myArray = { "Class", "Lost", "Active", "Prospect" };
        retVal.Add(myArray);


        for (int i = 1; i <= weeks; i++)
        {
            var dateOffset = -(i * 7);
            DateTime date = DateTime.Today.AddDays(dateOffset);

            var prospect =
                from c in myList
                where c.CustomerStartDate <= date
                    && c.LastInvoiceDate > date
                    && c.LastOrderDate > date
                select c;
            var prospectCount = prospect.Count();

            var active =
                from c in myList
                where c.CustomerStartDate <= date
                    && c.LastInvoiceDate >= date.AddDays(-365)
                select c;
            var activeCount = active.Count();

            var lost =
                from c in myList
                where c.CustomerStartDate <= date
                    && c.LastInvoiceDate <= date.AddDays(-365)
                    && c.LastInvoiceDate >= date.AddDays(-730)
                select c;
            var lostCount = lost.Count();

            myArray = new object[] { i, lostCount, activeCount, prospectCount };
            retVal.Add(myArray);

        }

        return retVal;
    }