SQL Server DATE conversion error - sql

Here is my query:
SELECT
*
FROM
(SELECT
A.Name, AP.PropertyName, APV.Value AS [PropertyValue],
CONVERT(DATETIME, APV.VALUE, 101) AS [DateValue]
FROM dbo.Account AS A
JOIN dbo.AccountProperty AS AP ON AP.AccountTypeId = A.AccountTypeId
JOIN dbo.AccountPropertyValue AS APV ON APV.AccountPropertyId = APV.AccountPropertyId
AND APV.AccountId = A.AccountId
WHERE
A.AccountTypeId = '19602AEF-27B2-46E6-A068-7E8C18B0DD75' --VENDOR
AND AP.PropertyName LIKE '%DATE%'
AND ISDATE(APV.Value) = 1
AND LEN(SUBSTRING( REVERSE(APV.Value), 0 , CHARINDEX( '/', REVERSE(APV.Value)))) = 4 --ENSURE 4 digit year
) AS APV
WHERE
APV.DateValue < GETDATE()
It results in the following error:
Conversion failed when converting date and/or time from character string.
If you comment out the WHERE APV.DateValue < GETDATE() clause then there is no error and I get the 300+ rows. When I enable the WHERE clause I get the error.
So you are going to tell me my data is jacked up right? Well that's what I thought, so I tried to figure out where the problem in the data was, so I started using TOP() to isolate the location. Problem was once I use the TOP() function the error went away, I only have 2000 rows of data to begin with. So I put a ridiculous TOP(99999999) on the inner SELECT and now the entire query works.
The inner SELECT returns the same number of rows with or without the TOP().
WHY???
FYI, this is SQL that works:
SELECT
*
FROM
(SELECT TOP(99999999)
A.Name, AP.PropertyName, APV.Value AS [PropertyValue],
CONVERT(DATETIME, APV.VALUE, 101) AS [DateValue]
FROM dbo.Account AS A
JOIN dbo.AccountProperty AS AP ON AP.AccountTypeId = A.AccountTypeId
JOIN dbo.AccountPropertyValue AS APV ON APV.AccountPropertyId = APV.AccountPropertyId
AND APV.AccountId = A.AccountId
WHERE
A.AccountTypeId = '19602AEF-27B2-46E6-A068-7E8C18B0DD75' --VENDOR
AND AP.PropertyName LIKE '%DATE%'
AND ISDATE(APV.Value) = 1
AND LEN(SUBSTRING(REVERSE(APV.Value), 0 , CHARINDEX( '/', REVERSE(APV.Value)))) = 4
) AS APV
WHERE
APV.DateValue < GETDATE()

The problem that you are facing is that SQL Server can evaluate the expressions at any time during the query processing -- even before the WHERE clause gets evaluated. This can be a big benefit for performance. But, the consequence is that errors can be generated by rows not in the final result set. (This is true of divide-by-zero as well as conversion errors.)
Fortunately, SQL Server has a work-around for the conversion problem. Use try_convert():
TRY_CONVERT( DATETIME, APV.VALUE, 101) AS [DateValue]
This returns NULL rather than an error if there is a problem.
The reason why some versions work and others don't is because of the order of execution. There really isn't a way to predict what does and does not work -- and it could change if the execution plan for the query changes for other reasons (such as table statistics). Hence, use try_convert().

My guess is that your date is such that APV.VALUE contains also data that cannot be converted into a date, and should be filtered out using the other criteria?
Since SQL Server can decide to limit the data first using the criteria you have given:
APV.DateValue < CONVERT( DATETIME, GETDATE(),101)
And if there is data that cannot be converted into the date, then you will get the error.
To make it more clear, this is what is being filtered:
CONVERT( DATETIME, APV.VALUE, 101) AS [DateValue]
And if there is any data that cannot be converted into a date using 101 format, the filter using getdate() will fail, even if the row would not be included in the final result set for example because AP.PropertyName does not contain DATE.
Since you're using SQL Server 2012, using try_convert instead of convert should fix your problem
And why it works with top? In that case SQL Server cannot use the criteria from the outer query, because then the result might change, because it might affect the number of rows returned by top

