MySQL Get rows between months - mysql

I'm trying to SELECT the visitors of my site per month for the current year.
For every different IP/user_agent combination there will be added a row per minute. To track the hits and the unique visitors.
My scheme looks like this:
CREATE TABLE `stats` (
`id` int(11) unsigned NOT NULL auto_increment,
`domain` varchar(40) NOT NULL,
`ip` varchar(20) NOT NULL,
`user_agent` varchar(255) NOT NULL,
`domain_id` int(11) NOT NULL,
`date` timestamp NOT NULL default CURRENT_TIMESTAMP,
`referrer` varchar(400) default NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
Now i would like to get all the unique visitors for all the months of a given year.
But i would like to make the unique visitors only unique for 24 hours. So not for the whole month.
It would be possible to just use date >= NOW() - INTERVAL 1 MONTH), but this will jump to 2008 Dec after 2009 Jan. So it should only show the months of a given year.
Are there function to do the same for a month (count the visitors per week, so 4 rows with the first until the fourth week)?
Thanks!

You want to get the number of unique visitors for each site per month?
Something like this should work:
SELECT COUNT(*), domain_id, m, y FROM
(
SELECT ip, user_agent, COUNT(ID) AS hits, domain_id,
DAY(date) as d, MONTH(date) as m, YEAR(date) as y
FROM `stats`
GROUP BY domain_id, ip, d, m, y
) AS tb
GROUP BY tb.m, tb.y
First it groups by day in the subquery, then it groups again by month in the surrounding query.
I'm not entirely sure what your intention was, but you should be able to adapt this technique for your purpose.
(edit: added year component to query)

Related

"Row Before" in a sorted query

I'm trying to find a row immediately before and after a given row, with a order by clause. The use case is "previous entry" and "next entry" links in a vaguely blog-like system. The engine is MySQL 5.6. The table schema is
CREATE TABLE `weekly_notes` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`year` int(4) DEFAULT NULL,
`week_number` int(11) DEFAULT NULL,
`header_text` text NOT NULL,
`image_filename` varchar(128) DEFAULT NULL,
`boundry_image_filename` varchar(128) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
and the query is ordered by year desc, week_number desc. The criteria for selecting a row is year and week_number.
Sample Data
insert into weekly_notes values
(101,2018,53,'Week 53 from year 2018',NULL,NULL),
(102,2019,50,'Week 50 from year 2019', NULL, NULL),
(103,2019,51,'Week 51 from year 2019', NULL, NULL),
(104,2019,52,'Week 52 from year 2019', NULL, NULL),
(105,2020,1,'Week 1 from year 2020', NULL, NULL),
(106,2019,53,'Week 53 from year 2019', NULL, NULL),
(107,2020,2,'Week 2 from year 2020', NULL, NULL),
(108,2020,3,'Week 3 from year 2020', NULL, NULL),
(109,2020,4,'Week 4 from year 2020', NULL, NULL);
The select criteria are year an week, so I would like to be able to select the week "before" 2020-01 and get the row for 2019-53 or the week before 2020-03 and get the row for 2020-02
You can use lag() -- on however you are defining the ordering. For instance, if you wanted the row after a certain header_text:
select wn.*
from (select wn.*,
lag(header_text) over (order by year, week) as prev_header_text
from weekly_notes wn
) wn
where prev_header_text = <what you are looking for>
This assumes that "after" is chronological. I suppose "after" with a descending sort could actually mean the row before, in which case you would use lead() instead of lag().
For before a particular week/year combo, you can use:
select wn.*
from (select wn.*,
lag(week) over (order by year, week) as prev_week,
lag(year) over (order by year, week) as prev_year
from weekly_notes wn
) wn
where prev_week = #week and prev_year = #year

mysql query to find contiguous datetime records with start and stop fields

