Data changes in a CTE after SELECT query - sql

I have a query like this :
USE [MyDataBase]
GO
DECLARE #day DATE = '2017-05-18'
DECLARE #camp VARCHAR(500) = '9015';
WITH ODCALLS AS (SELECT * FROM [dbo].[ODCalls_2017_05]
WHERE CONVERT(DATE, CallLocalTime) = #day AND LastCampaign = #camp AND CallType = 1
)
SELECT COUNT(*) FROM ODCALLS -- this returns 2998
SELECT DATEPART(HOUR, CallLocalTime) AS dHOUR, COUNT(*) AS [Calls Received]
FROM ODCALLS
GROUP BY DATEPART(HOUR, CallLocalTime)
ORDER BY dHOUR -- this returns 24 rows as there are 24 hours in a day
SELECT COUNT(*) FROM ODCALLS -- this returns 2907
The instance is SQL SERVER EXPRESS 2014 and the data in the OdCalls_2017_05 table doesn't change, here' a preview of the results :
I have no idea why this happens, does any one have any explanation ?
Thanks

I'm surprised that works at all. Common Table Expressions do not act like temporary tables. The query is used as input into the next query (like a View can), and optimization may be performed across both the subsequent query and the CTE, so a result set from the CTE may never exist on its own.
Subsequent calls to the CTE shouldn't work, as that violates the definition of a CTE. See https://blogs.sentryone.com/aaronbertrand/backtobasics-ctes/ for more.

Related

SQL Server list too long

