Laravel count categories join - mysql

I have three tables that I need to ultimately query from, and I am looking for the most efficient way of going about it.
The tables are: products, product_colors, and categories.
The relationships are products.category_id => categories.id and product_colors.product_id => products.id (filtered by product_colors.color_id)
First I have a product table, and that product has a one to many relationship with a color table, so I need to filter that based on the color.
Second, I need to filter it by the category, and return the amount of products in each category it fits.
I have the following query which will ultimately count the products in each of the categories that I want, but it doesn't filter based on the first filter of colors.
SELECT categories.name, SUM(IF(category_id IS NULL, 0, 1)) AS categories
FROM products JOIN categories ON (categories.id = products.category_id)
WHERE(products.gender = 1) GROUP BY categories.id;
I'm not too familiar with what is efficient or not in terms of queries or joins, but what I was thinking of maybe getting a list of product IDs which match the color being filtered, and then somehow doing a join on just those IDs.
Edit: in my answer below, I've come up with a solution, but it is using DB:raw -- is there any way around that?

I've managed to come up with the following query that will effectively return what I need, with the option of sorting by gender as well.
mysql> SELECT categories.name, SUM(IF(category_id IS NULL, 0, 1)) AS
categories FROM products LEFT JOIN product_colors ON
(product_colors.product_id = products.id) LEFT OUTER JOIN categories
ON (categories.id = products.category_id) WHERE(products.gender = 1 AND
product_colors.color_id = 1) GROUP BY categories.id;
+--------------+------------+
| name | categories |
+--------------+------------+
| トップス | 35 |
| アウター | 1 |
| ボトムス | 7 |
| シューズ | 21 |
| 帽子 | 2 |
+--------------+------------+
5 rows in set (0.00 sec)
Edit: I've rewritten in it Eloquent.
$categories = Category::select('categories.name',DB::raw('SUM(IF(category_id IS NULL, 0, 1)) AS categories'))
->join('products','categories.id','=','products.category_id')
->join('product_colors','product_colors.product_id','=','products.id','left outer')
->where('products.gender','=',1)
->where('product_colors.color_id','=',1)
->groupBy('categories.id')
->get();
The solution over all I feel isn't too bad, but I don't like the fact it is using DB:raw. If anyone has an alternative to that, I would appreciate it.

Related

SELECT using three tables w/ 1000+ entries

For transaction listing I need to provide the following columns:
log_out.timestamp
items.description
log_out.qty
category.name
storage.name
log_out.dnr ( Representing the users id )
Table structure from log_out looks like this:
| id | timestamp | storageid | itemid | qty | categoryid | dnr |
| | | | | | | |
| 1 | ........ | 2 | 23 | 3 | 999 | 123 |
As one could guess, I only store the corresponding ID's from other tables in this table. Note: log_out.id is the primary key in this table.
To get the the corresponding strings, int's or whatever back, I tried two queries.
Approach 1
SELECT i.description, c.name, s.name as sname, l.*
FROM items i, categories c, storages s, log_out l
WHERE l.itemid = i.id AND l.storageid = s.id AND l.categoryid = c.id
ORDER BY l.id DESC
Approach 2
SELECT log_out.id, items.description, storages.name, categories.name AS cat, timestamp, dnr, qty
FROM log_out
INNER JOIN items ON log_out.itemid = items.id
INNER JOIN storages ON log_out.storageid = storages.id
INNER JOIN categories ON log_out.categoryid = categories.id
ORDER BY log_out.id DESC
They both work fine on my developing machine, which has approx 99 dummy transactions stored in log_out. The DB on the main server got something like 1100+ tx stored in the table. And that's where trouble begins. No matter which of these two approaches I run on the main machine, it always returns 0 rows w/o any error *sigh*.
First I thought, it's because the main machine uses MariaDB instead of MySQL. But after I imported the remote's log_out table to my dev-machine, it does the same as the main machine -> return 0 rows w/o error.
You guys got any idea what's going on ?
If the table has the data then it probably has something to do with JOIN and related records in corresponding tables. I would start with log_out table and incrementally add the other tables in the JOIN, e.g.:
SELECT *
FROM log_out;
SELECT *
FROM log_out
INNER JOIN items ON log_out.itemid = items.id;
SELECT *
FROM log_out
INNER JOIN items ON log_out.itemid = items.id
INNER JOIN storages ON log_out.storageid = storages.id;
SELECT *
FROM log_out
INNER JOIN items ON log_out.itemid = items.id
INNER JOIN storages ON log_out.storageid = storages.id
INNER JOIN categories ON log_out.categoryid = categories.id;
I would execute all the queries one by one and see which one results in 0 records. Additional join in that query would be the one with data discrepancy.
You're queries look fine to me, which makes me think that it is probably something unexpected with the data. Most likely the ids in your joins are not maintained right (do all of them have a foreign key constraint?). I would dig around the data, like SELECT COUNT(*) FROM items WHERE id IN (SELECT itemid FROM log_out), etc, and seeing if the returns make sense. Sorry I can't offer more advise, but I would be interested in hearing if the problem is in the data itself.

