Each GROUP BY expression error in sql server - sql

I am building a stored procedure in MS SQL SERVER 2012. I want to display average level of current day.
CREATE PROCEDURE [dbo].[AverageP]
#UserID INT
AS
BEGIN
SELECT DAY(GETDATE()), AVG(Level) AS AvgLevel
FROM tab1
WHERE UserID = #UserID
GROUP BY DAY(GETDATE())
END
But i got this error:
Msg 164, Level 15, State 1, Procedure AverageP, Line 9 Each GROUP BY
expression must contain at least one column that is not an outer
reference.
Please, help me to solve this error!

You want a where clause, not group by. Something like this:
BEGIN
SELECT CAST(GETDATE() as DATE), AVG(Level) AS AvgLevel
FROM tab1
WHERE UserID = #UserID AND
tabl.SOMEDATEFIELD >= cast(GETDATE() as DATE) and
tab1.SOMEDATEFIELD < cast(GETDATE() + 1 as DATE)
END

It's complaining because you're not grouping by anything that is in tab1, but by the day of the current date.
Note that Day() is the number of the current day, like today it's 22 because it's May 22nd. If you're looking to look for the average for all Fridays you'll want Weekday() instead.
You probably don't mean to group at all if you just want the average level for all 22nds of the month across the data in your tab1. Is there a date column in tab1 that you mean to get the day of, and then in the where column say that the day of getdate() equals the day of that date column?

Actually you should be able to run this without a group at all.
SELECT DAY(GETDATE()) as [Day], AVG(Level) AS AvgLevel
FROM tab1
WHERE UserID = #UserID
But as Gordon points out we think you just want to filter by day. Either way the grouping is superfluous.

Related

SQL : Get last 3 month data (with only available data in the column), not from current month or today?

Anyone help me out on this scenario. I need to get a last 3 month data from particular column, which is not from current date but only from available date.
Ex :
I have one Table named Shop and column named OrderDate, In OrderDate i have only dates until 30-06-2017, but today's date is 07-02-2018. From this i need to get last 3 months data, which is Jun'17, May'17 and Apr'17.
If data available in till July'17 means i need result Jul'17,Jun'17 & May'17. And so on.
Any can help on this to achieve in SQL ?
Thanks in advance.
I assume that column OrderDate is of date or datetime datatype.
select * from Shop
where OrderDate >
(select dateadd(month, -3, max(OrderDate)) from Shop)
I think the easiest way to get this is to select the largest date in that table and use that to create the filter.
So;
Select *
from table
where date >
Dateadd
('m', -3,
( Select Max (date) from table)
)
Apologies in advance for poor formatting
You will have to find the maximum date in the table and use that:
select s.*
from Shop s,
(select convert(date,
format(
dateadd(MONTH, -3, max(OrderDate)), 'yyyyMM01')
,112) as fromDate from Shop) md
where s.OrderDate >= md.fromDate

SQL Query Statement Return Different Results

Newbie to the forum. I am trying to write a MS SQL query that returns the number of unique user logons in the last 24 hours. I am not a SQL expert. Below are two queries that I have. The 2nd one is and extension of the first where I want to group the result by date and hour. My question is, the 2nd query return result larger than the first. What am I missing? Thanks\
1ST
SELECT COUNT(DISTINCT(RESOURCE_ID)) AS 'UniqueLogonUsers'
FROM live.AUDIT_LOG WITH (NOLOCK)
WHERE AUDIT_TYPE = 0 AND AUDIT_TIME >= DateAdd(hh, -24, GETDATE())
2ND
SELECT CAST(AUDIT_TIME AS DATE) AS 'WhichDate',
DATEPART(hh, AUDIT_TIME) AS 'WhichHour',
COUNT(DISTINCT(RESOURCE_ID)) AS 'UniqueLogonUsers'
FROM live.AUDIT_LOG WITH (NOLOCK)
WHERE AUDIT_TYPE = 0 AND AUDIT_TIME >= DateAdd(hh, -24, GETDATE())
GROUP BY CAST(AUDIT_TIME AS DATE), DATEPART(hh, AUDIT_TIME)
ORDER By CAST(AUDIT_TIME AS DATE) DESC, DATEPART(hh, AUDIT_TIME) DESC
Your first query should only return 1 row - a count of logins in the last 24 hours. Your second breaks that count into rows for each Day/Hour combination, so thus will have more rows - one for each combination, with the counts of the logins during that hour.
Could it be because you are using GETDATE() which would have returned a different value each time? Can you try putting a fixed date in there instead of GETDATE()?

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.