I am trying to create a view which joins 2 tables but the results are dependent on the query. Here is a example of what I want to achieve.
I have a table called sessionaccounting and a table called sessionaccountingdailysplit.
sessionaccounting contains all our accounting data and sessionaccountingdailysplit is the sessionaccounting data split by date. They are joined by the foreign key sessionaccountingid
How the two tables work in unison is as follows:
for the row in sessionaccounting :
starttime - '2012-01-01', endtime - '2012-01-03', usage - 10000
for the rows in sessionaccountingdailysplit :
date - '2012-01-01', usage - 3000
date - '2012-01-02', usage - 5000
date - '2012-01-03', usage - 2000
Now what I want to do is if I run a view called vw_sessionaccounting as
SELECT *
FROM vw_sessionaccounting
WHERE starttime >= '2011-01-01' AND starttime <= '2011-01-02';
it must only sum the first two dates from sessionaccountingdailysplit and replace the usage in sessionaccounting accordingly for each effected row. (most cases sessionaccountingdailysplit wont have a row as there was no split)
So as above if I run
SELECT *
FROM sessionaccounting
WHERE starttime >= '2011-01-01' AND starttime <= '2011-01-02';
I will get the result of
starttime - '2012-01-01', endtime - '2012-01-03', usage - 10000
but if I run the query
SELECT *
FROM vw_sessionaccounting
WHERE starttime >= '2011-01-01'
AND starttime <= '2011-01-02';
I will get the result of
starttime - '2012-01-01', endtime - '2012-01-03', usage - 8000
Your question is a bit vague in several respects. But from what I gather and guess, your query (view) could look like this:
SELECT s.starttime
,s.endtime
,COALESCE(max(sd.date), s.endtime) AS effective_endtime_max_2_days
,COALESCE(sum(sd.usage), s.usage) AS usage_max_2_days
FROM sessionaccounting s
LEFT JOIN sessionaccountingdailysplit sd USING (sessionaccountingid)
WHERE sd.sessionaccountingid IS NULL -- no split ..
OR (starttime + 2) > sd.date -- .. or only the first two days of the split
GROUP BY s.starttime, s.endtime
Major points
Use a LEFT JOIN because:
... most cases sessionaccountingdailysplit wont have a row as there was no split
Only include the first two days: (starttime + 2) > sd.date
Be sure to include sessions without spit: WHERE sd.sessionaccountingid IS NULL OR
Use table aliases to get your monstrous table names out of the way:
FROM sessionaccounting s
sum() the usage for the first two days. If there was no spit take the original total usage instead: COALESCE(sum(sd.usage), s.usage) AS usage_max_2_days
Related
I have a table that contains sensor data with a column timestamp that holds the unix timestamp of the time the sensor measurement has been taken.
Now I would like to SELECT all measurements within a certain date/time range with a specific time step.
I figured the first part out myself like you can see in my posted code snippet below.
// With $date_start and $date_stop in the format: '2010-10-01 12:00:00'
$result = mysqli_query($connection, "SELECT sensor_1
FROM sensor_table
WHERE timestamp >= UNIX_TIMESTAMP($date_start)
AND timestamp < UNIX_TIMESTAMP($date_stop)
ORDER BY timestamp");
Now is there a convenient way in MySQL to include a time step size into the same SELECT query?
My table contains thousands of measurements over months with one measurement taken every 5 seconds.
Now let's say I would like to SELECT measurements in between 2010-10-01 12:00:00 and 2010-10-02 12:00:00 but in this date/time range only SELECT one measurement every 10 minutes? (as my table contains measurements taken every 5 seconds).
Any smart ideas how to solve this in a single query?
(also other ideas are very welcome :))
Since you take one measurement every 5 seconds, the difference between $date_start and the first matching measurement cannot be greater than 4. We then take one entry every 600 seconds (allowing for some discrepancy from clock to clock...)
SELECT sensor_1
FROM sensor_table
WHERE timestamp >= UNIX_TIMESTAMP($date_start)
AND
timestamp < UNIX_TIMESTAMP($date_stop)
AND
((timestamp - UNIX_TIMESTAMP($date_start)) % 600) BETWEEN 0 AND 4
ORDER BY timestamp;
It is not elegant, but you can do:
SELECT s.sensor_1
FROM sensor_table s
WHERE s.timestamp >= UNIX_TIMESTAMP($date_start) AND
s.timestamp < UNIX_TIMESTAMP($date_stop) AND
s.timestamp = (SELECT MIN(s2.timestamp)
FROM sensor_table s2
WHERE s2.timestamp >= 60 * 10 * FLOOR(UNIX_TIMESTAMP(s.timestamp) / (60 * 10)) AND
s2.timestamp < s2.timestamp >= 60 * 10 * (1 + FLOOR(UNIX_TIMESTAMP(s.timestamp) / (60 * 10)))
)
ORDER BY timestamp;
This selects the first in each 10 minute period.
I think that you could use a simple cursor in plSQL
CREATE TABLE StoreValuesId
(
valueId int primary key;
)
CREATE OR REPLACE procedure_store[date_start date,date_stop date]
DECLARE date_startUpdated date , date_stopUpdated date , date_diff TIME(7) = '00:10:00'
IS
BEGIN
SELECT date_start INTO date_startUpdated;
SELECT date_stop INTO date_stopUpdated;
IF timestamp BETWEEN date_start and date_stop then
INSERT INTO StoreValuesId values(timestamp)
date_startUpdated=DATEADD(SECOND, DATEDIFF(SECOND, 0, date_diff), date_startUpdated);
date_stopUpdated=DATEADD(SECOND, DATEDIFF(SECOND, 0, date_diff), date_stopUpdated);
END IF
COMMIT;
END
Then again the syntax might be wrong but I hope you'll get the idea (haven't played with sql in a while)
There is an existing Dates dimension table which I need to query.
The structure looks like this:
ID | Year | Month | Day
How can I query for a range of dates?
Example: I want to get the dates from 03/02/2018 to 03/02/2020
I've tried the following:
SELECT *
from Dates dateDim
where (dateDim.Year >= 2018 and dateDim.Month >= 2 and dateDim.Day >= 3)
and (dateDim.Year <= 2020 and dateDim.Month <= 2 and dateDim.Day <= 3);
With standard SQL you can compare tuples (=multiple columns) with a single expression:
select *
from dates
where (year, month, day) >= (2018,2,3) and (year, month, day) <= (2020, 1, 3);
Not all DBMS products support that however, but you didn't mention a specific DBMS product and the tag sql refers to "standard SQL".
Online example: https://rextester.com/NTZH63192
But you should really consider change that to a single column of type DATE.
SELECT *
from Dates dateDim
where dateDim.Year * 10000 + dateDim.Month * 100 + dateDim.Day
between 20180203 and 20200203
As has been commented, change your Dates table so it actually holds DATE types. You can easily retrieve the year/month/day from a date. Most typical operations that involve your date table will involve conversion to a date, then some operation (like adding days/months etc) then converting back. If you store these thigns as dates you save that first step. Over the life of the DB storing it properly will save you more than whatever spurious reason they're stored like this is intended to help with
You could also do this mathematically (or stringily):
SELECT * FROM dates WHERE year * 10000 + month * 100 + day BETWEEN 20180203 and 20200103
But it's a bit of a lame hack compared to storing the data properly in the first place
If you are using MS-SQL, I would recommend, if Year, Month and Day are INT's, converting these to a DateTime and you have much more control, something like this should suffice:
SELECT
*
FROM
Dates.dateDim AS foo
WHERE
CAST(CAST(foo.Year AS varchar) + '-' + CAST(foo.Month AS varchar) + '-' + CAST(foo.Day AS varchar) AS DATETIME) BETWEEN '2018/02/03' AND '2020/02/03'
I am trying to nest a few queries but so far am getting back error 1242: Subquery returns more than 1 row. I want more than one row, as I am working on a number of records.
I have 2 tables. One has a commencement date stored in 3 columns; yr_comm, mth_comm, day_comm. The 2nd table has a period of service (in years) for a number of users which is expressed as an integer (2.71, 3.45, etc).
I need to take this start date (from table 1), and add on the period of service (from table 2) to obtain an end date, but I only need to display the year.
I have 2 queries which work just fine when seperate, they result in the required values, however I am having trouble combining the queries to get the desired end result.
Query 1: Concatenate the 3 commencement values into date format
SELECT concat_ws('-', yr_comm, mth_comm, day_comm) AS date_comm
FROM table 1
Query 2: Convert the integer yrs_service into days
SELECT format(yrs_served * 365, 0) AS days_served
FROM table 2
Query 3: Use date_add function to add the days service to the commencement date
SELECT date_add(date_comm, INTERVAL days_served DAY) AS date_left
Can anyone suggest how I can achieve the above? Many thanks in advance.
EDIT - Here is the full query I am working on:
SELECT prime_minister.pm_name, yr_comm, party, ADDDATE(
(SELECT CONCAT_WS('-', yr_comm, mth_comm, day_comm) FROM ministry), INTERVAL
(SELECT FORMAT(yrs_served * 365, 0) FROM prime_minister) YEAR) AS date_left
FROM ministry JOIN prime_minister USING (pm_name)
WHERE party NOT LIKE '%labor%'
AND prime_minister.pm_name = ministry.pm_name
ORDER BY pm_name;
you can use user variables
SET #date = CONCAT_WS('-', 2012,1,1); -- paste your query here
SET #toAdd = (SELECT MONTH(CURDATE())); -- paste your query here
SELECT DATE_ADD(#date, INTERVAL #toAdd DAY) AS date_left
SQLFiddle Demo
which is the same as
SET #date = CONCAT_WS('-', 2012,1,1); -- paste your query here
SET #toAdd = (SELECT MONTH(CURDATE())); -- paste your query here
SELECT #date + INTERVAL #toAdd DAY AS date_left
SQLFiddle Demo
or without using variable, which is more longer,
SELECT (CONCAT_WS('-', 2012,1,1)) + INTERVAL (SELECT MONTH(CURDATE())) DAY AS date_left
SQLFiddle Demo
I have a table recording the accumulative total visit numbers of some web pages every day. I want to fetch the real visit numbers in a specific day for all these pages. the table is like
- record_id page_id date addup_number
- 1 1 2012-9-20 2110
- 2 2 2012-9-20 1160
- ... ... ... ...
- n 1 2012-9-21 2543
- n+1 2 2012-9-21 1784
the result I'd like to fetch is like:
- page_id date increment_num(the real visit numbers on this date)
- 1 2012-9-21 X
- 2 2012-9-21 X
- ... ... ...
- N 2012-9-21 X
but I don't want to do this in php, cause it's time consuming. Can I get what I want with SQL directives or with some mysql functions?
Ok. You need to join the table on itself by joining on the date column and adding a day to one side of the join.
Assuming:
date column is a legitimate DATE Type and not a string
Every day is accounted for each page (no gaps)
addup_number is an INT of some type (BIGINT, INT, SMALLINT, etc...)
table_name is substituted for your actual table name which you don't indicate
Only one record per day for each page... i.e. no pages have multiple counts on the same day
You can do this:
SELECT t2.page_id, t2.date, t2.addup_number - t1.addup_number AS increment_num
FROM table_name t1
JOIN table_name t2 ON t1.date + INTERVAL 1 DAY = t2.date
WHERE t1.page_id = t2.page_id
One thing to note is if this is a huge table and date is an indexed column, you'll suffer on the join by having to transform it by adding a day in the ON clause, but you'll get your data.
UPDATED:
SELECT today.page_id, today.date, (today.addup_number - yesterday.addup_number) as increment
FROM myvisits_table today, myvisits_table yesterday
WHERE today.page_id = yesterday.page_id
AND today.date='2012-9-21'
AND yesterday.date='2012-9-20'
GROUP BY today.page_id, today.date, yesterday.page_id, yesterday.date
ORDER BY page_id
Something like this:
SELECT date, SUM(addup_number)
FROM your_table
GROUP BY date
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.