How can I get the earliest created_at date when using groupBy in an eloquent statement?
I have a table with the following:
| id | name | email | created_at |
|----|--------|---------|---------------------|
| 1 | test | 1#1.com | 2018-01-01 09:00:00 |
| 2 | test | 1#1.com | 2018-01-02 09:00:00 |
| 3 | test 2 | 2#2.com | 2018-01-02 09:00:00 |
| 4 | test 2 | 2#2.com | 2018-01-03 09:00:00 |
| 5 | test | 1#1.com | 2018-01-03 09:00:00 |
| 6 | test | 1#1.com | 2018-01-04 09:00:00 |
| 7 | test | 1#1.com | 2018-01-05 09:00:00 |
| 8 | test 3 | 3#3.com | 2018-01-06 09:00:00 |
| 9 | test 3 | 3#3.com | 2018-01-07 09:00:00 |
when running the following query:
Customers::OrderBy('created_at', 'desc')->groupBy('email')->paginate('5');
I was expecting to get the results in the following order:
Test 3 (created: 2018-01-07)
Test (created: 2018-01-05)
Test 2 (created: 2018-01-03)
But instead I am getting so it doesn't look like it's respecting the OrderBy
Test 3 (created: 2018-01-06)
Test 2 (created: 2018-01-02)
Test (created: 2018-01-01)
Here is the SQL fiddle
http://sqlfiddle.com/#!9/89ee39/2
# EDIT - Answer based on response
Thanks to Nesku, finally query to get this working was:
return DB::table('customers')
->select(DB::raw('email, max(created_at)'))
->groupBy('email')
->orderBy('max(created_at)', 'desc')
->get();
You can't order by created_at because it contains multiples values, you can however take the max of created_at for each email and order by that.
The SQL query would look like this :
SELECT email, max(created_at)
FROM `customers`
GROUP BY email
ORDER BY max(created_at) DESC
http://sqlfiddle.com/#!9/89ee39/12
Related
I have a form where my users can set Criteria to search their customer database, however I am struggling to pull the query together for this (working in Laravel 5.7).
Currently the criteria customers can set is as follows:
Customer has “exactly | more | less” than X visits
Customer first visit was “exactly | more | less” than X days
Customer last visit was “exactly | more | less” than X days
Customer provider is “facebook | twitter | email | any”
I am now trying to get my head around how I can build this into a query, I can’t even produce a tangible example! My hurdle seems to be checking the first record & the last record to make sure it meets the criteria.
SQLFiddle: http://sqlfiddle.com/#!9/68407/1
My Table:
| id | name | email | provider | created_at
---------------------------------------------------------------------------------------
| 1 | Mr Smith | mr.smith#example.com | facebook | 2018-11-01 09:00:00 |
| 2 | Mrs Smith | mrs.smith#example.com | facebook | 2018-11-01 09:00:00 |
| 3 | Miss Smith | miss.smith#example.com | email | 2018-11-01 09:00:00 |
| 4 | Doctor Smith | doctor.smith#example.com | email | 2018-11-01 09:00:00 |
| 5 | Lord Smith | lord.smith#example.com | twitter | 2018-11-01 09:00:00 |
| 6 | Lady Smith | lady.smith#example.com | email | 2018-11-01 09:00:00 |
| 7 | Mr Smith | mr.smith#example.com | facebook | 2018-11-02 09:00:00 |
| 8 | Mrs Smith | mrs.smith#example.com | facebook | 2018-11-02 09:00:00 |
| 9 | Doctor Smith | doctor.smith#example.com | email | 2018-11-02 09:00:00 |
| 10 | Lord Smith | lord.smith#example.com | twitter | 2018-11-02 09:00:00 |
| 11 | Lady Smith | lady.smith#example.com | email | 2018-11-02 09:00:00 |
| 12 | Mr Smith | mr.smith#example.com | facebook | 2018-11-03 09:00:00 |
| 13 | Mrs Smith | mrs.smith#example.com | facebook | 2018-11-03 09:00:00 |
| 14 | Miss Smith | miss.smith#example.com | email | 2018-11-03 09:00:00 |
| 15 | Lord Smith | ord.smith#example.com | twitter | 2018-11-03 09:00:00 |
| 16 | Lady Smith | lady.smith#example.com | email | 2018-11-03 09:00:00 |
Example customer criteria for the query:
Customer with more than 2 visits
Customer first visit was more than 2 days ago
Customer last visit was more than 1 day ago
Customer provider is Facebook
Current query:
$Customers = Customer::groupBy('email')
->havingRaw('COUNT(*) > 2’)
->where('created_at', '<', Carbon::now()->subDays(2)->toDateTimeString())
->get();
I can’t figure out how to pull the first customer record check that it more than 2 days old, and then pull their last record and make sure it is more than 1 day old.
I know that my current query is completely useless for what I am trying to achieve, but again I am struggling to pull this together.
Expected results:
| id | name | email | provider | created_at
---------------------------------------------------------------------------------------
| 12 | Mr Smith | mr.smith#example.com | facebook | 2018-11-03 09:00:00 |
| 13 | Mrs Smith | mrs.smith#example.com | facebook | 2018-11-03 09:00:00 |
The query you are looking for is:
select * from customers group by email having count(*) > 2 and min(created_at) <= '2018-10-02 09:00:00' and max(created_at) <= '2018-10-03 09:00:00' and provider = 'facebook'
assuming the current time is 2018-10-04 09:00:00.
In eloquent:
$Customers = Customer::groupBy('email')
->havingRaw('COUNT(*) > 2')
->havingRaw('max(created_at) < ?' , [Carbon::now()->subDays(2)->toDateTimeString()])
->havingRaw('min(created_at) < ?' , [Carbon::now()->subDays(1)->toDateTimeString()])
->havingRaw('provider = ?' , ['facebook'])
->get();
On a separate note, using eloquent you can chain methods, like the following
$customers = Customer::groupBy('email');
if( $includeCount ) {
$customers->havingRaw('COUNT(*) > 2');
}
...
...
...
The SQL query in the MySQL dialect to make that selection is as follow
SELECT id, name, email, provider, created_at
FROM customers
WHERE provider = 'facebook'
GROUP BY email
HAVING
count(*) > 2
AND min(created_at) < date_sub(now(), interval 2 day)
AND max(created_at) < date_sub(now(), interval 1 day)
That can be translated as an Eloquent query like so
$havingClause = 'count(*) > ? AND min(created_at) < ? AND max(created_at) < ?';
$havingClauseBindings = [
2,
Carbon::now()->subDays(2)->toDateTimeString(),
Carbon::now()->subDays(1)->toDateTimeString()
];
$customers = Customer::where('provider', 'facebook')
->groupBy('email')
->havingRaw($havingClause, $havingClauseBindings)
->get();
I have input table:
+-----+------------+---------------------+---------+-------------+
| id | client_id | subscription_date | program |how_long_subs|
+-----+------------+---------------------+---------+-------------+
| 1 | 1 | 2016-05-20 00:00:00 | W90 | 12 |
| 2 | 4 | 2017-01-01 00:00:00 | W90 | 6 |
| 314 | 7 | 2017-03-21 00:00:00 | AB | 12 |
| 3 | 8 | 2016-12-19 00:00:00 | W90 | 12 |
| 4 | 9 | 2018-06-08 00:00:00 | W90 | 12 |
| 5 | 10 | 2017-06-12 00:00:00 | W90 | 12 |
| 313 | 10 | 2017-01-18 00:00:00 | AB | 12 |
| 377 | 11 | 2018-12-22 00:00:00 | AB | 12 |
| 308 | 11 | 2017-12-22 00:00:00 | AB | 12 |
| 6 | 11 | 2017-12-22 00:00:00 | W90 | 12 |
| 7 | 13 | 2017-01-05 00:00:00 | W90 | 6 |
| 8 | 21 | 2017-03-10 00:00:00 | W90 | 12 |
| 325 | 22 | 2017-03-02 00:00:00 | AB | 1 |
+-----+------------+---------------------+---------+-------------+
id is unique autoincremented column for this table client_id is to connect with clients table. I am looking for a result in form:
+------------+----------+-----------------------------------------+--------+
| client_id | programs | dates |how_long|
+------------+----------+-----------------------------------------+--------+
| 1 | W90 | 2016-05-20 00:00:00 | 12 |
| ... | | | |
| 11 | W90|AB | 2017-12-22 00:00:00|2018-12-22 00:00:00 | 12|12 |
+------------+----------+-----------------------------------------+--------+
so every client_id has 3 columns: programs, dates, how_long
so far I have below for columns programy and na_ile
SELECT
client_id,
GROUP_CONCAT(DISTINCT program SEPARATOR '|') AS programs
FROM subscriptions GROUP BY client_id
but I have no idea how to do this for dates, so everything is in good order.
in example result I chose some client that has multiple rows for different programs, to best illustrate, as you can see dates in result are MAX date for certain program.
To give you better picture what I'm trying to do is process my input table, to be able to calculate remaining subscription for different programs.
Formula is:
if client_id has no records in subscriptions then use date from clients table
and return difference between that date +1 year and today.
if client_id has records in subscriptions then
for every unique program client has in subs table:
get max subscription_date and corresponding how_long_subs for that max date/program. add how_long_subs months to subscription date and return difference from now in days.
My plan is to get result table as described above and use php to process rows to get something like this:
example result for client_id: 11
W90: 2017-12-22 00:00:00 +12 months - now()
AB: 2018-12-22 00:00:00 +12 months - now()
Can you please help me with mySQL query and advice if approach im taking is ok or maybe there is some much easier way of doing it.
Thank you
SELECT A.client_id,
GROUP_CONCAT(DISTINCT A.program SEPARATOR '|') programs,
GROUP_CONCAT(DISTINCT A.subscription_date SEPARATOR '|') dates,
GROUP_CONCAT(A.how_long_subs SEPARATOR '|') how_long
FROM
(SELECT client_id, subscription_date, MAX(program) program, how_long_subs
FROM subscriptions
GROUP BY client_id, subscription_date,how_long_subs) A
GROUP BY A.client_id;
See DEMO on SQL Fiddle.
You could use a subquery for get the max date
SELECT client_id, GROUP_CONCAT( program SEPARATOR '|') programs,
GROUP_CONCAT(max_date SEPARATOR '|') max_date
FROM (
SELECT
client_id,
programs,
max(subscription_date) max_date
FROM subscriptions
GROUP BY client_id, program )
I have quiet interesting task at my university.
I have DB table:
----------------------------------------
| id | fee | status | created_at |
----------------------------------------
| 1 | 10 | COMPLAINABLE | 2018-05-01 |
| 2 | 15 | COMPLAINABLE | 2018-05-01 |
| 3 | 18 | COMPLAINABLE | 2018-05-02 |
| 4 | 1 | COMPLAINABLE | 2018-05-03 |
| 5 | 2 | COMPLAINABLE | 2018-05-03 |
----------------------------------------
I wrote SQL:
SELECT created_at AS ts, SUM(fee) AS value
FROM leads_ads
WHERE status NOT IN ('COMPLAINED', 'COMPLAIN_ACCEPTED')
GROUP BY DATE_FORMAT(created_at, '%Y-%m')
ORDER BY created_at ASC;
And at first result was fine:
-------------------------
| ts | value |
-------------------------
| 2018-05-01 | 25 |
| 2018-05-02 | 18 |
| 2018-05-03 | 3 |
-------------------------
But now i have to rewrite sql to get result like this:
---------------------------------------------------------------
| ts0 |value0| ts1 | value1 | ...... | tsn | valuen |
---------------------------------------------------------------
|2018-05-01| 25 |2018-05-02| 18 | ...... | tsn | valuen |
---------------------------------------------------------------
I tried many ways to do it but can't fine a right result(tried TRANSPOSE pivot table). So I decided to ask here. maybe someone will push me to the right way.
Thanks
Look at this article (this shows how you can resolve this with dynamic SQL): http://onlybluefeet.com/2015/01/18/how-to-rotate-rows-into-columns-in-mysql/
I have got this mySQL table:
id | url | origin | categroy | date
1 | url.com/1 | US | news | 2015-10-01 12:39:19
2 | url.com/1 | | | 2015-10-01 13:59:34
3 | url.com/2 | CN | news | 2015-09-01 12:45:26
4 | url.com/3 | | | 2015-08-12 09:10:10
5 | url.com/3 | US | news | 2015-09-14 04:56:36
6 | url.com/4 | US | | 2015-09-09 12:12:09
is there a way to group the entries by the url and remove the duplicates in one mySQL call without creating a new table?
desired table:
1 | url.com/1 | US | news | 2015-10-01 12:39:19
3 | url.com/2 | CN | news | 2015-09-01 12:45:26
5 | url.com/3 | US | news | 2015-09-14 04:56:36
6 | url.com/4 | US | | 2015-09-09 12:12:09
I am a beginner and I tried to solve this with a php script which failed. I guess there is a fairly easy SQL-answer to that, but I couldn't find an easy answer on Stackoverflow.
Thank you very much!
Try this query
SELECT id, url,
SUBSTRING_INDEX(GROUP_CONCAT(origin ORDER BY date DESC),',',1) as origin_value,
SUBSTRING_INDEX(GROUP_CONCAT(category ORDER BY date DESC),',',1) as category_value,
SUBSTRING_INDEX(GROUP_CONCAT(date ORDER BY date DESC),',',1) as date_value FROM tablename
GROUP BY url
ORDER BY url,date DESC
I have the following 3 tables: unit, stage, stats.
unit stage
+----+--------+ +----+-------+---------------------+
| id | status | | id |unit_id| date |
+----+--------+ +----+-------+---------------------+
| 1 | 2 | | 1 | 2 | 2013-11-22 00:00:00 |
| 2 | 3 | | 2 | 2 | 2013-11-26 12:00:00 |
| 3 | 3 | | 3 | 3 | 2013-10-11 00:00:00 |
| 4 | 0 | | 4 | 1 | 2013-12-29 00:00:00 |
+----+--------+ +----+-------+---------------------+
stats
+----+----------+---------------------+-------+
| id | stage_id | date | clicks|
+----+----------+---------------------+-------+
| 1 | 1 | 2013-11-22 00:00:00 | 10 |
| 2 | 1 | 2013-11-23 00:00:00 | 20 |
| 3 | 1 | 2013-11-24 00:00:00 | 25 |
| 4 | 2 | 2013-11-26 00:00:00 | 15 |
| 5 | 2 | 2013-11-27 12:00:00 | 21 |
| 6 | 3 | 2013-12-29 00:00:00 | 8 |
+----+----------+---------------------+-------+
I need a request, that will produce the following response:
+---------+---------------------+-----------------------+
| unit.id | stage.min.date | sum(stats.max.clicks) |
+---------+---------------------+-----------------------+
| 2 | 2013-11-22 00:00:00 | 46 |
| 3 | 2013-12-29 00:00:00 | 8 |
+---------+---------------------+-----------------------+
by the following rules:
1) unit.id - show only units with unit.status=3
2) stage.min.date - minimal stage.date for corresponding unit_id
3) sum(stats.max.clicks) - sum of stats.clicks with max dvalues for each stage_id associated with corresponding unit_id. In my example 46 = 25(stage_id=1) + 21(stage_id=2)
The problem is in min.date and sum of clicks - I have no idea how to get it in one query. Definitely it`s not a problem to do it using php code and several requests.
Schema in SQL Fiddle
Thanks in advance.
I just ask myself, why I do this? Your example resonse has an error, and does not match your fiddle... but:
SELECT
cc.unit_id, MIN(cc.date) as stage_min_date , SUM(dd.clicks) as stats_max_clicks
FROM
stage cc
LEFT JOIN (
SELECT
bb.stage_id, bb.clicks
FROM
stats bb LEFT JOIN (
SELECT id, stage_id, MAX(date) AS max_date
FROM stats
GROUP BY stage_id
) aa
ON
aa.max_date = bb.date
WHERE
aa.max_date IS NOT NULL
) dd
ON cc.id = dd.stage_id
LEFT JOIN unit ee
ON ee.id = cc.unit_id
WHERE ee.status = 3
GROUP BY cc.unit_id
...