将 Excel 数据“日期范围”传递给 SQL 查询

Posted

技术标签:

【中文标题】将 Excel 数据“日期范围”传递给 SQL 查询【英文标题】:Pass Excel Data "Date Range" to SQL Query 【发布时间】:2021-05-19 20:22:00 【问题描述】:

我有一个 115 行的大型 SQL 代码,我的任务是放入 Excel 中,并允许使用两个单元格的用户更改 SQL 代码的日期范围并显示结果。我已经尝试了其他几个主题,但没有成功。我正在使用 Microsoft 365。每个用户在其 PC 上都有一个名为“Reports55”的 ODBC 连接 在 excel 中,我有两个选项卡(摘要)和(SQLData)。在(摘要)选项卡上,单元格 A2 是 1StartDate,单元格 B2 是 2EndDate。 (SQLDate) 选项卡是显示 SQL 查询数据的地方。任何帮助将不胜感激。

技能水平

VBA:初学者(请为这个老家伙哑巴) SQL:中级 Excel:中级
DECLARE @1StartDate AS DATE
DECLARE @2EndDate AS DATE
SET @1StartDate = '2020-04-27'
SET @2EndDate = '2021-05-27'

SELECT 
    'SHIP' AS RPT ,
    SO_Detail_Ext.LateReason ,
    (SO_Detail.ordnum_28 + SO_Detail.linnum_28 + SO_Detail.delnum_28) AS "Order",   
    SO_Detail.custid_28 AS "CustID",
    SO_Detail.Prtnum_28 AS "Part",
    CONVERT(VARCHAR(10), SO_Master.ORDDTE_27, 101) AS "OrdDte",
    CONVERT(VARCHAR(10), SO_Detail.Shpdte_28, 101) AS "Shpdte",
    ExactMAX.dbo.NumShopDays(SO_Master.ORDDTE_27,SO_Detail.shpdte_28) AS "LT_ToShip",
    CONVERT(VARCHAR(10), SO_Detail.ORGDUE_28, 101) AS "OrgDue" ,
    CONVERT(VARCHAR(10), SO_Detail.Curdue_28, 101) AS "Curdue_PDSL",
    CONVERT(VARCHAR(10), SO_Detail.CUSDUE_28, 101) AS "Custdue_RDSL",
    SO_Detail.Price_28 AS "Price",
    SO_Detail.Shpqty_28 AS "ShpQty",
    Customer_Master.udfkey_23 AS "UDFKey",
    Customer_Master.Slster_23 AS "Territory",
    Account_Types.DESCRPTN_104 AS "AccType",
    Customer_Master.SLSTER_23 AS "WordArea" , --'Undefined World Area'
    Customer_Master_Ext.CustomerClass ,
    (SO_Detail.Price_28 * SO_Detail.Shpqty_28) AS "Extended", 
    CAST('1' AS INTEGER) AS "Shipped",
    CASE WHEN SO_Detail.Shpdte_28 <= SO_Detail.Curdue_28  THEN 1 ELSE 0 END AS "PDSL_OnTime", 
    CASE 
    WHEN SO_Detail_Ext.LateReason = 'SSI' THEN '1' --Customer caused late order
    WHEN SO_Detail.shpdte_28 <= SO_Detail.Cusdue_28  THEN 1 ELSE 0 END AS "RDSL_OnTime", 
    CASE WHEN Customer_Master_Ext.CustomerClass = 'TRADE' 
    THEN 1 ELSE 0 END AS "Trade_Shipped", --01 Total Trade Lines Shipped
    CASE WHEN SO_Detail.Shpdte_28 <= SO_Detail.Curdue_28
    AND Customer_Master_Ext.CustomerClass = 'TRADE' THEN 1 ELSE 0 END AS "Trade_Ontime_PDSL",
    CASE WHEN SO_Detail.Shpdte_28 <= SO_Detail.CUSDUE_28 
    AND Customer_Master_Ext.CustomerClass = 'TRADE' THEN 1 ELSE 0 END AS "Trade_Ontime_RDSL",
    CASE WHEN Customer_Master_Ext.CustomerClass = 'TRADE'
    AND isnull(Part_Master_Ext.ExpressShip, 0) = '1' THEN 1 ELSE 0 END AS "Trade_Exp_Shipped", 
    CASE WHEN SO_Detail.Shpdte_28 <= SO_Detail.CUSDUE_28 
    AND Customer_Master_Ext.CustomerClass = 'TRADE'
    AND isnull(Part_Master_Ext.ExpressShip, 0) = '1' THEN 1 ELSE 0 END AS "Trade_EXP_Ontime_RDSL", 
    CASE WHEN SO_Detail.Shpdte_28 <= SO_Detail.Curdue_28 
    AND Customer_Master_Ext.CustomerClass = 'TRADE'
    AND isnull(Part_Master_Ext.ExpressShip, 0) = '1' THEN 1 ELSE 0 END AS "Trade_EXP_Ontime_PDSL", 
    CASE WHEN Customer_Master_Ext.CustomerClass = 'TRADE'
    AND isnull(Part_Master_Ext.ExpressShip, 0) <> '1' THEN 1 ELSE 0 END AS "Trade_MTO_Shipped", 
    CASE WHEN SO_Detail.Shpdte_28 <= SO_Detail.CUSDUE_28 
    AND Customer_Master_Ext.CustomerClass = 'TRADE'
    AND isnull(Part_Master_Ext.ExpressShip, 0) <> '1' THEN 1 ELSE 0 END AS "Trade_MTO_Ontime_RDSL", 
    CASE WHEN SO_Detail.Shpdte_28 <= SO_Detail.Curdue_28 
    AND Customer_Master_Ext.CustomerClass = 'TRADE'
    AND isnull(Part_Master_Ext.ExpressShip, 0) <> '1' THEN 1 ELSE 0 END AS "Trade_MTO_Ontime_PDSL", 
    CASE WHEN Customer_Master_Ext.CustomerClass IN ('INTERCO', 'INTRA-CO') 
    THEN 1 ELSE 0 END AS "Intraco_Shipped", -- 03 Intraco Lines Shipped
    CASE WHEN SO_Detail.Shpdte_28 <= SO_Detail.Curdue_28 
    AND Customer_Master_Ext.CustomerClass IN ('INTERCO', 'INTRA-CO') 
    THEN 1 ELSE 0 END AS "Intraco_OnTime_PDSL",
    CASE 
    WHEN SO_Detail_Ext.LateReason = 'SSI' AND Customer_Master_Ext.CustomerClass IN ('INTERCO', 'INTRA-CO') THEN '1' --Customer caused late order
    WHEN SO_Detail.Shpdte_28 <= SO_Detail.CUSDUE_28 
    AND Customer_Master_Ext.CustomerClass IN ('INTERCO', 'INTRA-CO')
    THEN 1 ELSE 0 END AS "Intraco_Ontime_RDSL",
    CASE WHEN isnull(Part_Master_Ext.ExpressShip, 0) = '1'
    THEN 1 ELSE 0 END AS "EXP_Shipped",
    CASE WHEN SO_Detail.Shpdte_28 <= SO_Detail.CURDUE_28 
    AND isnull(Part_Master_Ext.ExpressShip, 0) = '1' THEN 1 ELSE 0 END AS "EXP_Ontime_PDSL",
    CASE 
    WHEN SO_Detail_Ext.LateReason = 'SSI' AND isnull(Part_Master_Ext.ExpressShip, 0) = '1' THEN '1'
    WHEN SO_Detail.Shpdte_28 <= SO_Detail.CUSDUE_28 
    AND isnull(Part_Master_Ext.ExpressShip, 0) = '1' THEN 1 ELSE 0 END AS "EXP_Ontime_RDSL" ,