I am trying to run this SQL statement:
select *
from table
where 1 = 1
and date >= '1/1/2020'
and id = any (**'list of 1300 items'**)
order by date asc;
The issue is that my list in the second and statement is actually over 1000 expressions, so it does not let me run this statement. Does anyone know a better way to do this? Basically, I have and ID that I want to locate in our database and the easiest way I know to do this is with a list but clearly mine is too long. I'm new to this so any insight would be greatly appreciated!
There are multiple ways to do that. For example, if your SQL server version is not old, you could use openJSON(). ie:
DECLARE #ids VARCHAR(MAX) = '[1,3,34,434,43]' -- your 1300+ ids
select *
from yourtable
where [date] >= '20200101'
and id IN (SELECT [value] FROM OPENJSON(#ids))
order by [date] asc;
You can think of string_split, if your SQL Server version is 2016 or later. Thanks to #Cetin Bazos for the basic script.
DECLARE #ids NVARCHAR(MAX) = '1,3,34,434,43' -- your 1300+ ids
select *
from yourtable
where [date] >= '20200101'
and id IN (SELECT [value] FROM STRING_SPLIT(#ids,','))
order by [date] asc;

Iterate through SQL table creating another SQL Table

Wonder if someone could cast an eye over the following problem:
I'm running a SQL SELECT statement which gives me the following results:
DATE NumberOfHours
2017-05-01 4
2017-06-01 38
2017-07-01 68
And what I'm trying (like to be able to) to do is off the back of this table create another table that contains 4 rows for 2017-05-01, 38 Rows for 2017-06-01 and 68 rows for 2017-07-01. So I end up with a table that's got 110 rows in it.
I'm at a bit of a loss as to how this could be achieved...could anyone assist?
////////////////////////////////////////////////////////////
Using the response listed by Gordon Linoff I managed to get this working working by using:
with cte as (
SELECT DATEADD(month, datediff(month,0,L.DateAdded),0) AS 'Date', CEILING(SUM(l.CPDHours))AS NumberOfHours
FROM WebsiteICA_SF.dbo.CPD_Log L
WHERE L.DateAdded >= DATEADD(month, -6, GETDATE())
AND (L.Provider = 'ICA' OR L.Provider like 'International Compli%')
GROUP BY DATEADD(month, datediff(month,0,L.DateAdded),0)
union all
select date, NumberOfHours - 1
from cte
where NumberOfHours > 1
)
select 1 AS 'ObId', date, 'ICA' AS Provider, '# ICA' AS DataType
from cte
order by DATEADD(month, datediff(month,0,cte.Date),0)
OPTION (maxrecursion 10000);
One simple method is a recursive CTE:
with cte as (
select date, NumberOfHours
from t
union all
select date, NumberOfHours - 1
from cte
where NumberOfHours > 1
)
select date
from cte;
By default, this is limited to a maximum of 100 hours. However, that is easily changed using the MAXRECURSION option.
Other methods generally rely on a second table to generate numbers. I also like this approach because it is a gentle introduction to recursive CTEs.
Here is a nice SQL Fiddle.
So you have a result set with 3 rows and one column in it which tells you how many rows it represents. You want to generate that many rows.
Not sure what you want to store in that, but here is a solution to the base problem:
Create a table (temp table or CTE is fine too) which contains only one column, storing numbers from 0 to whatever. This is called Tally Table or Numbers Table.
Join this table to your resultset:
WITH NumbersCTE AS (
-- This will give you a bunch of Numbers
-- Persist a table if you want to use it more frequently
SELECT ROW_NUMBER() OVER (ORDER BY name) AS Number FROM sys.columns
)
SELECT
MT.Date,
N.Number
FROM
dbo.MyTable MT
INNER JOIN NumbersCTE N
ON N.Number <= MT.NumberOfHours
As Pieter Geerkens pointed out in the comments, the above method is not the best to generate a numbers table, but for demostration puposes it is fine.
For more info about how to generat tally tables in SQL Server, you can check
http://www.sqlservercentral.com/blogs/dwainsql/2014/03/27/tally-tables-in-t-sql/

Calculating current consecutive days from a table

I have what seems to be a common business request but I can't find no clear solution. I have a daily report (amongst many) that gets generated based on failed criteria and gets saved to a table. Each report has a type id tied to it to signify which report it is, and there is an import event id that signifies the day the imports came in (a date column is added for extra clarification). I've added a sqlfiddle to see the basic schema of the table (renamed for privacy issues).
http://www.sqlfiddle.com/#!3/81945/8
All reports currently generated are working fine, so nothing needs to be modified on the table. However, for one report (type 11), not only I need pull the invoices that showed up today, I also need to add one column that totals the amount of consecutive days from date of run for that invoice (including current day). The result should look like the following, based on the schema provided:
INVOICE MESSAGE EVENT_DATE CONSECUTIVE_DAYS_ON_REPORT
12345 Yes July, 30 2013 6
54355 Yes July, 30 2013 2
644644 Yes July, 30 2013 4
I only need the latest consecutive days, not any other set that may show up. I've tried to run self joins to no avail, and my last attempt is also listed as part of the sqlfiddle file, to no avail. Any suggestions or ideas? I'm quite stuck at the moment.
FYI: I am working in SQL Server 2000! I have seen a lot of neat tricks that have come out in 2005 and 2008, but I can't access them.
Your help is greatly appreciated!
Something like this? http://www.sqlfiddle.com/#!3/81945/14
SELECT
[final].*,
[last].total_rows
FROM
tblEventInfo AS [final]
INNER JOIN
(
SELECT
[first_of_last].type_id,
[first_of_last].invoice,
MAX([all_of_last].event_date) AS event_date,
COUNT(*) AS total_rows
FROM
(
SELECT
[current].type_id,
[current].invoice,
MAX([current].event_date) AS event_date
FROM
tblEventInfo AS [current]
LEFT JOIN
tblEventInfo AS [previous]
ON [previous].type_id = [current].type_id
AND [previous].invoice = [current].invoice
AND [previous].event_date = [current].event_date-1
WHERE
[current].type_id = 11
AND [previous].type_id IS NULL
GROUP BY
[current].type_id,
[current].invoice
)
AS [first_of_last]
INNER JOIN
tblEventInfo AS [all_of_last]
ON [all_of_last].type_id = [first_of_last].type_id
AND [all_of_last].invoice = [first_of_last].invoice
AND [all_of_last].event_date >= [first_of_last].event_date
GROUP BY
[first_of_last].type_id,
[first_of_last].invoice
)
AS [last]
ON [last].type_id = [final].type_id
AND [last].invoice = [final].invoice
AND [last].event_date = [final].event_date
The inner most query looks up the starting record of the last block of consecutive records.
Then that joins on to all the records in that block of consecutive records, giving the final date and the count of rows (consecutive days).
Then that joins on to the row for the last day to get the message, etc.
Make sure that in reality you have an index on (type_id, invoice, event_date).
You have multiple problems. Tackle them separately and build up.
Problems:
1) Identifying consecutive ranges: subtract the row_number from the range column and group by the result
2) No ROW_NUMBER() functions in SQL 2000: Fake it with a correlated subquery.
3) You actually want DENSE_RANK() instead of ROW_NUMBER: Make a list of unique dates first.
Solutions:
3)
SELECT MAX(id) AS id,invoice,event_date FROM tblEventInfo GROUP BY invoice,event_date
2)
SELECT t2.invoice,t2.event_date,t2.id,
DATEDIFF(day,(SELECT COUNT(DISTINCT event_date) FROM (SELECT MAX(id) AS id,invoice,event_date FROM tblEventInfo GROUP BY invoice,event_date) t1 WHERE t2.invoice = t1.invoice AND t2.event_date > t1.event_date),t2.event_date) grp
FROM (SELECT MAX(id) AS id,invoice,event_date FROM tblEventInfo GROUP BY invoice,event_date) t2
ORDER BY invoice,grp,event_date
1)
SELECT
t3.invoice AS INVOICE,
MAX(t3.event_date) AS EVENT_DATE,
COUNT(t3.event_date) AS CONSECUTIVE_DAYS_ON_REPORT
FROM (
SELECT t2.invoice,t2.event_date,t2.id,
DATEDIFF(day,(SELECT COUNT(DISTINCT event_date) FROM (SELECT MAX(id) AS id,invoice,event_date FROM tblEventInfo GROUP BY invoice,event_date) t1 WHERE t2.invoice = t1.invoice AND t2.id > t1.id),t2.event_date) grp
FROM (SELECT MAX(id) AS id,invoice,event_date FROM tblEventInfo GROUP BY invoice,event_date) t2
) t3
GROUP BY t3.invoice,t3.grp
The rest of your question is a little ambiguous. If two ranges are of equal length, do you want both or just the most recent? Should the output MESSAGE be 'Yes' if any message = 'Yes' or only if the most recent message = 'Yes'?
This should give you enough of a breadcrumb though
I had a similar requirement not long ago getting a "Top 5" ranking with a consecutive number of periods in Top 5. The only solution I found was to do it in a cursor. The cursor has a date = #daybefore and inside the cursor if your data does not match quit the loop, otherwise set #daybefore = datediff(dd, -1, #daybefore).
Let me know if you want an example. There just seem to be a large number of enthusiasts, who hit downvote when they see the word "cursor" even if they don't have a better solution...
Here, try a scalar function like this:
CREATE FUNCTION ConsequtiveDays
(
#invoice bigint, #date datetime
)
RETURNS int
AS
BEGIN
DECLARE #ct int = 0, #Count_Date datetime, #Last_Date datetime
SELECT #Last_Date = #date
DECLARE counter CURSOR LOCAL FAST_FORWARD
FOR
SELECT event_date FROM tblEventInfo
WHERE invoice = #invoice
ORDER BY event_date DESC
FETCH NEXT FROM counter
INTO #Count_Date
WHILE ##FETCH_STATUS = 0 AND DATEDIFF(dd,#Last_Date,#Count_Date) < 2
BEGIN
#ct = #ct + 1
END
CLOSE counter
DEALLOCATE counter
RETURN #ct
END
GO

