MySQL Procedures behaving weird once variables are inserted - mysql

I'm having a frustrating time with MySQL and a procedure I'm writing and wanted to see if anyone else had any clues as to what is going on.
The procedure I have below will dynamically transpose rows in a table to columns. It is working perfectly except for the very last WHERE statement.
If I have either of the following WHERE statements it works fine.
WHERE t.company_id = v_company_id AND t.class = 'asset_property'
WHERE t.company_id IN (v_company_id) AND t.class = 'asset_property'
However, if I also want to grab records where the company_id could be 1 OR whatever value is passed in, it refuses to run. The only change made to the query is to the 'WHERE` statement. None of these versions will work.
WHERE (t.company_id = v_company_id OR t.company_id = 1) AND t.class = 'asset_property'
WHERE t.company_id IN (v_company_id, 1) AND t.class = 'asset_property'
WHERE t.company_id IN (161, 1) AND t.class = 'asset_property'
Appreciate any assistance that someone more versed in MySQL can provide. I've tried debugging as best I can and the only error I can seem to find is that when I try to get t.company_id to be one of two values, there is a generic error saying Invalid table name.
The full procedure is below.
(See Update 1)
Finally, if I generate the working statement above, then manually add in the lines for t.company_id = 1 records, it works perfectly and creates the following query.
(See Update 1)
Update 1
So that others can simulate what I'm seeing, I've built a test DB with test data in it so you can try this yourself. Also removing some of the code above to save space...
Trying to add this here blew out my character limit. I've uploaded it to my personal site. (Please let me know if this is not a safe thing to do!)
Test DB
Once you've created this DB, here are some procedures and SQL statements that you can run to test it and simulate what I'm doing.
The Procedure fill_tableau_ap that is in the Test DB will run and will create a table with the data in it (but is missing the company 1 records).
That Procedure generates the following SQL:
CREATE TABLE dw_161.tableau_ap AS
SELECT a.id as "Asset ID (ap)",
max(case when ap.property_id = 2473 then ap.text_value else null end) as "Business Class" ,
max(case when ap.property_id = 2294 then ap.date_value else null end) as "COI Date" ,
max(case when ap.property_id = 2293 then ap.date_value else null end) as "Compliance Date" ,
max(case when ap.property_id = 2291 then ap.text_value else null end) as "Fuel Type" ,
max(case when ap.property_id = 2292 then ap.text_value else null end) as "Operator" ,
max(case when ap.property_id = 2296 then ap.numeric_value else null end) as "PAX Capacity" ,
max(case when ap.property_id = 2295 then ap.date_value else null end) as "Registration Expiry" ,
max(case when ap.property_id = 2290 then ap.text_value else null end) as "Taxi Plate" ,
max(case when ap.property_id = 2323 then ap.text_value else null end) as "VIN"
FROM dw_161.equipment AS a
JOIN dw_161.asset_properties ap ON ap.asset_id = a.id
JOIN dw_161.sites AS s on a.site_id = s.id
WHERE dw_161.s.company_id = 161
GROUP BY a.id;
If I manually, add in a single item from company 1 into that above statement as below, I can run this on the DB and it also works. Here's what it looks like in case you also want to run it on the Test DB.
CREATE TABLE dw_161.tableau_ap AS
SELECT a.id as "Asset ID (ap)",
max(case when ap.property_id = 2473 then ap.text_value else null end) as "Business Class" ,
max(case when ap.property_id = 2294 then ap.date_value else null end) as "COI Date" ,
max(case when ap.property_id = 2293 then ap.date_value else null end) as "Compliance Date" ,
max(case when ap.property_id = 2291 then ap.text_value else null end) as "Fuel Type" ,
## Next field added manually from company 1 into the query
max(case when ap.property_id = 213 then ap.numeric_value else null end) as "Odometer" ,
max(case when ap.property_id = 2292 then ap.text_value else null end) as "Operator" ,
max(case when ap.property_id = 2296 then ap.numeric_value else null end) as "PAX Capacity" ,
max(case when ap.property_id = 2295 then ap.date_value else null end) as "Registration Expiry" ,
max(case when ap.property_id = 2290 then ap.text_value else null end) as "Taxi Plate" ,
max(case when ap.property_id = 2323 then ap.text_value else null end) as "VIN"
FROM dw_161.equipment AS a
JOIN dw_161.asset_properties ap ON ap.asset_id = a.id
JOIN dw_161.sites AS s on a.site_id = s.id
WHERE dw_161.s.company_id = 161
GROUP BY a.id;
So we can conclude, that if I can get the statement generated correctly, it will run and work. So it must be a problem with the statement generation.
Breaking the procedure into parts, there is the second half that feeds the list of Asset Properties into the first half. That's where I think things are going haywire. Specifically, in this section of the Procedure:
SELECT i.id as ap_id, item as ap_name, asset_property_data_type as ap_data_type
FROM dw_161.config_items as i
JOIN dw_161.config_tables as t on t.id = i.table_id
WHERE t.company_id IN (v_company_id) AND t.class = 'asset_property'
ORDER BY ap_name
So what happens if I pull out the variable v_company_id and manually enter what I am ultimately look for and run this, as follows? Well naturally it works and provides a list of all the asset properties. (there are no duplicates here.)
SELECT i.id as ap_id, item as ap_name, asset_property_data_type as ap_data_type
FROM dw_161.config_items as i
JOIN dw_161.config_tables as t on t.id = i.table_id
WHERE t.company_id IN (161,1) AND t.class = 'asset_property'
ORDER BY ap_name
Update 2
ARGH!!! Whilst going through all of this and making sure that it was completely repeatable for others looking at it, I noticed one of the fields was registered as decimal in the DB when we store both numeric and decimal data in a single field called numeric_value in the table. That ulitimately was my problem and I appeared to have Rubber Ducked myself. Changing the procedure to translate any fields stored in the DB to the actual column names solved my problem!
Here is the finished procedure in case anyone is interested. It's an interesting bit of code for transposing rows into columns in a DB if others need that.
BEGIN
SET #sql = concat('DROP TABLE IF EXISTS dw_', v_company_id, '.tableau_ap;');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET SESSION group_concat_max_len = 1000000;
SET #sql = (select concat('CREATE TABLE dw_', v_company_id, '.tableau_ap AS SELECT a.id as "Asset ID (ap)", ',
group_concat(
concat('max(case when ap.property_id = ',ap_id, ' then ap.', ap_data_type, '_value else null end) as "', ap_name,'" ')
),
'FROM dw_', v_company_id, '.equipment AS a ',
'JOIN dw_', v_company_id, '.asset_properties ap ON ap.asset_id = a.id ',
'JOIN dw_', v_company_id, '.sites AS s on a.site_id = s.id ',
'WHERE dw_', v_company_id, '.s.company_id = ', v_company_id,' ',
'GROUP BY a.id;'
)
from
(
SELECT i.id as ap_id, item as ap_name,
(CASE asset_property_data_type
WHEN 'numeric' THEN 'numeric'
WHEN 'decimal' THEN 'numeric'
WHEN 'calculated' THEN 'numeric'
WHEN 'text' THEN 'text'
WHEN 'date' THEN 'date'
WHEN 'asset' THEN 'id'
WHEN 'employee' THEN 'id'
WHEN 'bar-code' THEN 'text'
WHEN 'currency' THEN 'numeric'
WHEN 'time' THEN 'date'
WHEN 'duration' THEN 'date'
WHEN 'serial' THEN 'text'
END) AS ap_data_type
FROM dw_161.config_items as i
JOIN dw_161.config_tables as t on t.id = i.table_id
WHERE t.company_id IN (v_company_id,1) AND t.class = 'asset_property'
ORDER BY ap_name) a
)
;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END

