英文:
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
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
运算符执行的是字符串之间的顺序比较,而不是日期比较。
在处理计算机时,有一个重要的概念需要理解:
文化/国际化问题意味着在字符串和日期(或数值)值之间进行转换要比我们通常期望的要昂贵和容易出错。尽量避免这种情况。
这有一些重要的影响:
-
服务器上的日期和日期时间值在内部根本不存储为字符串;它不是人类可读的格式。
-
使用字符串进行日期操作通常是最慢且最不可靠的选项。
-
在代码中有一些首选的日期文字格式,通常与您自己的文化背景不匹配。在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
在这里查看它:
当然,如果您可以在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:
-
Date and Datetime values in the server are not stored as strings at all internally; it is not a human-readable format
-
Using strings to do date operations is generally the slowest and least-reliable option
-
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(':', certifiedby, charindex('/', certifiedby))-1),4), -- year
REPLACE(SUBSTRING(certifiedBy, CHARINDEX(':', certifiedBy, 1) + 1, 2),'/',''), -- month
REPLACE(SUBSTRING(certifiedBy, CHARINDEX('/', certifiedBy, 1) + 1, 2),'/','') -- day
),'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
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论