SQL | View Column as Multiple Columns Based on Conditions - sql

Newbie Postgresql (9.6.6) question here :)
I want to create a View that will split a single column into several columns, based on different conditions.
Example Table
Name Score Season
------- ------- --------
John 12 Fall
John 15 Winter
John 13 Spring
Sally 17 Fall
Sally 10 Winter
Sally 14 Spring
Henry 16 Fall
Henry 12 Winter
Henry 18 Spring
I want the View to dislay something that looks like this:
Name Fall Score Winter Score Spring Score
------- ------------ -------------- --------------
John 12 15 13
Sally 17 10 14
Henry 16 12 18
Where the "Score" field is broken out into several different columns, each one populated based on WHERE clause that references the "Season" field. I've looked into both Window Functions and CASE Statements for accomplishing this purpose, but haven't been successfully thus far.
Any help is greatly appreciated!

Selecting from the entire table while grouping over the Name and then conditionally SUMming over the Score column will work:
SELECT
"Name",
SUM(CASE WHEN "Season" = 'Fall' THEN "Score" ELSE 0 END) AS "Fall",
SUM(CASE WHEN "Season" = 'Winter' THEN "Score" ELSE 0 END) AS "Winter",
SUM(CASE WHEN "Season" = 'Spring' THEN "Score" ELSE 0 END) AS "Spring"
FROM "mytable"
GROUP BY "Name"
Whether or not you use SUM() is up to you and how your data looks. If you have one row per (Name, Season) pair then SUM() will work equally as well as MAX()

You need a pivot table:
On SQL server you can do something like this example (hope it's the same for postgress), in others versions of SQL exist the pivot relational operators, but I'm not sure if Pivot works on Postgres
Example:
CREATE TABLE #Table
(
Name nvarchar(400),
Score int,
Season nvarchar(400)
)
insert into #Table values ( 'John ',12,'Fall')
insert into #Table values ( 'John ',15,'Winter' )
insert into #Table values ( 'John ',13,'Spring' )
insert into #Table values ( 'Sally',17,'Fall ' )
insert into #Table values ( 'Sally',10,'Winter' )
insert into #Table values ( 'Sally',14,'Spring' )
insert into #Table values ( 'Henry',16,'Fall' )
insert into #Table values ( 'Henry',12,'Winter' )
insert into #Table values ( 'Henry',18,'Spring' )
select
c.Name
,sum(c.[Fall Score]) as [Fall Score]
,sum(c.[Winter Score]) as [Winter Score]
,sum(c.[Spring Score]) as [Spring Score]
from
(SELECT
t.name,
case
when t.Season = 'Fall' then t.Score
when t.Season = 'Winter' then 0
when t.Season = 'Spring' then 0
end as [Fall Score],
case
when t.Season = 'Fall' then 0
when t.Season = 'Winter' then t.Score
when t.Season = 'Spring' then 0
end as [Winter Score],
case
when t.Season = 'Fall' then 0
when t.Season = 'Winter' then 0
when t.Season = 'Spring' then t.Score
end as [Spring Score]
from #Table t
)as c
group by c.name

Related

Compare two rows (both with different ID) & check if their column values are exactly the same. All rows & columns are in the same table

I have a table named "ROSTER" and in this table I have 22 columns.
I want to query and compare any 2 rows of that particular table with the purpose to check if each column's values of that 2 rows are exactly the same. ID column always has different values in each row so I will not include ID column for the comparing. I will just use it to refer to what rows will be used for the comparison.
If all column values are the same: Either just display nothing (I prefer this one) or just return the 2 rows as it is.
If there are some column values not the same: Either display those column names only or display both the column name and its value (I prefer this one).
Example:
ROSTER Table:
ID
NAME
TIME
1
N1
0900
2
N1
0801
Output:
ID
TIME
1
0900
2
0801
OR
Display "TIME"
Note: Actually I'm okay with whatever result or way of output as long as I can know in any way that the 2 rows are not the same.
What are the possible ways to do this in SQL Server?
I am using Microsoft SQL Server Management Studio 18, Microsoft SQL Server 2019-15.0.2080.9
Please try the following solution based on the ideas of John Cappelletti. All credit goes to him.
SQL
-- DDL and sample data population, start
DECLARE #roster TABLE (ID INT PRIMARY KEY, NAME VARCHAR(10), TIME CHAR(4));
INSERT INTO #roster (ID, NAME, TIME) VALUES
(1,'N1','0900'),
(2,'N1','0801')
-- DDL and sample data population, end
DECLARE #source INT = 1
, #target INT = 2;
SELECT id AS source_id, #target AS target_id
,[key] AS [column]
,source_Value = MAX( CASE WHEN Src=1 THEN Value END)
,target_Value = MAX( CASE WHEN Src=2 THEN Value END)
FROM (
SELECT Src=1
,id
,B.*
FROM #roster AS A
CROSS APPLY ( SELECT [Key]
,Value
FROM OpenJson( (SELECT A.* For JSON Path,Without_Array_Wrapper,INCLUDE_NULL_VALUES))
) AS B
WHERE id=#source
UNION ALL
SELECT Src=2
,id = #source
,B.*
FROM #roster AS A
CROSS APPLY ( SELECT [Key]
,Value
FROM OpenJson( (SELECT A.* For JSON Path,Without_Array_Wrapper,INCLUDE_NULL_VALUES))
) AS B
WHERE id=#target
) AS A
GROUP BY id, [key]
HAVING MAX(CASE WHEN Src=1 THEN Value END)
<> MAX(CASE WHEN Src=2 THEN Value END)
AND [key] <> 'ID' -- exclude this PK column
ORDER BY id, [key];
Output
+-----------+-----------+--------+--------------+--------------+
| source_id | target_id | column | source_Value | target_Value |
+-----------+-----------+--------+--------------+--------------+
| 1 | 2 | TIME | 0900 | 0801 |
+-----------+-----------+--------+--------------+--------------+
A general approach here might be to just aggregate over the entire table and report the state of the counts:
SELECT
CASE WHEN COUNT(DISTINCT ID) = COUNT(*) THEN 'Yes' ELSE 'No' END AS [ID same],
CASE WHEN COUNT(DISTINCT NAME) = COUNT(*) THEN 'Yes' ELSE 'No' END AS [NAME same],
CASE WHEN COUNT(DISTINCT TIME) = COUNT(*) THEN 'Yes' ELSE 'No' END AS [TIME same]
FROM yourTable;

Not able to add row wise data in column form

EmpID (Primary Key) Sale Items Paid
ABC chair Yes
WXY chair Under Review
PER Laptop Yes
ABC Chair Yes
Now i want to create another table where i want to insert data Like below
Emp ID Chair Laptop
ABC 2 0
WXY 1 0
My query to insert is
Select Emp Id from EMP,count(sales_item) as chair where Sales_Item = 'chair'
it is working now how to add Laptop (3rd Column ) . can you please suggest
You would use conditional aggregation:
Select EmpId,
sum(case when sales_item = 'chair' then 1 else 0 end) as chairs,
sum(case when sales_item = 'laptop' then 1 else 0 end) as laptops
from EMP
group by EmpId;
There is no reason to store this in a separate table. If you like, you can create a view. Then when you access the view, you know the data is up-to-date.
You could use pivot for the expected result:
DECLARE #t TABLE(
EmpID varchar(3)
,SaleItems varchar(10)
,Paid varchar(20)
)
INSERT INTO #t VALUES
('ABC', 'chair', 'Yes')
,('WXY', 'chair', 'Under Review')
,('PER', 'Laptop', 'Yes')
,('ABC', 'Chair', 'Yes')
SELECT piv.EmpID, ISNULL(piv.chair, 0) AS chair, ISNULL(piv.Laptop, 0) AS Laptop
FROM(
SELECT EmpID, SaleItems, 1 cnt
FROM #t
) x
PIVOT
(
SUM(cnt)
FOR SaleItems IN ([chair], [Laptop])
) piv

