CTE 和 Substring/CHARINDEX

huangapple go评论67阅读模式
英文:

CTE and Substring/CHARINDEX

问题

我认为CTE(公共表达式)有点像一个临时表,我可以从以下查询中引用它。

但是,我对只在使用最后一个查询的WHERE子句时出现的错误感到困惑。

CertifiedBy字段包含name:date:id:格式的数据。我正在检查certifiedBy是否为NULL,如果是,将CERT_DATE设置为1/1/1970,否则从CertifiedBy中提取日期并将其放入CERT_DATE字段。

如果我运行查询的第一部分,直到SELECT * FROM Certified,我得到了预期的结果,CERT_DATE都是正确的。

但是,当我添加/执行WHERE CERT_DATE BETWEEN '2/1/2023' AND '2/22/2023'时,我会收到错误消息:

传递给LEFT或SUBSTRING函数的无效长度参数。

我原以为一旦Certified被创建,我可以引用它的数据?从错误信息来看,似乎它仍然在尝试提取日期?

有什么线索吗?谢谢。

英文:

I thought CTE was kinda like a temporary table where I could reference to from the following query.
I'm confused on the error I am getting ONLY when using the WHERE of the last query.

Certifiedby field has name:date:id:.
I am checking to see if certifiedby is NULL, if so make CERT_DATE=1/1/1970, otherwise extract the date from Certifiedby into CERT_DATE.

If I run the first part of the query up to SELECT * FROM Certified
I get the expected results and the CERT_DATE are all correct.

But when I add/execute the WHERE CERT_DATE BETWEEN '2/1/2023' AND '2/22/2023' I get the error

> Invalid length parameter passed to the LEFT or SUBSTRING function.

I thought once Certified was created I could reference it's data? From the error it seems it's still trying to extract the date?

Clues? thank you

SQLFiddle Sample

WITH Certified AS (
    select
        certifiedBy,
        CASE WHEN certifiedBy IS NULL  THEN '1/1/1970' ELSE SUBSTRING(certifiedBy, CHARINDEX(':', certifiedBy, 1) + 1, CHARINDEX(':', certifiedBy, 
        CHARINDEX(':', certifiedBy, 1) + 1) - CHARINDEX(':', certifiedBy, 1) - 1)  
	    END AS CERT_DATE
    From dbo.PO_Orders 
    WHERE poreference = 'shev'  
    )
SELECT * FROM Certified 
WHERE CERT_DATE BETWEEN '2/1/2023' AND '2/22/2023' 
ORDER BY CERT_DATE

I'm expecting to get back all records with CERT_DATE within the date range.

答案1

得分: 0

I think you need to evaluate your derived date as either a date for format it yyyyMMdd. You cannot really compare M/d/yyyy.

in SQL parlance,

SELECT CASE WHEN '1/1/1970' < '10/1/1902' THEN 1 ELSE 0 END;

result:

-----------
1

And, as far as CTE's go, they're not very different from sub-queries, except you can also do cool things like recursive queries.

In your SQL Fiddle, a query that works is:

WITH Certified AS
(
    SELECT certifiedBy,
        CONVERT(DATE, CASE WHEN certifiedBy IS NULL THEN '19700101'
            ELSE
                SUBSTRING (
                    certifiedBy, CHARINDEX (':', certifiedBy, 1) + 1,
                    CHARINDEX (':', certifiedBy, CHARINDEX (':', certifiedBy, 1) + 1) - CHARINDEX (':', certifiedBy, 1) - 1
                )
        END) AS CERT_DATE
    FROM dbo.PO_Orders
    WHERE poreference = 'shev'
)
SELECT *
FROM Certified
WHERE CERT_DATE  BETWEEN '20230201' AND '20230222' 
ORDER BY CERT_DATE;

Note: removed dashes from the DATE strings per @joel-coehoorn's comment. Apparently certain regions will flip month and day when using 'yyyy-MM-dd' format.

英文:

I think you need to evaluate your derived date as either a date for format it yyyyMMdd. You cannot really compare M/d/yyyy.

in SQL parlance,

SELECT CASE WHEN '1/1/1970' < '10/1/1902' THEN 1 ELSE 0 END;


result:

-----------
1

And, as far as CTE's go, they're not very different from sub-queries, except you can also do cool things like recursive queries.

In your SQL Fiddle, a query that works is:

WITH Certified AS
(
	SELECT certifiedBy,
		CONVERT(DATE, CASE WHEN certifiedBy IS NULL THEN '19700101'
			ELSE
				SUBSTRING (
					certifiedBy, CHARINDEX (':', certifiedBy, 1) + 1,
					CHARINDEX (':', certifiedBy, CHARINDEX (':', certifiedBy, 1) + 1) - CHARINDEX (':', certifiedBy, 1) - 1
				)
		END) AS CERT_DATE
	FROM dbo.PO_Orders
	WHERE poreference = 'shev'
)
SELECT *
FROM Certified
WHERE CERT_DATE  BETWEEN '20230201' AND '20230222' 
ORDER BY CERT_DATE;

