"Row Before" in a sorted query - mysql

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

Related

Convert MySQL query to Laravel query builder code

I am working with agricultural product management system. I have a question regarding a MySQL query. I would like to know how to create the same query using Laravel query builder:
SELECT
vegitables.name, vegitables.image, vegitables.catagory,
AVG(price_wholesale),
SUM(CASE WHEN rank = 1 THEN price_wholesale ELSE 0 END) today,
SUM(CASE WHEN rank = 2 THEN price_wholesale ELSE 0 END) yesterday
FROM (
SELECT
veg_id, price_wholesale, price_date,
RANK() OVER (PARTITION BY veg_id ORDER BY price_date DESC) as rank
FROM old_veg_prices
) p
INNER JOIN vegitables ON p.veg_id = vegitables.id
WHERE rank in (1,2)
GROUP BY veg_id
This Output result get when run query in database:
Following two table are used to get today price yesterday price and price average get from each product.
CREATE TABLE `vegitables` (
`id` bigint(20) UNSIGNED NOT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`image` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`catagory` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`total_area` int(11) NOT NULL COMMENT 'Total area of culativate in Sri Lanka (Ha)',
`total_producation` int(11) NOT NULL COMMENT 'Total production particular product(mt)',
`annual_crop_count` int(11) NOT NULL COMMENT 'how many time can crop pre year',
`short_dis` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
ALTER TABLE `vegitables`
ADD PRIMARY KEY (`id`);
ALTER TABLE `vegitables`
MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
COMMIT;
CREATE TABLE `old_veg_prices` (
`id` bigint(20) UNSIGNED NOT NULL,
`veg_id` int(11) NOT NULL,
`price_wholesale` double(8,2) NOT NULL,
`price_retial` double(8,2) NOT NULL,
`price_location` int(11) NOT NULL,
`price_date` date NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
ALTER TABLE `old_veg_prices`
ADD PRIMARY KEY (`id`);
ALTER TABLE `old_veg_prices`
MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;
COMMIT;
I try this site to convert to MySQL query to query builder code. But it show some error's could find it out. Any Way i want to run this code in Laravel with any method??
Your query will not return the data for yesterday and today; it will return the data for two most recent dates (e.g. if today is 2021-11-01 and most recent two dates for for carrots are 2021-10-25 and 2021-10-20 it will use those two dates). Using RANK() ... IN (1, 2) is also incorrect because it can return ranks such as 1 followed by 3 instead of 2.
To get today and yesterday prices you don't need window functions. Just use appropriate where clause and conditional aggregation:
SELECT vegitables.name
, vegitables.image
, vegitables.catagory
, AVG(old_veg_prices.price_wholesale) AS avgwholesale
, SUM(CASE WHEN old_veg_prices.price_date = CURRENT_DATE - INTERVAL 1 DAY THEN old_veg_prices.price_wholesale END) AS yesterday
, SUM(CASE WHEN old_veg_prices.price_date = CURRENT_DATE THEN old_veg_prices.price_wholesale END) AS today
FROM vegitables
INNER JOIN old_veg_prices ON vegitables.id = old_veg_prices.veg_id
WHERE old_veg_prices.price_date IN (CURRENT_DATE - INTERVAL 1 DAY, CURRENT_DATE)
GROUP BY vegitables.id -- other columns from vegitables table are functionally dependent on primary key
The Laravel equivalent would be:
DB::table('vegitables')
->Join('old_veg_prices', 'old_veg_prices.veg_id', '=', 'vegitables.id')
->whereRaw('old_veg_prices.price_date IN (CURRENT_DATE - INTERVAL 1 DAY, CURRENT_DATE)')
->select(
'vegitables.name',
'vegitables.image',
'vegitables.catagory',
DB::raw('AVG(old_veg_prices.price_wholesale) AS avgwholesale'),
DB::raw('SUM(CASE WHEN old_veg_prices.price_date = CURRENT_DATE - INTERVAL 1 DAY THEN old_veg_prices.price_wholesale END) AS yesterday'),
DB::raw('SUM(CASE WHEN old_veg_prices.price_date = CURRENT_DATE THEN old_veg_prices.price_wholesale END) AS today')
)
->groupBy(
'vegitables.id',
'vegitables.name',
'vegitables.image',
'vegitables.catagory'
)
->get();
"Query builder" features of abstraction products often leave out some possible SQL constructs. I recommend you abandon the goal of reverse engineering SQL back to Laravel and simply perform the "raw" query.
Also...
rank() OVER (PARTITION BY veg_id ORDER BY price_date DESC) as rank
requires MySQL 8.0 (MariaDB 10.2).
And suggest you avoid the alias "rank" since that is identical to the name of a function.

sql query to find matching pairs in mysql

Hypothetical question for the community :
Given any 2 books in a library (book 1,book 2), how many customers have had both checked out at the same time within the year?
Table
CREATE TABLE `check0` (
`lib_card_num` int NOT NULL,
`first_name` varchar(45) NOT NULL,
`last_name` varchar(45) NOT NULL,
`checkout_timestamp_utc` timestamp(6) NOT NULL,
`due_date` date NOT NULL,
`type` varchar(10) DEFAULT NULL,
`name` varchar(45) DEFAULT NULL,
`unique_id` int NOT NULL,
PRIMARY KEY (`unique_id`),
KEY `parent_index1` (`lib_card_num`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
;
Data
INSERT INTO testlibrary.check0
(lib_card_num,FIRST_NAME,LAST_NAME,CHECKOUT_TIMESTAMP_UTC,DUE_DATE,TYPE,NAME,UNIQUE_ID)
VALUES
(1,'ROB','BLOW',DATE_ADD(current_timestamp(), INTERVAL -30 day),DATE_ADD(current_date(),INTERVAL -5 DAY),'PHYSICAL','SHEBA',100),
(1,'ROB','BLOW',DATE_ADD(current_timestamp(), INTERVAL -30 day),DATE_ADD(current_date(),INTERVAL -5 DAY),'ELECTRONIC','THUNDERPOINT',101),
(2,'JOHN','DOE',DATE_ADD(current_timestamp(), INTERVAL -15 day),DATE_ADD(current_date(),INTERVAL -1 DAY),'ELECTRONIC','THUNDERPOINT',102),
(2,'JOHN','DOE',DATE_ADD(current_timestamp(), INTERVAL -15 day),DATE_ADD(current_date(),INTERVAL -1 DAY),'PHYSICAL','SHEBA',103),
(3,'JANE','DOE',DATE_ADD(current_timestamp(), INTERVAL -45 day),DATE_ADD(current_date(),INTERVAL -20 DAY),'PHYSICAL','SHEBA',104),
(3,'JANE','DOE',DATE_ADD(current_timestamp(), INTERVAL -45 day),DATE_ADD(current_date(),INTERVAL -20 DAY),'ELECTRONIC','THUNDERPOINT',105)
Maybe try this
select count(lib_card_num) as result
from
(select distinct check0.lib_card_num from sys.check0
where check0.checkout_timestamp_utc in
(SELECT checkout_timestamp_utc
FROM
(SELECT check0.checkout_timestamp_utc, COUNT(check0.checkout_timestamp_utc) count
FROM sys.check0
GROUP BY check0.checkout_timestamp_utc) sub1
WHERE sub1.count > 1)) sub2;
Given any 2 books in a library (book 1,book 2), how many customers have had both checked out at the same time within the year?
This doesn't sound very theoretical. But you can approach this using aggregation. However, your description doesn't have a column called customer or book_title, so you'll need to adapt for your data model. This is a theoretical question after all:
To get the customers with both books:
select customer
from check0 c
where book_title in ('book 1', 'book 2')
group by customer
having count(distinct book_title) = 2;
You can then count these with an additional subquery:
select count(*)
from (select customer
from check0 c
where book_title in ('book 1', 'book 2')
group by customer
having count(distinct book_title) = 2
) c;

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.

MySQL Get rows between months

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)