In the transportation industry, regulations limit hours-of-service.
I have a mysql database that includes a table with an auto_increment field called Task_ID, an integer field with a foreign key called User_ID, and two datetime fields, Start_DT, and End_DT.
Table:
Task_ID | User_ID | Start_DT | End_DT
An employee's shift can include several tasks, each creating a record.
I already have a query that identifies the most recent endtime, by employee, (User_ID). (The html user interface prevents data entry where the starttime would be later than the endtime.).
I need to create a query that would return all the records contiguous to the most recent endtime (by employee). In other words, all the records in which the starttime of the current record is equal to the end time of the previous record (by employee). The number of tasks (records) in the series varies.
Should I nest enough subqueries to be confident that no contiguous series of tasks will exceed that in number of tasks, or is there a way to look for a gap and return all records later than the gap?
There's lots of advice for finding gaps in a single field, but my searching hasn't found much appropriate to this question.
(Responding to Strawberry's comments below:)
1)
CREATE TABLE `hoursofservice` (
`Task_ID` MEDIUMINT(8) UNSIGNED NOT NULL AUTO_INCREMENT,
`User_ID` SMALLINT(5) UNSIGNED NOT NULL,
`Location` VARCHAR(20) NULL DEFAULT NULL,
`Task` VARCHAR(30) NULL DEFAULT NULL,
`Start_DT` DATETIME NOT NULL,
`End_DT` DATETIME NULL DEFAULT NULL,
`Comment_1` VARCHAR(20) NULL DEFAULT NULL,
`Comment_2` VARCHAR(256) NULL DEFAULT NULL,
`Bad_Data` BIT(1) NOT NULL DEFAULT b'0',
PRIMARY KEY (`Task_ID`),
UNIQUE INDEX `Task_ID` (`Task_ID`),
INDEX `FK_hoursofservice_employee_id` (`User_ID`),
CONSTRAINT `FK_hoursofservice_employee_id` FOREIGN KEY (`User_ID`) REFERENCES `employee_id` (`User_ID`) ON UPDATE CASCADE
and ...
INSERT INTO hoursofservice (User_ID, Location, Task, Start_DT, End_DT, Comment_1, Comment_2)
SELECT User_ID, Location, Task, Start_DT, End_DT, Comment_1, Comment_2 FROM read_text_file;
2) Result set would be records selected from the table such that the start time from the most recent record would equal the end time from the previous, and likewise until there was no record that met the condition. (These would be ordered by User_ID.)
Something like this might work for you:
SELECT islands.[all relevant fields from "theTable"]
FROM (
SELECT [all relevant fields from "theTable"]
, #i := CASE
WHEN #prevUserId <> User_ID THEN 1 -- Reset "island" counter for each user
WHEN #prevStart <> End_DT THEN #i + 1 -- Increment "island" counter when gaps found
ELSE #i -- Do not change "island" counter
END AS island
, #prevStart := Start_DT -- Remember this Start_DT value for the next row
, #prevUserId := User_ID -- Remember this User_ID value for the next row
FROM (
SELECT t.*
FROM theTable AS t
WHERE End_DT < [known or "ceiling" End_DT]
AND [limiting condition (for speed),
something like Start_DT > [ceiling] - INTERVAL 3 DAY
]
ORDER BY User_ID, End_DT DESC
) AS candidates -- Insures rows are in appropriate order for use with session variables.
-- Allows us to treat the inclosing query somewhat like a loop.
, (SELECT #i := 0
, #prevStart := '9999-12-31 23:59:59'
, #prevUserId := -1
) AS init -- Initializing session variables;
-- can actually be done in preceeding SET statements
-- if the connection is preserved between the SETs and the query.
ORDER BY User_ID, Start_DT
) AS islands -- Data from "theTable" should now have appropriate "islands" calculated
WHERE islands.island = 1 -- Only keep the first "island" for each user
ORDER BY islands.User_ID, islands.Start_DT
;

Get data grouped by 12 weeks period

