SQL group by into a pivot - sql

I can do this on my table:
SELECT country, COUNT(*)
FROM table1
GROUP BY country, type
This query gives me
country type COUNT(*)
Canada first 22
Canada second 42
Canada third 15
Australia second 23
Australia third 18
but I need to get
country first second third
Canada 22 42 15
Australia 23 18 0
Complexity is, 3 columns here just an example; and have about 20 different; and I have over 200 countries... I have found something like this https://dba.stackexchange.com/questions/28406/group-by-two-columns?newreg=febbf51c648e4c17a2ebcb798bff1261, but # of columns I'd end up is rendering this approach infeasible.
Any thoughts?

You would use conditional aggregation:
select country,
sum(case when type = 'first' then 1 else 0 end) as cnt_first,
sum(case when type = 'second' then 1 else 0 end) as cnt_second,
. . .
from t
group by country;
You may find that generating the sum(case) expressions is more easily done in a spreadsheet then typing them out (although copy and paste is really pretty simple for 20 rows).

If you are using MySQL then you already know the answer from the link.
In SQL server you can do this.
SELECT country, [First],[Second],[Third]
FROM
(
SELECT country, [type],COUNT(*)cnt
FROM table1
GROUP BY country, [type]
) AS SourceTable
PIVOT
(
max(cnt)
FOR [type] IN ([First],[Second],[Third])
) AS PivotTable;
And please share your table structure with sample data for better understanding of your problem.

The pivoting style for a select statement is closely related with the DBMS been used. Presuming the DBMS you're using as MySQL depending on the shared link within the question, use
SELECT country, SUM(type='first') AS first,
SUM(type='second') AS second,
SUM(type='third') AS third
FROM table1
GROUP BY country
as a static query, and that could be converted to the following code block in order to make it dynamic
SET #sql = NULL;
SELECT GROUP_CONCAT(
CONCAT(
'SUM(type="',type,'" ) AS ',type
)
)
INTO #sql
FROM ( SELECT DISTINCT type FROM table1 ) AS t;
SET #sql = CONCAT('SELECT country,',#sql,' FROM table1 GROUP BY country');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Demo

Related

Dynamic transpose for unknown row value into column name on postgres

I have table such:
customer_number
label
value
1
address
St. John 1A
1
phone
111111111
1
email
john#cena.com
2
address
St. Marry 231A
2
phone
222222222
2
email
please#marry.me
I want new table or view so it's become:
customer_number
address
phone
email
1
St. John 1A
111111111
john#cena.com
2
St. Marry 231A
222222222
please#marry.me
but in the future there are possibility to add different label, for example there might be new label called occupation.
Important to note, I don't know the value of the label column, so it's should iterate to any value inside that column.
Is there any way to do this?
You can't have a "dynamic" pivot as the number, names and data types of all columns of a query must be known to the database before the query is actually executed (i.e. at parse time).
I find aggregating stuff into a JSON easier to deal with.
select customer_number,
jsonb_object_agg(label, value) as props
from the_table
group by customer_number
If your frontend can deal with JSON values directly, you can stop here.
If you really need a view with one column per attribute, you can them from the JSON value:
select customer_number,
props ->> 'address' as address,
props ->> 'phone' as phone,
props ->> 'email' as email
from (
select customer_number,
jsonb_object_agg(label, value) as props
from the_table
group by customer_number
) t
I find this a bit easier to manage when new attributes are added.
If you need a view with all labels, you can create a stored procedure to dynamically create it. If the number of different labels doesn't change too often, this might be a solution:
create procedure create_customer_view()
as
$$
declare
l_sql text;
l_columns text;
begin
select string_agg(distinct format('(props ->> %L) as %I', label, label), ', ')
into l_columns
from the_table;
l_sql :=
'create view customer_properties as
select customer_number, '||l_columns||'
from (
select customer_number, jsonb_object_agg(label, value) as props
from the_table
group by customer_number
) t';
execute l_sql;
end;
$$
language plpgsql;
Then create the view using:
call create_customer_view();
And in your code just use:
select *
from customer_properties;
You can schedule that procedure to run in regular intervals (e.g. through a cron job on Linux)
Generally-speaking SQL is not good at pivotting dynamically.
Here is a query that will pivot the data for you. However, it is not dynamic i.e. if a future occupation label was added then you would have to change the query. Not sure whether that is acceptable or not :
select customer_number,
max(value) filter (where label='address') as address,
max(value) filter (where label='phone') as phone,
max(value) filter (where label='email') as email
from your_customer_table
group by customer_number
Bit of an assumption that you are running Postgres 9.4 or better here so that the filter function is supported. If not then it can be re-worked using case statements :
select customer_number,
max(case when label='address' then value else null end) as address,
max(case when label='phone' then value else null end) as phone,
max(case when label='email' then value else null end) as email
from your_customer_table
group by customer_number
I used cross apply to solve this problem ..
Here is my query
select distinct tb9.customer_number, tb9_2.*
from Table_9 tb9 cross apply
(select max(case when tb9_2.[label] like '%address%' then [value] end) as [address],
max(case when tb9_2.[label] like '%phone%' then [value] end) as [phone],
max(case when tb9_2.[label] like '%email%' then [value] end) as [email]
from Table_9 tb9_2
where tb9.customer_number = tb9_2.customer_number
) tb9_2;

