How to store n number of SELECT results into variable? - sql

So, i have this statement that returns OrderDates of all orders made by a customer. I'm trying to store the orderdates into a variable or multiple variable, to calculate average.
I know that for a specific result we can do something like this
DECLARE #tempvar DATE
SET #tempvar = (SELECT OrderDate From Orders WHERE CustID = '1234')
But issue is, what if select statement returns variable number of results (variable rows), like 0 results or 2 results or 4?
I know we can get number of rows returned by a select statement by ##ROWCOUNT variable.
To put my issue in simpler words, i need to store newest and oldest date returned by select statement, and divide it by number of rows returned.

If your main objective is to get the average of the dates, for that you don't need to store the dates to a variable, you can do it directly like following.
SELECT CAST(AVG(CAST(OrderDate AS FLOAT)) AS DATETIME) FROM Orders WHERE CustID = '1234'
You can also store the average of all date inside a variable, you can do it like following.
DECLARE #AvgDate DATETIME
SELECT #AvgDate = CAST(AVG(CAST(OrderDate AS FLOAT)) AS DATETIME)
FROM Orders WHERE CustID = '1234'
Still if you want to store the dates into some variable, in that case to store more than 1 value better choose a table datatype as following.
DECLARE #OrderDates TABLE(OrderDate DATETIME)
INSERT INTO #OrderDates(OrderDate)
SELECT OrderDate From [Orders] WHERE CustID = '1234'
--TO GET AVG
SELECT CAST(AVG(CAST(OrderDate AS FLOAT)) AS DATETIME) FROM #OrderDates ;
-- RETURNS ALL ROW
SELECT * FROM #OrderDates
Edit :
To put my issue in other words, i need to get number of days between
highest and smallest date returned by SELECT statement
To find the days between MAX date and Min Date, you can do like following.
SELECT DATEDIFF(day, MIN(OrderDate), MAX(OrderDate)) FROM #OrderDates
OR
SELECT DATEDIFF(day, MIN(OrderDate), MAX(OrderDate)) FROM Orders WHERE CustID = '1234'

I think this gets you what you want in just one line of code:
select dateadd(d, avg(datediff(d, '1970-01-01', OrderDate)), '19700101') from Orders where CustID = '1234'
In response to comment below, try:
select
dateadd(d, datediff(d, '1970-01-01', max(OrderDate)) - datediff(d, '1970-01-01', min(OrderDate)) / count(*), '1970-01-01') as requested_calc,
dateadd(d, avg(datediff(d, '1970-01-01', OrderDate)), '19700101') as avg_date
from Orders
where CustID = '1234'
But for the life of me, I don't understand why you would want this calc. So I kept the other too.
I hope this helps.

Related

SQL get customers by age

