MySql - Selecting First And Last Row (Not Min/Max) - mysql

I have a MySql table with daily stock market data in the following order:
_date, _opening_price, _high_price, _low_price, _close_price
I'm trying to transform this data into weekly data by using:
SELECT
MAX(_date) AS _date,
WEEK(_date) AS weeknum,
_opening_price,
MAX(_high_price) AS _high_price,
MIN(_low_price) AS _low_price,
_closing_price
FROM myTable
GROUP BY weeknum ORDER BY _date;
How do I select _opening_price so that it is the first _opening_price from within that week's daily data? Likewise, how do I select _closing_price so that it is the last _closing_price within the week's daily data?
Here's an example:
For week ending 2007-01-05, the opening_price should be taken from 2007-01-03 (red) and the closing price should be taken from 2007-01-05 (green). Similarly, for week ending on 2007-01-12, opening price should be from 2007-01-08 and closing price from 2007-01-12.

Try this solution:
SELECT
MAX(a._date) weekending,
MAX(CASE WHEN a._date = b.mindate THEN a._opening_price END) openingprice,
MAX(CASE WHEN a._date = b.maxdate THEN a._closing_price END) closingprice
FROM myTable a
INNER JOIN
(
SELECT
CONCAT(YEAR(_date), '-', WEEK(_date)) weeknum,
MIN(_date) mindate,
MAX(_date) maxdate
FROM myTable
GROUP BY weeknum
) b ON a._date IN (b.mindate, b.maxdate)
GROUP BY b.weeknum

Related

Calculating consecutive occurences in MySQL

I have a quick question in relation to windowing in MySQL
SELECT
Client,
User,
Date,
Flag,
lag(Date) over (partition by Client,User order by Date asc) as last_date,
lag(Flag) over (partition by Client,User order by Date asc) as last_flag,
case when Flag = 1 and last_flag = 1 then 1 else 0 end as consecutive
FROM db.tbl
This query returns something like the below. I am trying to work out the number of consecutive times that the Flag column was 1 for each user most recently, if they had 11110000111 then we should take the final three occurences of 1 to determine that they had a consecutive flag of 3 times.
I need to extract the start and end date for the consecutive flag.
How would I go about doing this, can anyone help me :)
If we use the example of 11110000111 then we should extract only 111 and therefore the 3 most recent dates for that customer. So in the below, we would need to take 10.01.2023 as the first date and 24.01.2023 as the last date. The consecutive count should be 3
Output:
Use aggregation and string functions:
WITH cte AS (
SELECT Client, User,
GROUP_CONCAT(CASE WHEN Flag THEN Date END ORDER BY Date) AS dates,
CHAR_LENGTH(SUBSTRING_INDEX(GROUP_CONCAT(Flag ORDER BY Date SEPARATOR ''), '0', '-1')) AS consecutive
FROM tablename
GROUP BY Client, User
)
SELECT Client, User,
NULLIF(SUBSTRING_INDEX(SUBSTRING_INDEX(dates, ',', -consecutive), ',', 1), '') AS first_date,
CASE WHEN consecutive > 0 THEN SUBSTRING_INDEX(dates, ',', -1) END AS last_date,
consecutive
FROM cte;
Another solution with window functions and conditional aggregation:
WITH
cte1 AS (SELECT *, SUM(NOT Flag) OVER (PARTITION BY Client, User ORDER BY Date) AS grp FROM tablename),
cte2 AS (SELECT *, MAX(grp) OVER (PARTITION BY Client, User) AS max_grp FROM cte1)
SELECT Client, User,
MIN(CASE WHEN Flag THEN Date END) AS first_date,
MAX(CASE WHEN Flag THEN Date END) AS last_date,
SUM(Flag) AS consecutive
FROM cte2
WHERE grp = max_grp
GROUP BY Client, User;
See the demo.
Made an attempt to get the result with more simpler queries and here is my approach taking advantage of lastDate and lastFlag column too.
Run here
WITH eTT
AS
( SELECT Client, User, NULLIF(MAX(Date),
(SELECT MAX(Date) FROM tt t2 WHERE t1.Client=t2.Client AND t1.User=t2.User)) as endDate
FROM tt t1 WHERE LastFlag=0 OR LastFlag IS NULL GROUP BY Client, User
)
SELECT Client, User,
(CASE WHEN MAX(endDate) IS NULL THEN NULL ELSE MIN(Date) END) as first_date,
(CASE WHEN MAX(endDate) IS NULL THEN NULL ELSE MAX(Date) END) as last_date,
(CASE WHEN MAX(endDate) IS NULL THEN NULL ELSE COUNT(endDate) END) as consecutive
FROM tt LEFT JOIN eTT USING (Client, User)
WHERE Date >= endDate OR endDate IS null GROUP BY Client, User;
EDIT
The original table doesn't have LastDate and LastFlag columns and were created using OP's initial query.
Since the method used is not apparantly supported but I get an impression that OP somehow manages to do that on their side.
Hence another cte called tt can be added before eTT containing that query.