See Update 2 above in the original post.

Related

How to I convert postgres SQL to SQLite for the to_jsonb postgres function?

I am moving my database from postgres to sqlite.
I have this postgres SQL statement
select
s.id,
(
select to_jsonb(alb)
from (
select
sum(case when "statusId" = 'passed' then 1 else 0 end) as "passed",
sum(case when "statusId" = 'failed' then 1 else 0 end) as "failed"
from "Script"
where "suiteId" = s.id
GROUP BY s.id
) alb
) as "statuses"
from "Suite" as s
This gives me results in this format
How can I make an equivalent SQL statement in SQLite? And If SQLite does not have this functionality, then is there any work arounds?
EDIT
based on #peak 's response, I have updated the query to the following:
select
s.id,
(
select json_object(alb)
from (
SELECT
sum(case when "statusId" = 'passed' then 1 else 0 end) as "passed",
sum(case when "statusId" = 'failed' then 1 else 0 end) as "failed"
from "Script"
where "suiteId" = s.id
) alb
) states
from "Suite" s;
But i am getting the error: Result: no such column: alb
Does any one know how to use the json_object with the above query?
If you cannot or don't want to load the JSON1 extension, you could simply construct the JSON text, e.g. by:
select "{""passed"": " || 'passed' || ", ""failed"":" || 'failed' || "}"
Otherwise, you could use the json_object function, as described on the JSON1 page: https://www.sqlite.org/json1.html

