Laravel Eloquent Query Multiple records based on set criteria - php

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();

Related

GroupBy Email & get earliest created_at date in Group - Laravel Eloquent

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

Mysql query: Find least used words

I have a table of words used in the title of articles. I want to find which words which are used the least in the set or article titles.
Example:
Titles:
"Congressman Joey of Texas does not sign bill C1234."
"The pretty blue bird flies at night in Texas."
"Congressman Bob of Arizona is the signs bill C1234."
The table would contain the following.
Table WORDS_LIST
----------------------------------------------------
| INDEX ID | WORD | ARTICLE ID |
----------------------------------------------------
| 1 | CONGRESSMAN | 1234 |
| 2 | JOEY | 1234 |
| 3 | SIGN | 1234 |
| 4 | BILL | 1234 |
| 5 | C1234 | 1234 |
| 6 | TEXAS | 1234 |
| 7 | PRETTY | 1235 |
| 8 | BLUE | 1245 |
| 9 | BIRD | 1245 |
| 10 | FLIES | 1245 |
| 11 | NIGHT | 1245 |
| 12 | TEXAS | 1245 |
| 13 | CONGRESSMAN | 1246 |
| 14 | BOB | 1246 |
| 15 | ARIZONA | 1246 |
| 16 | SIGNS | 1246 |
| 17 | BILL | 1246 |
| 18 | C1234 | 1246 |
----------------------------------------------------
In this case, the words "pretty,blue, flies, night" would be the used in the least number of articles.
I would appreciate any ideas on how to best create this query. So far below is what I started with. I can also write something in PHP but figured a query would be faster.
SELECT distinct a1.`word`, count(a1.`word`)
FROM mmdb.words_list a1
JOIN mmdb.words_list b1
ON a1.id = b1.id AND
upper(a1.word) = upper(b1.word)
where date(a1.`publish_date`) = '2017-06-09'
group by `word`
order by count(a1.`word`);
I don't see why a self-join is necessary. Just do something like this:
select wl.word, count(*)
from mmdb.words_list wl
where date(wl.`publish_date`) = '2017-06-09'
group by wl.word
order by count(*);
You can add a limit to get a fixed number of words. If publish_date is already a date, you should do the comparison as:
where publish_date = '2017-06-09'
If it has a time component:
where publish_date >= '2017-06-09' and publish_date < '2017-06-10'
This expression allows MySQL to use an index.
Try this. It's a bit more simple and should return the correct results:
SELECT `WORD`,
COUNT(*) as `num_articles`
FROM `WORDS_LIST`
WHERE date(`publish_date`) = '2017-06-09'
GROUP BY `WORD`
ORDER BY COUNT(*) ASC;

MySQL: group occurrences per date

I guess this is a recurring question but I haven't found the right topic to point me in the right direction.
I have chat table which pretty much looks like:
+---------+----------------+---------------------+-----------------+
| id(int) | author(string) | date(datetime) | message(string) |
+---------+----------------+---------------------+-----------------+
| 1 | John | 2016-01-01 17:18:00 | I |
| 2 | Mary | 2016-01-01 14:22:00 | Just |
| 3 | John | 2016-01-01 09:02:00 | Want |
| 4 | John | 2016-01-02 17:18:00 | To |
| 5 | Mary | 2016-01-03 18:26:00 | Say |
| 6 | John | 2016-01-03 10:42:00 | Hello |
+---------+----------------+---------------------+-----------------+
What I would like to get:
+------------+------+------+
| day | Mary | John |
+------------+------+------+
| 2016-01-01 | 1 | 2 |
| 2016-01-02 | 0 | 1 |
| 2016-01-03 | 1 | 1 |
+------------+------+------+
Am I obligated to do a subquery in the COUNT statement ?
So far I came up with:
SELECT DATE(date) as day,
(SELECT COUNT(id) FROM chat WHERE author = 'Mary') AS 'Mary'
(SELECT COUNT(id) FROM chat WHERE author = 'John') AS 'John'
FROM chat
GROUP BY day
ORDER BY day ASC
But this is giving me the total message count per author at each row:
+------------+------+------+
| day | Mary | John |
+------------+------+------+
| 2016-01-01 | 2 | 4 |
| 2016-01-02 | 2 | 4 |
| 2016-01-03 | 2 | 4 |
+------------+------+------+
Just use conditional aggregation:
SELECT DATE(date) as day,
SUM(author = 'Mary') AS Mary,
SUM(author = 'John') AS John
FROM chat
GROUP BY day
ORDER BY day ASC
I might be wrong, but it looks for me like you search for a solution, where you have a small group of the same users? Even if it is true, I would change the logic in a way to avoid generating the query and having problems for larger amounts of users. It might not be the perfect solution for your current problem, but might help to save some time in the future. So I would use a different pattern.
As query, I would use something like this:
SELECT DATE(`date`) AS day,
`author`,
COUNT(`id`) AS messagecount
FROM `chat`
GROUP BY `day`, `author`
ORDER BY `day` ASC
With this you would get a result like this:
+------------+--------+--------------+
| day | author | messagecount |
+------------+--------+--------------+
| 2016-01-01 | Mary | 2 |
| 2016-01-01 | John | 4 |
| 2016-01-02 | Mary | 2 |
| 2016-01-02 | John | 4 |
+------------+--------+--------------+
After that, you can group the result in PHP to have the desired effect, for example generate an array like this using the date as key:
array(
'2016-01-01' => array(
'day' => '2016-01-01',
'Mary' => 2,
'John' => 4
),
'2016-01-02' => array(
'day' => '2016-01-02',
'Mary' => 2,
'John' => 4
),
)

mysql group and remove duplicates

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

SQL request for several tables and operations in MySQL

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
...

Categories