SUM on a single column based on different tables on SQL

I have two tables, min_attribution and max_attribution which looks like this
session_id attribution
1 search
2 home
session_id attribution
1 search
2 other
And here is the MRS
CREATE TABLE min_attribution
(session_id INT,
attribution VARCHAR(20)
)
CREATE TABLE max_attribution
(session_id INT,
attribution VARCHAR(20)
)
Insert into min_attribution values (1,'search')
Insert into min_attribution values (2,'home')
Insert into max_attribution values (1,'search')
Insert into max_attribution values (2,'other')
I am trying to write a query where, depending on the value of attribution, a score is given and added for each user ID. For example, if in the first table the value for attribution is search, add 40 and do the same with the other table, but adding 30. Expected output:
session_id search home other
1 70 0 0
2 0 40 30
What I did was trying to create a column for each of the possible attribution values (there are only a few) and add the results from each table, starting with "search", but it is not adding properly. This is my query
SELECT min_attribution.session_id, SUM(
(CASE WHEN min_attribution.attribution = "search" THEN 40 ELSE 0 END) +
(CASE WHEN max_attribution.attribution = "search" THEN 30 ELSE 0 END)) search
FROM min_attribution,
max_attribution
GROUP BY min_attribution.session_id
And the resulting table (current output, only for the search column):
session_id search
1 110
2 30
Any ideas? ( I am using BigQuery)
I think you want union all:
select session_id,
40 * countif(attribute = 'search'),
40 * countif(attribute = 'home'),
40 * countif(attribute = 'other')
from ((select session_id, attribution
from min_attribution
) union all
(select session_id, attribution
from max_attribution
)
) s
group by session_id;

