我没有空间让标题更具描述性,抱歉,但这正是我想要实现的目标:
我正在为网页上的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秒。
非常感谢您的回复!
答案 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;
}