I am trying to do a select from CTE based on a condition.
There is a variable I've declared for today's period (#PRD). It holds the value of what period we are currently in.
Now I would like to do a selection from a table that will restrict what information is returned based on whether we are in the first half of the year or not.
For instance, we are in period 2 so I want everything returned from my CTE which falls between PRD 1 and 5. IF we were in say period 6 (after 5), then yes I'd want everything returned from the table.
This is the pseudocode of what I'm trying to accomplish:
SELECT
CASE
WHEN #PRD <= 5
THEN (SELECT * FROM DISPLAY WHERE PERIOD IN (1,2,3,4,5))
ELSE (SELECT * FROM DISPLAY)
END
I'm getting an error:
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
Please any thoughts on how I can do this?
Thanks x
EDITED/UPDATED:
More of the code involves a CTE and is really long. Bottom line is lets say I have this CTE
;WITH DISPLAY as (
select * from lots_of_things
)
SELECT * FROM DISPLAY
Having done a regular select on this CTE, it returns data that looks like this:
PERIOD (INT) DEPARTMENT GROUP BUDGET
1 ENERGY HE 500
2 ENERGY HE 780
3 ENERGY HE 1500
4 ENERGY HE 4500
5 ENERGY HE 400
6 ENERGY HE 3500
7 ENERGY HE 940
8 ENERGY HE 1200
I want it to show me just the top 5 rows if we the current period is 1,2,3,4,5. But to display ALL table rows if we are in any other period like 6,7,8,9 and onwards. The current period is held in the variable #PRD which is derived from doing a comparison of today's date with ranges held in a table. The value is accurate and also type INT
Hope this helps
SQL FIDDLE
This will work:
SELECT * FROM DISPLAY WHERE (#PRD > 5 OR PERIOD IN (1, 2, 3, 4, 5))
If this code confuses you, what's happening is that we check if #PRD > 5 and if that returns true, our expression is always true so we return all the rows.
If the variable is less or equal to 5 (like you checked in your example), the first check is false and then we check if the period is the list.
This might be a solution:
IF #PRD <= 5
SELECT * FROM DISPLAY WHERE PERIOD IN (1,2,3,4,5)
ELSE
SELECT * FROM DISPLAY
UPD
In this case you should use variable instead of CTE, if it's possible.
DECLARE #PRD INT;
SELECT #PRD = PERIOD FROM SOME_TABLE WHERE ...
Related
I am using MySQL 8 and need to create a stored procedure
I have a single table that has a DATE field and a value field which can be 0 or any other number. This value field represents the daily amount of rain for that day.
The table stores data between today and 10 years.
I need to find out how many periods of rain there will be in the next 10 years.
So, for example, if my table contains the following data:
Date - Value
2018-06-09 - 0
2018-06-10 - 50
2018-06-11 - 0
2018-06-12 - 15
2018-06-13 - 17
2018-06-14 - 0
2018-06-15 - 0
2018-06-16 - 12
2018-06-17 - 123
2018-06-18 - 17
Then the SP should return 3, because there were 3 periods of rain.
Any help in getting me closer to the answer will be appreciated!
You don't need to have a stored procedure for this.
A solution with MySQL's 8.0 LEAD function this supports dates with gaps.
The complete table needs to be scanned but i don't think that a huge problem with ~3560 records.
Query
SELECT
SUM(filter_match = 1) AS number
FROM (
SELECT
((t.value = 0) AND (LEAD(t.value) OVER (ORDER BY t.date ASC) != 0)) AS filter_match
FROM
t
) t
see demo https://www.db-fiddle.com/f/sev4NqgLsFPgtNgwzruwy/2
By the way, would you mind expanding your answer to understand how
LEAD and SUM work together?
LEAD(t.value) OVER (ORDER BY t.date ASC) simply means get the next value from the next record ordered by date.
this demo shows it nicely https://www.db-fiddle.com/f/sev4NqgLsFPgtNgwzruwy/6
SUM(filter_match = 1) is a conditional sum. in this case the alias filter_match needs to be true.
see what filter_match is demo https://www.db-fiddle.com/f/sev4NqgLsFPgtNgwzruwy/8
In MySQL aggregate functions can have a SQL expression something like 1 = 1 (which is always true or 1) or 1 = 0 (which is always false or 0).
The conditional sum only sums up when the condition is true.
see demo https://www.db-fiddle.com/f/sev4NqgLsFPgtNgwzruwy/7
Use MySQL join:
SELECT COUNT(*) Number_of_Periods
FROM yourTable A JOIN yourTable B
ON DATE(A.`DATE`)=DATE(B.`DATE` - INTERVAL 1 DAY)
AND A.`VALUE`=0 AND B.`VALUE`>0;
See Demo on DB Fiddle.
I Need to retrieve values from database to plot them in graph. For that I need to get values on criteria basis. Data matching different criteria has to be returned as different rows/ column to my query
(i.e)
I have a table called TABLEA which has a column TIME. I need to get the value based on time critreia as a result, count of rows which are matching TIME>1 and TIME<10 as a result, TIME>11 and TIME <20 as a result and so on. Is it possible to get the values in a single query. I use Mysql with JDBC.
I should plot all the counts in a graph
Thanks in advance.
select sum(case when `time` between 2 and 9 then 1 else 0 end) as count_1,
sum(case when `time` between 12 and 19 then 1 else 0 end) as count_2
from your_table
This can be done with CASE statements, but they can get kind of verbose. You may just want to rely on Boolean (true/false) logic:
SELECT
SUM(TIME BETWEEN 1 AND 10) as `1 to 10`,
SUM(TIME BETWEEN 11 and 20) as `11 to 20`,
SUM(TIME BETWEEN 21 and 30) as `21 to 30`
FROM
TABLEA
The phrase TIME BETWEEN 1 AND 10) will either returnTRUEorFALSEfor each record.TRUEbeing equivalent to1andFALSEbeing equivalent to0`, we then only need sum the results and give our new field a name.
I also made the assumption that you wanted records where 1 <= TIME <= 10 instead of 1 < TIME < 10 which you stated since, as stated, it would drop values where the TIME was 1,10,20, etc. If that was your intended result, then you can just adjust the TIME BETWEEN 1 AND 10 to be TIME BETWEEN 2 AND 9 instead.
I am trying to calculate user's reputation for this month and then to find 4 nearest other results (2 are lower and 2 are higher) so at all to find 5 results at a sequence.
For example the reputation for certain user is 4500 so I should get at the end results: 2750, 3000, 4500, 4650, 8900
This is the query I am having (it only selects for the certain user his reputation in the current month): SELECT SUM(reputation_change) FROM activity WHERE user_id = '1' AND YEAR(datetime) = YEAR(CURDATE()) AND MONTH(datetime) = MONTH(CURDATE())
My table is as following:
So the question is: how to make this to be performance-fair? Don't I have to restructuralize table and to add just for each user column reputation_this_month?
Thanks for all your suggestions.
You can run a MySQL routine every night that creates a different table based off of the query above. You'll see faster results when you SELECT from this table and you won't be taxing your production table with resource intensive queries
I am currently trying to summarise some data tables into a report. Each record in the table consists of a date range, something like this:
StartDate EndDate
--------------------
13/04/13 15/04/13
17/04/13 24/04/13
28/04/13 03/05/13
05/05/13 10/05/13
Assuming the date ranges signify something like days of leave, I want to be able to calculate the total amount of days of leave per month. I came across the DatePart function which seems to work apart from one edge case: when the date range crosses a month boundary. Since the DatePart function returns the month for one given date, I am no longer able to use that to determine the amount of days of leave for that edge case record (in the example above it is record 3), since it applies to two separate months.
Ideally I want my final table to look like:
Month #OfDays
--------------------
4 11 (1st record - 2, 2nd record - 7, 3rd record - 2)
5 8 (3rd record - 3, 4th record - 5)
I've considered some messy options, such as populating a temporary table having each record signifying a different day and then doing a query on that, but I am not sure how this ties in with a report. Right now my report record source is the (incorrect) query, is it possible to have a record source as a VBA function that returns a recordsource?
Another thing I thought was to possibly to have an initial query that splits up any edge cases into two seperate records, where the date range only covers one month, and then use that for my final grouping query. Is that even possible?
I feel there may be a much simpler solution to this problem yet I can't see it.
If anyone has any ideas it would be much appreciated!
To accomplish your task using Access queries you will need to create a table named [Numbers] with a single Number (Long Integer) column named [n] containing the numbers 1, 2, 3, ... up to the highest year you expect to be working with. I created mine as follows
n
----
1
2
3
...
2499
2500
You'll also need to paste the following VBA function into an Access Module
Public Function IsValidDayOfYear(YearValue As Long, DayValue As Long) As Boolean
Dim IsLeapYear As Boolean
If (YearValue Mod 400) = 0 Then
IsLeapYear = True
ElseIf (YearValue Mod 100) = 0 Then
IsLeapYear = False
ElseIf (YearValue Mod 4) = 0 Then
IsLeapYear = True
Else
IsLeapYear = False
End If
IsValidDayOfYear = (DayValue <= IIf(IsLeapYear, 366, 365))
End Function
Let's assume that your source table is called [DateRanges]. We'll start by creating a query that generates every day of the year for each year represented in the source table. The trick here is that DateSerial() "rolls over" month boundaries, so
DateSerial(2013, 1, 32) = #2013-02-01#
and
DateSerial(2013, 1, 234) = #2013-08-22#
SELECT DateSerial(yr.n, 1, dy.n) AS [Date]
FROM Numbers yr, Numbers dy
WHERE
(
yr.n
BETWEEN (SELECT MIN(DatePart("yyyy", DateRanges.StartDate)) FROM DateRanges)
AND (SELECT MAX(DatePart("yyyy", DateRanges.EndDate)) FROM DateRanges)
)
AND (dy.n < 367) AND IsValidDayOfYear(yr.n, dy.n)
For your sample data, that query returns all days in 2013.
Let's save that query as [AllDays]. Now we can use it to extract the individual days for each date range (omitting StartDate so the final counts match yours in the question)
SELECT [Date] FROM AllDays
WHERE EXISTS
(
SELECT * FROM DateRanges
WHERE AllDays.[Date] BETWEEN DateAdd("d", 1, DateRanges.StartDate) AND DateRanges.EndDate
)
That returns the individual days corresponding to each range, i.e.,
Date
----------
2013-04-14
2013-04-15
2013-04-18
2013-04-19
2013-04-20
2013-04-21
2013-04-22
2013-04-23
2013-04-24
2013-04-29
2013-04-30
2013-05-01
2013-05-02
2013-05-03
2013-05-06
2013-05-07
2013-05-08
2013-05-09
2013-05-10
We can save that query as [RangeDays] and then use it to calculate our counts by month...
SELECT DatePart("m", [Date]) AS [Month], COUNT(*) AS NumOfDays
FROM RangeDays
GROUP BY DatePart("m", [Date])
...returning
Month NumOfDays
----- ---------
4 11
5 8
Assume this table:
id date
----------------
1 2010-12-12
2 2010-12-13
3 2010-12-18
4 2010-12-22
5 2010-12-23
How do I find the average intervals between these dates, using MySQL queries only?
For instance, the calculation on this table will be
(
( 2010-12-13 - 2010-12-12 )
+ ( 2010-12-18 - 2010-12-13 )
+ ( 2010-12-22 - 2010-12-18 )
+ ( 2010-12-23 - 2010-12-22 )
) / 4
----------------------------------
= ( 1 DAY + 5 DAY + 4 DAY + 1 DAY ) / 4
= 2.75 DAY
Intuitively, what you are asking should be equivalent to the interval between the first and last dates, divided by the number of dates minus 1.
Let me explain more thoroughly. Imagine the dates are points on a line (+ are dates present, - are dates missing, the first date is the 12th, and I changed the last date to Dec 24th for illustration purposes):
++----+---+-+
Now, what you really want to do, is evenly space your dates out between these lines, and find how long it is between each of them:
+--+--+--+--+
To do that, you simply take the number of days between the last and first days, in this case 24 - 12 = 12, and divide it by the number of intervals you have to space out, in this case 4: 12 / 4 = 3.
With a MySQL query
SELECT DATEDIFF(MAX(dt), MIN(dt)) / (COUNT(dt) - 1) FROM a;
This works on this table (with your values it returns 2.75):
CREATE TABLE IF NOT EXISTS `a` (
`dt` date NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
INSERT INTO `a` (`dt`) VALUES
('2010-12-12'),
('2010-12-13'),
('2010-12-18'),
('2010-12-22'),
('2010-12-24');
If the ids are uniformly incremented without gaps, join the table to itself on id+1:
SELECT d.id, d.date, n.date, datediff(d.date, n.date)
FROM dates d
JOIN dates n ON(n.id = d.id + 1)
Then GROUP BY and average as needed.
If the ids are not uniform, do an inner query to assign ordered ids first.
I guess you'll also need to add a subquery to get the total number of rows.
Alternatively
Create an aggregate function that keeps track of the previous date, and a running sum and count. You'll still need to select from a subquery to force the ordering by date (actually, I'm not sure if that's guaranteed in MySQL).
Come to think of it, this is a much better way of doing it.
And Even Simpler
Just noting that Vegard's solution is much better.
The following query returns correct result
SELECT AVG(
DATEDIFF(i.date, (SELECT MAX(date)
FROM intervals WHERE date < i.date)
)
)
FROM intervals i
but it runs a dependent subquery which might be really inefficient with no index and on a larger number of rows.
You need to do self join and get differences using DATEDIFF function and get average.