I have a table with orders data:
CREATE TABLE `orders` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
`price` decimal(6,3) unsigned NOT NULL,
`created` datetime DEFAULT NULL,
`paid` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
I need to get price sum of orders in 12 weeks periods, where first period starts on a week when the very first order was paid, and each next period starts one week later than previous (this to have possibility to compare data in 12 weeks periods). So eventually there must be periods like 1-12, 2-13, etc.
Important detail: I can't use variables in query, because this query will be used in BI Tool, where queries with variables behave unexpected.
Did it next way:
SELECT `periods`.*, SUM(`orders`.`price`) AS `revenue`
FROM (SELECT DISTINCT FROM_DAYS(TO_DAYS(`paid`) - MOD(TO_DAYS(`paid`) -1, 7)) AS `period_start`,
(FROM_DAYS(TO_DAYS(`paid`) - MOD(TO_DAYS(`paid`) -1, 7)) + INTERVAL 12 WEEK) AS `period_end`
FROM `orders`
) AS `periods`
LEFT JOIN `orders` ON DATE(`orders`.`paid`) BETWEEN `periods`.`period_start` AND `periods`.`period_end`
GROUP BY `periods`.`period_end`
Small explanation: at first I define 12 week periods in subquery, using same table data. By doing this
DISTINCT FROM_DAYS(TO_DAYS(`paid`) - MOD(TO_DAYS(`paid`) -1, 7))
I shift paid value to the beginning of week.
With this
(FROM_DAYS(TO_DAYS(`paid`) - MOD(TO_DAYS(`paid`) -1, 7)) + INTERVAL 12 WEEK)
I add 12 weeks to the date means beginning of week.
Eventually I get result like this:
| period_start | period_end |
| 2015-07-19 | 2015-10-11 |
| 2015-07-26 | 2015-10-18 |
Then I simply join orders table and group data by end of period.
Looks like it does what I need.

Sum(count()) for last four months individually?

I have to find out tot(count) values with months for last 4 months individually how I can do this
table stucture was
CREATE TABLE `lime_survey_16579` (
`id` int(11) NOT NULL auto_increment,
`submitdate` datetime default NULL,
`lastpage` int(11) default NULL,
`startlanguage` varchar(20) collate utf8_unicode_ci NOT NULL,
`token` varchar(36) collate utf8_unicode_ci default NULL,
`16579X10X31` varchar(5) collate utf8_unicode_ci default NULLPRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=480 ;
here $survey = lime_survey_16579
and $temp= 16579X10X31
here 16579X10X31 has values like A1 A2 A3 ..
How I can do this to give output like
tot month
2 july
4 aug
6 sep
9 oct
Can anybody help me?
Please try below for all data:
SELECT count(id) as tot, MONTHNAME(submitdate) as `month`
FROM lime_survey_16579
GROUP BY month(submitdate)
ORDER BY month(submitdate)
To limit the data to last 4 month, please try below:
SELECT count(id) as tot, MONTHNAME(submitdate) as `month`
FROM lime_survey_16579
GROUP BY month(submitdate)
ORDER BY month(submitdate) DESC
LIMIT 4
Sel's solution is not going to work if you have data going into the previous year. Presumably, you want only the most recent "jul" record, not the sum of all of them.
For this, you can do:
SELECT count(id) as tot, MONTHNAME(submitdate) as `month`
FROM lime_survey_16579
GROUP BY monthname(submitdate), year(submitdate)
ORDER BY min(submitdate) DESC
LIMIT 4
You could optionally put the year on the SELECT line as well.
I replaced the month(submitdate) in the group by and order by clauses with other expressions. This avoids using a MySQL (mis) feature called hidden column, where columns not mentioned in group by clause and not aggregated are allowed in the select clause. Other SQL engines (as well as the standard) do not permit this.

How to create a query for monthly total in MySQL?

I have the following DB.
CREATE TABLE IF NOT EXISTS `omc_order` (
`order_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`customer_id` int(10) unsigned NOT NULL,
`total` decimal(10,2) NOT NULL,
`order_date` datetime NOT NULL,
`delivery_date` datetime NOT NULL,
`payment_date` datetime NOT NULL,
PRIMARY KEY (`order_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=32;
I want to create a statistic page to see total payment and total order monthly.
In one page I want to display something like this.
Month Year Total Order Total Payment
Sep 09 800 760
Oct 09 670 876
Nov
...
...
Now I am not sure how to create query for this.
Could anyone give me some ideas how to approach this?
You can use the Mysql date and time functions to group the rows for each month and them sum them up. Something like this could be a starting point:
SELECT monthname(payment_date) AS Month,
year(payment_date) AS Year,
sum(total) AS 'Total Order',
sum(??) AS 'Total Payment'
FROM omc_order
ORDER BY payment_date
GROUP BY month(payment_date),
year(payment_date);
I'm not sure how you compute the values for Total Payment.
Edit: sum() is a MySQL function.
you need to Cast the datetime to a mount , then group by
SELECT SUM(foo), MONTH(mydate) DateOnly FROM a_table GROUP BY DateOnly;
see a close question :
MySQL/SQL: Group by date only on a Datetime column
The following should help you get started.
select sum(total) from omc_order group by month(order_date), year(order_date)