joining tables and get count from one table in yii framework - php

I am new to yii and I faces a small issue.In one of my project,I have two models employees and departments.In admin side,while listing the departments(grid view),I have to show the number of employees in each department.I have done the page for listing the departments but cannot able to add the number of employees in each department.
my employee table consists of :
empid
deptid
empname
my department table consists of:
deptid
deptname
In mysql (phpmyadmin interface) I can write a query to combine the two tables and get the correct result:
SELECT D. * , count( E.deptid) AS emp_count
FROM department AS D
LEFT JOIN employee AS E ON D.id = E.deptid
GROUP BY D.id
The result of this query:
id name emp_count
1 Accounts 0
2 Development 2
3 Quality Control 1
4 Operations 0
5 Human Resources 2
6 System 1
How can I implement this in grid view in yii?

below is the code for how to do join in cgridview,
add this code in your search function.
$criteria->with = array('relation_name');
$criteria->addCondition('relation_name.column_name ='.comparision_variable);
$model = Model::model()->findAll($criteria);

add following code in above answer
$criteria->select = 't. * , count( employee.deptid)';
$criteria->group = 't.id';
first line will select the columns you needed.
Second line is for group by.

Related

Join two queries where one is grouped

I'm creating simple PHP / Laravel app and I have reached a problem:
I need to somehow join 2 queries, but I am not sure how to.
I could do it with some messy PHP loops, but I know that's not the right approach.
my queries in Laravel's code looks:
$cars = Car::get();
$drives = Drive::select(DB::raw('car_id, max(odometer) as odometer'))->groupBy('car_id')->get();
in plain SQL something like:
$cars = 'SELECT * FROM cars';
$drives = 'SELECT car_id, max(odometer) as odometer FROM drive GROUP BY car_id';
(drive)'car_id' = (cars)'id'
Update: Forgot to mention: I have 'odometer' field in 'car' and get max('odometer') from drives, so if there is something returned from 'drives' it should overwrite the one returned from 'car'
DRIVE:
id
car_id
user_id
odometer
liters_filled
route
is_private
is_refill
is_full
checque
date
deleted_at
created_at
updated_at
CARS:
id
title
plate_number
year
tank_size
odometer
user_id
deleted_at
created_at
updated_at
Thank you for your time and help!
To be honest I'm not 100% on what you are asking but you could try the following:
SELECT cr.id ,
dr.oodometer
FROM Car cr
INNER JOIN (SELECT dr.car_id,MAX(dr.oodometer) AS oodometer
FROM Drive dr
GROUP BY car_id ) dr ON dr.car_id = cr.id
SELECT cars.*, max(driver.oodometer) as odometer FROM cars
INNER JOIN driver on cars.id=driver.car_id
group by driver.car_id
This query will join the 2 tables for you and get you the info. The table names i used are your variable names for the sake of the example.
Edit for Laravel:
In config/database.php at mysql change :
'strict' => true,
to false.

MySQL formatting results based on table name and multiple table data

