Transforming multiple rows into columns w. single row - sql

I need some help figuring out how best to transform an an array into a row-vector. My array looks like this:
+-----+-------+----------+
| ID | Grade | Quantity |
+-----+-------+----------+
| Ape | Water | Y |
| Ape | Juice | Y |
| Ape | Milk | Y |
+-----+-------+----------+
Each ID can have up to 4 rows distinguished only by grade (Water, Juice, Beer, Milk); the list of possible values is static.
My desired output is this:
+-----+----------+-------+-------+------+------+
| ID | Quantity | Water | Juice | Beer | Milk |
+-----+----------+-------+-------+------+------+
| Ape | Y | 1 | 1 | 0 | 1 |
+-----+----------+-------+-------+------+------+
My own efforts have carried me as far as the PIVOT-operator, which transforms Grade-values into columns, but it doesn't group the rows by ID, leaving me with an equal number of rows post-transformation.
SELECT ID, Quantity, Water, Juice, Beer, Milk
FROM
(SELECT ID, Grade, Quantity FROM Feeding WHERE ID = 'Ape') src
PIVOT(
COUNT(Quantity) FOR [Grade] IN (ID, Quantity, Water, Juice, Beer, Milk)
)AS TransformData
Output:
+-----+----------+-------+-------+------+------+
| ID | Quantity | Water | Juice | Beer | Milk |
+-----+----------+-------+-------+------+------+
| Ape | Y | 1 | 0 | 0 | 0 |
| Ape | Y | 0 | 1 | 0 | 0 |
| Ape | Y | 0 | 0 | 0 | 1 |
+-----+----------+-------+-------+------+------+
Any suggestions?

How about;
;WITH Feeding(id,grade,quantity) as (
select 'Ape','Water','Y' union all
select 'Ape','Juice','Y' union all
select 'Ape','Juice','Y' union all
select 'Ape','Juice','Y' union all
select 'Ape','Juice','Y' union all
select 'Ape','Milk', 'N'
)
SELECT * FROM
(SELECT ID, Grade, Quantity agg, Quantity FROM Feeding WHERE ID = 'Ape') src
PIVOT ( COUNT(agg) FOR [Grade] IN (Water, Juice, Beer, Milk) ) AS TransformData
--
ID Quantity Water Juice Beer Milk
Ape N 0 0 0 1
Ape Y 1 4 0 0

you can try following query:-
SELECT ID, Quantity, CASE WHEN Grade = 'WATER' THEN 1 ELSE 0 END AS WATER,
CASE WHEN Grade = 'JUICE' THEN 1 ELSE 0 END AS JUICE,
CASE WHEN Grade = 'BEER' THEN 1 ELSE 0 END AS BEER,
CASE WHEN Grade = 'MILK' THEN 1 ELSE 0 END AS MILK
FROM YOUR_TABLE;

select id, quantity,
case when grade = 'Water' then 1 else 0 end as Water,
when grade = 'Juice' then 1 else 0 end as Juice,
when grade = 'Milk' then 1 else 0 end as Milk,
when grade = 'Beer' then 1 else 0 end as Beer
from feeding
As the value list is static, this is a way to do it.

Try this
SELECT id
,quantity
,CASE
WHEN grade = 'Water'
THEN 1
ELSE 0
END AS Water
,when grade = 'Juice' then 1 ELSE 0 END AS Juice
,when grade = 'Milk' then 1 ELSE 0 END AS Milk
,when grade = 'Beer' then 1 ELSE 0 END AS Beer
FROM feeding

Try this to get single row result
SELECT Id,Quantity,
SUM(CASE WHEN Grade='Water' THEN 1 ELSE 0 END) AS Water,
SUM(CASE WHEN Grade='Juice ' THEN 1 ELSE 0 END) AS Juice ,
SUM(CASE WHEN Grade='Beer' THEN 1 ELSE 0 END) AS Beer,
SUM(CASE WHEN Grade='Milk' THEN 1 ELSE 0 END) AS Milk
FROM Feed F
GROUP BY Id,Quantity
SQL Fiddle Demo

Related

How to know arithmetic mean of two count values