How to write this sql query

I have a SQL Server table with the following structure
cod_turn (PrimaryKey)
taken (bit)
time (datetime)
and several other fields which are irrelevant to the problem. I cant alter the table structure because the app was made by someone else.
given a numeric variable parameter, which we will assume to be "3" for this example, and a time, I need to create a query which looking from that time on, it looks the first 3 consecutive records which are not marked as "taken". I cant figure out how to make the query in pure sql, if possible.
PS: I accepted the answer because it was correct, but I made a bad description of the problem. I will open another question later. Feeling stupid after seeing the size of the answers =)
SELECT TOP 3 * FROM table WHERE taken = 0 AND time>=#Time ORDER BY time
Where #Time is whatever time you pass in.
Assuming current versions of SQL Server and assuming you've named you "numeric variable parameter" as #top int. Note:the parenthesis around #top are required when using a parameter-ized TOP
SELECT TOP (#top)
cod_turn,
taken ,
time
FROM yourtable
WHERE Taken = 0 AND time>=#Time
ORDER BY time DESC
You can also do
with cte as
(
SELECT
ROW_NUMBER() over (order by time desc) rn
cod_turn,
taken ,
time
FROM yourtable
WHERE Taken = 0 AND time>=#Time
)
SELECT
cod_turn,
taken ,
time
FROM CTE
WHERE rn <= #top
ORDER BY time DESC
SELECT TOP 3
*
FROM
table
WHERE
time >= #inserted_time
AND taken = 0
ORDER BY
cod_turn ASC
select MT.*
from
(
select cod_turn, ROW_NUMBER() OVER (ORDER BY cod_turn) [RowNumber] -- or by time
from myTable
where taken = 0
and time >= #myTime
) T
inner join myTable MT on MT.cod_turn = T.cod_turn
where T.RowNumber < #myNumber
select top 3 * from theTable where taken = 0 and time > theTime orderby time

How do I "Declare the scalar variable" in a VIEW in Sql Server (2005)

I´m trying to create a VIEW in SQL Server 2005.
The SQL code is working as such (I´m using it in VS2008), but in SQL Server I´m unable to save it, as error message "Declare the scalar variable #StartDate" and "Declare the scalar variable #EndDate" pops up.
Here is the code:
WITH Calendar AS (SELECT CAST(#StartDate AS datetime) AS Date
UNION ALL
SELECT DATEADD(d, 1, Date) AS Expr1
FROM Calendar AS Calendar_1
WHERE (DATEADD(d, 1, Date) < #EndDate))
SELECT C.Date, C2.Country, COALESCE (SUM(R.[Amount of people per day needed]), 0) AS [Allocated testers]
FROM Calendar AS C CROSS JOIN
dbo.Country AS C2 LEFT OUTER JOIN
dbo.Requests AS R ON C.Date BETWEEN R.[Start date] AND R.[End date] AND R.CountryID = C2.CountryID
GROUP BY C.Date, C2.Country
And my question is of course - exactly how should I declare them?
I tried to put the following first in the code:
DECLARE #StartDate smalldatetime
DECLARE #EndDate smalldatetime
But that didn´t do the trick, just as I expected - it only gave me another pop-up message:
"The Declare cursor SQL construct or statement is not supported."
As Alex K has mentioned, you should write it as a inline table valued function. Here is the article that describes about it.
In short, syntax would be something like
CREATE FUNCTION dbo.GetForPeriod
( #StartDate datetime, #EndDate datetime)
RETURNS TABLE
RETURN
SELECT [[ your column list ]]
FROM [[ table list]
WHERE [[some column] BETWEEN #StartDate AND #EndDate
You can have one select query (however complex, can use CTE). And then you will use it as
SELECT * FROM dbo.GetForPeriod('1-Jan-2010', '31-Jan-2010')
If by VIEW you mean an SQL Server native view (CREATE VIEW ...) then you cannot use local variables at all (you would use a table-valued udf instead).
If you mean something else, then adding DECLARE #StartDate DATETIME, #EndDate DATETIME makes that statement parse fine, is it the entirety of the SQL?
Here is a sample query that uses CTE to nicely emulate internal variable construction. You can test-run it in your version of SQL Server.
CREATE VIEW vwImportant_Users AS
WITH params AS (
SELECT
varType='%Admin%',
varMinStatus=1)
SELECT status, name
FROM sys.sysusers, params
WHERE status > varMinStatus OR name LIKE varType
SELECT * FROM vwImportant_Users
yielding output:
status name
12 dbo
0 db_accessadmin
0 db_securityadmin
0 db_ddladmin
also via JOIN
WITH params AS ( SELECT varType='%Admin%', varMinStatus=1)
SELECT status, name
FROM sys.sysusers INNER JOIN params ON 1=1
WHERE status > varMinStatus OR name LIKE varType
also via CROSS APPLY
WITH params AS ( SELECT varType='%Admin%', varMinStatus=1)
SELECT status, name
FROM sys.sysusers CROSS APPLY params
WHERE status > varMinStatus OR name LIKE varType
try replacing all your #X, #Y with A.X and A.Y, add to your code:
FROM (SELECT X = 'literalX', Y = 'literalY') A
then you have put all your literals in one spot and have only one copy of them.