I am coming into this project and have a little MySQL background to do basic SELECTs and INSERTs and whatnot. But, this is making me beat my head against the wall.
I have a typical user information table in MySQL:
USERS
+-------+----------+---------+-----+
|user_id|first_name|last_name|email|
+-------+----------+---------+-----+
1 tim jones tj#acme.com
2 sarah peteres sp#acme.com
3 larry doe ld#acme.com
Then I have multiple product tables:
PRODUCTS_ONE
+-------+-------+---------+----------+--------------+
|prod_id|user_id|prod_name|prod_width|prod_ship_date|
+-------+-------+---------+----------+--------------+
1 1 bowl nine 1-1-16
2 1 fork one 1-2-16
3 2 plate eleven 1-3-16
PRODUCTS_TWO
+-------+-------+----------+--------+--------------+
|prod_id|user_id|prod_state|prod_job|prod_ship_date|
+-------+-------+----------+--------+--------------+
1 3 maine min 1-1-16
2 2 texas max 1-2-16
3 1 ohio min 1-1-16
I have 15 total PRODUCT tables that all have prod_id, users_id, and prod_ship_date. The other fields might all be different based on what product table they are in. But, all the different PRODUCT tables have those three common fields.
What I am trying to accomplish is to get a list of USER info and PRODUCT info for products that match a certain ship date.
I want to find all the users and what product table and product id they are getting on a certain date.
So, if I searched on a ship date of 1-1-16, I would get something like:
+----------------+-----------+-------------+-----------+
|users.first_name|users.email|product_table|products_id|
+----------------+-----------+-------------+-----------+
tim tj#acme.com one,two 1,3
larry ld#acme.com one 3
If I searched on a ship date of 1-2-16, I would get something like:
+----------------+-----------+-------------+-----------+
|users.first_name|users.email|product_table|products_id|
+----------------+-----------+-------------+-----------+
tim tj#acme.com one 2
sarah sp#acme.com two 2
I hope this all makes sense. Unfortunately, I cannot change the structure or layout of the various product tables due to legacy issues.
I just can't figure out the MySQL statement to use to get something like this.
The above results will be used for reporting purposes.
you could write a query like this:
select first_name,email,prod_id,group_concat(product_table) as product_table from (
select u.user_id ,first_name,email,prod_id, 'one' as product_table from users u join products_one p on u.user_id = p.user_id where prod_ship_date = '2016-01-01 00:00:00'
union
select u.user_id,first_name,email,prod_id, 'two' as product_table from users u join products_two p on u.user_id = p.user_id where prod_ship_date = '2016-01-01 00:00:00'
) a
group by a.user_id
order by user_id,product_table
and so on.
With the second group concat and order by
select first_name,email,group_concat(prod_id),group_concat(product_table) as product_table from (
select u.user_id ,first_name,email,prod_id, 'one' as product_table from users u join products_one p on u.user_id = p.user_id where prod_ship_date = '2016-01-01 00:00:00'
union
select u.user_id,first_name,email,prod_id, 'two' as product_table from users u join products_two p on u.user_id = p.user_id where prod_ship_date = '2016-01-01 00:00:00'
) a
group by a.user_id
order by user_id,product_table
Check out this sqlfiddle

Need SQL query with good performance to select data that does NOT match criteria