I have table answers where I store information.
| EMPLOYEE | QUESTION_ID | QUESTION_TEXT | SELECTED_OPTION_ID | SELECTED_OPTION_TEXT |
|----------|-------------|------------------------|--------------------|----------------------|
| Mark | 1 | Do you like soup? | 1 | Yes |
| Kate | 1 | Do you like soup? | 1 | Yes |
| Jone | 1 | Do you like soup? | 2 | No |
| Kim | 1 | Do you like soup? | 3 | I don't know |
| Alex | 1 | Do you like soup? | 2 | No |
| Bond | 1 | Do you like soup? | 1 | Yes |
| Ford | 1 | Do you like soup? | 3 | I don't know |
| Mark | 2 | Do you like ice cream? | 2 | No |
| Kate | 2 | Do you like ice cream? | 1 | Yes |
| Jone | 2 | Do you like ice cream? | 1 | Yes |
| Kim | 2 | Do you like ice cream? | 1 | Yes |
| Alex | 2 | Do you like ice cream? | 2 | No |
| Bond | 2 | Do you like ice cream? | 1 | Yes |
| Ford | 2 | Do you like ice cream? | 3 | I don't know |
Formulas:
value_1 = (Number of users who answered "No" or "I don't know" to the first question) / (The total number of people who answered to the first question)
value_2 = (Number of users who answered "No" or "I don't know" to the second question) / (The total number of people who answered to the first question)
I can separately find the values according to the above formulas. For example value_1:
select
count(*)
from
answers
where
question_id = 1
and (
selected_option_id in (2, 3)
or
selected_option_text in ('No', 'I don\'t know')
)
My question is how to arithmetic mean of these 2 values correctly by one sql query?
In other words I need to find average value:
You could use a condition sum
select (sum( case when QUESTION_ID = 1 AND
SELECTED_OPTION_ID in ( 2,3) THEN 1 else 0 end )::float /
sum( case when QUESTION_ID = 1 then 1 else 0 end)::float )*100 first_question_rate,
(sum( case when QUESTION_ID = 2 AND
SELECTED_OPTION_ID in ( 2,3) THEN 1 else 0 end )::float /
sum( case when QUESTION_ID = 2 then 1 else 0 end)::float)*100 second_question_rate,
(( sum( case when QUESTION_ID = 1 AND SELECTED_OPTION_ID in ( 2,3) THEN 1 else 0 end )::float /
sum( case when QUESTION_ID = 1 then 1 else 0 end)::float +
sum( case when QUESTION_ID = 2 AND SELECTED_OPTION_ID in ( 2,3) THEN 1 else 0 end )::float /
sum( case when QUESTION_ID = 2 then 1 else 0 end) ::float)/2)*100 avg
from answer
Are you looking for something like below-
SELECT
SUM(CASE WHEN QUESTION_ID = 1 AND SELECTED_OPTION_TEXT <> 'Yes' THEN 1 ELSE 0 END)
/
SUM(CASE WHEN QUESTION_ID = 1 THEN 1 ELSE 0 END) value_1 ,
SUM(CASE WHEN QUESTION_ID = 2 AND SELECTED_OPTION_TEXT <> 'Yes' THEN 1 ELSE 0 END)
/
SUM(CASE WHEN QUESTION_ID = 2 THEN 1 ELSE 0 END) value_2
FROM answers
For getting average, please use the below script-
SELECT (A.value_1+A.value_2)/2.0
FROM
(
SELECT
SUM(CASE WHEN QUESTION_ID = 1 AND SELECTED_OPTION_TEXT <> 'Yes' THEN 1 ELSE 0 END)*1.0
/
SUM(CASE WHEN QUESTION_ID = 1 THEN 1 ELSE 0 END)*1.0 value_1 ,
SUM(CASE WHEN QUESTION_ID = 2 AND SELECTED_OPTION_TEXT <> 'Yes' THEN 1 ELSE 0 END)*1.0
/
SUM(CASE WHEN QUESTION_ID = 2 THEN 1 ELSE 0 END)*1.0 value_2
FROM answers
)A
I'm pretty sure you want conditional aggregation. I suspect you want:
select question_id,
count(*) filter (where selected_option_id in (2, 3)) as num_2_3,
avg( selected_option_id in (2, 3)::int ) as ratio_2_3
from answers
group by question_id;
For each question, this provides the number of answers that are 2 or 3 and the ratio of those answers to all answers.

Check box like functionality in table using query in SQL Server