Because number of records in the table < 999..99. And regarding the error it seems like SQL engine evaluates the WHERE clause after converting to date so you can try this:
SELECT *
FROM (
SELECT A.Name
, AP.PropertyName
, APV.Value AS [PropertyValue]
,
CASE
WHEN SDATE(APV.Value) = 1
THEN CONVERT( DATETIME, APV.VALUE, 101)
ELSE NULL
END AS [DateValue]
FROM dbo.Account AS A
JOIN dbo.AccountProperty AS AP
ON AP.AccountTypeId = A.AccountTypeId
JOIN dbo.AccountPropertyValue AS APV
ON APV.AccountPropertyId = APV.AccountPropertyId
AND APV.AccountId = A.AccountId
WHERE A.AccountTypeId = '19602AEF-27B2-46E6-A068-7E8C18B0DD75' --VENDOR
AND AP.PropertyName LIKE '%DATE%'
AND LEN( SUBSTRING( REVERSE(APV.Value), 0 , CHARINDEX( '/', REVERSE(APV.Value)))) = 4 --ENSURE 4 digit year
) AS APV
WHERE APV.DateValue IS NOT NULL AND APV.DateValue < GETDATE()

Related

Alternative to cast function in join when it comes to stored proc optimisation?

What is the best alternative for cast function used to convert datatype in SQL query inside stored proc where join is used?
I have read often that its not good to use function and need help with optimization of query below.
SELECT
A.DATE,
A.Value curr ,
ISNULL( CAST( CONVERT ( DECIMAL(18,4), CASE WHEN #INV = 0 THEN NULLIF(ISNULL(tblRate.currBid,0),0) ELSE 1.00 / NULLIF(ISNULL(tblRate.currBid,0) ,0) END ) AS VARCHAR) ,#TempHardcodeValue) AS currBid,
A.Value +' - '+ A.currName As ConcatenatedCurr
FROM #TEMPTable A
LEFT JOIN tblCurrRates (NOLOCK) tblRate ON A.Date = cast(tblRate.currDate as date) AND A.currCode = tblRate.currCode
ORDER BY A.Date ,A.Value
As you can see cast function is used, so is there any other way to reqrite without cast?
Actually, SQL Server is probably okay with the cast. The one exception to using functions is casting date/times to dates -- SQL Server recognizes the conversion for the use of indexes.
But, you can do:
FROM #TEMPTable A LEFT JOIN
tblCurrRates r
ON r.currDate >= A.Date AND
r.currDate < DATEADD(day, 1, A.Date) AND
r.currCode = a.currCode
This would allow the code to use an index on tblCurrRates(currCode, currDate). I suspect, though, that your code would also use that index.

My SQL - How to run different WHERE statements on different weekdays, like one for monday and another for friday

I missunderstod this a lot and thanks again for all the help.
I eventually found a solution for my case and it didn't even involve anything like
IF Monday THEN SELECT
I didn't even needed a CASE-statement.
What I had to do was to put different WHERE statements with an OR in between.
I have stated my final code bolow:
SELECT * FROM
(SELECT
t.pay_date
, t.supp_name
, t.client AS row_client
, t.ip_status
, t.bank_account
, t.remitt_curr AS remitt_curr
, t.remitt_id
, t.apar_id
, t.payment_id
FROM
aipheader t
WHERE
DATEPART(DW, GETDATE()) IN (2,3,4)
AND
t.pay_date BETWEEN '2019-01-01' AND DATEADD(DAY,-4,GETDATE())
OR
DATEPART(DW, GETDATE()) IN (5,6)
AND
t.pay_date BETWEEN '2019-01-01' AND DATEADD(DAY,-2,GETDATE())) x
ORDER BY 1
The statement above will execute the OR-block that is true, in my case the one with the rigt weekday.
********* OLD QUESTIONS BELOW ***********
I want to run different SELECT statements on different days off the week.
I can actually get the following code to work:
select
case
when DATEPART(DW, GETDATE()) = 4 then
(select 'HELLO' )
ELSE
(select 'GOODBYE')
end
On a wendsday the code above returns "HELLO", every other workday it would returns "Goodbye". So far so good!
As long as the select statement only return one value it seams to work, but I realy want a full table like below.
The only differance here should be the [* FROM table] part, and it breaks it all:
select
case
when DATEPART(DW, GETDATE()) = 4 then
(select * FROM table1)
ELSE
(select * FROM table2)
end
If I can't even make the above to work I can not make different SELECTs for different workdays, so this is the hard ting to figure out.
I have tried to encapsulate things with () and add a few SELECTs in different ways but it do not work, so it is probably some SQL principal.
I get an error that I think says that this is ok if it is only one value that's returned:
SqlState 37000 Native 116 [Microsoft][ODBC SQL Server Driver][SQL
Server]Only one expression can be specified in the select list when
the subquery is not introduced with EXISTS.
THANKS FOR ALL OF THE REPLYS I NOW UNDERSTAND IT A BIT BETTER.
I was not looking to add a column, I tought I could make a complete new SELECT statement. I now see that my CASE statement only adds a column, as you point out.
So then my first ide might be a better aproce, to actually modify the WHERE-clause in the select statement.
I have now tried that but it does not work.
SELECT * FROM aipheader t
WHERE
CASE
WHEN DATEPART(DW, GETDATE()) = 4 THEN
t.pay_date = '2019-10-15'
ELSE
t.pay_date = '2019-10-17'
END
It returns the error:
SqlState 37000 Native 102 [Microsoft][ODBC SQL Server Driver][SQL
Server]Incorrect syntax near '='.
I have tried the simpler:
SELECT * FROM aipheader t
WHERE
t.pay_date BETWEEN DATEADD(DAY,-120,GETDATE()) AND DATEADD(DAY,-5,GETDATE())
The above is a simpler form, but that one returns the right values.
But the one obove that one with the CASE-clause return the error about "="-sign.
Maybee the CASE part in a WHERE statement do not work.
After your edit, the correct way to filter your date would be the following:
SELECT
*
FROM
aipheader t
WHERE
t.pay_date = CASE WHEN DATEPART(DW, GETDATE()) = 4 THEN '2019-10-15' ELSE '2019-10-17' END
However, do you really want to hard-code the 2019-10-15 and 2019-10-17 dates? Seems like these should be computed automatically as time goes.
Be careful when using DATEPART with DW, since the week number starting point can actually change depending on the server's and/or current session settings. Check this example:
DECLARE #TestDate DATE = '2020-01-01' -- Wednesday
SET DATEFIRST 1 -- 1: Monday, 7: Sunday
SELECT DATEPART(DW, #TestDate) -- Returns 3
SET DATEFIRST 7 -- 1: Sunday, 7: Saturday
SELECT DATEPART(DW, #TestDate) -- Returns 4!
So whenever checking for a particular day of the week, make sure to force the DATEFIRST session parameter to a particular value so it's consistent with your checks.
I do not fully understand what you are trying to achieve.
Of course your statement will not work, because your select statement in the case when returns multiple columns and rows. That cannot work, because your case when statement will display one column!
So what you can do is the following:
select
case
when DATEPART(DW, GETDATE()) = 4 then
'HELLO'
ELSE
'GOODBYE'
end as [someField],
*
from
[table]
This query will have all rows and columns from table as output + the someField displaying HELLO or GOODBYE.
If you want to display different data from the table on the different dates (wednesday or not wednesday), you can alter the query this way:
select
case
when DATEPART(DW, GETDATE()) = 4 then
[table].[field1]
ELSE
[table].[field2]
end as [someField],
*
from
[table]
Is this ur looking for ? Sub query should return 1 row and specify col name
select
case
when DATEPART(DW, GETDATE()) = 4 then
(select top 1 col1 from table1 )
ELSE
(select top 1 col2 from table2 )
end as result
From table
CASE WHEN can only return a single value for each THEN, and never a Result Set with multiple rows and/or columns. That is just how CASE WHEN works.
You can achieve what you need like this:
DECLARE #DAY INT = DATEPART(DW, GETDATE())
select * FROM table
WHERE (#DAY = 4 AND table.SomeCol = SomeValue)
OR (#DAY = 5 AND table.SomeCol = SomeOtherValue)
... repeat for other days
Or like this:
DECLARE #DAY INT = DATEPART(DW, GETDATE())
IF #DAY = 4
(select * FROM table WHERE table.SomeCol = SomeValue)
ELSE IF #DAY = 5
(select * FROM table WHERE table.SomeCol = SomeOtherValue)
... repeat for other days
Or like this:
DECLARE #DAY INT = DATEPART(DW, GETDATE())
select * FROM table
WHERE #DAY = 4 AND table.SomeCol = SomeValue
UNION ALL
select * FROM table
WHERE #DAY = 5 AND table.SomeCol = SomeOtherValue
UNION ALL
... repeat for other days
My preference would probably be either the 1st or 2nd form.
A query must return before-known columns. If the tables table1 and table2 have different columns, you cannot write one query that returns, say, table1's five columns one day and table2's nine columns another.
As long as the resulting columns stay the same, however, you can join optionally. Here is a small example:
select
p.project_id,
p.project_name,
coalesce(c1.name, c2.name) as team_member,
coalesce(c1.salary, c2.salary * (1.0 + c2.tax / 100.0)) as team_member_salary,
coalesce(c1.job_name, c2.job_title) as team_member_job
from project p
left join crew1 c1 on c1.project_id = p.project_id and datepart(dw, getdate()) = 4
left join crew2 c2 on c2.project_id = p.project_id and datepart(dw, getdate()) <> 4
order by p.project_id;
Not sure I really understand what you are trying to achieve here. This example assumes that you are reading from different tables based on the date and will return all of the rows from both tables:
select * from (
select 'HELLO' as col1, * from table1 where DATEPART(DW, GETDATE()) = 4
UNION ALL
select 'GOODBYE' as col1, * from table2 where DATEPART(DW, GETDATE()) <> 4
)
order by col1

correct count of grouped results

I have a procedure:
ALTER PROCEDURE [dbo].[GetActualFeedbackQueueTree]
#dtNow datetime
as
BEGIN
select
count(f.Id) as [Total],
f.AccountCode,
f.AccountName,
f.Utc,
f2.CityCode,
f2.CityName
from
InnerPortal.Feedback.QueueFeedback f
left join
InnerPortal.Feedback.QueueFeedback f2
on
f2.AccountCode = f.AccountCode
where
(f.Done is null or f.Done = 0) and
(f.Busy is NULL or f.Busy = 0) and
((DATEPART(hour, DATEADD(HOUR, f.Utc, #dtNow)) >= 9 ) and
(DATEPART(hour, DATEADD(HOUR, f.Utc, #dtNow)) <= 20))
group by
f.AccountCode, f2.CityCode,
f2.CityName, f.AccountName, f.Utc
END
I group rows by AccountName and by CityName. As result we have something like a tree. The problem is the [Total] not calculates correctly.
Then I get a select for a special AccountCode the count if much less then get me as result the procedure. For example:
select count(f.Id) from Feedback.QueueFeedback f where f.AccountCode = '01507'
returns 16 rows but the procedure result is 256.
The target is to get a count of collected rows with the same account. How to make it work correctly?
Thanks.
Software: T-Sql, Ms Sql server 2012
Pretty sure you want
count(distinct(f.Id))

SQL Merging 4 Queries to one

Im having a slight issue merging the following statements
declare #From DATE
SET #From = '01/01/2014'
declare #To DATE
SET #To = '31/01/2014'
--ISSUED SB
SELECT
COUNT(pm.DateAppIssued) AS Issued,
pm.Lender,
pm.AmountRequested,
p.CaseTypeID
FROM BPS.dbo.tbl_Profile_Mortgage AS pm
INNER JOIN BPS.dbo.tbl_Profile AS p
ON pm.FK_ProfileId = p.Id
WHERE CaseTypeID = 2
AND (CONVERT(DATE,DateAppIssued, 103)
Between CONVERT(DATE,#From,103) and CONVERT(DATE,#To,103))
And Lender > ''
GROUP BY pm.Lender,p.CaseTypeID,pm.AmountRequested;
--Paased
SELECT
COUNT(pm.DatePassed) AS Passed,
pm.Lender,
pm.AmountRequested,
p.CaseTypeID
FROM BPS.dbo.tbl_Profile_Mortgage AS pm
INNER JOIN BPS.dbo.tbl_Profile AS p
ON pm.FK_ProfileId = p.Id
WHERE CaseTypeID = 2
AND (CONVERT(DATE,DatePassed, 103)
Between CONVERT(DATE,#From,103) and CONVERT(DATE,#To,103))
And Lender > ''
GROUP BY pm.Lender,p.CaseTypeID,pm.AmountRequested;
--Received
SELECT
COUNT(pm.DateAppRcvd) AS Received,
pm.Lender,
pm.AmountRequested,
p.CaseTypeID
FROM BPS.dbo.tbl_Profile_Mortgage AS pm
INNER JOIN BPS.dbo.tbl_Profile AS p
ON pm.FK_ProfileId = p.Id
WHERE CaseTypeID = 2
AND (CONVERT(DATE,DateAppRcvd, 103)
Between CONVERT(DATE,#From,103) and CONVERT(DATE,#To,103))
And Lender > ''
GROUP BY pm.Lender,p.CaseTypeID,pm.AmountRequested;
--Offered
SELECT
COUNT(pm.DateOffered) AS Offered,
pm.Lender,
pm.AmountRequested,
p.CaseTypeID
FROM BPS.dbo.tbl_Profile_Mortgage AS pm
INNER JOIN BPS.dbo.tbl_Profile AS p
ON pm.FK_ProfileId = p.Id
WHERE CaseTypeID = 2
AND (CONVERT(DATE,DateOffered, 103)
Between CONVERT(DATE,#From,103) and CONVERT(DATE,#To,103))
And Lender > ''
GROUP BY pm.Lender,p.CaseTypeID,pm.AmountRequested;
Ideally I would like the result of theses query's to show as follows
Issued, Passed , Offered, Received,
All in one table
Any Help on this would be greatly appreciated
Thanks
Rusty
I'm fairly certain in this case the query can be written without the use of any CASE statements, actually:
DECLARE #From DATE = '20140101'
declare #To DATE = '20140201'
SELECT Mortgage.lender, Mortgage.amountRequested, Profile.caseTypeId,
COUNT(Issue.issued) as issued,
COUNT(Pass.passed) as passed,
COUNT(Receive.received) as received,
COUNT(Offer.offered) as offered
FROM BPS.dbo.tbl_Profile_Mortgage as Mortgage
JOIN BPS.dbo.tbl_Profile as Profile
ON Mortgage.fk_profileId = Profile.id
AND Profile.caseTypeId = 2
LEFT JOIN (VALUES (1, #From, #To)) Issue(issued, rangeFrom, rangeTo)
ON Mortgage.DateAppIssued >= Issue.rangeFrom
AND Mortgage.DateAppIssued < Issue.rangeTo
LEFT JOIN (VALUES (2, #From, #To)) Pass(passed, rangeFrom, rangeTo)
ON Mortgage.DatePassed >= Pass.rangeFrom
AND Mortgage.DatePassed < Pass.rangeTo
LEFT JOIN (VALUES (3, #From, #To)) Receive(received, rangeFrom, rangeTo)
ON Mortgage.DateAppRcvd >= Receive.rangeFrom
AND Mortgage.DateAppRcvd < Receive.rangeTo
LEFT JOIN (VALUES (4, #From, #To)) Offer(offered, rangeFrom, rangeTo)
ON Mortgage.DateOffered >= Offer.rangeFrom
AND Mortgage.DateOffered < Offer.rangeTo
WHERE Mortgage.lender > ''
AND (Issue.issued IS NOT NULL
OR Pass.passed IS NOT NULL
OR Receive.received IS NOT NULL
OR Offer.offered IS NOT NULL)
GROUP BY Mortgage.lender, Mortgage.amountRequested, Profile.caseTypeId
(not tested, as I lack a provided data set).
... Okay, some explanations are in order, because some of this is slightly non-intuitive.
First off, read this blog entry for tips about dealing with date/time/timestamp ranges (interestingly, this also applies to all other non-integral types). This is why I modified the #To date - so the range could be safely queried without needing to convert types (and thus ignore indices). I've also made sure to choose a safe format - depending on how you're calling this query, this is a non issue (ie, parameterized queries taking an actual Date type are essentially format-less).
......
COUNT(Issue.issued) as issued,
......
LEFT JOIN (VALUES (1, #From, #To)) Issue(issued, rangeFrom, rangeTo)
ON Mortgage.DateAppIssued >= Issue.rangeFrom
AND Mortgage.DateAppIssued < Issue.rangeTo
.......
What's the difference between COUNT(*) and COUNT(<expression>)? If <expression> evaluates to null, it's ignored. Hence the LEFT JOINs; if the entry for the mortgage isn't in the given date range for the column, the dummy table doesn't attach, and there's no column to count. Unfortunately, I'm not sure how the interplay between the dummy table, LEFT JOIN, and COUNT() here will appear to the optimizer - the joins should be able to use indices, but I don't know if it's smart enough to be able to use that for the COUNT() here too....
(Issue.issued IS NOT NULL
OR Pass.passed IS NOT NULL
OR Receive.received IS NOT NULL
OR Offer.offered IS NOT NULL)
This is essentially telling it to ignore rows that don't have at least one of the columns. They wouldn't be "counted" in any case (well, they'd likely have 0) - there's no data for the function to consider - but they would show up in the results, which probably isn't what you want. I'm not sure if the optimizer is smart enough to use this to restrict which rows it operates over - that is, turn the JOIN conditions into a way to restrict the various date columns, as if they were in the WHERE clause too. If the query runs slow, try adding the date restrictions to the WHERE clause and see if it helps.
You could either as Dan Bracuk states use a union, or you could use a case-statement.
declare #From DATE = '01/01/2014'
declare #To DATE = '31/01/2014'
select
sum(case when (CONVERT(DATE,DateAppIssued, 103) Between CONVERT(DATE,#From,103) and CONVERT(DATE,#To,103)) then 1 else 0 end) as Issued
, sum(case when (CONVERT(DATE,DatePassed, 103) Between CONVERT(DATE,#From,103) and CONVERT(DATE,#To,103)) then 1 else 0 end) as Passed
, sum(case when (CONVERT(DATE,DateAppRcvd, 103) Between CONVERT(DATE,#From,103) and CONVERT(DATE,#To,103)) then 1 else 0 end) as Received
, sum(case when (CONVERT(DATE,DateOffered, 103) Between CONVERT(DATE,#From,103) and CONVERT(DATE,#To,103)) then 1 else 0 end) as Offered
, pm.Lender
, pm.AmountRequested
, p.CaseTypeID
FROM BPS.dbo.tbl_Profile_Mortgage AS pm
INNER JOIN BPS.dbo.tbl_Profile AS p
ON pm.FK_ProfileId = p.Id
WHERE CaseTypeID = 2
And Lender > ''
GROUP BY pm.Lender,p.CaseTypeID,pm.AmountRequested;
Edit:
What I've done is looked at your queries.
All four queries have identical Where Clause, with the exception of the date comparison. Therefore I've created a new query, which selects all your data which might be used in one of the four counts.
The last clause; the data-comparison, is moved into a case statement, returning 1 if the row is between the selected date-range, and 0 otherwise. This basically indicates whether the row would be returned in your previous queries.
Therefore a sum of this column would return the equivalent of a count(*), with this date-comparison in the where-clause.
Edit 2 (After comments by Clockwork-muse):
Some notes on performance, (tested on MS-SQL 2012):
Changing BETWEEN to ">=" and "<" inside a case-statement does not affect the cost of the query.
Depending on the size of the table, the query might be optimized quite a lot, by adding the dates in the where clause.
In my sample data (~20.000.000 rows, spanning from 2001 to today), i got a 48% increase in speed by adding.
or (DateAppIssued BETWEEN #From and #to )
or (DatePassed BETWEEN #From and #to )
or (DateAppRcvd BETWEEN #From and #to )
or (DateOffered BETWEEN #From and #to )
(There were no difference using BETWEEN and ">=" and "<".)
It is also worth nothing that i got a 6% increase when changing the #From = '01/01/2014' to #From '2014-01-01' and thus omitting the convert().
Eg. an optimized query could be:
declare #From DATE = '2014-01-01'
declare #To DATE = '2014-01-31'
select
sum(case when (DateAppIssued >= #From and DateAppIssued < #To) then 1 else 0 end) as Issued
, sum(case when (DatePassed >= #From and DatePassed < #To) then 1 else 0 end) as Passed
, sum(case when (DateAppRcvd >= #From and DateAppRcvd < #To) then 1 else 0 end) as Received
, sum(case when (DateOffered >= #From and DateOffered < #To) then 1 else 0 end) as Offered
, pm.Lender
, pm.AmountRequested
, p.CaseTypeID
FROM BPS.dbo.tbl_Profile_Mortgage AS pm
INNER JOIN BPS.dbo.tbl_Profile AS p
ON pm.FK_ProfileId = p.Id
WHERE 1=1
and CaseTypeID = 2
and Lender > ''
and (
(DateAppIssued >= #From and DateAppIssued < #To)
or (DatePassed >= #From and DatePassed < #To)
or (DateAppRcvd >= #From and DateAppRcvd < #To)
or (DateOffered >= #From and DateOffered < #To)
)
GROUP BY pm.Lender,p.CaseTypeID,pm.AmountRequested;
I do however really like Clockwork-muse's answer, as I prefer joins to case-statements, where posible :)
The all-in-one queries here in other answers are certainly elegant, but if you are in a rush to get something working as a one-off, or if you agree the following approach is easy to read and maintain when you have to revisit it some time down the road (or someone else less skilled has to work out what's going on) - here's a skeleton of a Common Table Expression alternative which I believe is quite clear to read :
WITH Unioned_Four AS
( SELECT .. -- first select : Issued
UNION ALL
SELECT .. -- second : Passed
UNION ALL
SELECT .. -- Received
UNION ALL
SELECT .. -- Offered
)
SELECT
-- group fields
-- SUMs of the count fields
FROM Unioned_Four
GROUP BY .. -- etc
Obviously the fields have to match in the 4 parts of the UNION, requiring dummy fields returning zero in each one.
So you could have kept the simple approach that you started with, but wrapped it up as a derived table using the CTE syntax to allow you to have the four counts all on one row per GROUPing. Also if you have to add extra filtering to specific queries of the four, then it's easier to meddle with the individual SELECTs - the flipside being (of course) that further requirements for all four would need to be duplicated!

Get date from substring in where clause of a sql select *EDITED*

I have a date stored within a text field with other text. Why didn't they just put this in a date field? I have no idea, but I do not have the power to change it now. Elsewhere in the code, I am doing this to get the records where this date is in a certain range. It works fine.
For Each i As InventoryItem In inventoryList
index = i.Notes.IndexOf("on {") + 4
scanned_dt = i.Notes.Substring(index, i.Notes.Length - index - 1)
If Date.Parse(scanned_dt) >= startDate And Date.Parse(scanned_dt) <= endDate Then
...
I am now trying to get a total of items for a certain date range.
This sql statement works to get the total for all dates. How can I update the Where clause to only count the items where i.Notes contains a date between startDate and endDate
Dim sql As String = "Select COUNT(inv_PartNum) from lester.inventory i join lester.vendor v on v.vendor_ID = i.vendor_ID Where v.vendor_Name = '" + vendorName + "' AND i.inv_Desc LIKE '%" + size + "%'"
*EDITED*
I came up with this sql select statement:
SELECT COUNT(i.inv_PartNum) FROM cdms.lester.inventory AS I
join CDMS.lester.vendor AS v on v.vendor_ID = i.vendor_ID Where v.vendor_Name = 'JVE-285'
AND CONVERT(DATETIME,
SUBSTRING(i.inv_Notes, CharIndex('on {', i.inv_Notes)+4, len(i.inv_Notes)-(CharIndex('on {', i.inv_Notes) + 4)
),101) BETWEEN '01-01-2011' AND '04-04-2011'
But I am getting this error:
The conversion of a char data type to a datetime data type resulted in an out-of-range datetime value.
In trying to figure it out I created the following sql select statement:
SELECT * FROM (
SELECT i.inv_Notes,
CONVERT(DATETIME,SUBSTRING(i.inv_Notes, CharIndex('on {',i.inv_Notes)+4,LEN(i.inv_Notes)-(CharIndex('on {', i.inv_Notes) + 4)),101) AS d
FROM cdms.lester.inventory AS i
join CDMS.lester.vendor AS v ON v.vendor_ID = i.vendor_ID WHERE v.vendor_Name = 'JVE-285') AS s
My inv_Notes column contains a string like "Assigned to Tool Trailer {JVE-285} on {4/8/2011}"
When I run the query as shown above, I get my inv_Notes column along with the date column. The dates all show in this format "2011-04-08 00:00:00.000" and no errors are thrown.
However as soon as I add a WHERE clause, I get the error: The conversion of a char data type to a datetime data type resulted in an out-of-range datetime value.
I've tried formatting the date every which way, but always get the error...
WHERE s.d > CONVERT(DATETIME, '2011-1-1', 101)
WHERE s.d < GetDate()
WHERE s.d > '20110101'
WHERE s.d >= '2011-01-01'
WHERE s.d >= '01-01-2011'
WHERE s.d > '01/01/2011'
EDIT
I've also tried
WHERE s.d IS NOT NULL
and get the same error. This obviously isn't working the way I think it's working b/c at the point of the WHERE, the conversion should have already successfully happened.
SOLUTION
Got this working
SELECT * FROM (
SELECT i.inv_Notes,
SUBSTRING(i.inv_Notes, CharIndex('} on {',i.inv_Notes)+6,LEN(i.inv_Notes)-(CharIndex('} on {', i.inv_Notes) + 6))
AS d
FROM cdms.lester.inventory AS i
join CDMS.lester.vendor AS v ON v.vendor_ID = i.vendor_ID WHERE v.vendor_Name = 'JVE-285') AS s
WHERE PARSENAME(REPLACE(s.d, '/', '.'), 1)+
RIGHT('00'+PARSENAME(REPLACE(s.d, '/', '.'), 3),2)+
RIGHT('00'+PARSENAME(REPLACE(s.d, '/', '.'),2),2) BETWEEN '20110407' AND '20110409'
I tried to cast that to int and do an integer comparison but then I get an error. Figured out that the problem was that some of the inv_Notes fields contain data like "Item Added {3/11/2011}" None of those meet the conditions for the inner select, so in my mind if it's not selected in the inner select, it shouldn't be a problem for the condition of the outer select. However, it was trying to cast 2011{311 to int and throwing an error. I'm sure it was trying to cast that to date and that's why I had the previous problems.
Try adding something similar to this WHERE clause into your statement:
WHERE CAST(SUBSTRING(Notes, CHARINDEX(Notes, 'on {') + 4, Length?) AS Date) BETWEEN StartDate And EndDate
The idea been that you extract the relevant part of the string by combining the SubString and CharIndex methods, and then convert this expression to a date format so that it can be used with the Between Operator.
Best of Luck
Part 2 Update:
As you have been able to select the date but not use it in the where clause, I suggest using it as a select statement and then wrapping this in another statement e.g:
SELECT *
FROM
(SELECT CAST(SUBSTRING(Notes, CHARINDEX(Notes, 'on {') + 4, Length?) AS Date) AS dtmNotes)
WHERE dtmNotes BETWEEN Start And End
This is just to illustrate, but include the whole of your first select statement in the wrapping.
Try this...
assuming that the table with the embedded date is called InventoryList and the column is called inv_Notes
SELECT *
FROM InventoryList i
WHERE
CAST(
substring(
i.inv_Notes,
patindex('%on {%',i.inv_Notes) +4,
patindex('%[0-9][0-9][0-9][0-9]}%',i.inv_Notes)-patindex('%on {%',i.inv_Notes)
) as Datetime)
BETWEEN '12/1/2011' AND '12/23/2011
EDIT: More restrictive looking for a "9/9999}" pattern
SELECT *
FROM InventoryList i
WHERE
CAST(
substring(
i.inv_Notes,
patindex('%on {%',i.inv_Notes) +4,
patindex('%[0-9]/[0-9][0-9][0-9][0-9]}%',i.inv_Notes)-patindex('%on {%',i.inv_Notes)
) as Datetime)
BETWEEN '12/1/2011' AND '12/23/2011