mysql max and min subquery using date range

I have the following query:
SELECT
(Date + INTERVAL -(WEEKDAY(Date)) DAY) `Date`,
I would like to use a subquery here to get the oldest and newest inventory from the max and min Date:
(select sellable from clabDevelopment.fba_history_daily where Date =
max(Date))
max(Date), min(Date),
ASIN,
ItemSKU,
it.avgInv,
kt.Account, kt.Country, SUM(Sessions) `Sessions`, avg(Session_Pct)`Session_Pct`,
sum(Page_Views)`Page_Views`, avg(Page_Views_Pct)`Page_Views_Pct`, avg(Buy_Box_Pct)`Buy_Box_Pct`,
sum(Units_Ordered)`Units_Ordered`, sum(Units_Ordered_B2B) `Units_Ordered_B2B`,
avg(Unit_Session_Pct)`Unit_Session_Pct`, avg(Unit_Session_Pct_B2B)`Unit_Session_Pct_B2B`,
sum(Ordered_Product_Sales)`Ordered_Product_Sales`, sum(Total_Order_Items) `Total_Order_Items`, sum(Actual_Sales) `Actual_Sales`,
sum(Orders) `Orders`, sum(PPC_Revenue) `PPC_Revenue`, sum(PPC_Orders) `PPC_Orders`,
sum(Revenue)`Revenue`, sum(Sales_Tax_Collected) `Sales_Tax_Collected`, sum(Total_Ad_Spend) `Total_Ad_Spend`, sum(Impressions) `Impressions`,
sum(Profit_after_Fees_before_Costs) `Profit_after_Fees_before_Cost`
FROM clabDevelopment.KPI_kpireport as kt
left outer join
(SELECT Month(Date) as mnth, sku, account, country, avg(sellable)`avgInv` FROM clabDevelopment.`fba_history_daily`
where sellable >= 0
group by Month(Date), sku, account, country) as it
on kt.ItemSKU = it.SKU
and kt.Account = it.account
and kt.Country = it.country
and it.mnth = Month(kt.Date)
WHERE kt.Country = 'USA' or kt.Country = 'CAN'
GROUP BY Account, Country,(Date + INTERVAL -(WEEKDAY(Date)) DAY), ItemSKU
ORDER BY Date desc
The sub-query would be from the same table I am joining on the bottom except I group by month there. So I want to run this subquery and grab the value under sellable for the date of max(Date):
(select sellable from clabDevelopment.`fba_history_daily where Date = max(Date))
When I do it this way I get invalid use of group function.
Without known your schema and the engine/db it is difficult to understand the problem. But, here is a best guess with the following schema:
fba_history_daily
- mnth
- sku
- account
- country
- sellable
- SKU
KPI_kpireport
- Account
- Country
- ItemSKU
- Account
- Date
- Country
- ASIN
The following query would give you what you're looking for. This uses a GROUP_CONCAT in order to build the required results through aggregation. With the nested query join MySQL might be building a temporary table within memory to sort through those records which would not be optimal. You can check this using EXPLAIN and you would see Using temporary in the details.
SELECT
(Date + INTERVAL -(WEEKDAY(Date)) DAY) `Date`,
ASIN,
ItemSKU,
-- MIN
(SUBSTRING_INDEX(GROUP_CONCAT(it.sellable ORDER BY it.Date ASC),',', 1) AS minSellable),
-- MAX
(SUBSTRING_INDEX(GROUP_CONCAT(it.sellable ORDER BY it.Date DESC),',', 1) AS maxSellable),
-- AVG
AVG(it.sellable) avgInv,
kt.Account, kt.Country, SUM(Sessions) `Sessions`, avg(Session_Pct)`Session_Pct`,
sum(Page_Views)`Page_Views`, avg(Page_Views_Pct)`Page_Views_Pct`, avg(Buy_Box_Pct)`Buy_Box_Pct`,
sum(Units_Ordered)`Units_Ordered`, sum(Units_Ordered_B2B) `Units_Ordered_B2B`,
avg(Unit_Session_Pct)`Unit_Session_Pct`, avg(Unit_Session_Pct_B2B)`Unit_Session_Pct_B2B`,
sum(Ordered_Product_Sales)`Ordered_Product_Sales`, sum(Total_Order_Items) `Total_Order_Items`, sum(Actual_Sales) `Actual_Sales`,
sum(Orders) `Orders`, sum(PPC_Revenue) `PPC_Revenue`, sum(PPC_Orders) `PPC_Orders`,
sum(Revenue)`Revenue`, sum(Sales_Tax_Collected) `Sales_Tax_Collected`, sum(Total_Ad_Spend) `Total_Ad_Spend`, sum(Impressions) `Impressions`,
sum(Profit_after_Fees_before_Costs) `Profit_after_Fees_before_Cost`
FROM KPI_kpireport as kt
left outer join fba_history_daily it on
kt.ItemSKU = it.SKU
and kt.Account = it.account
and kt.Country = it.country
and Month(it.Date) = Month(kt.Date)
and it.sellable >= 0
WHERE kt.Country = 'USA' or kt.Country = 'CAN'
GROUP BY Account, Country,(Date + INTERVAL -(WEEKDAY(Date)) DAY), ItemSKU
ORDER BY Date desc

Select column(s) corresponding to max/min of another column without joins

I have a table (id, employee_id, device_id, logged_time) [simplified] that logs attendances of employees from biometric devices.
I generate reports showing the first in and last out time of each employee by date.
Currently, I am able to fetch the first in and last out time of each employee by date, but I also need to fetch the first in and last out device_ids of each employee. The entries are not in sequential order of the logged time.
I do not want to (and probably cannot) use joins as in one of the reports the columns are dynamically generated and can lead to thousands of joins. Furthermore, these are subqueries and are joined to other queries to get further details.
A sample setup of the table and queries are at http://sqlfiddle.com/#!9/3bc755/4
The first one just shows lists the entry and exit time by date of every employee
select
attendance_logs.employee_id,
DATE(attendance_logs.logged_time) as date,
TIME(MIN(attendance_logs.logged_time)) as entry_time,
TIME(MAX(attendance_logs.logged_time)) as exit_time
from attendance_logs
group by date, attendance_logs.employee_id
The second one builds up an attendance chart given a date range
select
`attendance_logs`.`employee_id`,
DATE(MIN(case when DATE(`attendance_logs`.`logged_time`) = '2017-09-18' THEN `attendance_logs`.`logged_time` END)) as date_2017_09_18,
MIN(case when DATE(`attendance_logs`.`logged_time`) = '2017-09-18' THEN `attendance_logs`.`logged_time` END) as entry_2017_09_18,
MAX(case when DATE(`attendance_logs`.`logged_time`) = '2017-09-18' THEN `attendance_logs`.`logged_time` END) as exit_2017_09_18,
DATE(MIN(case when DATE(`attendance_logs`.`logged_time`) = '2017-09-19' THEN `attendance_logs`.`logged_time` END)) as date_2017_09_19,
MIN(case when DATE(`attendance_logs`.`logged_time`) = '2017-09-19' THEN `attendance_logs`.`logged_time` END) as entry_2017_09_19,
MAX(case when DATE(`attendance_logs`.`logged_time`) = '2017-09-19' THEN `attendance_logs`.`logged_time` END) as exit_2017_09_19
/*
* dynamically generated columns for dates in date range
*/
from `attendance_logs`
where `attendance_logs`.`logged_time` >= '2017-09-18 00:00:00' and `attendance_logs`.`logged_time` <= '2017-09-19 23:59:59'
group by `attendance_logs`.`employee_id`;
Tried:
Similar to max and min logged_time of each date using case, tried to select the device_id where logged_time is max/min.
```MIN(case
when
`attendance_logs.logged_time` = MIN(
case when DATE(`attendance_logs`.`logged_time`)
= '2017-09-18' THEN `attendance_logs`.`logged_time` END
)
then `attendance_logs`.`device_id` end) as entry_device_2017_09_18 ```
This results in invalid use of group by
A quick hack for your query to pick the device id for in and out by using GROUP_CONCAT with in SUBSTRING_INDEX
SUBSTRING_INDEX(GROUP_CONCAT(case when DATE(`l`.`logged_time`) = '2017-09-18' THEN `l`.`device_id` END ORDER BY `l`.`device_id` desc),',',1) exit_device_2017_09_18,
Or if device id will be same for each in and its out then simply it can be written with GROUP_CONCAT only
GROUP_CONCAT(DISTINCT case when DATE(`l`.`logged_time`) = '2017-09-18' THEN `l`.`device_id` END)
DEMO
To avoid joins I suggest you try "correlated subqueries" instead:
select
employee_id
, logdate
, TIME(entry_time) entry_time
, (select MIN(l.device_id)
from attendance_logs l
where l.employee_id = d.employee_id
and l.logged_time = d.entry_time) entry_device
, TIME(exit_time) exit_time
, (select MAX(l.device_id)
from attendance_logs l
where l.employee_id = d.employee_id
and l.logged_time = d.exit_time) exit_device
from (
select
attendance_logs.employee_id
, DATE(attendance_logs.logged_time) as logdate
, MIN(attendance_logs.logged_time) as entry_time
, MAX(attendance_logs.logged_time) as exit_time
from attendance_logs
group by
attendance_logs.employee_id
, DATE(attendance_logs.logged_time)
) d
;
see: http://sqlfiddle.com/#!9/06e0e2/3
Note: I have used MIN() and MAX() on those subqueries only to avoid any possibility that these return more than one value. You could use limit 1 instead if you prefer.
Note also: I do not normally recommend correlated subqueries as they can cause performance issues, but they do supply the data you need.
oh, and please try to avoid using date as a column name, it isn't good practice.

Need a query to find consecutive orders by month

I am having trouble writing this query.
I need to get the current number of orders that were shipped in consecutive months.
Example: if the current month is November and they placed orders in July, August, September, October, November, it would return 5 for that user. If they didn't place an order in November, it would return 0 because their streak is broken.
The tables I'm concerned with are customer, order, and date.
Use a cross join between the date table and the customer table to get a row for every customer / month combination and then left join that against the order table to get the details, using group by to get the counts.
Something like this, although you will need to modify it to cope with the column names being reserved words.
SELECT customer.name, month.name, COUNT(order.id)
FROM customer
CROSS JOIN date
LEFT OUTER JOIN order
ON customer.id = order.customer_id
AND MONTH(date.date) = MONTH(order.date)
WHERE date.date BETWEEN startofdaterange AND endofdaterange
GROUP BY customer.name, month.name
Or if I have misread the question, and you need a count of the orders if they order every month in the range, or 0 if they skipped a month then something like this (not tested so expect a typo or 2, would need the table def to test):-
SELECT name, CASE WHEN MonthCount = MonthOrderCount THEN OrderCount ELSE 0 END AS ContinuousOrderMonths
FROM (
SELECT CustName, COUNT(MonthName) AS MonthCount, SUM(MonthOrderCount) AS OrderCount, SUM(CASE WHEN MonthOrderCount > 0 THEN 1 ELSE 0 END)
FROM (
SELECT customer.name AS CustName, month.name AS MonthName, COUNT(order.id) AS MonthOrderCount
FROM customer
CROSS JOIN date
LEFT OUTER JOIN order
ON customer.id = order.customer_id
AND MONTH(date.date) = MONTH(order.date)
WHERE date.date BETWEEN startofdaterange AND endofdaterange
GROUP BY customer.name, month.name )Sub1 ) Sub2
GROUP BY CustName
If you want a list of customers and a comma separated list of orders per month:-
SELECT CustName, GROUP_CONCAT(CAST(MonthsOrder AS CHAR))
FROM (
SELECT customer.name AS CustName, month.name, COUNT(order.id) AS MonthsOrder
FROM customer
CROSS JOIN date
LEFT OUTER JOIN order
ON customer.id = order.customer_id
AND MONTH(date.date) = MONTH(order.date)
WHERE date.date BETWEEN startofdaterange AND endofdaterange
GROUP BY customer.name, month.name) Sub1
GROUP BY CustName
You might have to expand this to get the month name with each one and force the order
Here you replace now and static date as per columnname :
select (case
when (month(now())=11 and
(month('2012-02-02')>=7 and month('2012-02-02')<=11))
then 5
else
0 end) as 'month'
from tablename

Issue using the count function in SQL

I am running this query to get a count of the bookedby users total number of sales with insurance and total number of sales without insurance. However, all of the users are getting the same count for some reason. how can i change my query to show each users totals instead.
what i want is basically to figure out how many bookings each user had with and without insurance sales
bookedby is the agent
and T0 is the table that includes the information about bookings that do not include insurance
and t1 is the table that includes information about bookings with insurance
while both tables provide the same information how can i get a total by booked by for each agent from both tables
SELECT t0.BookedBy, count(t0.resnumber) as NonInsurance, COUNT(t1.resnumber) as Insurance
FROM (SELECT BookedBy, ResNumber, DATEPART(year, BookDate) AS Year, DATEPART(month, BookDate) AS month
FROM dbo.ResGeneral
WHERE ResNumber NOT IN (SELECT ResNumber FROM dbo.ResItinerary_insurance)
and ResStatus = 'a'
GROUP BY BookedBy, ResNumber, BookDate) t0
left JOIN (SELECT BookedBy, ResNumber, DATEPART(year, BookDate) AS Year, DATEPART(month, BookDate) AS month
FROM dbo.ResGeneral
WHERE ResNumber IN (SELECT ResNumber FROM dbo.ResItinerary_insurance)
and ResStatus = 'a') t1
ON t1.year = t0.year
group by t0.bookedby
I think this query is equivalent:
SELECT g.BookedBy,
SUM(CASE WHEN i.ResNumber IS NULL THEN 1 ELSE 0 END) AS NonInsurance,
SUM(CASE WHEN i.ResNumber IS NOT NULL THEN 1 ELSE 0 END) AS Insurance
FROM dbo.ResGeneral g
LEFT JOIN dbo.ResItinerary_insurance i
ON g.ResNumber = i.ResNumber
WHERE g.ResStatus = 'a'
GROUP BY g.BookedBy;
Your join condition looks incorrect:
ON t1.year = t0.year
This will cross join all rows with the same year. You probably want to use a more specific condition, for example t1.BookedBy = t0.BookedBy.