Can you join a table with another table you are pivoting on a field you are creating with the pivot?

I have a table I need to pivot that contains a value I need to join with a field in another table. I'm trying to determine if I can do this in one step, or if I need to pivot the first table and then join them together. GROUP_ID is in field_name in the redcap_data table and needs to be joined with group_id in redcap_data_access_groups.
CREATE VIEW vwGlobalHealthInfants AS
SELECT rd.record as record_id,
MAX(CASE WHEN rd.field_name = '__GROUP_ID__' THEN rd.value ELSE NULL END) as GroupId,
g.group_name as hospno,
MAX(CASE WHEN rd.field_name = 'admission_temperature' THEN rd.value ELSE NULL END) as adtemp,
MAX(CASE WHEN rd.field_name = 'antenatal_care' THEN rd.value ELSE NULL END) as antecare,
MAX(CASE WHEN rd.field_name = 'anti_hypertensive' THEN rd.value ELSE NULL END) as antihyper,
MAX(CASE WHEN rd.field_name = 'anticonvulsants' THEN rd.value ELSE NULL END) as anticonvul
FROM (
redcapVON.redcap_data rd
JOIN redcapVON.redcap_data_access_groups g ON ( (
GroupId = g.group_id
) )
)
WHERE (
rd.project_id = 12
)
GROUP BY rd.record
I don't know if it's possible to get it to recognize the GroupID field in the join before the pivot.
CTE would solve it. We just don't have the most updated version of mysql to run it. 2 steps it is.

group by picking columns with null value - mysql

