This has probably been covered before, but my skill level with SQL is low enough that I'm not even sure how to properly search for what I want!
I think what I want to do is relatively simple, and it seems that using pivoting might be the solution, but I'm really not sure about the syntax for that.
I have a SELECT query which can return data in this format:
TeamID | PlayerName
--------+-------------------
1 | Arthur Schiller
1 | Dimitre Rogatchev
1 | Mohamed Hechmeh
1 | Santosh Pradhan
2 | Adriano Ferrari
2 | Amanda Compagnone
2 | Danail Delchev
2 | David Bergin
I want to create columns from the ID with rows filled with the appropriate names, like this:
1 | 2
------------------+-------------------
Arthur Schiller | Adriano Ferrari
Dimitre Rogatchev| Amanda Compagnone
Mohamed Hechmeh | Danail Delchev
Santosh Pradhan | David Bergin
The purpose is to use the returned data in a php mysqli_fetch_assoc call to display the two columns on a website.
Thanks in advance for any help!
Unfortunately MySQL doesn't support windowing functions to generate a unique value for each of those rows per team. You can create a derived "row number" using variables or you could use a correlated subquery similar to:
select
max(case when teamid = 1 then playername else '' end) Team1,
max(case when teamid = 2 then playername else '' end) Team2
from
(
select TeamId,
PlayerName,
(select count(*)
from yourtable d
where t.teamId = d.TeamId
and t.playername <= d.PlayerName) rn
from yourtable t
) d
group by rn;
See SQL Fiddle with Demo.
Note: depending on the size of your data you might have some performance issues. This works great with smaller datasets.
you cannot do this with a pivot because there is no common ground to pivot off of.. what you can do is make a row count that you join on for the second team.. try this
SELECT t.playername as '1', f.playername as '2'
FROM
( SELECT #a := #a + 1 as id, playername
FROM players
WHERE teamid = 1
)t
LEFT JOIN
( SELECT #b := #b +1 as id , playername
FROM players
WHERE teamid = 2
)f ON f.id = t.id
CROSS JOIN (SELECT #a :=0, #b :=0)t1
DEMO
This will be much easier to do in the PHP code rather than trying to do it in the SQL. Just perform the query and then loop through the results adding them to an array for each team.
In general SQL is good for storing, associating, indexing and querying.
Massaging it into the right format you need to display is usually easier, cleaner and neater to do in code though.
Related
I have a table that contains id and country name, and I need to convert them so the id with more than 1 country will display in 1 row.I have been searching in this forum for over an hour and found nothing.
I tried if using the pivot function can help me to achieve the result i wanted, but I feel like using pivot does not work on my case here.
This is a mini version of the table I have. The number of distinct value in the field "country" will be over 100, so I can just say something like when county = '..' as this will be to repetitive.
enter code here
+----+--------+
| id | country|
+----+--------+
| 1 | US |
| 1 | UK |
| 2 | JP |
+----+--------+
Desired outcome I am looking for:
enter code here
+----+-----------+-----------+
| id | country_1 | country_2 |
+----+-----------+-----------+
| 1 | US | UK |
| 2 | JP | null |
+----+-----------+-----------+
I found this question which is similar but it is the opposite of what I am trying to achieve.
MySQL statement to pivot table without using pivot function or a union
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
update:
Thank you so much for all of your helps. I may not have used the queries of yours to solve my problem - as of the fact that the syntax is a bit diff running in snowflake. However, I got the insights I need from all of you.
here is my solution:
enter code here
select t1.id,
max(iff(t1.row_number = 1, t1.country ,null)) as country_1,
max(iff(t1.row_number = 2, t1.country ,null)) as country_2,
max(iff(t1.row_number = 3, t1.country, null)) as country_3
from
(
select id, country, row_number() over (partition by id order by id ) as
row_number
from table
) t1
group by t1.id
Whereas you could do it with "pivoting", what will happen when you have 3 countries? Or 4? Or 17?
May I suggest this:
SELECT id,
GROUP_CONCAT(country)
FROM tbl
GROUP BY id;
You will get something like:
1 US,UK
2 JP
use aggregation
select id, max(case when id=1 then country end ) as country_1,
max(case when id=2 then country end ) as country_2
from tbale group by id
As you comment on #Rick answer you have max 3 country for each id then you can use this
select
id,
(select country from test where test.id=t.id limit 0,1)as country_1,
(select country from test where test.id=t.id limit 1,1)as country_2,
(select country from test where test.id=t.id limit 2,1)as country_3
from test as t
group by id;
DEMO
You can try this following script with RowNumber generated per id. As you confirmed there are maximum 3 country per id, we can easily generate your desired result set by handling RowNumber 1,2 & 3
SELECT ID,
MAX(CASE WHEN RowNumber = 1 THEN country ELSE NULL END) Country_1,
MAX(CASE WHEN RowNumber = 2 THEN country ELSE NULL END) Country_2,
MAX(CASE WHEN RowNumber = 3 THEN country ELSE NULL END) Country_3
FROM
(
SELECT id,
country,
#row_num :=IF(#prev_value = concat_ws('',id),#row_num+1,1)AS RowNumber
,#prev_value := concat_ws('',id)
FROM tbale
ORDER BY id
)A
GROUP BY id
There's no "dynamic" PIVOT in SQL. You need to specify the list of columns when writing the query. Your options are:
If you know the number of columns in advance, then #ZaynulAbadinTuhin solution is the easier. It seems, however, this is not your case.
If you don't know the number of columns in advance and you want them all concatenated in a single column, then #Rick James solution is the best.
Otherwise, you can still use some kind of dynamic SQL in your app or in a stored procedure that will build the SQL query at runtime, based on the existing values of the table. But this solution would require much more programming. It's not a single/simple SQL query anymore. See Rick James's Pivoting in MySQL stored procedure.
I have a table like this:
userid | trackid | path
123 70000 ad
123 NULL abc.com
123 NULL Apply
345 70001 Apply
345 70001 Apply
345 NULL Direct
345 NULL abc.com
345 NULL cdf.com
And I want a query like this. When path='abc.com', num_website +1; when path='Apply', num_apply +1
userid | num_website | num_Apply | num_website/num_Apply
123 1 1 1
345 1 2 0.5
My syntax looks like this:
select * from
(select userid,count(path) as is_CWS
from TABLE
where path='abc.com'
group by userid
having count(path)>1) a1
JOIN
(select userid,count(userid) as Apply_num from
where trackid is not NULL
group by userid) a2
on a1.userid=a2.userid
My question is
1. how to have the field num_website/num_apply in term of my syntax above?
2. is there any other easier way to get the result I want?
Any spots shared will appreciate.
The simplest way to do it would be to change the select line:
SELECT a1.userid, a1.is_CWS, a2.Apply_num, a1.is_CWS/a2.Apply_num FROM
(select userid,count(path) as is_CWS
from TABLE
where path='abc.com'
group by userid
having count(path)>1) a1
JOIN
(select userid,count(userid) as Apply_num
from TABLE
where trackid is not NULL
group by userid) a2
on a1.userid=a2.userid
and then continue with the rest of your query as you have it. The star means "select everything." If you wanted to select only a few things, you would just list those things in place of the star, and if you wanted to select some other values based on those things, you would put those in the stars as well. In this case a1.is_CWS/a2.Apply_num is an expression, and MySql knows how to evaluate it based on the values of a1.is_CWS and a2.Apply_num.
In the same vein, you can do a lot of what those subqueries are doing in a single expression instead of a subquery. objectNotFound has the right idea. Instead of doing a subquery to retrieve the number of rows with a certain attribute, you can select SUM(path="abc.com") as Apply_num and you don't have to join anymore. Making that change gives us:
SELECT a1.userid,
SUM(path="abc.com") as is_CWS,
a2.Apply_num,
is_CWS/a2.Apply_num FROM
TABLE
JOIN
(select userid,count(userid) as Apply_num
FROM TABLE
where trackid is not NULL
group by userid) a2
on a1.userid=a2.userid
GROUP BY userid
Notice I moved the GROUP BY to the end of the query. Also notice instead of referencing a1.is_CWS I now reference just is_CWS (it's no longer inside the a1 subtable so we can just reference it)
You can do the same thing to the other subquery then they can share the GROUP BY clause and you won't need the join anymore.
to get you started ... you can build on top of this :
select
userid,
SUM(CASE WHEN path='abc.com'then 1 else 0 end ) as num_website,
SUM(CASE WHEN path='Apply' and trackid is not NULL then 1 else 0 end ) as Apply_Num
from TABLE
WHERE path='abc.com' or path='Apply' -- may not need this ... play with it
group by userid
Let's take this table for an example...
m_tid | m_tid2 | m_hteam_score | m_ateam_score
2 5 69 30
5 2 0 5
I'm bad at custom making tables, sorry...
So let's take this data, now m_tid and m_tid2 are columns for TID's that are in a separate table of their own.
Now what I want to do, is collect the score for team id2 (or team id1) of all the scores... How would I count two columns for whether or not the team is on m_tid and m_tid2
I don't have a query made, but I wouldn't know how I would go about making a query for this anyways. :(
The expected results would be something like this
m_tid | m_tid_score | m_tid2 | m_tidscore2
5 35 2 69
If you want to get the total score for each team, here is one method using correlated subqueries:
select t.*,
(coalesce((select sum(s.m_hteam_score) from scores s where s.m_tid = t.tid), 0) +
coalesce((select sum(s.m_ateam_score) from scores s where s.m_tid2 = t.tid), 0)
) as totalscore
from teams t;
Here's another option using conditional aggregation:
select o.id, sum(case when y.m_tid = o.id then y.m_hteam_score
when y.m_tid2 = o.id then y.m_ateam_score
else 0 end) score
from othertable o
join yourtable y on o.id in (y.m_tid, y.m_tid2)
group by o.id
I have an SQL table setup similar to this:
name | subject |
-------+----------+
Harry | Painting |
Sandra | Soccer |
Sandra | English |
How can I write a select statement that merges the rows if they have multiple subject, so it would output a result like this:
name | subject 1 | subject 2 |
-------+------------+------------+
Harry | Painting | |
Sandra | Soccer | English |
You shouldn't. The best approach is to join the tables so you have:
Harry, Painting
Sandra, Soccer
Sandra, English
And then process these rows in your scripting language (PHP etc) to turn it into the hierarchical data structure you desire
In your example above, what would happen when there's 3 subjects per person, 10, 100, etc.
SQL only really works with two dimensional data. For three dimensions you either need to pre/post process as i've suggested, or move to something like NoSQL mongoDB that deals with structured objects instead of table rows.
Since you mentioned that the maximum number of subjects is only 2, you can therefore, generate a sequential number for each name and used that to pivot the columns.
SELECT Name,
MAX(CASE WHEN rn = 1 THEN Subject END) Subject1,
MAX(CASE WHEN rn = 2 THEN Subject END) Subject2
FROM
(
SELECT A.name,
A.subject,
(
SELECT COUNT(*)
FROM tableName c
WHERE c.name = a.name AND
c.subject <= a.subject) AS rn
FROM TableName a
) aa
GROUP BY Name
SQLFiddle Demo
The above is an SQL way.
You need a PIVOT routine. Serach for this in the engine of your choice.
Some RDBMSs have this built in. There is an alternative using a CASE statement in the SELECT clause, for which there are many blog posts out there.
You can accomplish this in one statement using a combination of substring_index() and group_concat() like this (SQLfiddle)
SELECT DISTINCT s.name,
substring_index(p.subject_list, ',', 1) AS "subject_1",
IF(instr(p.subject_list, ','),
substring_index(p.subject_list, ',', -1),
NULL
) AS "subject_2"
FROM subjects s
JOIN (SELECT name, GROUP_CONCAT(subject) AS "subject_list"
FROM subjects
GROUP BY name
) p on p.name = s.name
;
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