MySQL join and column names

Let's say that I have the following tables in my MySQL database:
TABLE Products
| id | some_column1 | some_column2 |
TABLE ProductProperties
| id | product_id | name |
Oversimplified, but sufficient. Now I want to get all products with properties. I do:
SELECT * FROM `Products` JOIN `ProductProperties` ON Products.id = ProductProperties.product_id
What do I get?
| id | some_column1 | some_column2 | id | product_id | name |
It's not cool, and I want to make it cool in one of the two ways:
1) To get the array of objects like in Product table, but extended by one more member, which would be the array of properties which matched JOIN. I've sort of figured out already that it's impossible?
2) To get the array like this (I'd still have to iterate over it in PHP to join all properties in one product into one object):
| product_id | some_column1 | some_column2 | property_id | product_id | name |
So I'd like to rename the column ProductProperties.id into ProductProperties.property_id. If I could remove ProductProperties.product_id from the output too, that would be ideal, but for now, I only want the way to rename one column in the output. Or to prefix it by table name. Or something like that.
Doable?
You should explicitly name the columns and not use *. Then, don't return redundant columns:
SELECT p.id as productid, p.some_column1, p.some_column2,
pp.id as ProductPropertiesId, pp.name
FROM `Products` p JOIN `ProductProperties` pp
ON p.id = pp.product_id
Also, table aliases make such a query more readable.
SELECT Products.id product_id,
Products.some_column1,
Products.some_column2,
ProductProperties.id property_id,
ProductProperties.name
FROM `Products`
JOIN `ProductProperties`
ON Products.id = ProductProperties.product_id

MySQL select distinct across multiple tables

I have a query that selects all columns from multiple tables, but it's returning multiples of the same values (I only want distinct values).
How can I incorporate something like this? When I try this, it still
Select Distinct A.*, B.*, C.*....
Does distinct only work when selecting the column names and not all (*) ? In this reference it says distinct in reference to column names, not across all of the tables. Is there any way that I can do this?
edit - I added more info below
Sorry guys, I just got back onto my computer. Also, I just realized that my query itself is the issue, and Distinct has nothing to do with it.
So, the overall goal of my Query is to do the following
Generate a list of friends that a user has
Go through the friends and check their activities (posting, adding friends, etc..)
Display a list of friends and their activities sorted by date (I guess like a facebook wall kind of deal).
Here are my tables
update_id | update | userid | timestamp //updates table
post_id | post | userid | timestamp //posts table
user_1 | user_2 | status | timestamp //friends table
Here is my query
SELECT U.* , P.* ,F.* FROM posts AS P
JOIN updates AS U ON P.userid = U.userid
JOIN friends AS F ON P.userid = F.user_2 or F.user_1
WHERE P.userid IN (
select user_1 from friends where user_2 = '1'
union
select user_2 from friends where user_1 = '1'
union
select userid from org_members where org_id = '1'
union
select org_id from org_members where userid = '1'
)
ORDER BY P.timestamp, U.timestamp, F.timestamp limit 30
The issue I'm having with this (that I thought was related to distinct), is that if values are found to meet the requirements in, say table Friends, a value for the Posts table will appear too. This means when I'm displaying the output of the SQL statement, it appears as if the Posts value is shown multiple times, when the actual values I'm looking for are also displayed
The output will appear something like this (notice difference between post value in the rows)
update_id | update | userid | timestamp | post_id | post | userid | timestamp | user_1 | user_2 | status | timestamp
1 | update1 | 1 | 02/01/2013 | 1 | post1| 1 | 2/02/2013| 1 | 2 | 1 | 01/30/2013
1 | update1 | 1 | 02/01/2013 | 2 | post2| 1 | 2/03/2013| 1 | 2 | 1 | 01/30/2013
So, as you can see, I thought I was having a distinct issue (because update1 appeared both times), but the query actually just selects all the values regardless. I get the results I'm looking for in the Post table, but all the other values are returned. So, when I display the table in PHP/HTML, the Post value will display, but I also get duplicates of the updates (just for this example)
When you select distinct *, you select every row, including the one that makes the record unique. If you want something better than what you are getting, you have to type the individual column names in your select clause.
It would be easy if you explain a little more what is the connection between the tables you'r querying, because you can use joins, unions (as mentioned above) or even group by's ...
Your updated post shows one of the JOIN conditions as:
JOIN friends AS F ON P.userid = F.user_2 OR F.user_1
This is equivalent to:
JOIN friends AS F ON (P.userid = F.user_2 OR F.user_1 != 0)
and will include many rows that you did not intend to include.
You probably intended:
JOIN friends AS F ON (P.userid = F.user_2 OR P.userid = F.user_1)
I think you want this:
select *
from tableA
union
select *
from tableB
union
select *
from tableC
This assumes that HHS tables all have the same number of columns and they are of the same data type. This not, you'll have to select specific columns to make it so.

SQL join or merge results?

Look at this DB schema:
The users can order products. For any order I add a new record in the orders table and a new record in orders-products N:M table for any single product ordered by the user (and fill pieces field). But, when a user creating an order, I need to show the list of all products and fill the pieces field with the quantity ordered for any product.
Usually, to do it, I use two queries.
The first query get all products in the products table:
SELECT * FROM products;
The second query gets the products ordered in the orders-products table filtering them by orders-idorder FK:
SELECT orders_idorder AS idorder, products_idproduct AS idproduct, pieces FROM orders_products WHERE orders_idorder=1;
Then I merge the results in a single array and use it to display the complete list of products with the pieces ordered for each one. The final result is something like this:
+---------+-----------+--------------+-------+--------+
| idorder | idproduct | description | price | pieces |
+---------+-----------+--------------+-------+--------+
| 1 | 1 | Product 1 | 10.20 | 2 |
| 1 | 2 | Product 22 | 11.00 | NULL |
| 1 | 3 | Product 333 | 19.22 | NULL |
| 1 | 4 | Product 4444 | 9.20 | NULL |
+---------+-----------+--------------+-------+--------+
Note: In the above example there are 4 records in the products table and just 1 record in the orders-products table (it has idorder=1, idproduct=1 and pieces=2).
-> Here you can find the SQL Dump to test the queries.
Merging arrays built from 2 queries is the best way?
I can do it with a single query?
What do you think about the performance?
Generally letting the databse optimize the merge will be better than what you can do in code. The db is usually better at sorting too.
How you structure the query depends on your desired result set. If you want all products regardless of whether they appear in the orders table then you'd use an OUTER JOIN otherwise an INNER JOIN would filter out products that have never been ordered.
If you give us your desired results for some sample data we might be able to help you with the query, but give it a shot yourself first.
set #searchOrderId = 1;
select ifnull(op.orders_idorder, #searchOrderId) AS idOrder,
p.idproduct,
p.description,
p.price,
op.pieces
from products p
left join orders_products op on op.products_idproduct = p.idproduct
where ifnull(op.orders_idorder, #searchOrderId) = #searchOrderId ;
SQL Fiddle I was playing with to test it.
Sure you can get the information using a single query by JOINing the tables together. Is it what you're having troubles with?
I think that in general, executing a single query to retrieve the results will perform better than executing two queries and merging the results. It is impossible to say what the ideal plan for execution is, given the information provided. Table sizes, architecture, etc will play a role.
For retrieving all of the products from a single order, try:
select
o.idorder,
p.idproduct,
p.description,
p.price,
op.pieces
from
orders o
inner join orders_products op
on o.idorder = op.orders_idorder
inner join products p
on op.products_idproduct = p.idproduct
where
o.idorder = 1
I see, you wanted a LEFT JOIN from Products table into Orders_Products. This will give you result you're looking for.

MySQL join multiple rows from one table while selecting only a single row from the others

Maybe a bit of a strange title description, but i basically want to achieve something the GROUP_CONCAT() function does, only then keep the double entries.
I have four tables i want to join, client, doctor, physio and records
Depending on the variable $client i want to get the client details, attending doctor and therapist (one single row from three tables) and join all records for that user.
Say that in this case the $client = 1. The records table has five records where the column r_client_id = 1. If i run a query like below i only get one record from the records table, namely the first occurrence where r_client_id = 1 (which makes sense of course):
SELECT
client.c_id, client.c_name
doctor.d_name,
physio.p_name,
records.r_record
FROM
adm_clients AS client
INNER JOIN
norm_client_doctor AS ncd ON ncd.ncd_client_id = client.c_id
INNER JOIN
adm_doctor AS doctor ON doctor.d_id = ncd.ncd_doctor_id
INNER JOIN
norm_client_physio AS ncp ON ncp.ncp_client_id = client.c_id
INNER JOIN
adm_physio AS physio ON physio.p_id = ncp.ncp_physio_id
LEFT JOIN
adm_doctor_records AS records ON records.r_client_id = client.c_id
WHERE
client.c_id = '".$client."'
Now assume the five records where r_client_id = 1 are like so:
+------+-------------+-------------------+----------+
| r_id | r_client_id | r_record | r_date |
+------+-------------+-------------------+----------+
| 1 | 1 | regular visit | 10/10/12 |
+------+-------------+-------------------+----------+
| 3 | 1 | emergency control | 24/10/12 |
+------+-------------+-------------------+----------+
| 7 | 1 | regular visit | 08/09/12 |
+------+-------------+-------------------+----------+
| 18 | 1 | delivery | 03/01/12 |
+------+-------------+-------------------+----------+
| 20 | 1 | health checkup | 10/12/11 |
+------+-------------+-------------------+----------+
I want my output to be in an array like so:
Client 1
- Name Doctor
- Name Physio
Records
- Emergency control, 24/10/12
- Regular visit, 10/10/12
- Regular visit, 08/09/12
- Delivery, 03/01/12
- Health checkup, 10/12/11
The closest one i can image is a to add a GROUP_CONCAT() on the records, but that, of course, groups the 'regular visit', so i'll get 4 rows instead of 5
GROUP_CONCAT(DISTINCT records.r_record SEPARATOR '|')
[..]
echo(str_replace("|","<br>",$show->r_record));
Anybody an idea how to display all the matching records? I have the feeling i'm close, but i'm out of options by now..
Edit:
I forgot to mention that when i remove the DISTINCT, it displays all the records twice..
SOLVED:
Got it working like so:
GROUP_CONCAT(DISTINCT
CONCAT (records.r_date, '~', records.r_record, '~', records.r_paraph)
SEPARATOR '|') AS clientDoctorRecords,
Try:
SELECT
client.c_id, client.c_name
doctor.d_name,
physio.p_name,
GROUP_CONCAT(records.r_record)
FROM
adm_clients AS client
INNER JOIN
norm_client_doctor AS ncd ON ncd.ncd_client_id = client.c_id
INNER JOIN
adm_doctor AS doctor ON doctor.d_id = ncd.ncd_doctor_id
INNER JOIN
norm_client_physio AS ncp ON ncp.ncp_client_id = client.c_id
INNER JOIN
adm_physio AS physio ON physio.p_id = ncp.ncp_physio_id
LEFT JOIN
adm_doctor_records AS records ON records.r_client_id = client.c_id
WHERE
client.c_id = '".$client."'
GROUP BY
client.c_id
If you want r_date to come along with record in one column, then you can use plain CONCAT first and then do a GROUP_CONCAT on it.