Inserting columns with a case statement - mysql

I have an issue where I would like to look for statements in a single column and each time I find them insert a new column into the mix. Im using a mysql database.
For example say I have data like this
Class Sub
-----------------
1 math
1 tech
2 math
2 english
3 math
I would like the data to be output like this:
Class math tech english
---------------------------
1 Y Y N
2 Y N Y
3 Y N N
I am trying to use CASE statements to find the values in the column, but the problem is that it will only return one result in the column for the value it finds, and i end up getting the same class repeated with a case statement for each column. Combining the case statements wont work as that still gives me a single column.

You need to wrap the CASE expression in an aggregate and add a GROUP BY. In this case MAX will work as alphabetically Y comes after N
(SQL Fiddle Demo)
SELECT class,
MAX(CASE
WHEN ( community_id = 'Math' ) THEN 'Y'
ELSE 'N'
END) AS Math,
MAX (CASE
WHEN ( community_id = 'tech' ) THEN 'Y'
ELSE 'N'
END) AS tech,
MAX (CASE
WHEN ( community_id = 'english' ) THEN 'Y'
ELSE 'N'
END) AS english
FROM x_class_community
GROUP BY class

You need to be using PIVOT functionality. You haven't specified your DBMS, however here is a link for SQL Server: Using PIVOT and UNPIVOT

If you are using SQL Server then you can use the PIVOT function:
select class,
isnull(math, 'N') math,
isnull(tech, 'N') tech,
isnull(english, 'N') english
from
(
SELECT class, community_id, 'Y' as flag
FROM x_class_community
) src
pivot
(
max(flag)
for community_id in (math, tech, english)
) piv
See SQL Fiddle with Demo
The result is:
| CLASS | MATH | TECH | ENGLISH |
---------------------------------
| 1 | Y | Y | N |
| 2 | Y | N | Y |
| 3 | Y | N | N |

Related

MySQL select query with AND condition on same columns of same table

