I have a SQL query which gives the correct result, but performs too slow.
The query operates on the following three tables:
customers contains lots of customer data like name, address, phone
etc. To simplify the table i am only using the name.
customdatas contains certain custom (not customer) data. (The
tables are created in software, which is why the plural form is wrong
for this table)
customercustomdatarels associates custom data with a customer.
customers
Id Name (many more columns)
-----------------------------------------------------------------------
8053c6f4c5c5c631054ddb13d9186117 MyCustomer ...
2efd2aa5711ddfade1f829b12dd88cf3 CheeseFactory ...
customdata
id key
-------------------------------------------------
22deb172c1af6e8e245634a751871564 favoritsport
86eea84d296df9309ad6ff36fd7f856e favoritcheese
customercustomdatarels (relation between customer and custom data - with corresponding value)
customer customdata value
-------------------------------------------------------------------------------------
8053c6f4c5c5c631054ddb13d9186117 22deb172c1af6e8e245634a751871564 cycling
8053c6f4c5c5c631054ddb13d9186117 86eea84d296df9309ad6ff36fd7f856e cheddar
2efd2aa5711ddfade1f829b12dd88cf3 22deb172c1af6e8e245634a751871564 football
2efd2aa5711ddfade1f829b12dd88cf3 86eea84d296df9309ad6ff36fd7f856e mouldy
What i want is a table basically consisting of all data in customers with an variable amount of extra columns, corresponding to the custom data specified in customercustomdatarels.
These columns should be defined somewhere and I have therefore created the following table which defines such extra columns and maps them to a key in the customdata table:
test_customkeymapping
colkey customkey
---------------------
1 favoritsport
2 favoritcheese
The result should then be:
Name ExtraColumn_1 ExtraColumn_2
---------------------------------------------
CheeseFactory football mouldy
MyCustomer cycling cheddar
(ExtraColumn_1 is therefore synonym for a customers' favorite sport and ExtraColumn_2 is a synonym for a customers' favorit cheese.)
This result is achieved by executing the following query:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT('MAX(CASE
WHEN ckm.colkey = ', colkey, ' THEN
(SELECT value FROM customercustomdatarels ccdr2
LEFT JOIN customdatas cd2
ON cd2.id = ccdr2.customdata
WHERE cd2.key = ckm.customkey AND c.Id = ccdr2.customer)
END) AS ', CONCAT('`ExtraColumn_', colkey, '`'))
) INTO #sql
FROM test_customkeymapping;
SET #sql = CONCAT('SELECT c.Name, ', #sql, '
FROM customers c
LEFT JOIN customercustomdatarels ccdr
ON c.Id = ccdr.customer
LEFT JOIN customdatas cd
ON cd.Id = ccdr.customdata
LEFT JOIN test_customkeymapping ckm
ON cd.key = ckm.customkey
GROUP BY c.Id');
PREPARE stmt FROM #sql;
EXECUTE stmt;
This works. But is too slow (for 7000 customers it takes ~10 seconds).
The query was greatly influenced by the solution in this question:
MySQL Join Multiple Rows as Columns
How do I optimize this query?
I don't understand why you are using a subquery in the group_concat() statement. Wouldn't this generate the code that you really want to run?
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT('MAX(CASE WHEN ckm.colkey = ', colkey, ' THEN ccd.value END) AS ',
CONCAT('ExtraColumn_', colkey, ''))
) INTO #sql
FROM test_customkeymapping;
SET #sql = CONCAT('SELECT c.Name, ', #sql, '
FROM customers c
LEFT JOIN customercustomdatarels ccdr
ON c.Id = ccdr.customer
LEFT JOIN customdatas cd
ON cd.Id = ccdr.customdata
LEFT JOIN test_customkeymapping ckm
ON cd.key = ckm.customkey
GROUP BY c.Id');
PREPARE stmt FROM #sql;
EXECUTE stmt;
Note: This is untested, but the idea is the same. Use the values from the main from statement for your work rather than the values from some extra, unnecessary subquery.
Related
Please some one help me tI have two Tables in my MYSQL database,
the first table - (Employee Table) consists of
EmployeeNo| EmployeeName
the second table - (Attendance table) consists of
DATE | TIME | STATUS| EmployeeNo
and a calendar table that holds only date for every month
I want to generate attendance sheet similar to this
Click here
I end up writing the following SQL, but it gives me syntax error
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your
MySQL server version for the right syntax to use near 'from (select cal.calendarDate,
emp.first_name,emp.nu' at line 2
and my query ,
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'max(CASE WHEN calemp.calendarDate = ''',date_format(calendarDate, '%Y-%m-%d'),''' THEN coalesce(att.inorout, ''P'') END) AS `',date_format(calendarDate, '%Y-%m-%d'), '`'
)
) INTO #sql
FROM yearly_date_calendar
where calendarDate >= '2016-11-01'
and calendarDate <= '2016-11-30';
SET #sql = CONCAT('SELECT calemp.first_name,calemp.nurse_code,',#sql,'
from
(
select cal.calendarDate,emp.first_name,emp.nurse_code
from yearly_date_calendar cal
cross join syscare_caregiver emp
) calemp
left join syscare_employee_attendance att
on calemp.nurse_code = att.emp_code
and calemp.calendarDate = att.attendance_date
where calemp.calendarDate>=''2016-11-01''
and calemp.calendarDate <= ''2016-11-30''
group by calemp.first_name, calemp.nurse_code,calemp.calendarDate
');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
You can use below query and fetch all database result
select EmployeeTable.EmployeeNo,Employee Table.EmployeeName,Attendance table.DATE,Attendance table.TIME,Attendance table.STAUS,Attendance table.EmployeeNo from Employee Table INNER JOIN Attendance table ON
EmployeeTable.EmployeeNo=Attendance table.EmployeeNo
I am using IBatis and Spring framework.
I would like to execute multi queries but I could not get the response.
<select id="getUIs" resultMap="blpUiVOMap" parameterClass="Map">
SELECT
GROUP_CONCAT(DISTINCT
CONCAT('COUNT(CASE WHEN bug.BUG_STT = ', STT_ID, ' THEN 1 END) AS ''bugI', STT_ID, '''')
) INTO #sql
from BLP_STT <![CDATA[;]]>
SET #sql = CONCAT('SELECT ui.UI_ID, ui.UI_NM, cat.CAT_ID, cat.CAT_NM, cat.PRNT_ID, cat1.CAT_NM as PRNT_NM,',#sql,'FROM BLP_UI ui JOIN BLP_CAT cat ON ui.CAT_ID = cat.CAT_ID JOIN BLP_CAT cat1 ON cat.PRNT_ID = cat1.CAT_ID JOIN BLP_BUG bug ON ui.UI_ID = bug.UI_ID JOIN BLP_STT stt ON bug.BUG_STT = stt.STT_ID WHERE 1 = 1 GROUP BY ui.UI_ID ORDER BY ui.UI_ID ASC') <![CDATA[;]]>
PREPARE stmt FROM #sql <![CDATA[;]]>
EXECUTE stmt <![CDATA[;]]>
DEALLOCATE PREPARE stmt <![CDATA[;]]>
</select>
In MySQL, this query is working fine.
The <select> will create a single PreparedStatement in JDBC. You are trying to execute commands from the MySQL command line. This won't work in JDBC or MyBatis.
You will need to create a single query that combines all your strings. Maybe something like this?
SELECT ui.UI_ID, ui.UI_NM, cat.CAT_ID, cat.CAT_NM, cat.PRNT_ID, cat1.CAT_NM as PRNT_NM,
(SELECT
GROUP_CONCAT(DISTINCT
CONCAT('COUNT(CASE WHEN bug.BUG_STT = ', STT_ID, ' THEN 1 END) AS ''bugI', STT_ID, '''')
)
from BLP_STT
)
FROM BLP_UI ui JOIN BLP_CAT cat ON ui.CAT_ID = cat.CAT_ID JOIN BLP_CAT cat1 ON cat.PRNT_ID = cat1.CAT_ID JOIN BLP_BUG bug ON ui.UI_ID = bug.UI_ID JOIN BLP_STT stt ON bug.BUG_STT = stt.STT_ID WHERE 1 = 1 GROUP BY ui.UI_ID ORDER BY ui.UI_ID ASC')
Note, you also do not need to put a ; in the XML either.
I have got three tables that looks as follows.
usedetails [ID,first_name,last_name,telephone,email]
address [ID,streetnumber,streetname,town,county,postcode,userdetailsID]
BOOKING [ID,customerID,pickup_address_id,dropoff_address_id,charge,no_of_passenger]
Address table holds two types of address ie pickoff and dropoff. I would like to display each of the two addresses as one string. The following is my query.
query = "SELECT A.streetnumber,
A.streetname,
A.town,
A.postcode
AS pickup_point
AB.streetnumber,
AB.streetname,
AB.town,
AB.postcode
AS dropoff_point
UD.first_name,
UD.last_name,
UD.telephone,
UD.email
FROM userdetails UD
INNER JOIN booking B
ON B.customerID = UD.ID
INNER JOIN address A
ON B.pickup_address_id = A.ID
INNER JOIN address AB
ON AB.drop_off_address_id = A.ID
WHERE UD.ID = A.userdetailsID OR UD.ID = AB.userdetailsID";
Try CONCAT function:
SELECT CONCAT(A.streetnumber,
' ',
A.streetname,
' ',
A.town,
' ',
A.postcode) AS pickup_point, ...
Or CONCAT_WS function to pass separator as the first argument:
SELECT CONCAT_WS(' ',
A.streetnumber,
A.streetname,
A.town,
A.postcode) AS pickup_point, ...
I'm trying to pivotise my columns to get from rows to tables.
It's working like expected with the code I have now, but when my fields get updated I have to manually edit the query in order to update it.
I'm trying to automate the process but I'm not getting it to work. Any ideas?
This is the manual code which works:
SELECT
md.entity_guid AS guid, username, e.time_created, time_updated, e.enabled, banned,e.last_action, last_login,
MAX(IF(msn.string = 'question1', msv.string, NULL)) AS question1
FROM exp_metadata md
JOIN exp_metastrings msn ON md.name_id = msn.id
JOIN exp_metastrings msv ON md.value_id = msv.id
JOIN exp_users_entity u ON u.guid = md.entity_guid
JOIN exp_entities e ON e.guid = md.entity_guid
GROUP BY
guid
And this is the query I'm trying to do to automate it:
SET #sql = NULL;
SELECT
md.entity_guid AS guid, username, e.time_created, time_updated, e.enabled, banned, e.last_action, last_login,
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(msn.string = ''',
msn.string,
''', msv.string, NULL)) AS ',
msn.string
)
) INTO #sql
FROM exp_metadata md
JOIN exp_metastrings msn ON md.name_id = msn.id
JOIN exp_metastrings msv ON md.value_id = msv.id
JOIN exp_users_entity u ON u.guid = md.entity_guid
JOIN exp_entities e ON e.guid = md.entity_guid
SET #sql = CONCAT('SELECT e.guid, ', #sql, ' FROM exp_entities e GROUP BY e.guid');
I get an error in phpmyadmin about the last line.
When I delete the last line to see if it does anything, I get:
#1222 - The used SELECT statements have a different number of columns
Any help would be appreciated.
Thanks a lot, Dries
I'm using Wordpress and trying to create a view to make reviewing and analyzing my resort data a little easier. Resort data is stored in the post_meta table in Wordpress and referenced in a custom post type known as "resorts". The following query gives me the result set I want to parse:
SELECT a.id, a.post_type, a.post_title, b.post_id, b.meta_key, b.meta_value
FROM alpinezone_postmeta b
INNER JOIN alpinezone_posts a ON a.id = b.post_id
WHERE a.post_type = "resorts"
What I want to do with this result set is have each unique meta_key of a set I define become a column and then each row should be a unique b.post_id (or a.id), which corresponds to an individual resort's record.
So ultimately I end up with:
post_title | phone_num | state |
resort1 | 800-200-1111 | Vermont |
resort2 | 800-200-2222 | New Hampshire |
resort 3 | 800-200-2323 | Maine |
Basically ...... I'm not that great at MySQL so trying to figure out the best way to handle this. I do have a list of all the meta_key I want to place into columns, there are 36 of them capturing a range of information.
EDIT: Some more detail.
Current Structure - shows what table it comes from as well
*alpinezone_posts alpinezone_postmeta alpinezone_postmeta*
post_title meta_key meta_value
----------------------------------------------------------------
sugarloaf snow_phone 888-234-2222
sugarloaf vertical_feet 2300
sugarloaf site_url sugarloaf.com
wachusett snow_phone 888-111-2222
wachusett vertical_feet 1000
wachusett site_url wachusett.com
These two tables are joined on post_id from table alpinezone_postmeta and id from table alpinezone_posts.
Only want results where the post_type in table alpinezone_posts is = "resorts"
How I want it to look in new view
post_title snow_phone vertical_feet site_url
-------------------------------------------------------
sugarloaf 888-234-2222 2300 sugarloaf.com
wachusett 888-111-2222 1000 wachusett.com
You want to use is a cross-tabulated table or pivot table with "GROUP BY" like this example here: http://en.wikibooks.org/wiki/MySQL/Pivot_table
You need to use something other then SUM() for strings though... like MAX():
http://forums.mysql.com/read.php?20,75357,75367#msg-75367
SELECT p.post_title AS post_title,
MAX(IF(m.meta_key='phone_num',m.meta_value,0)) AS phone_num,
MAX(IF(m.meta_key='state',m.meta_value,0)) AS state
FROM alpinezone_postmeta m
INNER JOIN alpinezone_posts p ON m.post_id = p.id
WHERE p.post_type = 'resorts'
GROUP BY m.post_id`
I understand you have one post for each resort, each with a more or less complete set of metadata. If that's correct, then here's how you can build a query that produces the table you describe. It's not really elegant, but it works without using another programming language or Excel (Excel might be able to make what you want from an export of the post_meta table, but my Excel-fu is not good enough...)
SELECT posts.title,
m1.meta_value as <your meta key 1>,
m2.meta_value as <your meta key 2>,
m3.meta_value as <your meta key 3>,
...
FROM alpinezone_posts
LEFT OUTER JOIN alpinezone_postmeta AS m1 ON alpinezone_posts.id = m1.post_id,
LEFT OUTER JOIN alpinezone_postmeta AS m2 ON alpinezone_posts.id = m2.post_id,
LEFT OUTER JOIN alpinezone_postmeta AS m3 ON alpinezone_posts.id = m3.post_id,
...
WHERE
m1.meta_key = <your meta key 1>
AND m2.meta_key = <your meta key 2>
AND m3.meta_key = <your meta key 3>
...
Using LEFT OUTER JOIN instead of INNER JOIN makes sure you get a result row for each resort even if you don't have a value for every meta_key.
If you can use PHP and an HTML table would help you, you could also loop all posts and use the get_post_custom() function.
Here's a stored procedure I wrote that takes a given post type and pivots all related postmeta data. You don't need to know the meta keys, it will figure them all out for you.
This isn't the fastest query, and we use it only for migration and deep dives into data, but it does the job.
Note that this temporarily resets the max length of the concat function so you can build a large SQL statement:
CREATE PROCEDURE `wp_posts_pivot`(IN post_type_filter varchar(50))
BEGIN
/* allow longer concat */
declare max_len_original INT default 0;
set max_len_original = ##group_concat_max_len;
set ##group_concat_max_len=100000;
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT CONCAT('MAX(IF(pm.meta_key = ''',
meta_key,
''', pm.meta_value, NULL)) AS `',
meta_key,
'`'))
INTO #sql FROM
wp_posts p
INNER JOIN
wp_postmeta AS pm ON p.id = pm.post_id
WHERE
p.post_type = post_type_filter;
SET #sql = CONCAT('SELECT p.id
, p.post_title
, ', #sql, '
FROM wp_posts p
LEFT JOIN wp_postmeta AS pm
ON p.id = pm.post_id
where p.post_type=\'',post_type_filter,'\'
GROUP BY p.id, p.post_title');
/* reset the default concat */
set ##group_concat_max_len= max_len_original;
/*
select #sql;
*/
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
You can then call this with a simple call such as this one, which will select a single row for each 'page' post type along with all meta values:
call wp_posts_pivot('page');