I have a primary table with multiple records for the name but different quantity and colors.
TblPrimary: current table
id | name | color | Quan |
===+=========+=======+=======+
1 | Apple | Red | 10 |
2 | Banana | Yellow| 5 |
3 | Mango | Yellow| 8 |
4 | Apple | Green | 20 |
5 | Banana | Brown | 15 |
6 | Mango | Orange| 12 |
7 | Mango | Green | 5 |
This is my main table and I basically want data from the primary table like this. So basically the Quan in main table is sum of all individual Quan from the primary table. The colors (Red,Yellow,Brown) in the main table; are bits which indicate whether that color is present for the fruit or not in the primary table.
TblMain: new expected table
id | Name | Quan | Red | Yellow | Brown | Green | Orange |
===+=========+=======+=====+========+=======+=======+========+
1 | Apple | 30 | 1 | 0 | 0 | 1 | 0 |
2 | Banana | 20 | 0 | 1 | 1 | 0 | 0 |
3 | Mango | 25 | 0 | 1 | 0 | 1 | 1 |
I have got the below query and I have group by and the sum. I am not able to get the colors portion of the main table populated.
INSERT INTO TblMain(Name, Quan)
(SELECT Name, SUM(Quan)
FROM TblPrimary
GROUP BY Name)
You can try to use sum windows function in subquery then use condition aggregate function make your expect result.
select ROW_NUMBER() OVER(ORDER BY name) id,
name,
Quan,
SUM(CASE WHEN color = 'Red' THEN 1 ELSE 0 END) 'Red',
SUM(CASE WHEN color = 'Yellow' THEN 1 ELSE 0 END) 'Yellow',
SUM(CASE WHEN color = 'Brown' THEN 1 ELSE 0 END) 'Brown',
SUM(CASE WHEN color = 'Green' THEN 1 ELSE 0 END) 'Green',
SUM(CASE WHEN color = 'Orange' THEN 1 ELSE 0 END) 'Orange'
from (
SELECT name,
color,
SUM(Quan) OVER(PARTITION BY name ORDER BY name) Quan
FROM TblPrimary
) t1
group by name,Quan
sqlfiddle
Result
id name Quan Red Yellow Brown Green Orange
1 Apple 30 1 0 0 1 0
2 Banana 20 0 1 1 0 0
3 Mango 25 0 1 0 1 1
You can use PIVOT to convert values to columns. In your case this becomes a bit cumbersome, because you want the row sum as well as well as the 0/1 flags
SELECT
name,
ISNULL(Red,0) + ISNULL(Yellow,0) + ISNULL(Brown,0) + ISNULL(Green,0) + ISNULL(Orange,0) As Quan,
CASE WHEN Red IS NULL THEN 0 ELSE 1 END AS Red,
CASE WHEN Yellow IS NULL THEN 0 ELSE 1 END AS Yellow,
CASE WHEN Brown IS NULL THEN 0 ELSE 1 END AS Brown,
CASE WHEN Green IS NULL THEN 0 ELSE 1 END AS Green,
CASE WHEN Orange IS NULL THEN 0 ELSE 1 END AS Orange
FROM
(SELECT name, color, Quan
FROM dbo.TblPrimary) AS SourceTable
PIVOT
(
SUM(Quan)
FOR color IN (Red, Yellow, Brown, Green, Orange)
) AS PivotTable;
If you display the sums directly for the colors instead of the flags, the query simplifies to:
SELECT
name, Red, Yellow, Brown, Green, Orange
FROM
(SELECT name, color, Quan
FROM dbo.TblPrimary) AS SourceTable
PIVOT
(
SUM(Quan)
FOR color IN (Red, Yellow, Brown, Green, Orange)
) AS PivotTable;
And the result will be:
name Red Yellow Brown Green Orange
==========================================
Apple 10 null null 20 null
Banana null 5 15 null null
Mango null 8 null 5 12

SQL Subquery with a single table result with NULL