ExactMAX.dbo.NumShopDays(SO_Master.ORDDTE_27,SO_Detail.shpdte_28) AS "AVG_LT_Days" ,
ExactMAX.dbo.NumShopDays(SO_Master.ORDDTE_27,SO_Detail.Cusdue_28) AS "RDSL_AVG_LT_Days" ,
ExactMAX.dbo.NumShopDays(SO_Master.ORDDTE_27,SO_Detail.Curdue_28) AS "PDSL_AVG_LT_Days" ,
CASE WHEN Customer_Master_Ext.CustomerClass = 'TRADE'
AND isnull(Part_Master_Ext.ExpressShip, 0) <> '1' 
THEN ExactMAX.dbo.NumShopDays(SO_Master.ORDDTE_27,SO_Detail.shpdte_28) 
ELSE NULL END AS "Trade_MTO_AVGLT" ,  
CASE WHEN Customer_Master_Ext.CustomerClass IN ('INTERCO', 'INTRA-CO')
THEN ExactMAX.dbo.NumShopDays(SO_Master.ORDDTE_27,SO_Detail.shpdte_28) 
ELSE NULL END AS "Intraco_AVG_LT" , 
CAST('0' AS FLOAT) AS "Trade_Backlog_PDSL" , --09 Trade $$ Past Due Backlog by PDSL
CAST('0' AS FLOAT) AS "Trade_Backlog_RDSL" ,  --10 Trade $$ Past Due Backlog by RDSL
CASE WHEN Customer_Master_Ext.CustomerClass = 'TRADE'
THEN (SO_Detail.Price_28 * SO_Detail.Shpqty_28)
ELSE NULL
END AS "Trade_Sales$$" ,
NULL AS "Total_PastDue_Dollars_RDSL" , 
NULL AS "Total_PastDue_Lines_RDSL" , 
NULL AS "Total_PastDue_Dollars_PDSL" , 
NULL AS "Total_PastDue_Lines_PDSL" ,  
SO_Detail.CreatedBy ,
SO_Detail.CreationDate ,
Part_Sales.CRTLTO_29
    