Note: removed dashes from the DATE strings per @joel-coehoorn's comment. Apparently certain regions will flip month and day when using 'yyyy-MM-dd' format.

答案2

得分: 0

问题出在CTE中的CERT_DATE不是一个日期。它是一个字符串,对于某些文化背景的人来说,它看起来有点像日期... 但计算机和数据库只看到字符串。因此,BETWEEN运算符执行的是字符串之间的顺序比较,而不是日期比较。

在处理计算机时,有一个重要的概念需要理解:

文化/国际化问题意味着在字符串和日期(或数值)值之间进行转换要比我们通常期望的要昂贵和容易出错。尽量避免这种情况。

这有一些重要的影响:

  1. 服务器上的日期和日期时间值在内部根本不存储为字符串;它不是人类可读的格式。

  2. 使用字符串进行日期操作通常是最慢且最不可靠的选项。

  3. 在代码中有一些首选的日期文字格式,通常与您自己的文化背景不匹配。在SQL Server上,您有以下三个选项,不应偏离这些选项:

    yyyy-MM-ddTHH:mm:ss[.fff]
    yyyyMMdd HH:mm:ss[.fff]
    yyyyMMdd

有了这个理念,我们可以像这样重新编写原始代码,以便遵循更好的实践并获得预期的结果:

WITH Certified AS (
    SELECT certifiedBy,
        COALESCE(DATEFROMPARTS(  
           RIGHT(SUBSTRING(CertifiedBy, 1, CHARINDEX(':', certifiedby, CHARINDEX('/', certifiedby) - 1), 4), -- 年份  
           REPLACE(SUBSTRING(certifiedBy, CHARINDEX(':', certifiedBy, 1) + 1, 2), '/', ''), -- 月份
           REPLACE(SUBSTRING(certifiedBy, CHARINDEX('/', certifiedBy, 1) + 1, 2), '/', '') -- 日
        ), '19700101') as CERT_DATE
    FROM dbo.PO_Orders 
    WHERE  poreference = 'shev'  
)
SELECT  * 
FROM Certified 
WHERE CERT_DATE >= '20230201' AND CERT_DATE < '20230223' 
ORDER BY CERT_DATE

在这里查看它:

https://dbfiddle.uk/TUBKuAr6

当然,如果您可以在INSERT/UPDATE时将其作为实际的日期时间列捕获到应用程序中,那将性能提高了几个数量级。

英文:

The problem is the CERT_DATE column in the CTE IS NOT A DATE. It's a STRING that happens to look kinda like a date to people with certain cultural backgrounds... but the computer and database only see the string. Therefore the BETWEEN operator is doing an ordinal comparison between strings, rather than date comparisons.

There's an important concept to understand about date and datetime values when working with computers:

> Cultural/internationalization issues mean converting to and from strings and date (or numeric) values is far more expensive and error-prone than we generally expect. It's something to avoid as much as possible.

This has a number of implications:

  1. Date and Datetime values in the server are not stored as strings at all internally; it is not a human-readable format

  2. Using strings to do date operations is generally the slowest and least-reliable option

  3. There are certain preferred formats for date literals in your code that often do not match your own cultural background. On SQL Server you have these three options, and you should not deviate from them:

    yyyy-MM-ddTHH:mm:ss[.fff]
    yyyyMMdd HH:mm:ss[.fff]
    yyyyMMdd

With that in mind, we can re-write the original code like this to follow better practice and get expected results:

WITH Certified AS (
    SELECT certifiedBy,
        coalesce(datefromparts(  
           right(SUBSTRING(CertifiedBy, 1, charindex(&#39;:&#39;, certifiedby, charindex(&#39;/&#39;, certifiedby))-1),4), -- year  
           REPLACE(SUBSTRING(certifiedBy, CHARINDEX(&#39;:&#39;, certifiedBy, 1) + 1, 2),&#39;/&#39;,&#39;&#39;), -- month
           REPLACE(SUBSTRING(certifiedBy, CHARINDEX(&#39;/&#39;, certifiedBy, 1) + 1, 2),&#39;/&#39;,&#39;&#39;) -- day
        ),&#39;19700101&#39;) as CERT_DATE
    FROM dbo.PO_Orders 
    WHERE  poreference = &#39;shev&#39;  
)
SELECT  * 
FROM Certified 
WHERE CERT_DATE &gt;= &#39;20230201&#39; AND CERT_DATE &lt; &#39;20230223&#39; 
ORDER BY CERT_DATE

See it here:

> https://dbfiddle.uk/TUBKuAr6

Of course, even better if you can update the application to capture this as a real datetime column at INSERT/UPDATE time. That will perform better by multiple orders of magnitude.

huangapple
  • 本文由 发表于 2023年2月24日 05:35:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/75550550.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定