I have a database with
a company table
a country table
a company_country n:n table which defines which company is available in which country
a product table (each product belongs to one specific categoryId)
and a company_product_country n:n:n table that defines which company offers which product in which country.
The latter has the three primary key columns companyId, productId, countryId and the additional columns val and limitedAvailability. val is an ENUM with the values yes|no|n/a, and limitedAvailability is an ENUM with the values 0|1.
Products within categories 1 or 2 are available in all countries and therefore get countryId = 0. But at the same time, only these very products may have a limitedAvailability = 1.
An SQLFiddle with a test database can be found here: http://www.sqlfiddle.com/#!9/a065a/1/0
It contains five countries, products and companies.
Background information on what I need to select from the database:
A PHP script generates a search form where an arbitrary list of countries and products can be selected. The products are separated by categories (I did not add the category table in the sample database, because it is not needed in this case). For the first category, I can select whether to exclude products with limited availability.
Generating the desired result works fine:
It displays all companies that are available in the selected countries and have at least one of the selected products available. The result offers a column that defines how many of the selected products are available by company.
If the user defines that one or more categories should not contain products with limited availability, then the products within the corresponding categories will not count as a match if the company offers them with limited availability only.
I am pleased with the performance of this query. My original database has got around 15 countries, 100 companies and 150 products. Selecting everything in the search form occupies the MySQL server for around two seconds which is acceptable for me.
The problem:
After generating the result list of companies which matches as many product search criteria as possible, I use PHP to iterate through those companies and run another SQL query that should give me the list of products that the company does not offer corresponding to the search criteria. The following is an example query for companyId 1 to find out which products are not available when
the desired products have the productIds 2, 4 and 5
the product's country availability should be at least one of the countryIds 1, 2 or 3
the product should not have a limitedAvailability when it is from categoryId = 2:
SELECT DISTINCT p.name
FROM `product` p
LEFT JOIN `company_product_country` cpc ON `p`.`productId` = `cpc`.`productId` AND `cpc`.`companyId` = 1
WHERE NOT EXISTS(
SELECT *
FROM company_product_country cpcTmp
WHERE `cpcTmp`.`companyId` = 1
AND cpcTmp.val = 'yes'
AND (
cpcTmp.limitedAvailability = 0
OR p.categoryId NOT IN(2)
)
AND cpcTmp.productId = p.productId
)
AND p.`productId` IN (2,4,5)
AND countryId IN(0,1,2,3);
The database along with this query can be found on the SQLFiddle linked above.
The query generates the correct result, but its performance dramatically decreases with the number of products. My local SQL server needs about 4 seconds per company when searching for 150 products in 15 countries. This is inaccpetable when iterating through 100 companies. Is there any way to improve this query, like avoiding the IN(...) function containing up to 150 products? Or should I maybe split the query into two like so:
First fetch the unmatched products that do not have country Id 0 and are IN the desired countryIds
Then fetch the unmatched products in countryId = 0 and if applicable filter limitedAvailability = 0
?
Your help is gladly appreciated!
I would suggest writing the query like this:
SELECT p.name
FROM product p
WHERE EXISTS (select 1
from company_product_country cpc
where p.productid = cpc.productid and
cpc.companyid = 1 and
cpc.countryid in (1, 2, 3)
) and
NOT EXISTS (select 1
from company_product_country cpcTmp
where cpcTmp.productId = p.productId and
cpcTmp.companyId = 1 and
cpcTmp.val = 'yes' and
cpcTmp.limitedAvailability = 0
) AND
NOT EXISTS (select 1
from company_product_country cpcTmp
where cpcTmp.productId = p.productId and
cpcTmp.companyId = 1 and
cpcTmp.val = 'yes' and
p.categoryId NOT IN (2)
)
p.`productId` IN (2, 4, 5) ;
Then, you want the following indexes:
product(productid, categoryid, name)
company_product_country(productid, companyid, countryid)
company_product_country(productid, companyid, val, limitedavailability)
company_product_country(productid, companyid, val, category)
Note: these indexes completely "cover" the query, meaning that all columns in the query come from the indexes. For most purposes, is probably sufficient to have a single index on company_product_country. Any of the three would do.
Take the query that identifies the products that match the user selection. Subquery it and outer join it to the products table. Exclude the matches.
SQL Fiddle
SELECT p.name
FROM
product p LEFT JOIN
(
SELECT productId
FROM company_product_country cpcTmp
WHERE companyId = 1 AND
countryId IN (0,1,2,3) AND
(
productId IN (4, 5) OR
(productId = 2 AND limitedAvailability = 0)
)
) t
ON p.productId = t.productId
WHERE
t.productId IS NULL AND
p.productId IN (2,4,5)

MySQL Join and create new column value

I have an instrument list and teachers instrument list.
I would like to get a full instrument list with id and name.
Then check the teachers_instrument table for their instruments and if a specific teacher has the instrument add NULL or 1 value in a new column.
I can then take this to loop over some instrument checkboxes in Codeigniter, it just seems to make more sense to pull the data as I need it from the DB but am struggling to write the query.
teaching_instrument_list
- id
- instrument_name
teachers_instruments
- id
- teacher_id
- teacher_instrument_id
SELECT
a.instrument,
a.id
FROM
teaching_instrument_list a
LEFT JOIN
(
SELECT teachers_instruments.teacher_instrument_id
FROM teachers_instruments
WHERE teacher_id = 170
) b ON a.id = b.teacher_instrument_id
my query would look like this:
instrument name id value
--------------- -- -----
woodwinds 1 if the teacher has this instrument, set 1
brass 2 0
strings 3 1
One possible approach:
SELECT i.instrument_name, COUNT(ti.teacher_id) AS used_by
FROM teaching_instrument_list AS i
LEFT JOIN teachers_instruments AS ti
ON ti.teacher_instrument_id = i.id
GROUP BY ti.teacher_instrument_id
ORDER BY i.id;
Here's SQL Fiddle (tables' naming is a bit different).
Explanation: with LEFT JOIN on instrument_id we'll get as many teacher_id values for each instrument as teachers using it are - or just a single NULL value, if none uses it. The next step is to use GROUP BY and COUNT() to, well, group the result set by instruments and count their users (excluding NULL-valued rows).
If what you want is to show all the instruments and some flag showing whether or now a teacher uses it, you need another LEFT JOIN:
SELECT i.instrument_name, NOT ISNULL(teacher_id) AS in_use
FROM teaching_instrument_list AS i
LEFT JOIN teachers_instruments AS ti
ON ti.teacher_instrument_id = i.id
AND ti.teacher_id = :teacher_id;
Demo.
Well this can be achieved like this
SELECT
id,
instrument_name,
if(ti.teacher_instrument_id IS NULL,0,1) as `Value`
from teaching_instrument_list as til
LEFT JOIN teachers_instruments as ti
on ti.teacher_instrument_id = til.id
Add a column and check for teacher_instrument_id. If found set Value to 1 else 0.

