I have a table called stock_movements:
| product_id | type | qty |
|------------|------|------|
| 1 | A | 2 |
| 1 | A | 1 |
| 1 | A | 7 |
| 1 | B | -2 |
| 1 | B | -4 |
| 1 | B | -1 |
| 2 | A | 2 |
| 2 | A | 1 |
| 2 | A | 7 |
| 2 | B | -3 |
| 2 | B | -3 |
| 2 | B | -1 |
I am trying to create a collection of the products where which have the values of the sum of A and sum of B.
i.e.
Group by products, type
Sum qty for each type
The key thing is that it is a collection against the product, which is the bit I am struggling with. Does anyone have any advice?
So far I have got...
return $this->stockMovements()->groupBy('product_id')
->selectRaw('sum(qty), product_id')
->where('type', $this->type)
->get();
But this does not split out for types A and B.
You need a basic pivot query, which would look something like this in Laravel:
return $this->stockMovements()
->selectRaw("SUM(CASE WHEN type = 'A' THEN qty ELSE 0 END) AS sum_a, ".
"SUM(CASE WHEN type = 'B' THEN qty ELSE 0 END) AS sum_b, product_id")
->groupBy('product_id')
->get();
I removed the WHERE clause, because your data only seems to have two types, and there isn't much sense in restricting that. If you really want the sum of quantity for just A or B, then we can write a much simpler Laravel/MySQL query.
Using A Raw Expression
$result = DB::table('stock_movements')
->select(DB::raw('sum(qty) as sum_qty, type'))
->groupBy('type')
->get();
Rule of thumb for the use of aggregation functions in the SELECT clause.
If a select block does have a GROUP BY clause, any column
specification specified in the SELECT clause must exclusively occur as
a parameter of an aggregated function or in the list of columns given
in the GROUP BY clause, or in both.
For more details :
http://www.informit.com/articles/article.aspx?p=664143&seqNum=6
Related
Imagine this is my table:
----------------------------------------------------
| id | user_id | amount_1 | amount_2 | amount_3 |
----------------------------------------------------
| 1 | 1 | 2 | 3 | 4 |
----------------------------------------------------
| 2 | 2 | 2 | 1 | 1 |
----------------------------------------------------
| 3 | 2 | 1 | 2 | 2 |
----------------------------------------------------
| 4 | 3 | 2 | 1 | 4 |
----------------------------------------------------
I need a query that gives me one result set for every entry that belongs to my current user, and then returns everything else as a single combined row with the amounts summed.
So in this case if I am user 1, I should get the following rows back:
---------------------------------------
| id | amount_1 | amount_2 | amount_3 |
---------------------------------------
| 1 | 2 | 3 | 4 | my own amounts
---------------------------------------
| 2 | 5 | 4 | 7 | everyone else's amounts
---------------------------------------
Any tips?
I've considered it might be a better idea to just filter the data in the code (php). Please help i'm starting to hate myself
You could use a UNION in sql
select 1 id, amount_1, amount_2, amount_3
from my_table
where user_id = 1
union
select 2 , sum(amount_1) , sum(amount_2), sum(amount_3 )
from my_table
where user_id <> 1
You can do with one query using union:
SELECT user_id, amount_1, amount_2, amount_3
FROM table
WHERE user_id = YOUR_USER_ID
UNION
SELECT -1, SUM(amount_1) AS amount_1, SUM(amount_2) AS amount_2, SUM(amount_3) AS amount_3
FROM table
WHERE user_id != YOUR_USER_ID
You can use aggregation in one fell swoop:
select (case when user_id = 1 then id end) as my_user_or_not,
sum(amount_1), sum(amount_2), sum(amount_3)
from t
group by my_user_or_not;
The null values in the first column indicate another user. You have labelled the column id, which is a bit problematic if you were -- for instance -- to choose user_id = 2 in your example. NULL seems safer for this purpose.
User table:
| id | name | age |
|----|------------|-----|
| 1 | Apple | 22 |
| 2 | Strawberry | 23 |
| 3 | Orange | 50 |
| 4 | Mango | 30 |
Memberships table:
| id | user_id | expire_at |
|----|---------|----------------------|
| 1 | 1 | 2019-08-17T11:19:30Z |
| 2 | 1 | 2019-08-10T11:20:10Z |
| 3 | 2 | 2019-08-29T11:20:19Z |
| 4 | 3 | 2019-08-02T11:20:30Z |
| 5 | 3 | 2019-08-28T11:20:40Z |
Problom
I want select users with the latest 'expire_at'.
After reference: https://stackoverflow.com/a/2111420/5588637,
I tried the following:
SELECT
u.*,
m1.*
FROM
users u
INNER JOIN memberships m1 ON u.id = m1.user_id
LEFT JOIN memberships m2 ON u.id = m2.user_id
AND (
m1.expire_at < m2.expire_at
OR m1.expire_at = m2.expire_at
AND m1.id < m2.id
)
WHERE
m2.id IS NULL;
Result
The id will appear twice because I used to join.
| id | name | age | id | user_id | expire_at |
|----|------------|-----|----|---------|----------------------|
| 1 | Apple | 22 | 1 | 1 | 2019-08-17T11:19:30Z |
| 2 | Strawberry | 23 | 3 | 2 | 2019-08-29T11:20:19Z |
| 3 | Orange | 50 | 5 | 3 | 2019-08-28T11:20:40Z |
After change m1.* to m1.expire_at. I got the result I want.
| id | name | age | expire_at |
|----|------------|-----|----------------------|
| 1 | Apple | 22 | 2019-08-17T11:19:30Z|
| 2 | Strawberry | 23 | 2019-08-29T11:20:19Z |
| 3 | Orange | 50 | 2019-08-28T11:20:40Z |
online try: http://sqlfiddle.com/#!9/27fa22/4
Implement in Lavavel
Laravel Framework version: 5.6.39
I am trying to convert the above SQL into Laravel using Database: Query Builder.
$users = DB::table('users as u')
->select('u.*', 'm1.*')
->join('memberships as m1','u.id','=','m1.user_id')
->leftJoin('memberships as m2', function($join){
$join->on('u.id', '=', 'm2.user_id')
->where(function ($query) {
$query->where('m1.expire_at','<','m2.expire_at')
->orWhere('m1.expire_at','=','m2.expire_at')
->where('m1.id','<','m2.id');
});
})
->whereNull('m2.id')
->toSQL();
I'm using toSql(). This will convert it to SQL first to make sure it's same of above SQL.
SELECT
`u`.*,
`m1`.*
FROM
`users` AS `u`
INNER JOIN `memberships` AS `m1` ON `u`.`id` = `m1`.`user_id`
LEFT JOIN `memberships` AS `m2` ON `u`.`id` = `m2`.`user_id`
AND (
`m1`.`expire_at` < ?
OR `m1`.`expire_at` = ?
AND `m1`.`id` < ?
)
WHERE
`m2`.`id` IS NULL
? seems to be the characteristic of laravel, I believe it is same of above SQL.
when i change toSQL() to get(), the result following:
Collection { ▼
#items: []
}
The above result is wrong, so i tried remove
whereNull('m2.id') in Laravel code (WHERE m2.id IS NULL in SQL), let’s see what happened.
Laravel result
Collection { ▼
#items: array:5 [▼
0 => { ▼
+"id": 1
+"name": "Apple"
+"age": "Eric Yiu SL"
+"user_id": 1
+"expire_at": "2019-08-10T11:20:10Z"
}
...
]
Ideal result
| id | name | age | id | user_id | expire_at |
|----|------------|-----|----|---------|----------------------|
| 1 | Apple | 22 | 2 | 1 | 2019-08-10T11:20:10Z |
| 3 | Orange | 50 | 4 | 3 | 2019-08-02T11:20:30Z |
| 1 | Apple | 22 | 1 | 1 | 2019-08-17T11:19:30Z |
| 2 | Strawberry | 23 | 3 | 2 | 2019-08-29T11:20:19Z |
| 3 | Orange | 50 | 5 | 3 | 2019-08-28T11:20:40Z |
Comparing results, Laravel result missing second id which is memberships table id, i guess this is the reason of incorrect results.
I have searched the Internet, seems is this problem.
https://github.com/laravel/framework/issues/4962
But I failed after various attempts...
You cannot select two rows with the same name in Laravel. The second one will override the first one. Use an alias instead.
$users = DB::table('users as u')
->select('u.*', 'm1.id as membership_id')
->join('memberships as m1','u.id','=','m1.user_id')
->leftJoin('memberships as m2', function($join){
$join->on('u.id', '=', 'm2.user_id')
->where(function ($query) {
$query->whereColumn('m1.expire_at','<','m2.expire_at')
->orWhere(function ($query) {
$query->whereColumn('m1.expire_at','=','m2.expire_at')
->whereColumn('m1.id','<','m2.id');
});
});
})
->whereNull('m2.id')
->get();
Note: I also encapsulated the orWhere() in the join to avoid confusion about the order of AND/OR.
What also works is using a different order in the select. You can for example use the following:
$query->select([
'm1.*',
'm1.id as membership_id',
'u.*'
])
It will return all columns of both tables plus the new membership_id column. But if there is a column on the users table which is named similarly to a column on the memberships table, only the users table column is returned (e.g. created_at). What comes last in your list is returned.
EDIT:
As #Namoshek mentioned, you should not select everything because you have a duplicate key problem in your SQL query. I modified my answer so that it would match #RaymondNijland answer. And by the way, even for the table user, you should select exactly what you need. And not only for a duplicate key problem but also for the speed of your SQL query. We don't think about it enough but it can quickly make the difference on a big set of results.
Less data to send from the database to your PHP server = faster
You should try this one :
DB::table('users as u')
->select('u.*', 'm1.id as membership_id')
->join('memberships as m1','u.id','=','m1.user_id')
->leftJoin('memberships as m2', function ($join) {
$join->on('u.id', '=', 'm2.user_id')
->on(function($join) {
$join->on('m1.id', '<', 'm2.id')
->on(function($join) {
$join->on('m1.expire_at', '<', 'm2.expire_at')
->orOn('m1.expire_at', '=', 'm2.expire_at');
});
});
})
->whereNull('m2.id')
->toSQL()
As mentioned in Laravel's documentation on this page: https://laravel.com/api/5.8/Illuminate/Database/Query/JoinClause.html#method_on
You can pass a closure to the on() method and there is the orOn() method that you can use in this closure.
I tested it and it gives the same result as your SQL query.
I have a SQL table which looks like:
---------------------
| price |price_two |
---------------------
| 35.90 | 0 |
| 21.90 | 5 |
| 7.90 | 0 |
| 50.21 | 30.29 |
---------------------
And now I want to create a query in SQL which switches all values from price to price_two, where price_two is lower than price but NOT 0.
So, at the end I want this:
---------------------
| price |price_two |
---------------------
| 35.90 | 0 |
| 5 | 21.90 |
| 7.90 | 0 |
| 30.29 | 50.21 |
---------------------
Can I do that and if yes, how?
Greetings and Thank You!
Something like...
SQL Fiddle
UPDATE Price P
INNER JOIN Price P2
on P.Price = P2.Price
AND P2.Price_two = P.Price_two
AND P.Price_two < P.Price
AND P.Price_two != 0
SET P.Price = P2.Price_two,
P.Price_two = P2.Price;
We simply use a self join and use the 2nd table's data to update the first since we need to materialize the values and perform both updates at once or we lose the handle to original values!
I have three tables group_sentences, group_sentences_attributes and group_senteces_categories.
I have an attributes array which I am using in query with IN (after implode).
Then I have one category ID because they are stored recursively, so no need for an array.
I need to select one group number where is the biggest match for $attributesArray and of course category too.
Here is table group_sentences_attributes
+-----+-------+-----------+
| id | group | attribute |
+-----+-------+-----------+
| 1 | 1 | 3564 |
| 2 | 1 | 3687 |
| 3 | 1 | 3689 |
| 4 | 2 | 3687 |
| 5 | 2 | 3564 |
+-----+-------+-----------+
Here is group_sentences_category
+-----+-------+----------+
| id | group | category |
+-----+-------+----------+
| 1 | 1 | 1564 |
| 2 | 1 | 1221 |
| 3 | 1 | 1756 |
| 4 | 2 | 1358 |
| 5 | 2 | 1125 |
+-----+-------+----------+
Here is my query, but I am afraid that it won't do the job done.
SELECT group_categories.group
FROM group_categories, group_attributes
WHERE group_categories.category = '$category'
AND group_attributes.attribute IN ($attributesArray)
GROUP BY group_categories.group
ORDER BY count(group_attributes.attribute)
Any help would be appreciated, thanks.
First, the table in your query do not match the tables in the question. I am guessing they are simply missing the "sentence". Then, you have no join clause. Simple rule: Never use commas in the from clause.
group is a lousy name for a column, because it is a keyword in SQL. The following may be what you are looking for:
SELECT gc.groupid
FROM group_sentences_attributes sa JOIN
group_sentences_category sc
ON sa.groupid = sc.groupid
WHERE sc.category = '$category' AND
sa.attribute IN ($attributesArray)
GROUP BY sa.groupid
ORDER BY count(sa.attribute);
If you only want one row, then add LIMIT 1 to the end.
I'm new posting here but the community have been my best resource on my projects so far.
I'm a dumb/dummy Mysql "wanna be" and I'm in the middle of a project that is making me go mad.
I have a table from wordpress plugin buddypress that pairs meta_key and meta_values in order to create something akin to a taxonomy. My duty is to use these paired values to implement an advanced group search. Here is the original table:
--------------------------------------------
id | group_id | meta_key | meta_value
--------------------------------------------
1 | 1 | time-zone | Kwajalein
2 | 1 | playstyle | hardcore
3 | 1 | recruiting-status | Open
4 | 1 | ilvl | 115
5 | 1 | main-raid | Final Coil of Bahamut
6 | 1 | voicechat | fc.teamspeak3.com
etc....
Using a view I managed to create a more friendly searchable table for begginers :
gid| time-zone| playstyle | main-raid
--------------------------------------------
1 | | |
1 |Kwajalein | |
1 | | hardcore |
1 | | |
1 | | | Final Coil of Bahamut
1 | | |
And here is the view code:
SELECT distinct
group_id AS 'gid',
IF(meta_key='recruiting-status',meta_value,'') AS 'Recruitment',
IF(meta_key='server',meta_value,'') AS 'server',
IF(meta_key='time-zone',meta_value,'') AS 'tzone',
IF(meta_key='main-raid',meta_value,'') AS 'raid',
IF(meta_key='raid-days',meta_value,'') AS 'days',
IF(meta_key='playstyle',meta_value,'') AS 'playstyle',
IF(meta_key='raid-progression',meta_value,'') AS 'progression',
IF(meta_key='raid-time',meta_value,'') AS 'time',
IF(meta_key='tanker-spot',meta_value,'') AS 'tank',
IF(meta_key='healer-spot',meta_value,'') AS 'healer',
IF(meta_key='melee-dps-spot',meta_value,'') AS 'melee',
IF(meta_key='ranged-dps-spot',meta_value,'') AS 'ranged',
IF(meta_key='magic-dps-spot',meta_value,'') AS 'magic',
IF(meta_key='ilvl',meta_value,'') AS 'ilvl',
IF(meta_key='voicechat',meta_value,'') AS 'voice',
IF(meta_key='voicechatpass',meta_value,'') AS 'voicep',
FROM wpstatic_bp_groups_groupmeta
The point is, I need to merge that result (view) so all the group_id=1 or 2 or 3, etc stand in one single row, like this:
gid| time-zone| playstyle | main-raid
--------------------------------------------
1 |Kwajalein | hardcore | Final Coil of Bahamut
2 |SaoPaulo | regular | Second Coil of Bahamut
etc
Can anyone help me there?
Just surround your IFs in a MAX, or another aggregate function that will capture the non-empty strings (e.g., GROUP_CONCAT), and add a GROUP BY group_id add the end. For example,
SELECT
group_id AS gid,
MAX(IF(meta_key='recruiting-status',meta_value,'')) AS 'Recruitment',
MAX(IF(meta_key='server',meta_value,'')) AS 'server',
...
FROM wpstatic_bp_groups_groupmeta
GROUP BY group_id