I am working on a query and want to group the rows and return groupped data but my query is not working as expected.
my query-
select item, branch, packunit,packlevel,dealqty,PromotionFlag,PromotionID, PromotionEndDate, cnc,delivery, volumedeal, standard_price_scheme,
deliv_price_scheme
from
(
SELECT
`item` AS `item`,
`branch` AS `branch`,
`PackUnit` AS `PackUnit`,
`PackLevel` AS `PackLevel`,
`DealQty` AS `DealQty`,
`PromotionFlag` AS `PromotionFlag`,
`PromotionID` AS `PromotionID`,
`PromotionEndDate` AS `PromotionEndDate`,
SUM(`cnc`) AS `cnc`,
SUM(`delivery`) AS `delivery`,
SUM(`volumedeal`) AS `volumedeal`,
`standard_price_scheme` AS `standard_price_scheme`,
`deliv_price_scheme` AS `deliv_price_scheme`
FROM
(
SELECT DISTINCT
`Pricing_Today`.`item` AS `item`,
`Pricing_Today`.`branch` AS `branch`,
`Pricing_Today`.`price_scheme` AS `price_scheme`,
`Pricing_Today`.`PackUnit` AS `PackUnit`,
`Pricing_Today`.`PackLevel` AS `PackLevel`,
`Pricing_Today`.`DealQty` AS `DealQty`,
`Pricing_Today`.`PromotionFlag` AS `PromotionFlag`,
`Pricing_Today`.`PromotionID` AS `PromotionID`,
`Pricing_Today`.`PromotionEndDate` AS `PromotionEndDate`,
(CASE
WHEN (`Pricing_Today`.`PriceType` = 'C&C') THEN `Pricing_Today`.`Sell`
END) AS `cnc`,
(CASE
WHEN (`Pricing_Today`.`PriceType` = 'Delivery') THEN `Pricing_Today`.`Sell`
END) AS `delivery`,
(CASE
WHEN (`Pricing_Today`.`PriceType` = 'Volume Deal') THEN `Pricing_Today`.`Sell`
END) AS `volumedeal`,
(CASE
WHEN (`Pricing_Today`.`PriceType` = 'C&C') THEN `Pricing_Today`.`price_scheme`
END) AS `standard_price_scheme`,
(CASE
WHEN
((`Pricing_Today`.`PriceType` = 'Delivery')
OR (`Pricing_Today`.`PriceType` = 'Volume Deal'))
THEN
`Pricing_Today`.`price_scheme`
END) AS `deliv_price_scheme`
FROM
`Pricing_Today`
where item = 78867
and branch = 0
GROUP BY `Pricing_Today`.`item` , `Pricing_Today`.`PackUnit` , `Pricing_Today`.`PriceType`,`standard_price_scheme`,`deliv_price_scheme`
) as a
GROUP BY branch,`item` , `PackUnit`,`standard_price_scheme`,`deliv_price_scheme`
) as a
-- group by item, packunit
which returns -
BUT, when I group by item, pack I get this -
for cnc its showing null values. How do I eliminate null values and get the numbers?
Thanks in advance
You need to take aggregates of the CASE expressions:
SELECT
p.item,
p.branch,
p.price_scheme,
p.PackUnit,
p.PackLevel,
p.DealQty,
p.PromotionFlag,
p.PromotionID,
p.PromotionEndDate,
MAX(CASE WHEN p.PriceType = 'C&C' THEN p.Sell END) AS cnc,
MAX(CASE WHEN p.PriceType = 'Delivery' THEN p.Sell END) AS delivery,
MAX(CASE WHEN p.PriceType = 'Volume Deal' THEN p.Sell END) AS volumedeal,
MAX(CASE WHEN p.PriceType = 'C&C' THEN p.price_scheme END) AS standard_price_scheme,
MAX(CASE WHEN p.PriceType = 'Delivery' OR p.PriceType = 'Volume Deal'
THEN p.price_scheme END) AS deliv_price_scheme
FROM
Pricing_Today p
WHERE
item = 78867 AND branch = 0
GROUP BY
p.item,
p.branch,
p.price_scheme,
p.PackUnit,
p.PackLevel,
p.DealQty,
p.PromotionFlag,
p.PromotionID,
p.PromotionEndDate;
This is just a standard pivot query. The idea behind taking the MAX of a CASE expression is that if a given group of records has a single non NULL value, then MAX would correctly extract it. This works because MAX ignores NULL values.
Note that I removed the backticks from your query, none of which were necessary. I try to avoid using backticks unless they are really needed, because it makes the query harder to read.

MySql, Postgres, Oracle and SQLServer ignoring IS NOT NULL filter