I have a table like this
itemid | propertyname | propertyvalue
___________|______________|_______________
1 | point | 12
1 | age | 10
2 | point | 15
2 | age | 11
3 | point | 9
3 | age | 10
4 | point | 13
4 | age | 11
I need a query to select all items where age greater than 10 and point less than 12.
I tried
`select itemid from table where (propertyname="point" and propertyvalue < 12)
and (propertyname="age" and propertyvalue >10)`
It gives no results. How can I make it work?
you can use an inner join
SELECT
a.itemid
FROM
yourTable a
INNER JOIN
yourTable b
ON
a.itemid=b.itemid
AND a.propertyname='point'
AND b.propertyname='age'
WHERE
a.propertyvalue<12
AND b.propertyvalue>10
ok so in table a youre lookin for all items with the name point and a value smaller 12 and in table b youre looking for all items with the name age and a value greater 10. Then you only have to look for items, which are in both tables. For this you connect the two tables over the itemid. To connect tables you use the join. Hope this will help you to understand. If not ask again :)
To join a table to itself in the same query you can include the table twice in the FROM clause, giving it a different alias each time. Then you simply proceed with building your query as if you were dealing with two separate tables that just happen to contain exactly the same data.
In the query below the table example is aliased as a and b:
SELECT a.itemid
FROM example a, example b
WHERE a.itemid = b.itemid
AND a.propertyname = 'point'
AND a.propertyvalue < 12
AND b.propertyname = 'age'
AND b.propertyname > 10
Try It:
SELECT itemid FROM test_table WHERE propertyname="point" AND propertyvalue < 12 AND itemid IN(SELECT itemid FROM test_table WHERE propertyname="age" AND propertyvalue >10)
http://sqlfiddle.com/#!9/4eafc6/1
PLs Try this
select itemid from table where (propertyname="point" and propertyvalue < 12)
or (propertyname="age" and propertyvalue >10);
Here's one idea...
SELECT item_id
, MAX(CASE WHEN propertyname = 'point' THEN propertyvalue END point
, MAX(CASE WHEN propertyname = 'age' THEN propertyvalue END age
FROM a_table
GROUP
BY item_id
HAVING age+0 > 10
AND point+0 < 12;
You can use an inner join. Meaning, it's like you're going to work with 2 tables: the first one you're going to select the name="age" and val>10, and the second one is where you're going to select name="point" and val<12.
It's like you're creating an instance of your table that doesn't really exist. It's just going to help you extract the data you need at the same time.

Group by, converting rows to columns with no aggregate

I need to group the table during my query, and up until know I was doing that after querying - with the code as my group is quite complicated. But with the new data it appears to take minutes, and I'm thinking is there better way.
My current query results in this:
FKId | Name | A | B | C
1 Alpha 2 3 2
1 Beta 2 5 7
2 Alpha 8 1 10
2 Beta 7 -5 6
2 Gamma 1 2 3
And I convert it to this:
FKId | Alpha[A] | Alpha[B] | Alpha[C] | Beta[A] | Beta[B] | Beta[C] | Gamma[A] | Gamma[B] | Gamma[C]
1 2 3 2 2 5 7
2 8 1 10 7 -5 6 1 2 3
Is it possible to do with SQL? (and I assume it should be much faster than if I do this with code)
The names can be anything
I have very big number of colums A, B, C (like 20 - 30). The number of result columns can easily go to thousands as average project has about 100 names.
I have like 10-20 columns that I should group by, but doing a single group by FKId is fine - these columns are the same.
We use different SQL DBs, so I cannot use specific functions like PIVOT. I know that we used MySQL, MsSQL and SQLite a lot
We use NHibernate if it makes any difference.
I would also honor the solution done for MySQL if specific functions are used. We use it in 80% and it will already greatly improve the average performance if I could do that at least for MySQL.
Basically, you want to transpose the data. Here is what you can try. It should work across all databases but you need to know the columns A, B, C, etc beforehand:
create table my_table (
fkid integer,
name varchar(10),
a integer,
b integer,
c integer
);
insert into my_table values(1,'alpha',2,3,2)
,(1,'beta',2,5,7)
,(2,'alpha',8,1,10)
,(2,'beta',7,-5,6)
,(2,'gamma',1,2,3);
select fkid
, max(case when name = 'alpha' then a else null end) as alphaa
, max(case when name = 'alpha' then b else null end) as alphab
, max(case when name = 'alpha' then c else null end) as alphac
, max(case when name = 'beta' then a else null end) as betaa
, max(case when name = 'beta' then b else null end) as betab
, max(case when name = 'beta' then c else null end) as betac
, max(case when name = 'gamma' then a else null end) as gammaa
, max(case when name = 'gamma' then b else null end) as gammab
, max(case when name = 'gamma' then c else null end) as gammac
from my_table
group by fkid;

SQL query to pivot table on column value

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.

MySQL - One SQL request instead two (both request include diffrent COUNT(*)

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

Counting partial values or conditional values

I hope someone can lend an assist and some advise here. I'm trying to get a fairly complex result and not sure if I can do it as one query with subqueries, a union, or simply separate queries to be merged into excel after the fact.
I'm working with a legacy database from my predecessor with the following tables:
Business (columns working with: id, sector, state)
Forms (columns working with: Submitted (Y/N), id, business_id)
Inventory (Columns working with: In_stock (Y/N), id, form_id)
I'm trying to get a final result that looks like this:
| SubmittedForms | Unsubmitted Forms | Sector | State |
|-----------------------------------------------------|
| 10 | 5 | Agr | UT |
| 0 | 7 | Chem | MT |
| 2 | 1 | Bio | OK |
| 13 | 0 | Chem | NM |
The main problem I'm getting is that while submitted forms doesn't need any further arguments and is a simple count, the unsubmitted forms are dependent on the Inventory.in_stock='Y'. Here's my query for the submitted forms:
SELECT COUNT(Forms.id) AS Submitted, Business.sector, Business.state
FROM Forms
JOIN Business ON Forms.business_id=Business.id
WHERE Forms.submitted='Y'
GROUP BY Business.state, Business.sector
Unfortunately, I can't seem to get the unsubmitted forms number to calculate correctly. It just returns the total count of rows where in_stock is Y for that sector.
If it's easier to run a separate query for Submitted and Unsubmitted that's fine for the end result but I need some help getting the correct count of Unsubmitted forms with the in_stock flagged as Y. Also, I attempted to use a COUNT DISTINCT but takes way too long, was still running after 10 minutes. Another complication I can envision in a single query option is the possibility of 0/null values in either Submitted or Unsubmitted forms
Any help is greatly appreciated!
One option:
SELECT COUNT(CASE WHEN Forms.submitted = 'Y' THEN 1 END) SubmittedForms,
COUNT
( CASE WHEN Forms.submitted = 'N'
AND EXISTS ( SELECT 1
FROM Inventory
WHERE form_id = Forms.id
AND in_stock = 'Y'
)
THEN 1
END
) UnsubmittedForms,
Business.sector Sector,
Business.state State
FROM Forms
RIGHT
OUTER
JOIN Business
ON Forms.business_id = Business.id
GROUP
BY Business.sector,
Business.state
;
Another option, which might perform better:
SELECT COUNT(CASE WHEN Forms.submitted = 'Y' THEN 1 END) SubmittedForms,
COUNT(CASE WHEN Forms.submitted = 'N' THEN 1 END) UnsubmittedForms,
Business.sector Sector,
Business.state State
FROM ( SELECT *
FROM Forms
WHERE submitted = 'Y'
OR id IN ( SELECT DISTINCT form_id
FROM Inventory
AND in_stock = 'Y'
)
) Forms
RIGHT
OUTER
JOIN Business
ON Forms.business_id = Business.id
GROUP
BY Business.sector,
Business.state
;