I have an old database of entries from an abandoned "Joomgalaxy" Joomla plugin.
There are three tables, joomgalaxy_entries, joomgalaxy_fields, and joomgalaxy_entries_data
The id from the entries table matches the entry_id in the entries_data table, but the actual field name is saved in another table, fields
Can someone please help me with the correct SQL statement to obtain results like you can see below in Ultimate Goal? My MySQL knowledge is very basic, and from my searching it sounds like I need to use a LEFT JOIN, but I have no idea how to use the value from field_name as the column name for returned values
Thank You!!
joomgalaxy_entries
---------------------------------------
| id | title | longitude | latitude |
---------------------------------------
| 50 | John | -79.333333 | 43.669999 |
| 51 | Bob | -79.333333 | 43.669999 |
---------------------------------------
joomgalaxy_fields
This is just two examples below to keep it simple, there are more than just these two, so it would have to be able to handle dynamically using the field_name as the column name.
--------------------------------
| id | field_type | field_name |
--------------------------------
| 1 | textbox | websiteurl |
| 2 | dropdown | occupation |
--------------------------------
joomgalaxy_entries_data
"Technically" there shouldn't be any duplicate entries (fieldid and entry_id), so from my understanding that shouldn't affect using the field_name from above as the column name, but what if there ends up being one?
-------------------------------------
| fieldid | field_value | entry_id |
-------------------------------------
| 1 | google.com | 50 |
| 2 | unemployed | 50 |
| 1 | doctor.com | 51 |
| 2 | doctor | 51 |
-------------------------------------
Ultimate Goal
Ultimately trying to get this type of result, so I can then use that statement in MySQL Workbench to export the data that would look like this:
------------------------------------------------------------------
| id | title | longitude | latitude | websiteurl | occupation |
------------------------------------------------------------------
| 50 | John | -79.333333 | 43.669999 | google.com | unemployed |
| 51 | Bob | -79.333333 | 43.669999 | doctor.com | doctor |
------------------------------------------------------------------
EDIT:
There are more than just the two fields websiteurl and occupation, I was just using those two as examples, there are numerous fields that are all different, so in theory pulling the value from field_name would be used for the column name
You can use some conditional logic, like a CASE statement, along with an aggregate function like max() or min() to return those values as columns:
SELECT je.id,
je.title,
je.longitude,
je.latitude,
max(case when jf.fieldid = 1 then jed.field_value end) as WebsiteUrl,
max(case when jf.fieldid = 2 then jed.field_value end) as Occupation
FROM joomgalaxy_entries je
INNER JOIN joomgalaxy_entries_data jed
on je.id = jed.entry_id
GROUP BY je.id,
je.title,
je.longitude,
je.latitude
Using an INNER JOIN will only return the joomgalaxy_entries rows that have values in each table, if you want to return all joomgalaxy_entries even if there are no matching rows to join on in the other tables, then change the INNER JOIN to a LEFT JOIN.
You can write a simple SELECT query like this:
SELECT je.id, je.title, je.longitude, je.latitude,
(SELECT field_value FROM joomgalaxy_entries_data WHERE fieldid = 1 AND entry_id = je.id) AS websiteurl,
(SELECT field_value FROM joomgalaxy_entries_data WHERE fieldid = 2 AND entry_id = je.id) AS occupation
FROM joomgalaxy_entries je;
First step is easy:
SELECT JE.id, JE.title, JE.longitude, JE.latitude
FROM joomgalaxy_entries JE
Now you need to JOIN:
SELECT JE.id, JE.title, JE.longitude, JE.latitude,
JD.*
FROM joomgalaxy_entries JE
JOIN joomgalaxy_entries_data JD
ON JE.id = JD.entry_id
Now you need convert rows to columns
SELECT JE.id, JE.title, JE.longitude, JE.latitude,
MIN(CASE WHEN fieldid = 1 THEN JD.field_value END) as WebsiteUrl,
MIN(CASE WHEN fieldid = 2 THEN JD.field_value END) as Occupation
FROM joomgalaxy_entries JE
JOIN joomgalaxy_entries_data JD
ON JE.id = JD.entry_id
GROUP BY JE.id, JE.title, JE.longitude, JE.latitude
This depend on you only have two field for each entry, if number of field is dynamic you would need a different aproach.
This should work:
select id, title, longitude, latitude,
(select field_value from joomgalaxy_entries_data jed
where fieldid = (select id from joomgalaxy_fields
where field_name = 'websiteurl')
and jed.entry_id = je.id
) as websiteurl,
(select field_value from joomgalaxy_entries_data jed
where fieldid = (select id from joomlgalaxy_fields
where field_name = 'occupation')
and jed.entry_id = je.id) as occupation
from joomgalaxy_entries je;
Note that the reason to have a left join would be if either websiteurl or occupation were null, however, this solution should work in that case anyway.
Well, that certainly makes it a bit more difficult... :) Honestly, I'm not sure what you're asking is possible with a static sql query. I'm sure someone will speak up, however, if I'm wrong.
That said, I do have a few options you can try:
Option 1 - Generate the SQL Dynamically
Assuming this is mysql, if you execute the following SQL, it will generate the subqueries dynamically:
select concat('(select field_value from joomgalaxy_entries_data jed ',
'where fieldid = (select id from joomgalaxy_fields ',
'where field_name = ''', field_name, ''') ',
'and jed.entry_id = je.id) as ', field_name, ',')
from joomgalaxy_fields;
Take the result of that command, copy-paste it into a text editor and add the following at the beginning:
select id, title, longitude, latitude,
And the rest of this at the end:
from joomgalaxy_entries je;
Then run your new uber-query and go grab a cup of copy, lunch, or a good night's sleep depending on how much data is in your database.
Alternatively, you could add all of this to a stored procedure so you don't have to hand edit the SQL. Also, note that my syntax works for MySQL. Other databases have different concatenation operators so you may have to work around that if applicable. Also, with 50+ subqueries there is a good chance this uber-query will be quite slow, maybe too slow to make this option viable.
Option 2 - Create a table structured the way you want, and populate it
Hopefully, this is self-explanatory, but just create a new table with all of the necessary columns from the joomgalaxy_fields table. Then populate each column separately with a long series of what should be pretty straightforward sql commands. Granted this option is only viable if the database is no longer in use which I believe you indicated. From there the result is just:
select * from my_new_table;
I have a typical user table in addition to the following feature table
features:
-----------------------
| userId | feature |
-----------------------
| 1 | account |
| 1 | hardware |
| 2 | account |
| 3 | account |
| 3 | hardware |
| 3 | extra |
-----------------------
Basically I am trying to get some counts for reporting purposes. In particular, I am trying to find the number of users with accounts and hardware along with the total number of accounts.
I know I can do the following to get the total number of accounts
SELECT
COUNT(DISTINCT userId) as totalAccounts
FROM features
WHERE feature = "account";
I am unsure as to how to get the number of users with both accounts and hardware though. In this example dataset, the number I am looking for is 2. Users 1 and 3 have both accounts and hardware.
I would prefer to do this in a single query. Possibly using CASE (example for totalAccounts below):
SELECT
COUNT(DISTINCT(CASE WHEN feature = "account" THEN userId END)) as totalAccounts,
COUNT( ? ) as accountsWithHardware
FROM features;
These are two queries - one for the all user count, one for the two-features user count - that you can combine with a cross join:
select
count_all_users.cnt as all_user_count,
count_users_having_both.cnt as two_features_user_count
from
(
select count(distinct userid) as cnt
from features
) count_all_users
cross join
(
select count(*) as cnt
from
(
select userid
from features
where feature in ('account', 'hardware')
group by userid
having count(*) = 2
) users_having_both
) count_users_having_both;
UPDATE: With some thinking, there is a much easier way. Group by user and detect whether feature 1 and feature 2 exists. Then count.
select
count(*) as all_user_count,
count(case when has_account = 1 and has_hardware = 1 then 1 end)
as two_features_user_count
from
(
select
userid,
max(case when feature = 'account' then 1 else 0 end) as has_account,
max(case when feature = 'hardware' then 1 else 0 end) as has_hardware
from features
group by userid
) users;
Once again i need yours help ;). I have a lot data and mysql request are slower and slower so the need request that i need i want group in one comand.
My example DB structure:
|product|opinion (pos/neg)|reason|
__________________________________
|milk | pos | good |
|milk | pos |fresh |
|chocolate| neg | old |
|milk | neg | from cow|
So i need information about all diffrent product (GROUP BY) count of it, and count of pos opinion for each product. I want output like that:
|product|count|pos count|
_________________________
|milk | 3 | 2 |
|chocolate| 1 | 0 |
I hope that my explain was good enought ;)
Or go to work: I write two commands
SELECT COUNT(*) as pos count FROM table WHERE product = "milk" AND opinion = "pos" GROUP BY `opinion`
And Second one
SELECT product, COUNT(*) FROM table GROUP BY `product`
I don't know how to join this two request, maybe that is impossible? In my real use code i have additional category collumn and i use WHERE in second command too
select product,
count(*) as total_count
sum(
case when opinion='pos' then 1
else 0 end
) as pos_count
from the_table
group by product;
SELECT product,
COUNT(*) TotalCount,
SUM(opinion = 'pos') POSCount
FROM tableName
GROUP BY product
SUM(opinion = 'pos') is MySQL specific syntax that counts the total of result based from the result of boolean arithmethic. If you want it to be more RDBMS friends, use CASE
SUM(CASE WHEN opinion = 'pos' THEN 1 ELSE 0 END)
SQLFiddle Demo
I have table like this:
+----+---------+---------+--------+
| id | value_x | created | amount |
+----+---------+---------+--------+
value_x is set of six strings, lets say "one", "two", "three", etc.
I need to create report like this:
+--------------+-------------------------+-------------------+----------------------+
| day_of_month | "one" | "two" | [etc.] |
+--------------+-------------------------+-------------------+----------------------+
| 01-01-2011 | "sum(amount) where value_x = colum name" for this specific day |
+--------------+-------------------------+-------------------+----------------------+
Most obvious solution is:
SELECT SUM(amount), DATE(created) FROM `table_name` WHERE value_x=$some_variable GROUP BY DATE(created)
And loop this query six times with another value for $some_variable in every iteration, but I'm courious if is it possible to do this in single query?
What you're asking is called a "pivot table" and is typically achieved as below. The idea is for each potential value of value_x you either produce a 1 or 0 per row and sum 1's and 0's to get the sum for each value.
SELECT
DATE(created),
SUM(CASE WHEN value_x = 'one' THEN SUM(amount) ELSE 0 END) AS 'one',
SUM(CASE WHEN value_x = 'one' THEN SUM(amount) ELSE 0 END) AS 'two',
SUM(CASE WHEN value_x = 'one' THEN SUM(amount) ELSE 0 END) AS 'three',
etc...
FROM table_name
GROUP BY YEAR(created), MONTH(created), DAY(created)
This will come close:
SELECT
s.day_of_month
,GROUP_CONCAT(CONCAT(s.value_x,':',s.amount) ORDER BY s.value_x ASC) as output
FROM (
SELECT DATE(created) as day_of_month
,value_x
,SUM(amount) as amount
FROM table1
GROUP BY day_of_month, value_x
) s
GROUP BY s.day_of_month
You will need to read the output and look for the value_x prior to the : to place the items in the proper column.
The benefit of this approach over #Michael's approach is that you do not need to know the possible values of field value_x beforehand.
i have the following table Students:
id | status | school | name
----------------------------
0 | fail | skool1 | dan
1 | fail | skool1 | steve
2 | pass | skool2 | joe
3 | fail | skool2 | aaron
i want a result that gives me
school | fail | pass
---------------------
skool1 | 2 | 0
skool2 | 1 | 1
I have this but it's slow,
SELECT s.school, (
SELECT COUNT( * )
FROM school
WHERE name = s.name
AND status = 'fail'
) AS fail, (
SELECT COUNT( * )
FROM school
WHERE name = s.name
AND status = 'pass'
) AS pass,
FROM Students s
GROUP BY s.school
suggestions?
Something like this should work:
SELECT
school,
SUM(CASE WHEN status = 'fail' THEN 1 ELSE 0 END) as [fail],
SUM(CASE WHEN status = 'pass' THEN 1 ELSE 0 END) as [pass]
FROM Students
GROUP BY school
ORDER BY school
EDIT
Almost forgot, but you could also write the query this way:
SELECT
school,
COUNT(CASE WHEN status = 'fail' THEN 1 END) as [fail],
COUNT(CASE WHEN status = 'pass' THEN 1 END) as [pass]
FROM Students
GROUP BY school
ORDER BY school
I'm not sure if there's any performance benefit with second query. My guess would be if there is it's probably very small. I tend to use the first query because I think it's more clear but both should work. Also, I don't have a MySql instance handy to test with, but according to #Johan the ORDER BY clauses are unnecessary.
SELECT q.school, q.fail, q.failpass-q.fail as pass
FROM
(
SELECT s.school, sum(if(status = 'fail',1,0)) as fail, count(*) as failpass
FROM students s
GROUP BY s.school
) q
This way you save one conditional sum.
In MySQL a GROUP BY already orders the results, so a separate ORDER BY is not needed.