SQL Advice - selecting multiple rows from a table as an innerJoin

I've been working on a project that until now has only needed to find 1 row from the joined table. But now I need to grab multiple rows..
So as it stand my sql works something like:
Select rows for each company for this particular project which alone would find company details (name, id, telephone.. blah).
Then I join a table that contains form data submitted for each company (multiple forms - so multiple records)
Until now i have been specifying one formid to look for in the join, but now i need to specify multiple ones.
If I use WHERE form_id = 1 OR form_id = 2 OR form_id = 3 ... I get a result of only the first form match that is found per company..
If I mix up the query so it looks for the forms 1st and returns multiple records for each company with different form data - that works in this sense..
But I am then looping through this array in a view and creating a table row per record (previously each row was a new company) but using the latter would cause multiple records to show for the same company.
Any way I can do this? I tried group by with the latter method but this results in only 1 record again.
SELECT DISTINCT p.project_company_has_user_id, p.project_company_has_user_project_id, p.project_company_has_user_user_id, c.company_id, c.company_hall_no, c.company_company_name, c.company_type, c.company_country, c.company_stand_number, c.company_image_file_1, p2.project_id, p2.project_name, u.user_id, u.user_username, o.orders_id, o2.order_detail_id, o2.order_detail_product_id, f2.form_question_has_answer_id, f2.form_question_has_answer_request, f2.form_question_has_answer_form_id, f2.form_question_has_answer_user_id
FROM project_company_has_user p
INNER JOIN company c ON p.project_company_has_user_company_id = c.company_id
INNER JOIN project p2 ON p.project_company_has_user_project_id = p2.project_id
INNER JOIN user u ON p.project_company_has_user_user_id = u.user_id
INNER JOIN form f ON p.project_company_has_user_project_id = f.form_project_id
LEFT JOIN orders o ON p.project_company_has_user_user_id = o.orders_user_id
LEFT JOIN order_detail o2 ON ((o2.order_detail_orders_id = o.orders_id AND (o2.order_detail_product_id = 65 OR o2.order_detail_product_id = 68 OR o2.order_detail_product_id = 64)))
LEFT JOIN form_question_has_answer f2 ON ((f2.form_question_has_answer_form_id = 297 AND f2.form_question_has_answer_user_id = p.project_company_has_user_user_id))
WHERE (f.form_template_name = "custom" AND p.project_company_has_user_garbage_collection = 0 AND p.project_company_has_user_project_id = 48) AND (LCASE(c.company_country) LIKE "%uk%" OR LCASE(c.company_country) LIKE "%uk%") ORDER BY company_company_name asc
you need another field in order_detail as o2 . this field is row_index(position),etc for positioning record
LEFT JOIN order_detail o2 ON (o2.row_index=1 AND (o2.order_detail_orders_id = o.orders_id AND (o2.order_detail_product_id = 65 OR o2.order_detail_product_id = 68 OR o2.order_detail_product_id = 64)))
Personally I would use an Outer Join for the table of which elements you need to list all matches. Should you them need to clean up that data you can build the logic into the Join Condition (as step 2). Depending on the volume of data you are handling and whether or not you need to reuse it later in the same proc, you may want to post that primary dataset into a temp table and use that as source (primary) for your later logic.
Hope that helps. If you need the code, let me know, but it is pretty straight forward.
Regards
Mac

Categories