Stored procedure to count then divide a column

I don't exactly know how to title this question. But I am looking to create a stored procedure or procedures to create a new table with averages. I have 19 sites that I have collected survey data from. I want to count each column two but with two different conditions.
E.g.
SELECT COUNT(ColumnName)
FROM TableName
WHERE ColumnName = 3
SELECT COUNT(ColumnName)
FROM TableName
WHERE ColumnName = 4
From there I would like to add those two numbers together then divide by another count for another column in the table.
Basically I want to know how many surveys have the answer 3 and 4 then divide them by how many surveys were answered. Also keep in mind I want numbers based on each site.
Use group by:
select columnname
from tablename
where columnname in (3, 4)
group by columname;
You seems want :
select sum(case when col in (3,4) then 1 else 0 end) / count(*)
from table t
So I have gotten a bit closer to what I want trying to achieve but it is still not doing what I want it do. This is what I have come up with but I don't know how to get to divide by the sum. SELECT (SELECT COUNT() FROM Resident_Survey WHERE CanbealonewhenIwish = 3 and Village = 'WP' and Setting = 'LTC')+ (SELECT COUNT() FROM Resident_Survey WHERE CanbealonewhenIwish = 4 and Village = 'WP' and Setting = 'LTC')/ (SELECT COUNT(*) FROM Resident_Survey WHERE Village = 'WP' and Setting = 'LTC') AS ICanbealonewhenIwish
I figured it out. I was looking to create this query.
SELECT 100.0 *
COUNT(CASE WHEN Privacyisrespected IN (3,4)
THEN 1
ELSE NULL END) /
COUNT(*) AS Myprivacyisrespected
FROM Resident_Survey
WHERE Village = 'WP'
and Setting = 'LTC'

Group rows with similar strings

I have searched a lot, but most of solutions are for concatenation option and not what I really want.
I have a table called X (in a Postgres database):
anm_id anm_category anm_sales
1 a_dog 100
2 b_dog 50
3 c_dog 60
4 a_cat 70
5 b_cat 80
6 c_cat 40
I want to get total sales by grouping 'a_dog', 'b_dog', 'c_dog' as dogs and 'a_cat', 'b_cat', 'c_cat' as cats.
I cannot change the data in the table as it is an external data base from which I am supposed to get information only.
How to do this using an SQL query? It does not need to be specific to Postgres.
Use case statement to group the animals of same categories together
SELECT CASE
WHEN anm_category LIKE '%dog' THEN 'Dogs'
WHEN anm_category LIKE '%cat' THEN 'cats'
ELSE 'Others'
END AS Animals_category,
Sum(anm_sales) AS total_sales
FROM yourtables
GROUP BY CASE
WHEN anm_category LIKE '%dog' THEN 'Dogs'
WHEN anm_category LIKE '%cat' THEN 'cats'
ELSE 'Others'
END
Also this query should work with most of the databases.
By using PostgreSQL's split_part()
select animal||'s' animal_cat,count(*) total_sales,sum(anm_sales) sales_sum from(
select split_part(anm_cat,'_',2) animal,anm_sales from x
)t
group by animal
sqlfiddle
By creating split_str() in MySQL
select animal||'s' animal_cat,count(*) total_sales,sum(anm_sales) sales_sum from(
select split_str(anm_cat,'_',2) animal,anm_sales from x
)t
group by animal
sqlfiddle
You could group by a substr of anm_catogery:
SELECT SUBSTR(anm_catogery, 3) || 's', COUNT(*)
FROM x
GROUP BY anm_catogery
If you have a constant length of the appendix like in the example:
SELECT CASE right(anm_category, 3) AS animal_type -- 3 last char
, sum(anm_sales) AS total_sales
FROM x
GROUP BY 1;
You don't need a CASE statement at all, but if you use one, make it a "simple" CASE:
Simplify nested case when statement
Use a positional reference instead of repeating a possibly lengthy expression.
If the length varies, but there is always a single underscore like in the example:
SELECT split_part(anm_category, '_', 2) AS animal_type -- word after "_"
, sum(anm_sales) AS total_sales
FROM x
GROUP BY 1;