I have tried to select with sub queries in only a single table. but for some reason i got a NULL result.
The table I have look like this.
| id | ItemCode | ItemAmount | Counter |
-----------------------------------------
| 1 | 001 | 1 | Counter-1 |
| 2 | 001 | 1 | Counter-2 |
| 3 | 002 | 2 | Counter-1 |
| 4 | 002 | 2 | Counter-2 |
| 5 | 002 | 1 | Counter-2 |
I have tried this SQL :
select
id,
itemCode,
(select ItemAmount where Counter = 'Counter-1') as 'Count 1 Result',
(select Counter where Counter = 'COUNTER-1') as 'Count Is 1',
(select ItemAmount where Counter = 'Counter-2') as 'Count 2 Result',
(select Counter where Counter = 'COUNTER-2') as 'Count Is 2',
from
My_Table
and the result I got is :
| id | ItemCode | Count 1 Result | Count Is 1 | Count 2 Result | Count Is 2 |
----------------------------------------------------------------------
| 1 | 001 | 1 | Counter-1 | NULL | NULL |
| 2 | 001 | NULL | NULL | 1 | Counter-2 |
| 3 | 002 | 2 | Counter-1 | NULL | NULL |
| 4 | 002 | NULL | NULL | 2 | Counter-2 |
| 5 | 002 | NULL | NULL | 1 | Counter-2 |
As you can see, i got NULL result with the NULL Value. How can i do it with the result like this :
| id | ItemCode | Count 1 Result | Count Is 1 | Count 2 Result | Count Is 2 |
-------------------------------------------------------------------------
| 1 | 001 | 1 | Counter-1 | 2 | Counter-2 |
--------------------------------------------------------------------------
| 2 | 002 | 2 | Counter-1 | 3 | Counter-2 |
--------------------------------------------------------------------------
I want to make it no NULL value anymore and there is no double Item Code with the SUM Item Amount if the counter and item code is have the same value.
Is that even possible to do it with one table ? if it is how do i do that. thanks in advance
Try conditional aggregation, something like:
SELECT min(id) id,
itemcode,
sum(CASE
WHEN counter = 'Counter-1' THEN
itemamount
ELSE
0
END) count1result,
'Counter-1' countis1,
sum(CASE
WHEN counter = 'Counter-2' THEN
itemamount
ELSE
0
END) count2result,
'Counter-2' countis2
FROM my_table
GROUP BY itemcode;
You can try to use Aggregate function condition to make it.
Here is SQL-server sample:
CREATE TABLE My_Table(
id INT,
ItemCode VARCHAR(50),
ItemAmount INT,
Counter VARCHAR(50)
);
INSERT INTO My_Table VALUES (1, '001', 1 ,'Counter-1');
INSERT INTO My_Table VALUES (2, '001', 1 ,'Counter-2');
INSERT INTO My_Table VALUES (3, '002', 2 ,'Counter-1');
INSERT INTO My_Table VALUES (4, '002', 2 ,'Counter-2');
INSERT INTO My_Table VALUES (5, '002', 1 ,'Counter-2');
Query 1:
select
ROW_NUMBER() OVER(ORDER BY itemCode) id,
itemCode,
SUM(CASE WHEN Counter = 'Counter-1' THEN ItemAmount ELSE 0 END) as 'Count 1 Result',
MAX(CASE WHEN Counter = 'COUNTER-1' THEN Counter END) as 'Count Is 1',
SUM(CASE WHEN Counter = 'Counter-2' THEN ItemAmount ELSE 0 END) as 'Count 2 Result',
MAX(CASE WHEN Counter = 'COUNTER-2' THEN Counter END) as 'Count Is 2'
from
My_Table
GROUP BY
itemCode
Results:
| id | itemCode | Count 1 Result | Count Is 1 | Count 2 Result | Count Is 2 |
|----|----------|----------------|------------|----------------|------------|
| 1 | 001 | 1 | Counter-1 | 1 | Counter-2 |
| 2 | 002 | 2 | Counter-1 | 3 | Counter-2 |
Even though I'm not sure what you are expecting with out disturbing your code I have given the query. Implement in your requirement
;WITH CTE AS (select
itemCode,
(select SUM(ItemAmount) where Counter = 'Counter-1') as 'Count 1 Result',
(select MAX(Counter) where Counter = 'COUNTER-1') as 'Count Is 1',
(select SUM(ItemAmount) where Counter = 'Counter-2') as 'Count 2 Result',
(select MAX(Counter) where Counter = 'COUNTER-2') as 'Count Is 2'
from
My_table
GROUP BY
itemCode,Counter )
Select RANK()OVER ( ORDER BY itemcode)Id,
itemCode,
MAX([Count 1 Result])[Count 1 Result],
MAX([Count Is 1])[Count Is 1],
MAX([Count 2 Result])[Count 2 Result],
MAX([Count Is 2])[Count Is 2]
from CTE
GROUP BY itemCode
For the data retrieval part the following would give your data,
by grouping on ItemCode and Counter:
select
ItemCode,
Counter,
min(Id) as Id,
sum(ItemAmount) as ItemAmount,
from
My_Table
group by ItemCode, Counter
ItemCode Counter Id ItemAmount
======== ========= === ==========
001 Counter-1 1 1
001 Counter-2 1 2
002 Counter-1 3 1
002 Counter-2 3 3
To display that as so called pivot table, rows-to-columns, there are several solutions, one generic SQL solution:
In Modern SQL:
select ItemCode,
ItemAmount filter (where Counter = 'Counter-1') as A1,
ItemAmount filter (where Counter = 'Counter-2') as A2
from (select
ItemCode,
Counter,
min(Id) as Id,
sum(ItemAmount) as ItemAmount,
from
My_Table
group by ItemCode, Counter)
group by ItemCode
If filter is still not supported in the used SQL:
select ItemCode,
sum(case when Counter = 'Counter-1' then ItemAmount end) as A1,
sum(case when Counter = 'Counter-2' then ItemAmount end) as A2,