How i can get a list of customers, whose age will be 5 or 10 years on a given interval of two dates ?
The query for only one date is:
SELECT * FROM Customers C
WHERE DATEDIFF(MONTH,C.StartDate,#Date)=60
OR DATEDIFF(MONTH, C.StartDate,#Date)=120
You want customers where they are 5 or less at the start and 5 or greater at the end - this means that at some point in the range they were 5.
SELECT * FROM Customers C
WHERE (DATEDIFF(MONTH,C.StartDate,#StartDate)<=60 AND DATEDIFF(MONTH,C.StartDate,#EndDate) >=60)
OR (DATEDIFF(MONTH,C.StartDate,#StartDate)<=120 AND DATEDIFF(MONTH,C.StartDate,#EndDate) >=120)
You can use a query like below
See live demo
DECLARE #Date1 DATE
DECLARE #Date2 DATE
SET #Date1='03-19-2017'
SET #Date2='12-19-2017'
SELECT * FROM Customers C
WHERE ( DATEADD(m, 60,C.StartDate) BETWEEN #Date1 AND #Date2 )
OR
(DATEADD(m, 120,C.StartDate) BETWEEN #Date1 AND #Date2)
The syntax of T-SQL's datediff function is:
DATEDIFF ( datepart , startdate , enddate )
The order of dates IS important, to get a positive integer result the earlier date should be first and the later date should be second. If you do it the other way the results will be negative numbers.
You can overcome this by using ABS()
ABS( DATEDIFF ( datepart , date1 , date2 ) )

column values are not showing up

Why can't I get values 0 in the column when #processdate date = null or a Sunday (the transactions are not occurring on Sundays)
This was my original query that only gives me values in the table when #processdate is declared as any day of the week that is not Sunday or null
I need It to show me a 0 in the table when #processdate= null or any sunday
alter procedure USP_CTG_VolumeReport
#processdate date = '1/8/2017'
as
declare #date date = coalesce(#processdate, cast( getdate() as date))
select 'apa' as Source,
cast(AccountID_AssignDT as date)as 'Date',
count(*) as Total_Count
from apa_2000
where
cast(AccountID_AssignDT as date) = #date
group by
cast(AccountID_AssignDT as date)
I should be able to get the proper table with a subquery but for some reason --it is not working
Below is my subquery that is giving me the same result as my previous query
select
'apa' as Source,
cast(AccountID_AssignDT as date)as 'Date',
isnull((select count(*)
from apa_2000
where cast(AccountID_AssignDT as date) = #date)
, 0 ) as Total_Count
from apa_2000
where
cast(AccountID_AssignDT as date) = #date
group by
cast(AccountID_AssignDT as date)
The issue here is that those dates where no processing happens, so the counts would be zero, simply don't exist in the database. You are expecting the query to generate data which isn't there. What you need is a table which includes a list of all the dates you want to report against, and then to match the counts against those dates.
If you are always passing in a single date, the following would work, since the input date is the only date you need:
declare #date as date = '20170416'
select
'apa' as Source,
#date as 'Date',
count(apa_2000.AccountID_AssignDT) as Total_Count
from
(select 1 as x)x
LEFT OUTER JOIN
apa_2000 ON cast(AccountID_AssignDT as date) = #date

Select the value for each first day of the month

I have the below table
I want to select the value of [UsedSpace(MB)] for each first day of the month.
For example 1/5/2015 the value of [UsedSpace(MB)] should be 10.
I tried the below query, but without success.
Select Cast(DATEADD(mm,DATEDIFF(mm,0,ExecuteTime),0) AS DATE) AS [Monthly],
[UsedSpace(MB)]
from tbl_Test
group by DATEADD(mm,DATEDIFF(mm,0,ExecuteTime),0), [UsedSpace(MB)]
Order by DATEADD(mm,DATEDIFF(mm,0,ExecuteTime),0)
Please any suggestions.
If you know that you will always have a single record for each date then you could limit the results in the WHERE statement. If there may be multiple records on the same date or no record on a date then you should use a MIN function.
SELECT
CAST(ExecuteTime as Date) AS Date,
UsedSpace
FROM tbl_Test
WHERE Day(ExecuteTime) = 1
For the sample data provided, you can simply make a where clause for the first day of the month. If you need a more generic solution, you need to get the lowest date for each month, and then join this with the original table.
WITH cte AS
(
SELECT MIN(ExecuteTime) AS Monthly
FROM tbl_Test
GROUP BY DATEADD(m, DATEDIFF(m, 0, ExecuteTime), 0)
)
SELECT t.ExecuteTime, t.[UsedSpace(MB)]
FROM tbl_Test AS t
JOIN cte AS m
ON t.ExecuteTime = m.Monthly
ORDER BY t.ExecuteTime

SQL Checking for NULL and incrementals

I'd like to check if there is anything to return given a number to check against, and if that query returns no entries, increase the number until an entry is reached and display that entry. Currently, the code looks like this :
SELECT *
FROM news
WHERE DATEDIFF(day, date, getdate() ) <= #url.d#
ORDER BY date desc
where #url.d# is an integer being passed through (say 31). If that returns no results, I'd like to increase the number stored in #url.d# by 1 until an entry is found.
This kind of incremental querying is just not efficient. You'll get better results by saying - "I'll never need more than 100 results so give me these" :
SELECT top 100 *
FROM news
ORDER BY date desc
Then filtering further on the client side if you want only a particular day's items (such as the items with a common date as the first item in the result).
Or, you could transform your multiple query request into a two query request:
DECLARE
#theDate datetime,
#theDate2 datetime
SET #theDate = (SELECT Max(date) FROM news)
--trim the time off of #theDate
SET #theDate = DateAdd(dd, DateDiff(dd, 0, #theDate), 0)
SET #theDate2 = DateAdd(dd, 1, #theDate)
SELECT *
FROM news
WHERE #theDate <= date AND date < #theDate2
ORDER BY date desc
In MySQL:
SELECT news.*,
(
SELECT COUNT(*)
FROM news
WHERE date < DATEADD(day, GETDATE(), -#url.d#)
)
FROM news
WHERE date >= DATEADD(day, GETDATE(), -#url.d#)
ORDER BY
date DESC
LIMIT 1
In SQL Server:
SELECT TOP 1
news.*,
(
SELECT COUNT(*)
FROM news
WHERE date < DATEADD(day, GETDATE(), -#url.d#)
)
FROM news
WHERE date >= DATEADD(day, GETDATE(), -#url.d#)
ORDER BY
date DESC
Note that using this syntax makes your query sargable, that is an index can be used to filter on date efficiently.
First, I think you will probably want to avpod using the DateDiff function in your where clause, instead, compute the desired cutoff date and do use any computations on the date column within the where clause, this will be more efficient, so rather than
WHERE DATEDIFF(day, date, getdate() ) <= #url.d#
you would have something like
WHERE date >= #cutoffDate
where #cutoffDate is a computed date based on #url.d#
Now, as for grabbing the correct cutoff date. My assumption is that under normal circumstances, there will be articles returned from the request otherwise you would just grab articles from the most recent date. So, the approach that I would take would be to grab the OLDEST of the computed cutoff date (based on #url.d# and the MOST RECENT article date. Something like
-- #urld == #url.d
-- compute the cutoff date as the OLDEST of the most recent article and
-- the date based on #url.d
declare #cutoff datetime
select #cutoff = DateAdd(dd,-1*#urld,GetDate())
select #cutoff
select #cutoff = min(cutoffDate)
from
(SELECT Max(date) as cutoffDate from News
UNION
select #cutoff) Cutoff
-- grab the articles with dates that are more recent than the cutoff date
select *
from News
WHERE date >= #cutoff
I'm also guessing that you would probably want to round to midnight for the dates (which I didn't do here). This is a multi-query approach and should probably be implemented in a single stored procedure ... if this is what you are looking for.
Good luck with the project!
If you wanted the one row:
SELECT t.*
FROM NEWS t
WHERE t.id = (SELECT MAX(n.id)
FROM NEWS n
WHERE n.date BETWEEN DATEADD(day, -:url.d, getDate()) AND getDate())
It might not be obvious that the DATEADD is using a negative in order to go back however many number of days desired.
If you wanted all the rows in that date:
SELECT t.*
FROM NEWS t
WHERE t.date BETWEEN DATEADD(day, -:url.d, getDate()) AND getDate())

SQL for counting events by date

I feel like I've seen this question asked before, but neither the SO search nor google is helping me... maybe I just don't know how to phrase the question. I need to count the number of events (in this case, logins) per day over a given time span so that I can make a graph of website usage. The query I have so far is this:
select
count(userid) as numlogins,
count(distinct userid) as numusers,
convert(varchar, entryts, 101) as date
from
usagelog
group by
convert(varchar, entryts, 101)
This does most of what I need (I get a row per date as the output containing the total number of logins and the number of unique users on that date). The problem is that if no one logs in on a given date, there will not be a row in the dataset for that date. I want it to add in rows indicating zero logins for those dates. There are two approaches I can think of for solving this, and neither strikes me as very elegant.
Add a column to the result set that lists the number of days between the start of the period and the date of the current row. When I'm building my chart output, I'll keep track of this value and if the next row is not equal to the current row plus one, insert zeros into the chart for each of the missing days.
Create a "date" table that has all the dates in the period of interest and outer join against it. Sadly, the system I'm working on already has a table for this purpose that contains a row for every date far into the future... I don't like that, and I'd prefer to avoid using it, especially since that table is intended for another module of the system and would thus introduce a dependency on what I'm developing currently.
Any better solutions or hints at better search terms for google? Thanks.
Frankly, I'd do this programmatically when building the final output. You're essentially trying to read something from the database which is not there (data for days that have no data). SQL isn't really meant for that sort of thing.
If you really want to do that, though, a "date" table seems your best option. To make it a bit nicer, you could generate it on the fly, using i.e. your DB's date functions and a derived table.
I had to do exactly the same thing recently. This is how I did it in T-SQL (
YMMV on speed, but I've found it performant enough over a coupla million rows of event data):
DECLARE #DaysTable TABLE ( [Year] INT, [Day] INT )
DECLARE #StartDate DATETIME
SET #StartDate = whatever
WHILE (#StartDate <= GETDATE())
BEGIN
INSERT INTO #DaysTable ( [Year], [Day] )
SELECT DATEPART(YEAR, #StartDate), DATEPART(DAYOFYEAR, #StartDate)
SELECT #StartDate = DATEADD(DAY, 1, #StartDate)
END
-- This gives me a table of all days since whenever
-- you could select #StartDate as the minimum date of your usage log)
SELECT days.Year, days.Day, events.NumEvents
FROM #DaysTable AS days
LEFT JOIN (
SELECT
COUNT(*) AS NumEvents
DATEPART(YEAR, LogDate) AS [Year],
DATEPART(DAYOFYEAR, LogDate) AS [Day]
FROM LogData
GROUP BY
DATEPART(YEAR, LogDate),
DATEPART(DAYOFYEAR, LogDate)
) AS events ON days.Year = events.Year AND days.Day = events.Day
Create a memory table (a table variable) where you insert your date ranges, then outer join the logins table against it. Group by your start date, then you can perform your aggregations and calculations.
The strategy I normally use is to UNION with the opposite of the query, generally a query that retrieves data for rows that don't exist.
If I wanted to get the average mark for a course, but some courses weren't taken by any students, I'd need to UNION with those not taken by anyone to display a row for every class:
SELECT AVG(mark), course FROM `marks`
UNION
SELECT NULL, course FROM courses WHERE course NOT IN
(SELECT course FROM marks)
Your query will be more complex but the same principle should apply. You may indeed need a table of dates for your second query
Option 1
You can create a temp table and insert dates with the range and do a left outer join with the usagelog
Option 2
You can programmetically insert the missing dates while evaluating the result set to produce the final output
WITH q(n) AS
(
SELECT 0
UNION ALL
SELECT n + 1
FROM q
WHERE n < 99
),
qq(n) AS
(
SELECT 0
UNION ALL
SELECT n + 1
FROM q
WHERE n < 99
),
dates AS
(
SELECT q.n * 100 + qq.n AS ndate
FROM q, qq
)
SELECT COUNT(userid) as numlogins,
COUNT(DISTINCT userid) as numusers,
CAST('2000-01-01' + ndate AS DATETIME) as date
FROM dates
LEFT JOIN
usagelog
ON entryts >= CAST('2000-01-01' AS DATETIME) + ndate
AND entryts < CAST('2000-01-01' AS DATETIME) + ndate + 1
GROUP BY
ndate
This will select up to 10,000 dates constructed on the fly, that should be enough for 30 years.
SQL Server has a limitation of 100 recursions per CTE, that's why the inner queries can return up to 100 rows each.
If you need more than 10,000, just add a third CTE qqq(n) and cross-join with it in dates.