FROM 
    ExactMAX.dbo.SO_Detail LEFT JOIN
    ExactMAX.dbo.Part_Sales ON
    SO_Detail.PRTNUM_28 = Part_Sales.PRTNUM_29 LEFT JOIN
    ExactMAX.dbo.SO_Master ON
    SO_Detail.ORDNUM_28=SO_Master.ORDNUM_27 LEFT JOIN 
    ExactMAX.dbo.part_Master ON 
    SO_Detail.prtnum_28 = part_master.prtnum_01 LEFT JOIN
    ExactMAX.dbo.Account_Types ON
    Part_Master.ACTTYP_01=Account_Types.ACTTYP_104 LEFT JOIN 
    ExactMAX.dbo.CUSTOMER_MASTER ON 
    SO_Detail.CUSTID_28 = CUSTOMER_MASTER.CUSTID_23 LEFT JOIN
    ExactMAX.dbo.Part_Master_Ext ON
    SO_Detail.prtnum_28 = part_master_ext.prtnum_01 LEFT JOIN
    ExactMAX.dbo.Customer_Master_Ext ON
    Customer_Master.CUSTID_23 = Customer_Master_Ext.CUSTID_23 LEFT JOIN
    ExactMAX.dbo.SO_Detail_Ext ON
    SO_Detail_ext.ORDER_LIN_DEL = (SO_Detail.ORDNUM_28 + SO_Detail.LINNUM_28 + SO_Detail.DELNUM_28)

WHERE
    SO_Detail.Shpdte_28 BETWEEN @1StartDate AND @2EndDate
    AND SO_Detail.Status_28 IN ('4', '5') 
    AND Part_Master.Acttyp_01 <> '' 
    AND Part_Master.TYPE_01 IN ('A', 'C', 'P', 'S', 'X')

【问题讨论】:

This 文章应该可以帮助您。 【参考方案1】:

考虑参数化查询的以下步骤:

SQL

    将长而复杂的 SQL 保存在 .sql 文本文件中。以您需要的任何方式使用换行符和缩进进行格式化。保存在与工作簿相同的文件夹或集中位置,供所有用户在 VBA 中阅读。

    删除 DECLARESET 行,仅保留单个 SELECT 命令。

    SELECT 查询中,将每个 @ 变量替换为 qmarks ?,以便在 VBA 中进行参数化。

    WHERE
        SO_Detail.Shpdte_28 BETWEEN ? AND ?
    

VBA

    在工作簿模块或独立模块中启动 VBA Sub 子例程。

    设置与数据库的 ADO ODBC 连接。许多在线示例和教程。

    将 SQL 查询读入字符串变量:

    ' READ SQL QUERY FROM FILE INTO STRING
    With CreateObject("Scripting.FileSystemObject")
          strSQL = .OpenTextFile("C:\path\to\my\SQL\Query.sql", 1).readall
    End With
    

    使用连接对象和查询打开一个 ADO 命令对象。从它们位于工作簿中的任何单元格创建并绑定两个日期参数。然后执行命令渲染一个 ADO 记录集。

    ' DEFINE COMMAND OBJECT
    
    Set cmd = New ADODB.Command
    With cmd
        .ActiveConnection = conn   ' CONNECTION OBJECT
        .CommandType = adCmdText
        .CommandText = strSQL      ' SQL QUERY
    
        ' BIND DATE PARAMETERS FOR ? IN SQL, ASSUMING startDate AND endDate ARE VBA DATES
        .Parameters.Append .CreateParameter("dtparam1", adDate, adParamInput, , startDate)
        .Parameters.Append .CreateParameter("dtparam2", adDate, adParamInput, , endDate)
    
        ' BIND OUTPUT TO RECORDSET
        Set rs = .Execute
    End With
    

    使用Range.CopyFromRecordset 将记录集输出到从最左侧列开始的工作表。请注意:不填充列。循环通过记录集Fields 获取此类列名。

    ThisWorkbook.Worksheets("SQLData").Range("A2").CopyFromRecordset rs
    

(在整个 VBA 代码中,请务必在最顶部使用 Option Explicit 指定 Dim 变量,并结合适当的错误处理,通过将对象设置为 Nothing(有或没有运行时错误)来关闭记录集和连接并释放 ASO 资源.)

【讨论】:

以上是关于将 Excel 数据“日期范围”传递给 SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章

SQL between and 日期范围 筛选数据不符

如何在 Excel 中使用 SQL 连接获取日期范围提示

将MS Access表单日期传递到Oracle SQL

如何使用 argparse Python 在 SQL 查询中传递日期范围参数

在 R 闪亮的日期范围内传递 SQl 查询

SQL中按日期进行查询,如何截取日期进行查询