How to use Order By clause on a column containing string values separated by comma?

I have a table with a column named Skills which contains comma separated values for different employees like
EmpID Skills
1 C,C++,Oracle
2 Java,JavaScript,PHP
3 C,C++,Oracle
4 JavaScript,C++,ASP
5 C,C++,JavaScript
So I want to write a query which will order all the employees first who knows JavaScript, how can I get this result?
You should not use one attribute to store multiple values. That goes against relation DB principles.
Instead of that you should create additional table to store skills and refer to employee in it. Then, your query will looks like:
SELECT
*
FROM
employees
LEFT JOIN employees_skills
ON employee.id=employees_skills.employee_id
WHERE
employees_skills='JavaScript'
Try this
SELECT *
FROM
(
SELECT *
,CASE WHEN Skills LIKE '%JavaScript%' THEN 0 ELSE 1 END AS Rnk
FROM MyTable
) T
ORDER BY rnk,EmpID
DEMO
OR
SELECT * FROM #MyTable
ORDER BY CASE WHEN Skills LIKE '%JavaScript%' THEN 0 ELSE 1 END,EmpID
select EmpID, Skills
from Table1
order by case when Skills like '%JavaScript%' then 0 else 1 end
Try this:
SELECT *
FROM YourTable
ORDER BY PATINDEX('%JavaScript%', Skills) DESC
But this is a bad way. You should really normalize your table.
For MySQL
select Skills from myTable
order by case Skills
when "Javascript" then 0
when "Java" then 1 when "C++" then 2
end
and so on
For SQL Server
select Skills from myTable
order by case
when Skills="Javascript" then 1
when Skill="Java" then 2
else 3
end
Make sure to start SQL server from 1 (That I'm not sure).
Include an else before end that will show all remaining results.
For more details about SQL Server see this or see this
This works for DB2/400:
with s (id, skill, rest) as
(select id, '', sk from skills
union all
select id, substr(rest, 1, locate(',',rest)-1),
substr(rest,locate(',',rest)+1)
from s
where locate(',',rest) > 0)
select id, skill from s
where skill = 'JavaScript'
order by id

SQL Select Query With Count And Where Statement

I always seem to trip myself up during these types of SQL Statements. Here is what I'm attempting to accomplish.
My Example Table
Name Date Type
Bob 9/28/11 1
Bob 9/27/11 1
Bob 9/28/11 2
Debra 9/28/11 1
I'm trying to write a SQL Statement that would give me all the names, their total count occured, and then a Date Filter. I'll write a rought statement with completely wrong syntax, but I think it'll convey what I'm attempting to do...I think.
SELECT Name, Count(*) As Total
FROM Table
GROUP BY Name, Total
WHERE Date = Today (I'm aware you can't do a WHERE in a GROUP BY) AND Type = 1
Essentially, I would like to get back a set of data that would show the Name, how many instances of Type 1 for today.
I don't think I'm searching for the proper question to actually be able to effectively research this on my own as well, probably because I'm wording it improperly.
Any help would be appreciated!
Your WHERE was in the wrong place:
SELECT Name, Count(*) As Total
FROM Table
WHERE Date = Today AND Type = 1
GROUP BY Name
You just need to move your WHERE after your FROM.
SELECT Name, Count(*) As Total
FROM Table
WHERE [Date] = CAST(getdate() as Date) -- or 'Jan 1 1990'
AND [Type] = 1
GROUP BY Name
WHERE comes before GROUP
SELECT Name, Count(*) As Total
FROM Table
WHERE Date >= #Today --assuming your date passed in has no time
AND Date < DATEADD(d,1,#Today)
AND Type = 1
GROUP BY Name, Total
example you can run
SELECT LEFT(name,1) FROM sysobjects
WHERE name < 'p'
GROUP BY LEFT(name,1)