Find players who have all "win", "draw" and "loss" results

player1_id | score1 | score2 | player2_id
-----------+--------+--------+-----------
1 | 1 | 1 | 2
3 | 1 | 1 | 1
11 | 1 | 0 | 20
5 | 1 | 1 | 55
200 | 1 | 2 | 11
17 | 1 | 1 | 7
11 | 1 | 3 | 4
11 | 1 | 1 | 100
20 | 1 | 1 | 2
20 | 2 | 1 | 33
Player have "win", "draw" or "loss" results according to score1 and score2. I need find players, who have all "win", "draw" and "loss" results. In this case, players 11 and 20.
I am stuck here, any help greatly appreciated.
If I correctly understand, you need this:
select p from (
select player1_id as p, case when score1>score2 then 'W' when score1=score2 then 'D' when score1<score2 then 'L' end as res from your_table
union all
select player2_id as p, case when score1>score2 then 'L' when score1=score2 then 'D' when score1<score2 then 'W' end as res from your_table
) t
group by p
having count( distinct res ) = 3
You need to get all the players into one column, along with the scores or an indicator of the groups that you want:
select p1
from ((select player1_id as p1, player2_id as p2, score1 as s1, score2 as s2
from t
) union all
(select player2_id as p1, player1_id as p2, score2 as s1, score1 as s2
from t
)
) t
group by p1
having sum( (s1 > s2)::int) > 0 and
sum( (s1 = s2)::int) > 0 and
sum( (s1 < s2)::int) > 0;

SQL - Select rows where all values in a column are the same

I have an SQL question I've been struggling with and hope someone can help. I have the following data:
TEAM | USERID | STEP1 | STEP2 | STEP3 | STEP4 | STEP5
001 | 000001 | Y | Y | N | N | Y
001 | 000002 | Y | N | N | Y | Y
002 | 000003 | N | Y | Y | N | N
002 | 000004 | N | Y | Y | Y | Y
003 | 000005 | Y | N | N | Y | N
003 | 000006 | Y | Y | Y | N | Y
What I need to do is return the value of the TEAM where all values of any STEPx are 'N'. So in the example above I would need TEAM 001 and 002 to be returned because in TEAM 001 all values of STEP3 are 'N', and in TEAM 002 all values of STEP1 are 'N'.
Any help would be much appreciated.
select team
from table
group by team
having
sum(case step1 when 'N' then 0 else 1 end) = 0
or sum(case step2 when 'N' then 0 else 1 end) = 0
or sum(case step3 when 'N' then 0 else 1 end) = 0
or sum(case step4 when 'N' then 0 else 1 end) = 0
or sum(case step5 when 'N' then 0 else 1 end) = 0
Here's a fiddle: http://sqlfiddle.com/#!6/ecbff/3
It may be better to normalize your data so that you have an int column named STEP and one row per team/user/step. This would make your query much less awkward.
I went the other way, but Blorgbeard was much faster than me.
select TEAM
from TEAMS
group by TEAM
having
count(case STEP1 when 'Y' then 1 end) = 0
or count(case STEP2 when 'Y' then 1 end) = 0
or count(case STEP3 when 'Y' then 1 end) = 0
or count(case STEP4 when 'Y' then 1 end) = 0
or count(case STEP5 when 'Y' then 1 end) = 0
There is no pure sql-based solution, but each conrete database may have its own way to iterate over all columns.
How ever, this approach is bad.
You should normalize your database.
|TEAM|USER_ID|STEP_ID|VALUE|
|1 | 1 | 1 | Y |
After it, it it will be easy to join team with this tables and filter all teams with "Y"