I want to ask how to orderBy some of multiple columns.
In the items table, I have 3 columns 'is_recommended', 'is_popular', 'is_new' (tags)
I want to sort by the total number of tags for each row. For examples
id=1 has is_recommended=1, is_popular=1, is_new=1 => attributes = 3
id=2 has is_recommended=0, is_popular=0, is_new=1 => attributes = 1
id=3 has is_recommended=1, is_popular=0, is_new=1 => attributes = 2
Therefore, the order will be id2, id3, id1.
My code:
$items = Item::addSelect(['attributes' => Item::selectRaw('`is_recommended` + `is_popular` + `is_new` as attributes')])
->sortable()
->orderBy('id', 'DESC')->with('item_category', 'restaurant')->paginate(20);
Related
I'm having trouble working in Redbean PHP with querying a table with an array of json objects in a single field, and producing a report on it.
I want to get a report with subtotals of all staff with notes by the category. I know this should be easy/obvious but I'm just not getting it properly.
I have a database, with:
table clients
with columns:(int) client_id, (string) client_name, (array of json) notes
notes is an array of json with
(int) note_id, (int) note_category_id, (int) staff_id, (string) description, (memo) content, (date) note_date
table staff with columns (int) sid, (string) sname
table categories with columns (int) cat_id, (string) cat_name
So in pseudocode (since I'm still trying to figure this all out)
I need to run a query like: (with parameters in brackets)
R::getAll('Select * from Join (staff, categories, clients)
On (staff.sid=clients.services.staff_id, categories.cat_id=clients.services.note_category_id)
Where (clients.services.note_date Between [startdate] and [enddate],
categories.cat_name IN [chosencateg], staff.sname IN [pickednames])
Orderby sname Asc, cat_name Asc, note_date Desc ');
report output format:
Filters used: [picked filter choices if any]
-----------
[sname]
-- note category: [cat_name] 1
[note_date] 1 [description] 1 [content] 1
[note_date] 2 [description] 2 [content] 2
note category 1 subtotal
-- note category: [cat_name] 2
[note_date] 3 [description] 3 [content] 3
[note_date] 4 [description] 4 [content] 4
note category 2 subtotal
staff subtotal
[sname] 2 ...
I'm asking a fairly generic one because I'll have to work with a number of similar tables, and maybe seeing a query template will help my understanding.
Thanks for any help.
redbean is fantastic and - getAll is just scratching the surface and truly isn't working with redbean at all really... Read up on it here:
Here's a query template to get you started:
Query Template:
1)
R::getAll('Select * from Join (staff, categories, clients)
On (staff.sid=clients.services.staff_id, categories.cat_id=clients.services.note_category_id)
Where (clients.services.note_date Between :startdate and :enddate,
categories.cat_name IN (:chosencateg), staff.sname IN (:pickednames))
Orderby sname Asc, cat_name Asc, note_date Desc ');
You could also simply use:
2)
R::getAll('Select * from Join (staff, categories, clients)
On (staff.sid=clients.services.staff_id, categories.cat_id=clients.services.note_category_id)
Where (clients.services.note_date Between ? and ?,
categories.cat_name IN (?), staff.sname IN (?))
Orderby sname Asc, cat_name Asc, note_date Desc ');
The only difference is that query template 1 uses named parameters (so it's going to look to the array of params that you pass it to contain an associative array with parameters named in the same way as they are in the query). While template 2 requires simply an array of parameters with the indexes lined up with the order in which the ? marks appear in your query.
Anyway... the query should return an associative array of your columns representing rows. a var_dump would look something like this:
Array
(
[client_id] => 1,
[client_name] => "joe",
[noes] => "[
{note_id=1
,note_category_id=1
,staff_id=1
,description=blah blah
,content=blah blah blah blah
,content=some content for this note
,note_date=12/06/2018
}
]"
[sid] => 100,
[sname] => "some staff name"
[cat_id] => 100
[cat_name] => "some category name"
)
Notice how the notes field has just come out as a string (I know the json above is not properly formed json, I'm just trying to show an example).
I assume that what you want is to have that string converted into an array so you can work with it as if it were data and not a string. So the below should get you started with that:
Once you have it out of the database you should be able to access it like this:
$result = R::getAll($query,
['startdate'=> $mystartDate
,'enddate' => $myEndDate
,'chosencateg'=>$myChosenCategory
,'pickednames'=>$myPickedNames
]);
// this would output the json string to your screen
echo $result['notes'];
but it seems like you want to work with the json as if it were part of your data - so... you would need to decode it first.
// decode my notes field:
foreach(array_key($result) as $key) {
/* you are working with a multidimensional array in this loop
, use $key to access the row index. Each row index
will contain named column indexes that are column names from the database
*/
$result[$key]['decoded_notes'] = json_decode($result[$key]['notes'],true);
}
// I now have a new column in each row index, containing 'notes'
as another associative array
// the statement below now results in an array to string conversion error:
echo $result[someIndexNumber]['decoded_notes'];
So, I decided I would want this in MySQL (5.7) so as to use its capabilities. To do this I used string manipulation. MySQL 8 adds json_table functions which would have been nice to have.
I converted each array of JSON notes into lines of 'INSERT INTO temptable' to convert the array list into temptable rows,
one JSON object per row, adding the client_id to each object, then
EXECUTEing those statements.
SET #allnotes = (
SELECT json_arrayagg(REPLACE(`notes`,'{', CONCAT('{"id_client": ', id_client, ', '))) /* add id_client to each note object */
FROM clients
WHERE `notes` != '' AND `notes` != '[]' ); /* no empty note cases */
SET #allnotes = REPLACE(REPLACE(#allnotes ,'"[',''),']"','' ); /* flatten out outer array of each note */
SET #allnotes = REPLACE(REPLACE(#allnotes ,'{','("{'),'}','}")' ); /* INSERT INTO string formatting for the objects */
DROP TEMPORARY TABLE IF EXISTS jsonTemporary;
CREATE TEMPORARY TABLE IF NOT EXISTS jsonTemporary (anote json);
SET #allnotes = REPLACE(REPLACE(#allnotes,'[','INSERT INTO jsonTemporary (anote) VALUES '),']',';');
PREPARE astatement FROM #allnotes;
EXECUTE astatement;
/* totals */
SELECT concat(staff.last_name,", ",staff.first_name) AS sname,
categories.name AS cat_name,
count(anote->'$.id_client') AS cat_total,
FROM jsonTemporary
JOIN categories ON cast(anote->'$.note_category_id' as unsigned)=categories.id
JOIN clients ON clients.id_client=anote->'$.id_client'
JOIN staff ON staff.id=anote->'$.staff_id'
WHERE anote->'$.note_date' >= "2018-10-01" AND anote->'$.note_date' <= "2018-12-31"
GROUP BY sname, cat_name;
/* all notes */
SELECT concat(staff.last_name,", ",staff.first_name) AS sname,
categories.name AS cat_name,
anote->'$.note_date' AS n_date,
anote->'$.description' AS description,
anote->'$.content' AS content,
FROM jsonTemporary
JOIN categories ON cast(anote->'$.note_category_id' as unsigned)=categories.id
JOIN clients ON clients.id_client=anote->'$.id_client'
JOIN staff ON staff.id=anote->'$.staff_id'
WHERE anote->'$.note_date' >= "2018-10-01" AND anote->'$.note_date' <= "2018-12-31"
GROUP BY sname, cat_name;
DROP TEMPORARY TABLE IF EXISTS jsonTemporary;
What I'm trying to achieve is doing 3 queries in one go, to limit the n1+ problem :
given we have 3 models :
trips
id => int
price => float
city_id => uint
........
cities
id => int
name => varchar
........
ratings:
id => int
ratable_id => int
rate => small-int
......
pseudocode:
select from tours where price >= 100
-then from the result
select from cities where id in result.city_id as cities
select count from ratings where ratable_id in result.id as rates groupBy rate
so the result is
[
trips => list of the trips where price more than or equal 100
cities=> list of the cities those trips belongs to
rates => list of rating with it's count so like [1 => 5, 2 => 100] assuming that '1 and 2' are the actual rating , and '5,100' is the trips count
]
how would I achieve that?
Two ways to go, Use eloquent methods which is preferred approach or use joins a single query to get your desired results
Moving forward with eloquent way i assume you have defined your models and their mappings based on their type of relationship (1:m, m:m)
$trips= Trips::with('city')
->withCount('ratings')
->where('price', '>=', 100)
->get();
Moving forward with join
$trips = DB::table('trips as t')
->select('t.id', 't.price','c.name',DB::raw('count(*) as rating_count'))
->join('cities as c' ,'t.city_id', '=' , 'c.id')
->join('ratings as r' ,'t.ratable_id', '=' , 'r.id')
->where('t.price', '>=', 100)
->groupBy('t.id', 't.price','c.name')
->get();
Trip Model Relationship
public function city(){
return $this->belongsTo(City::class);
}
public function ratings(){
return $this->hasMany(Rating::class, 'ratable_id'); //assuming ratable_id is an id of trips table
}
Fetch Data
$trips= Trip::with('city', 'ratings')
->where('price', '>=', 100)
->get();
Print Data
foreach($trips as $trip){
$trip->city->name." - ". $trip->price." - ". $trip->ratings()->avg('rate');
}
Given these tables/fields:
people:
id
socialID
name
peopleStats:
id
socialID
drVisitsAmount
receipAmounts
operationsAmount
Im trying to get last 3 records from peopleStats (ordered by id DESC) for a given array of socialIDs (3 records per each socialID):
$dummyData = PeopleStats::find()
->where(['socialID' => $socialIDs])
->groupBy(['socialID'])
->orderBy(['id' => SORT_DESC])
->limit(3)
->all();
but i just get 3 records and not 3 per each. How can i get this data?
Thank you in advance,
In my Database i have a field with a comma separated list of ids (itemids).
For an example i fill some rows with test data:
Row | itemids
1 => 2,5,8,11,22,45
2 => 4,6,9,13
3 => 1,3,5,16,23
4 => 6,12,18,21,24
Now i have a search string which contains all ids i want to serch for:
searchstring => 4,23,40
What i want to do now is to get all rows from the database which contains one or more values from my search string. From my example i should get the following rows:
Row | itemids
2 => 4,6,9,13
3 => 1,3,5,16,23
How do i do this? Can you give me an example SQL Query?
Thanks for help!
Query for searchstring => 4,23,40
select * from
item_table
where find_in_set(4,itemids)>0
UNION
select * from
item_table
where find_in_set(23,itemids)>0
UNION
select * from
item_table
where find_in_set(40,itemids)>0
I have four tables that are linked.
images Table
cid link
=======================
1 something.jpg
2 else.jpg
terms Table
cid term is_attr
================================
1 Location 0
2 Caption 1
3 Camera Lens 0
tags Table
cid Name term_id
==============================
1 somewhere 1
2 BFE 1
3 A word 2
linked_tags Table
cid photo_id tag_id
==========================
1 1 1
2 1 2
3 1 3
if a Term is_attr == 1 the image should only have ONE entry in the linked_tags table for that term.
If I were to query the image table and get the tags at the same time, how would I do that?
I'd like to have (something) this returned:
_____________________________________________________________________________
cid |link |attributes |tags |
=========|==================|==================|====================|
1 |something.jpg |__________________|____________________|
| ||term |value |||term |value ||
| ||========|=======|||========|=========||
| ||caption |A word |||Location|somewhere||
| || | |||Location|BFE ||
This is what I am looking for (PHP side):
//Row 1
array(
'link' => "something.jpg",
'attributes' => array('caption'=>"A word"),
'tags' => array('Location'=>array('somewhere','BFE'))
);
//Notice 'caption' points to a string and 'location' points to an array
//Row 2
array(
'link' => "else.jpg",
'attributes' => array(),
'tags' => array()
);
// OR
array(
'link' => "else.jpg"
);
// OR
array(
'link' => "else.jpg",
'attributes' => array('caption'=>""),
'tags' => array();
);
SELECT i.*, GROUP_CONCAT(tag.name SEPARATOR ', ')
FROM image i
JOIN tag
ON tag.image_id = i.id
GROUP BY
i.id
Update:
Tabular format assumes same number of columns in each record, all having the same type across the records.
Your task is best done in three queries:
SELECT cid, link
FROM image
WHERE cid = 1
(cid and link)
SELECT t.name, tag.name
FROM linked_tags lt
JOIN tags
ON tags.cid = lt.tag_id
JOIN terms t
ON t.cid = tags.term_id
WHERE lt.photo_id = 1
AND t.is_attr = 1
(attributes)
SELECT t.name, tag.name
FROM linked_tags lt
JOIN tags
ON tags.cid = lt.tag_id
JOIN terms t
ON t.cid = tags.term_id
WHERE lt.photo_id = 1
AND t.is_attr = 0
(tags)
Most likely you'd want to the GROUP_CONCAT function.
edit:
phooey. Quassnoi beat me to the punch. Just as a warning: GROUP_CONCAT's output is length limited (usually 1024 bytes by default), but you can override it with the group_concat_max_length run-time setting.
Am I missing something, or does this just need a few LEFT JOINs?
$query = 'SELECT images.cid, images.link, terms.term, tags.name, terms.is_attr FROM images LEFT JOIN linked_tags ON images.cid = linked_tags.photo_id LEFT JOIN tags ON linked_tags.tag_id = tags.cid LEFT JOIN terms ON tags.term_id = terms.cid WHERE 1 ORDER BY images.cid';
$result = mysql_query($query);
The result set will give you multiple rows for each image, and you can use PHP to parse the data into whatever format you need like this.
$image_list = array();
while ($line = mysql_fetch_assoc($result)) { // fetch mysql rows until there are no more
if (!isset($image_list[$line['cid']]) { // checks to see if we have created this image array row yet
// if not, create new image in image array using cid as the key
$image_list[$line['cid']] = array('link' => $line['link'], 'attributes' => array(), 'tags' => array());
}
if ($line['term'] != null) { // if there are no linked attributes or tags, move on to the next image leaving both arrays empty, otherwise...
if ($line['is_attr']) {
// if attribute row, add attribute using term as key
$image_list[$line['cid']]['attributes'][$line['term']] = $line['name'];
} else {
// if tag row, add tag using term as key
$image_list[$line['cid']]['tags'][$line['term']] = $line['name'];
}
}
}
If I am reading you correctly, this should output exactly what you are looking for.