While I was preparing an answer to one of our fellows here on SO I've encounter an odd situation, at least to me. The original question is here: Pivot Table Omitting Rows that Have Null values
I've modified the query to use max instead of group_concat in order to show the "problem" in all databases.
SELECT
id,
max(case when colID = 1 then value else '' end) AS fn,
max(case when colID = 2 then value else '' end) AS ln,
max(case when colID = 3 then value else '' end) AS jt
FROM tbl
GROUP BY id
The result of this query is this:
ID FN LN JT
1 Sampo Kallinen Office Manager
2 Jakko Salovaara Vice President
3 (null) Foo No First Name
The user asks to filter the row with id 3 because the field value is null.
When it seems pretty obvious that only it needs to do was to add a WHERE value IS NOT NULL constraint on that query to achieve what the user expect. It won't work.
So I start to test it on the other databases to see what happens (Queries with the WHERE CLAUSE)
SELECT
id,
max(case when colID = 1 then value else '' end) AS fn,
max(case when colID = 2 then value else '' end) AS ln,
max(case when colID = 3 then value else '' end) AS jt
FROM tbl
WHERE value is not null
GROUP BY id
Mysql: http://sqlfiddle.com/#!2/78395/1
Postgres: http://sqlfiddle.com/#!15/78395/1
SQLServer: http://sqlfiddle.com/#!6/78395/1
Oracle: http://sqlfiddle.com/#!4/78395/1
For my surprise the result was the same, none worked.
Then I tried a different version of the same query:
SELECT * FROM (
SELECT
id,
max(case when colID = 1 then value else '' end) AS fn,
max(case when colID = 2 then value else '' end) AS ln,
max(case when colID = 3 then value else '' end) AS jt
FROM tbl
GROUP BY id
) T
WHERE fn IS NOT NULL
AND ln IS NOT NULL
AND jt IS NOT NULL
Oracle: http://sqlfiddle.com/#!4/78395/2 WORKED
MySql: http://sqlfiddle.com/#!2/78395/2
Postgres: http://sqlfiddle.com/#!15/78395/2
SQLServer: http://sqlfiddle.com/#!6/78395/2
The only way I could make it work on all databases was with this query:
SELECT
id,
max(case when colID = 1 then value else '' end) AS fn,
max(case when colID = 2 then value else '' end) AS ln,
max(case when colID = 3 then value else '' end) AS jt
FROM tbl
WHERE NOT EXISTS (SELECT * FROM tbl b WHERE tbl.id=b.id AND value IS NULL)
GROUP BY id
So I ask:
What is happening here that except for that specific case on Oracle all other DBs seem to ignore the IS NOT NULL filter?
To omit the row from the result if any of the source rows for the same id has value IS NULL, a solution in Postgres would be to use the aggregate function every() or (synonym for historical reasons) bool_and() in the HAVING clause:
SELECT id
, max(case when colID = 1 then value else '' end) AS fn
, max(case when colID = 2 then value else '' end) AS ln
, max(case when colID = 3 then value else '' end) AS jt
FROM tbl
GROUP BY id
HAVING every(value IS NOT NULL);
SQL Fiddle.
Explain
Your attempt with a WHERE clause would just eliminate one source row for id = 3 in your example (the one with colID = 1), leaving two more for the same id. So we still get a row for id = 3 in the result after aggregating.
But since we have no row with colID = 1, we get an empty string (note: not a NULL value!) for fn in the result for id = 3.
A faster solution in Postgres would be to use crosstab(). Details:
PostgreSQL Crosstab Query
Other RDBMS
While EVERY is defined in the SQL:2008 standard, many RDBMS do not support it, presumably because some of them have shady implementations of the boolean type. (Not dropping any names like "MySQL" or "Oracle" ...). You can probably substitute everywhere (including Postgres) with:
SELECT id
, max(case when colID = 1 then value else '' end) AS fn
, max(case when colID = 2 then value else '' end) AS ln
, max(case when colID = 3 then value else '' end) AS jt
FROM tbl
GROUP BY id
HAVING count(*) = count(value);
Because count() doesn't count NULL values. In MySQL there is also bit_and().
More under this related question:
Is there any equivalent to Postgresql EVERY aggregate function on other RDBMS?
It works in Oracle because Oracle handles NULL incorrectly in that NULL and '' are the same. The other databases don't do this because it is wrong. NULL is unknown, versus '' which is just a blank, empty string.
So if your where clause said something like WHERE (fn IS NOT NULL or fn <> '') you would probably get further.
I think this is a case where a HAVING clause will do what you need.
SELECT id, max ... (same stuff as before)
FROM tbl
GROUP by id
HAVING fn IS NOT NULL
AND ln IS NOT NULL
AND jt IS NOT NULL

SQL Select if else