Highlight multiple records in a date range

Working with SQL Server 2008.
fromdate todate ID name
--------------------------------
1-Aug-16 7-Aug-16 x jack
3-Aug-16 4-Aug-16 x jack
5-Aug-16 6-Aug-16 x tom
1-Aug-16 2-Aug-16 x john
3-Aug-16 4-Aug-16 x harry
5-Aug-16 6-Aug-16 x mac
Is there a way to script this so that I know if there are multiple names tagged to an ID in the same date range?
For example above, I want to flag that ID x has Name Jack and Tom tagged in the same date range.
ID multiple_flag
------------------------------------------------
x yes
y no
If there is a unique index in your table (in my example it is column i but you could also generate one by means of using ROW_NUMBER()) then you can do the following query based on an INNER JOIN to find overlapping date ranges:
CREATE TABLE #tmp (i int identity primary key,fromdate date,todate date,ID int,name varchar(32));
insert into #tmp (fromdate,todate,ID ,name) values
('1-Aug-16','7-Aug-16',3,'jack'),
('3-Aug-16','4-Aug-16',3,'tom'),
('5-Aug-16','6-Aug-16',3,'jack');
select a.*,b.name bname,b.i i2 from #tmp a
INNER join #tmp b on b.id=a.id AND b.i<>a.i
AND ( b.fromdate between a.fromdate and a.todate
OR b.todate between a.fromdate and a.todate)
(My id column is int). This will give you:
i fromdate todate ID name bname i2
- ---------- ---------- - ---- ----- --
1 2016-08-01 2016-08-07 3 jack tom 2
1 2016-08-01 2016-08-07 3 jack jack 3
Implement further filtering or grouping as required. I left a little demo here.
Please check the below sql, but it might not be the optimal one..
SELECT formdate,todate,id,tab1.name,
case when tab2.#Of >1 then 'yes' else 'no' end as multiple_flag
FROM tab1
inner join (SELECT Name, COUNT(*) as #Of
FROM tab1
GROUP BY Name) as tab2 on tab1.name=tab2.name
order by tab1.id ;
add your where condition, before the order by, if you need to add some date range on your sql.
change formdate to fromdate before run this sql, as I have used formdate in my machine.
The result looks like
One way to do it is using EXISTS CASE:
Please note this part of the query:
-- make sure the records date ranges overlap
AND t1.fromdate <= t2.todate
AND t2.fromdate <= t1.todate
for an explanation on testing for overlapping ranges, read the overlap wiki.
Create and populate sample data (Please save us this step in your future questions)
DECLARE #T as table
(
fromdate date,
todate date,
ID char(1),
name varchar(10)
)
INSERT INTO #T VALUES
('2016-08-01', '2016-08-07', 'x', 'jack'),
('2016-08-03', '2016-08-04', 'x', 'tom'),
('2016-08-05', '2016-08-06', 'x', 'jack'),
('2016-08-01', '2016-08-02', 'y', 'john'),
('2016-08-03', '2016-08-04', 'y', 'harry'),
('2016-08-05', '2016-08-06', 'y', 'mac')
The query:
SELECT DISTINCT id,
CASE WHEN EXISTS
(
SELECT 1
FROM #T t2
WHERE t1.Id = t2.Id
-- make sure it's not the same record
AND t1.fromdate <> t2.fromdate
AND t1.todate <> t2.todate
-- make sure the records date ranges overlap
AND t1.fromdate <= t2.todate
AND t2.fromdate <= t1.todate
)
THEN 'Yes'
ELSE 'No'
END As multiple_flag
FROM #T t1
Results:
id multiple_flag
---- -------------
x Yes
y No

group by + counting different column values

I have a simple table with id, gender, age and favoriteMovie.
Gender column has values male and female only.
I want to display all movie titles from movies column (group by movies) and in two separated columns number of males and number of females voting on selected movie.
Something like count(gender = 'male') as male, count(gender = 'female') as female
Database is ms-sql 2008
Any sugestion greatly appreciated
You can use case syntax (while change count into sum):
select favoriteMovie as movie,
sum(case
when gender = 'male' then
1
else
0
end) as male,
sum(case
when gender = 'female' then
1
else
0
end) as female
from MyTable
group by favoriteMovie
Not very good with pivots, but this worked for me.
declare #t table (id varchar(55),gender varchar(55),age varchar(55),favoriteMovie varchar(55))
insert into #t values(1,'m',16,'star wars')
insert into #t values(2,'f',16,'star trek')
insert into #t values(3,'m',16,'star trek')
insert into #t values(4,'f',16,'star wars')
insert into #t values(5,'m',16,'star wars')
And then query.
select favoriteMovie, sum([m]) as "m",sum([f]) as "f"
from #t
pivot
(
count(gender)
for gender in ([m],[f])
) as pt
group by favoriteMovie