I am selecting data from an MS SQL DB using a similar query to this:
$result = odbc_prepare(
$connection,
"SELECT
t1.id, thumbnail, description, brand, vendor,
case
when ".implode(' AND ', $exactSearchTermDesc)." then 1
else 0
end as exactdescriptionmatch,
case
when ".implode(' AND ', $exactSearchTermMarkDesc)."". $exactThesaurusMarkDesc ."
then 1 else 0
end as exactcontentmatch,
case
when ".implode(' AND ', $searchTermDesc)."" . $thesaurusDesc ." then 1
else 0
end as descriptionmatch,
case
when ".implode(' AND ', $searchTermMarkDesc)."". $thesaurusMarkDesc ." then 1
else 0
end as contentmatch
FROM Martin.dbo.item_search"
);
odbc_execute($result);
How do I parameterize the variables in the case statements?
I tried, for example, just trying to paramaterize the first variable with this to no avail...
$exactSearchTermDesc = implode(' AND ', $exactSearchTermDesc);
$result = odbc_prepare(
$connection,
"SELECT
t1.id, thumbnail, description, brand, vendor,
case when ? then 1 else 0 end as exactdescriptionmatch,
case when ".implode(' AND ', $exactSearchTermMarkDesc)."". $exactThesaurusMarkDesc ." then 1 else 0 end as exactcontentmatch,
case when ".implode(' AND ', $searchTermDesc)."" . $thesaurusDesc ." then 1 else 0 end as descriptionmatch,
case when ".implode(' AND ', $searchTermMarkDesc)."". $thesaurusMarkDesc ." then 1 else 0 end as contentmatch
FROM Martin.dbo.item_search"
);
odbc_execute($result array($exactSearchTermDesc));
The variables are as below. $terms is the user input.
$searchTerms = explode(' ', $terms);
$exactSearchTermDesc = array();
$exactSearchTermMarkDesc = array();
$searchTermDesc = array();
$searchTermMarkDesc = array();
foreach ($searchTerms as $term) {
$term = trim($term);
if (!empty($term)) {
$exactSearchTermDesc[] = "description LIKE '$term %'";
$exactSearchTermMarkDesc[] = "contains(marketingDescription, '$term')";
$searchTermDesc[] = "description LIKE '%$term%'";
$searchTermMarkDesc[] = "marketingDescription LIKE '%$term%'";
$searchTermVendor[] = "vendor LIKE '%$term%'";
}
}
$exactThesaurusDesc = " Or " . implode(' AND ', $exactThesaurusDesc);
$exactThesaurusMarkDesc = " Or " . implode(' AND ', $exactThesaurusMarkDesc);
$thesaurusDesc = " Or " . implode(' AND ', $thesaurusDesc);
$thesaurusMarkDesc = " Or " . implode(' AND ', $thesaurusMarkDesc);
$thesaurusVendor = " Or " . implode(' AND ', $thesaurusVendor);
Consider re-factoring your SQL process. Rather than piecing together dynamic SQL components at application layer (i.e., PHP) that can impact readability and maintainability, consider multiple self-joins to a temp table of terms with below definition:
CREATE TABLE tmp_terms (
id IDENTITY(1,1) NOT NULL,
term VARCHAR(255),
thesauraus_indicator BIT
)
Specifically, run an aggregate query across joined matches where MIN returns 0 or 1. With this approach, the same SQL query is maintained but the underlying terms populated by PHP will be the dynamic component.
Below runs the non-thesaurus part of previous CASE logic. Also, since CONTAINS cannot use another column but only a literal value, JOIN expressions use LIKE with the leading and trailing wildcard.
SELECT
i.id, i.thumbnail, i.description, i.brand, i.vendor,
MIN(case
when exact_desc.term IS NOT NULL
then 1
else 0
end) AS exact_description_match,
MIN(case
when market_match.term IS NOT NULL
then 1
else 0
end) AS market_match,
MIN(case
when desc_match.term IS NOT NULL
then 1
else 0
end) AS desc_match,
MIN(case
when vendor_match.term IS NOT NULL
then 1
else 0
end) AS vendor_match
FROM Martin.dbo.item_search i
LEFT JOIN tmp_terms exact_desc
ON i.description LIKE CONCAT(exact_desc.term, ' %')
AND exact_desc.thesaurus_indicator = 0
LEFT JOIN tmp_terms market_match
ON i.marketingDescription LIKE CONCAT('%', market_match.term, '%')
AND market_match.thesaurus_indicator = 0
LEFT JOIN tmp_terms desc_match
ON i.description LIKE CONCAT('%', desc_match.term, '%')
AND desc_match.thesaurus_indicator = 0
LEFT JOIN tmp_terms vendor_match
ON i.vendor LIKE CONCAT('%', vendor_match.term, '%')
AND vendor_match.thesaurus_indicator = 0
GROUP BY i.id, i.thumbnail, i.description, i.brand, i.vendor
To integrate the thesaurus match, use UNION in CTE or subquery before aggregation in outer query.
WITH sub AS
(SELECT
i.id, i.thumbnail, i.description, i.brand, i.vendor,
exact_desc.term AS exact_desc_term, market_match.term AS market_match_term,
desc_match.term AS desc_match_term, vendor_match.term AS vendor_match_term
FROM Martin.dbo.item_search i
LEFT JOIN tmp_terms exact_desc
ON i.description LIKE CONCAT(exact_desc.term, ' %')
AND exact_desc.thesaurus_indicator = 0
LEFT JOIN tmp_terms market_match
ON i.marketingDescription LIKE CONCAT('%', market_match.term, '%')
AND market_match.thesaurus_indicator = 0
LEFT JOIN tmp_terms desc_match
ON i.description LIKE CONCAT('%', desc_match.term, '%')
AND desc_match.thesaurus_indicator = 0
LEFT JOIN tmp_terms vendor_match
ON i.vendor LIKE CONCAT('%', vendor_match.term, '%')
AND vendor_match.thesaurus_indicator = 0
UNION
SELECT
i.id, i.thumbnail, i.description, i.brand, i.vendor,
th_exact_desc.term, th_market_match.term,
th_desc_match.term, th_vendor_match.term
LEFT JOIN tmp_terms th_exact_desc
ON i.description LIKE CONCAT(th_exact_desc.term, ' %')
AND th_exact_match.thesaurus_indicator = 1
LEFT JOIN tmp_terms th_market_match
ON i.marketingDescription LIKE CONCAT('%', th_market_match.term, '%')
AND th_market_match.thesaurus_indicator = 1
LEFT JOIN tmp_terms th_desc_match
ON i.description LIKE CONCAT('%', th_desc_match.term, '%')
AND th_desc_match.thesaurus_indicator = 1
LEFT JOIN tmp_terms th_vendor_match
ON i.vendor LIKE CONCAT('%', th_vendor_match.term, '%')
AND th_vendor_match.thesaurus_indicator = 1
)
SELECT sub.id, sub.thumbnail, sub.description, sub.brand, sub.vendor,
MIN(case
when sub.exact_desc_term IS NOT NULL
then 1
else 0
end) AS exact_description_match,
MIN(case
when sub.market_match_term IS NOT NULL
then 1
else 0
end) AS market_match,
MIN(case
when sub.desc_match_term IS NOT NULL
then 1
else 0
end) AS desc_match,
MIN(case
when sub.vendor_match_term IS NOT NULL
then 1
else 0
end) AS vendor_match
FROM sub
GROUP BY sub.id, sub.thumbnail, sub.description, sub.brand, sub.vendor
Note: Above queries may need adjustment to final end use needs and testing. Performance may vary depending on number of search terms. But ultimately, the concept is to interact with data in sets and not long concatenated CASE WHEN... logic that grows with search terms.
Finally, in PHP simply clean out and populate the temp table with new values using parameterization and run any of the above final query:
odbc_exec($connection, "DELETE FROM tmp_terms");
$stmt = odbc_prepare($connection,
"INSERT INTO tmp_terms (term, thesaurus_indicator)
VALUES (?, ?)");
// NON-THESAURUS TERMS
foreach($search_terms as $t) {
odbc_execute($stmt, array($t, 0));
}
// THESAURUS TERMS
foreach($th_search_terms as $t) {
odbc_execute($stmt, array($t, 1));
}
// RUN FINAL SELECT QUERY
$result = odbc_exec($connection, "my_final_above_query");
while(odbc_fetch_row($result)){
for($i=1; $i<=odbc_num_fields($result); $i++){
// ... odbc_result($result,$i);
}
}
Related
I have been testing my query with random strings to see if there will be results that will be displayed.
One string that I tested is cal, so the query should be simple, right?
SELECT c.`ID` as id, CONCAT(p.`Name`, ' > ', c.`Name`) as name, 'cal' as q
FROM `cq_provice` p JOIN `cq_city` c ON c.`provinceID` = p.`ID`
WHERE (CONCAT(p.`Name`, ' > ', c.`Name`) LIKE '%cal%') ORDER BY p.`Name`, c.`Name`
LIMIT 0, 50
However, with my 2 prepared sql queries: one using the codeigniter way and one using the usual:
#1
$this->db->select('c.ID as id');
$this->db->select('CONCAT(p.Name, \' > \', c.Name) as name');
$this->db->select($term.' as q');
$this->db->from('cq_provice p');
$this->db->join('cq_city c', 'c.provinceID = p.ID');
$this->db->like('CONCAT(p.Name, \' > \', c.Name)', $this->db->escape($this->input->post('search')), 'both');
$this->db->order_by('p.Name', 'ASC');
$this->db->order_by('c.Name', 'ASC');
$this->db->limit($result_count, $offset);
$query = $this->db->get();
#2
$query = "SELECT c.`ID` as id, CONCAT(p.`Name`, ' > ', c.`Name`) as name, ".$term." as q
FROM `cq_provice` p JOIN `cq_city` c ON c.`provinceID` = p.`ID`
WHERE (CONCAT(p.`Name`, ' > ', c.`Name`) LIKE
'%".$this->db->escape($this->input->post('search'))."%')
ORDER BY p.`Name`, c.`Name` LIMIT ".$offset.", ".$result_count;
And both of them is giving me the same query statement:
SELECT c.`ID` as id, CONCAT(p.`Name`, ' > ', c.`Name`) as name, 'cal' as q
FROM `cq_provice` p JOIN `cq_city` c ON c.`provinceID` = p.`ID`
WHERE (CONCAT(p.`Name`, ' > ', c.`Name`) LIKE '%'cal'%')
ORDER BY p.`Name`, c.`Name` LIMIT 0, 50
My problem lies in the WHERE statement WHERE (CONCAT(p.Name, ' > ', c.Name) LIKE '%'cal'%' wherein the LIKE statement should be '%cal%' so I tried to use trim and regex but they gave me the same result:
SELECT c.`ID` as id, CONCAT(p.`Name`, ' > ', c.`Name`) as name, 'cal' as q
FROM `cq_provice` p JOIN `cq_city` c ON c.`provinceID` = p.`ID`
WHERE (CONCAT(p.`Name`, ' > ', c.`Name`) LIKE 'Êl%')
ORDER BY p.`Name`, c.`Name` LIMIT 0, 50
How do I fix this? How can I prevent my query to translate my string as special characters?
Any help is highly appreciated.
$this->db->select(array('c.ID as id','CONCAT(p.Name, " > ", c.Name) as name',$term.' as q'));
$this->db->from('cq_provice p');
$this->db->join('cq_city c', 'c.provinceID = p.ID');
$this->db->like('CONCAT(p.Name, " > ", c.Name)', $this->db->escape($this->input->post('search')));
$this->db->order_by('p.Name ASC, c.Name ASC');
$this->db->limit($result_count, $offset);
$query = $this->db->get();
or with MATCH, AGAINST
$field = $this->db->escape($this->input->post('search'));
$this->db->select(array('c.ID as id','CONCAT(p.Name, " > ", c.Name) as name',$term.' as q'));
$this->db->from('cq_provice p');
$this->db->join('cq_city c', 'c.provinceID = p.ID');
$this->db->where('MATCH (p.Name,c.Name) AGAINST ('.$field.')', NULL, FALSE);
$this->db->order_by('p.Name ASC, c.Name ASC');
$this->db->limit($result_count, $offset);
$query = $this->db->get();
I working on one complex listing from database and decide to do all possible requests via one query.
Here is working example:
"SELECT
`c`.`categories_id`,
`c`.`categories_status`,
IF(`c`.`categories_status` = 1, 'Active', 'Not Active') AS `categories_status_name`,
TRIM(`cd`.`categories_name`) AS `categories_name`,
TRIM(`cd`.`concert_date`) AS `concert_date`,
TRIM(`cd`.`concert_time`) AS `concert_time`,
TRIM((
SELECT
CONCAT(
'{\"total_quantity\":',
SUM(CASE WHEN `p`.`products_quantity` > 0 THEN `p`.`products_quantity` ELSE 0 END),
',\"total_price\":\"',
SUM(`p`.`products_price`),
'\"}'
)
FROM
`products_to_categories` `ptc`,
`products` `p`
WHERE
`ptc`.`section_id` = `cd`.`section_id`
AND
`p`.`products_id` = `ptc`.`products_id`
)) AS `products_available`,
TRIM((
SELECT
CONCAT(
'{\"total_quantity\":',
SUM(CASE WHEN `op`.`products_quantity` > 0 THEN `op`.`products_quantity` ELSE 0 END),
',\"total_price\":\"',
SUM(`op`.`final_price`),
'\"}'
)
FROM
`products_to_categories` `ptc`,
`orders_products` `op`
WHERE
`ptc`.`section_id` = `cd`.`section_id`
AND
`op`.`products_id` = `ptc`.`products_id`
AND
`op`.`orders_products_status` != 1
)) AS `products_sold`,
TRIM((
SELECT
CONCAT(
'{\"total_quantity\":',
SUM(CASE WHEN `op`.`products_quantity` > 0 THEN `op`.`products_quantity` ELSE 0 END),
',\"total_price\":\"',
SUM(`op`.`final_price`),
'\"}'
)
FROM
`products_to_categories` `ptc`,
`orders_products` `op`
WHERE
`ptc`.`section_id` = `cd`.`section_id`
AND
`op`.`products_id` = `ptc`.`products_id`
AND
`op`.`orders_products_status` = 1
)) AS `products_pending`
FROM
`categories` `c`,
`categories_description` `cd`
WHERE
`c`.`categories_id` = `c`.`section_id`
AND
`cd`.`categories_id` = `c`.`categories_id`
GROUP BY `c`.`categories_id`
ORDER BY `c`.`categories_status` DESC;"
This work great but my main problem is how to do check IF/ELSE or CASE WHEN for custom defined new fields: products_available, products_sold and products_pending?
Problem is that if products not exists inside table orders_products I not get my generated JSON and that's made a small conflict inside PHP.
I can do check in PHP but want to avoid that part and just print JSON like:
{"total_quantity":0,"total_price":"0.0000"}
FROM
`products_to_categories` `ptc`,
`orders_products` `op`
*
WHERE `products_id` =
(SELECT COALESCE(`products_id`,0))
*
AND
`ptc`.`section_id` = `cd`.`section_id`
I am building a site search with select boxes to get certain categories & regions.
If selected am I including it in the query, but for some reason, using LIKE is it ignoring my WHERE section of the query!?!? Why is that or what am I doing wrong?
When I echo the query built by Codeigniter I get the fololowing:
(Note the ESCAPE '!')
Query echo:
SELECT SQL_CALC_FOUND_ROWS null as rows, ads.id AS id, location, provLabel, text,
adcat.id AS catid, ads.subcatid AS subcatid, ads.province R_rand, r_option, addate,
adcat.name AS catname, adsubcat.name AS subname, f_value, adtitle, ads.area, regionLabel,
adlink
FROM `ads`
JOIN `search_town` ON `search_town`.`townId`=`ads`.`townId`
JOIN `search_region` ON `search_region`.`regionId`=`ads`.`area`
JOIN `search_prov` ON `search_prov`.`provId`=`ads`.`province`
JOIN `adcat` ON `adcat`.`id`=`ads`.`catid`
JOIN `adsubcat` ON `adsubcat`.`id`=`ads`.`subcatid`
LEFT JOIN `adfields` ON `adfields`.`ad_id`=`ads`.`id`
WHERE `ads`.`catid` != 8 AND `ads`.`adactive` = 1 AND `scam` =0 AND `ads`.`province` = '1'
AND `ads`.`catid` = '3' AND `text` LIKE '%Nissan%' ESCAPE '!'
OR `f_value` LIKE '%Nissan%' ESCAPE '!'
GROUP BY `ads`.`id`
ORDER BY `addate` DESC
LIMIT 10
Here is the actual query in the controller:
public function get_search($fsearch, $fcategory, $fprovince, $farea, $limit, $start)
{
if($fcategory >=1){
$incl_cat=" AND ads.catid='$fcategory'";
}else{
$incl_cat='';
}
if($fprovince>=1){
$incl_prov=" AND ads.province='$fprovince'";
}else{
$incl_prov='';
}
if($farea >= 1){
$incl_area=" AND ads.area='$farea'";
}else{
$incl_area='';
}
$this->db->select('SQL_CALC_FOUND_ROWS null as rows, ads.id AS id, location, provLabel, text, adcat.id AS catid, ads.subcatid AS subcatid,ads.province
R_rand, r_option, addate, adcat.name AS catname, adsubcat.name AS subname, f_value, adtitle, ads.area, regionLabel,
adlink', FALSE);
$this->db->from('ads');
$this->db->join('search_town', 'search_town.townId=ads.townId');
$this->db->join('search_region', 'search_region.regionId=ads.area');
$this->db->join('search_prov', 'search_prov.provId=ads.province');
$this->db->join('adcat', 'adcat.id=ads.catid');
$this->db->join('adsubcat', 'adsubcat.id=ads.subcatid');
$this->db->join('adfields', 'adfields.ad_id=ads.id', 'left');
$where = "ads.catid!=8 AND ads.adactive=1 AND scam=0 $incl_prov $incl_cat $incl_area";
$this->db->where($where);
$this->db->like('text', $fsearch);
$this->db->or_like('f_value', $fsearch);
$this->db->group_by("ads.id");
$this->db->order_by('addate', 'DESC');
$this->db->limit($limit, $start);
$query = $this->db->get();
$return = $query->result_array();
echo $this->db->last_query();
$total_results=$this->db->query('SELECT FOUND_ROWS() count;')->row()->count;
$this->session->set_userdata('tot_search', $total_results);
return $return;
}
Your WHERE condition is
a AND b AND c AND d AND ... AND x OR y
If y is true the whole WHERE condition is true.
Perhaps you mean:
a AND b AND c AND d AND ... AND (x OR y)
The ESCAPE character defaults to \, but it looks like codeigniter has changed this to ! for you (presumably because \ is already an escape in PHP, so sometimes you need multiples and this can be confusing).
Currently this is irrelevant to your query. This would only be used if you need to match a % or an _ with !% or !_ (default \% or \_).
I want to build the following SQL query string using foreach loop
SELECT 'name' AS `field`. GROUP_CONCAT(DISTINCT `name` ORDER BY `name`) AS `value`
FROM product
UNION ALL
SELECT 'category' AS `field`. GROUP_CONCAT(DISTINCT `category` ORDER BY `category`) AS `value`
FROM product
UNION ALL
SELECT 'year' AS `field`. GROUP_CONCAT(DISTINCT `year` ORDER BY `year`) AS `value`
FROM product
The tricky part is that the following loop would result in an unnecessary "UNION ALL" in the last SELECT statement for year.
$selects = array("name"=>"Name","category"=>"Category","year"=>"Year");
$sql="";
foreach($selects as $select => $title)
{
$sql .= "SELECT '".$select."' AS `field` , GROUP_CONCAT( DISTINCT `".$select."`ORDER BY `".$select."` SEPARATOR '|' ) AS `value` FROM `product` UNION ALL";
}
Can anyone tell me how to remove the last UNION ALL from the last element (in this case,Year) in the array?
Use UNION ALL in a variable. Initialize it as a empty string.
Prepend it on each iteration. Then assign UNION ALL to the variable.
$selects = array("name"=>"Name","category"=>"Category","year"=>"Year");
$sql="";
$union_all="";
foreach($selects as $select => $title)
{
$sql .= $union_all . "SELECT '".$select."' AS `field` , GROUP_CONCAT( DISTINCT `".$select."`ORDER BY `".$select."` SEPARATOR '|' ) AS `value` FROM `product`";
$union_all=" UNION ALL ";
}
Give it a Try !!!
In your foreach, make an array of your queries without the UNION ALL. Then:
$sql = implode(' UNION ALL ', $queries);
The below code may solve your problem.
$selects = array("name"=>"Name","category"=>"Category","year"=>"Year");
$sql="";
foreach($selects as $select => $title)
{
$sql .= "SELECT '".$select."' AS `field` , GROUP_CONCAT( DISTINCT `".$select."`ORDER BY `".$select."` SEPARATOR '|' ) AS `value` FROM `product`". ($select!='Year')?" UNION ALL":"";
}
What I am trying to do is to get all of the users with the right conditions
so I'm building with a foreach statment a sub_queries to make it work.
the problem is that I got 100,000+ records in the Database.
and this kind of query takes forever to run.
I know I'm not doing it in the best way but I also tried left joins, which was really slow too..
this is the function I'm using:
public function get_affected_users_by_conditions($conditions, $mobile_type)
{
// Basic Query
// Selecting all of the users from `enswitch_mobile users` table
// The total and the `users` with the conditions
$sql = "SELECT COUNT(*) AS `users`,
(SELECT COUNT(*) FROM `enswitch_mobile_users`) AS `total`
FROM
`enswitch_mobile_users` AS `musers`
WHERE
`musers`.`phone_type` = :mobile_type";
$value_counter = 0;
$values = array();
// This is the foreach loop I was talking about
// I am looping all the conditons.
// and when theres a value i'm adding it as a subquery.
foreach($conditions as $cnd) {
switch ($cnd['condition']) {
// REALLY SLOW SUB-QUERY:
case 'talked_atleast':
$value_counter++;
// Here I'm trying to CUT the query by users who talked atleast $value seconds
$sql .= " AND (SELECT SUM(TIME_TO_SEC(TIMEDIFF(`finished_call`,`start_call`))) FROM `enswitch_calls` WHERE `user_id` = `musers`.`id`) >= :value".$value_counter;
$values[$value_counter] = $cnd['value'];
break;
// REALLY SLOW SUB-QUERY:
case 'purchase_atleast':
// Here I am trying to CUT the users by subquery who check if the users has bought at least $value times
$value_counter++;
$sql .= " AND (SELECT COUNT(*) FROM (SELECT user_id FROM enswitch_new_iphone_purchases
UNION
SELECT user_id FROM enswitch_new_android_purchases) AS p WHERE `status` > 0 AND user_id` = `musers`.`id`) >= :value".$value_counter;
$values[$value_counter] = $cnd['value'];
break;
// REALLY SLOW SUB-QUERY:
case 'never_purchase':
// Here I am trying to CUT the users by subquery to get only the users who never made a puchase.
$sql .= ' AND (SELECT COUNT(*) FROM (SELECT user_id FROM enswitch_new_iphone_purchases
UNION
SELECT user_id FROM enswitch_new_android_purchases) AS p WHERE `status` = 0 AND `user_id` = `musers`.`id`) = 0';
break;
}
}
$query = DB::query(Database::SELECT, $sql);
$query->bind(':mobile_type', $mobile_type);
// Looping the values and binding it into the SQL query!
foreach ($values as $k => $v) {
$query->bind(':value'.$k, $values[$k]);
}
// Executing query
$result = $query->execute();
return array('total_users' =>$result[0]['total'], 'affected_users'=>$result[0]['users']);
}
EDIT:
The Slowest Query as Requested: (MySQL)
SELECT COUNT(*) AS `users`,
( SELECT COUNT(*)
FROM `enswitch_mobile_users`
) AS `total`
FROM `enswitch_mobile_users` AS `musers`
WHERE `musers`.`phone_type` = 'iphone'
AND ( SELECT COUNT(*)
FROM ( SELECT `status`,
`user_id`
FROM `enswitch_new_iphone_purchases`
UNION
SELECT `status`,
`user_id`
FROM `enswitch_new_android_purchases`
) AS `p`
WHERE `p`.`status` > 0
AND `p`.`user_id` = `musers`.`id`
) >= 2
The subquery in the second SELECT column will execute for every m_users row that passes the WHERE condition:
SELECT
COUNT(*) AS users,
(SELECT COUNT(*) FROM enswitch_mobile_users) AS total <-- here's the problem
FROM enswitch_mobile_users AS musers
WHERE musers.phone_type = whatever
If I'm reading this correctly, you need a one-row result with the following columns:
users - number of enswitch_mobile_users rows with the specified phone_type
total - count of all enswitch_mobile_users rows
You can get the same result with this query:
SELECT
COUNT(CASE WHEN musers.phone_type = whatever THEN 1 END) AS users,
COUNT(*) AS total
FROM enswitch_mobile_users
The CASE checks for the phone type, and if it matches the one you're interested it it yields a 1, which is counted. If it doesn't match, it yields a NULL, which is not counted.