I need some help.. I need to output the value of DisplayName if the value of fullname return a null or empty as FullName.
something like
if(u.first_name && u.last_name are not empty)
output CONCAT(u.first_name,' ',u.last_name)
else
output max(case when t.field_id = 1 then t.value else '' end)
but how? also my query took 3.45 seconds :(..
SELECT * FROM(
SELECT
t.user_id,
##this code
max(case when t.field_id = 1 then t.value else '' end) DisplayName,
CONCAT(u.first_name,' ',u.last_name) FullName,
max(case when t.field_id = 270 then t.value else '' end) Gender,
max(case when t.field_id = 274 then t.value else '' end) Birthday,
max(case when t.field_id = 274 then FLOOR(DATEDIFF(CURRENT_DATE, STR_TO_DATE(t.value,'%m/%d/%Y'))/365) else '' end) Age,
max(case when t.field_id = 275 then t.value else '' end) Phone,
max(case when t.field_id = 286 then t.value else '' end) Email,
max(case when t.field_id = 71 then t.value else '' end) Occupation,
max(case when t.field_id = 73 then t.value else '' end) CurrentCountry,
max(case when t.field_id = 24 then t.value else '' end) Region,
max(case when t.field_id = 355 then t.value else '' end) Province,
max(case when t.field_id = 354 then t.value else '' end) City
FROM wp_bp_xprofile_data t
LEFT JOIN (
SELECT
user_id id,
max(case when meta_key = 'first_name' then meta_value end) first_name,
max(case when meta_key = 'last_name' then meta_value end) last_name
from wp_usermeta
GRoup by user_id
)as u
ON u.id = t.user_id
GROUP BY user_id
)temp
WHERE 1 = 1
##Some Condition to be added
LIMIT 0 , 30
I would change to a multiple join to same table repeatedly to get each field on its own criteria
as a possibility of performance. Start the profile data one for say the field_id = 1 as the basis,
then join all the rest on that same key, then no grouping by or max/case when on every tested row.
IF some fields may NOT exists, just have as LEFT-JOIN to the re-used table with alternate alias.
I would ensure you have a compound/covering index on your profile table with the following key
( user_id, field_id, value )
Then do the following query. The interesting thing here, is it basically opens the same table for each "component" part of the user_ID and gets them all lined up, but runs the query based only on a single instance of records associated with field_id = 1 in the where clause. All the other instances are in their respective aliases joined ON clause. So now, I just run through the records for field_id = 1, and if found on the other tables, grabs the value, otherwise blank (via coalesce). Similarly joined to the user meta-data table to get the first/last name parts.
if this works for you, I would be interested in knowing the performance one-way or the other. The benefit is that it doesn't have to query EVERYONE up front (the inner-most query) to then run the main query of all records, apply the group by, then stop at your 30 limit clause. It just gets up to the first 30 where field_id = 1 and its done. Now, if the population of "field_id = 1" is a low populated field, and you want to swap with another, so be it, you know your data better than us.
SELECT
DN.User_ID,
case when fn.user_id is null and ln.user_id is null then DN.value
when fn.user_id is null then ln.meta_value
when ln.user_id is null then fn.meta_value
else concat( fn.meta_value, ' ', ln.meta_value ) end as FinalName,
DN.Value DisplayName,
CONCAT( COALESCE( fn.meta_value, '' ),' ',COALESCE( ln.meta_value, '' )) FullName,
COALESCE( G.Value, '' ) Gender,
COALESCE( BD.Value, '' ) Birthday,
COALESCE( FLOOR( DATEDIFF( CURRENT_DATE, STR_TO_DATE( BD.value,'%m/%d/%Y'))/365), '' ) Age,
COALESCE( P.Value, '' ) Phone,
COALESCE( E.Value, '' ) Email,
COALESCE( O.Value, '' ) Occupation,
COALESCE( CC.Value, '' ) CurrentCountry,
COALESCE( R.Value, '' ) Region,
COALESCE( PR.Value, '' ) Province,
COALESCE( C.Value, '' ) City
from
wp_bp_xprofile_data DN
LEFT JOIN wp_bp_xprofile_data G
ON DN.User_ID = G.User_ID AND G.field_id = 270
LEFT JOIN wp_bp_xprofile_data BD
ON DN.User_ID = BD.User_ID AND BD.field_id = 274
LEFT JOIN wp_bp_xprofile_data P
ON DN.User_ID = P.User_ID AND P.field_id = 275
LEFT JOIN wp_bp_xprofile_data E
ON DN.User_ID = E.User_ID AND E.field_id = 286
LEFT JOIN wp_bp_xprofile_data O
ON DN.User_ID = O.User_ID AND O.field_id = 71
LEFT JOIN wp_bp_xprofile_data CC
ON DN.User_ID = CC.User_ID AND CC.field_id = 73
LEFT JOIN wp_bp_xprofile_data R
ON DN.User_ID = R.User_ID AND R.field_id = 24
LEFT JOIN wp_bp_xprofile_data PR
ON DN.User_ID = PR.User_ID AND PR.field_id = 355
LEFT JOIN wp_bp_xprofile_data C
ON DN.User_ID = C.User_ID AND C.field_id = 354
LEFT JOIN wp_usermeta FN
ON DN.User_ID = FN.User_ID AND FN.meta_key = 'first_name'
LEFT JOIN wp_usermeta LN
ON DN.User_ID = LN.User_ID AND LN.meta_key = 'last_name'
where
DN.field_id = 1
AND O.Value like '%Programmer%'
LIMIT
0, 30
I modified to add a FINAL Name by use of a case/when.
if both the alias FN/LN (first name / last name) are BOTH null, then get the DN (display name) value. Otherwise we have AT LEAST ONE or BOTH parts of the first/last name field. If the first name was null, just return the last name. If the last name was null, return the first name, otherwise BOTH first and last names were available, and return the concatinated version of the name.
As for applying extra WHERE clauses. As soon as you add a "required" criteria to the where clause against any of the left-join aliases, it will in-effect turn them into INNER JOINs and only include those that HAVE that component AND that component has the value you want... So, per your comment I would just add...
where
DN.field_id = 1
AND BD.User_ID IS NOT NULL
AND STR_TO_DATE( BD.value,'%m/%d/%Y') BETWEEN '2009-01-01' and '2012-01-01'
You could use your datediff for age and do that between 1 and 2, but just a preference to pick a date range of interest..
For your additional comment about Programmer filtering, the way you had it was trying to test ALL the joined aliases looking for programmer, but at the same time, all of them required because you wanted them all as NOT NULL. If you want a field required, but no other specific criteria, such as ALWAYS must have email address, then just change
LEFT JOIN wp_bp_xprofile_data E
to
JOIN wp_bp_xprofile_data E
if you want to make sure the field is considered only if it has a value other than space (such as your last/first name)
LEFT JOIN wp_usermeta FN
ON DN.User_ID = FN.User_ID
AND FN.meta_key = 'first_name'
AND LENGTH( TRIM( COALESCE( FN.meta_Value, '' ))) > 0
This will make sure the record is only considered when it actually has a non-empty string value.
Back to your consideration of "%Programmer%'... why would you ever expect a city, Province, region, etc to have a value of programmer. You should only require the Occupation instance to have the value. Also, because you had an OR, it was doing ALL the rows. You would need to have wrapped the conditions something like
where
DN.field_id = 1
AND ( O.Value like '%Programmer%'
OR E.Value like '%#gmail.com'
OR C.Value like 'Some City' )
Notice I wrapped my ADDITIONAL criteria OR components. This way it still only starts with the Field_ID = 1 entries, but based on the JOIN adds the extra criteria, and in this case, DN.field_id = 1 always, but ANY ONE (or more) of the OR conditions must also be true... The Occupation alias has a value like programmer, email alias has a value of gmail, the city alias like 'Some City'.
Hopefully this clarifies how to implement... Also, make note, that using any LIKE qualifier that has '%' leading the string needs to compare through the whole string and will slow the query down some, but since the join is based on the user ID and the field_ID value, that should still keep it fast (relatively).
Try this:
SELECT if( trim(CONCAT(first_name,' ', lastName))='',
DisplayName,
CONCAT(first_name,' ', lastName) ) as Name
...rest of the fieds on TA subquery
FROM (
(SELECT
t.user_id,
max(case when t.field_id = 1 then t.value else '' end) DisplayName,
max(case when t.field_id = 270 then t.value else '' end) Gender,
max(case when t.field_id = 274 then t.value else '' end) Birthday,
max(case when t.field_id = 274
then FLOOR( DATEDIFF( CURRENT_DATE,
STR_TO_DATE( t.value, '%m/%d/%Y')) / 365)
else '' end) Age,
max(case when t.field_id = 275 then t.value else '' end) Phone,
max(case when t.field_id = 286 then t.value else '' end) Email,
max(case when t.field_id = 71 then t.value else '' end) Occupation,
max(case when t.field_id = 73 then t.value else '' end) CurrentCountry,
max(case when t.field_id = 24 then t.value else '' end) Region,
max(case when t.field_id = 355 then t.value else '' end) Province,
max(case when t.field_id = 354 then t.value else '' end) City
FROM wp_bp_xprofile_data t
GROUP BY user_id
) TA
LEFT JOIN
( SELECT user_id id,
max(case when meta_key = 'first_name'
then meta_value end) first_name,
max(case when meta_key = 'last_name'
then meta_value end) last_name
from wp_usermeta
GRoup by user_id ) as U on (ta.user_id = U.id)
)temp
WHERE 1 = 1
##Some Condition to be added
LIMIT 0 , 30