how to join or merge as one row in SQL - sql

I have these 2 tables;
table A
| ID | Name | S_ID |
|----|--------|------|
| 1 | mark | 1 |
| 2 | john | 2 |
table B (rows are not limited to 5 and Scores could be more than 3)
| ID | S_ID | Score |
|-------------------|
| 1 | 1 | 90% |
| 2 | 1 | 80% |
| 3 | 1 | 10% |
| 4 | 2 | 10% |
| 5 | 2 | 12% |
Normally using "GROUP_CONCAT" would work but is there any way to achieve this;

You are asking for a pivot query, either with or without a fixed number of columns. Assuming the former, we can use ROW_NUMBER here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY S_ID ORDER BY ID) rn
FROM tableB
)
SELECT
a.ID,
a.Name,
MAX(CASE WHEN rn = 1 THEN b.S_ID END) AS S_ID_1,
MAX(CASE WHEN rn = 1 THEN b.Score END) AS Score_1,
MAX(CASE WHEN rn = 2 THEN b.S_ID END) AS S_ID_2,
MAX(CASE WHEN rn = 2 THEN b.Score END) AS Score_2,
MAX(CASE WHEN rn = 3 THEN b.S_ID END) AS S_ID_3,
MAX(CASE WHEN rn = 3 THEN b.Score END) AS Score_3
FROM cte
GROUP BY
a.ID,
a.Name;

Related

Single query to split out data of one column, into two columns, from the same table based on different criteria [SQL]

I have the following data in a table, this is a single column shown from a table that has multiple columns, but only data from this column needs to be pulled into two column output using a query:
+----------------+--+
| DataText | |
| 1 DEC20 DDD | |
| 1 JUL20 DDD | |
| 1 JAN21 DDD | |
| 1 JUN20 DDD500 | |
| 1 JUN20 DDD500 | |
| 1 JUN20DDDD500 | |
| 1 JUN20DDDD500 | |
| 1 JUL20 DDD800 | |
| 1 JUL20 DDD800 | |
| 1 JUL20DDDD800 | |
| 1 JUL20DDDD400 | |
| 1 JUL20DDDD400 | |
+----------------+--+
Required result: distinct values based on the first 13 characters of the data, split into two columns based on "long data", and "short data", BUT only giving the first 13 characters in output for both columns:
+-------------+-------------+
| ShortData | LongData |
| 1 DEC20 DDD | 1 JUN20 DDD |
| 1 JUL20 DDD | 1 JUN20DDDD |
| 1 JAN21 DDD | 1 JUL20 DDD |
| | 1 JUL20DDDD |
+-------------+-------------+
Something like:
Select
(Select DISTINCT LEFT(DataText,13)
From myTable)
Where LEN(DataText)=13) As ShortData
,
(Select DISTINCT LEFT(DataText,13)
From myTable)
Where LEN(DataText)>13) As LongData
I would also like to query/"scan" the table only once if possible. I can't get any of the SO examples modified to make such a query work.
This is quite ugly, but doable. As a starter, you need a column that defines the order of the rows - I assumed that you have such a column, and that is called id.
Then you can select the distinct texts, put them in separate groups depending on their length, and finally pivot:
select
max(case when grp = 0 then dataText end) shortData,
max(case when grp = 1 then dataText end) longData
from (
select
dataText,
grp,
row_number() over(partition by grp order by id) rn
from (
select
id,
case when len(dataText) <= 13 then 0 else 1 end grp,
substring(dataText, 1, 13) dataText
from (select min(id) id, dataText from mytable group by dataText) t
) t
) t
group by rn
If you are content with ordering the records by the string column itself, it is a bit simpler (and, for your sample data, it produces the same results):
select
max(case when grp = 0 then dataText end) shortData,
max(case when grp = 1 then dataText end) longData
from (
select
dataText,
grp,
row_number() over(partition by grp order by dataText) rn
from (
select distinct
case when len(dataText) <= 13 then 0 else 1 end grp,
substring(dataText, 1, 13) dataText
from mytable
) t
) t
group by rn
Demo on DB Fiddle:
shortData | longData
:---------- | :------------
1 DEC20 DDD | 1 JUL20 DDD80
1 JAN21 DDD | 1 JUL20DDDD40
1 JUL20 DDD | 1 JUL20DDDD80
null | 1 JUN20 DDD50
null | 1 JUN20DDDD50

SQL - Select in Select

I have the below working query from which I create a stacked-column-graph.
I now want to be able to filter top 10 [Prod_Model] with the highest count of [plugin ID] in total.
I tried to create an additional column summing [Plugin ID] (vulCnt) for each product disregarding [Risk].
My efforts to use INNER JOIN or SELECT inside the SELECT failed.
My working query:
select t.Prod_Model,
count(t.[Plugin ID]) as vulCnt,
(case t.Risk
when 'Critical' then 1
when 'High' then 2
when 'Medium' then 3
when 'Low' then 4
Else 5
End) As Rsk_Levl,
t.Risk
from ************ t
where t.prod_model <>''
group by t.Prod_Model, t.Risk
order by t.Prod_Model
Result is -
|Prod_Model|vulCnt| Risk_Level| Risk |
|procut 1 | 4 | 1 | Critical|
|procut 1 | 2 | 1 | High |
|procut 1 | 6 | 1 | Medium |
|procut 1 | 1 | 1 | Low |
|procut 2 | 4 | 1 | Critical|
|procut 2 | 2 | 1 | High |
|procut 2 | 6 | 1 | Medium |
|procut 2 | 1 | 1 | Low |
Now I need top##.
Help will be much appreciated.
I think that you want a window sum to generate the additional column. You can then use it this information to rank and limit:
select *
from (
select
Prod_Model,
count([Plugin ID]) as vulCnt,
(case Risk
when 'Critical' then 1
when 'High' then 2
when 'Medium' then 3
when 'Low' then 4
Else 5
End) As Rsk_Levl,
Risk,
dense_rank() over(order by cnt desc) rn
from (
select t.*, count(*) over(partition by Prod_Model) cnt
from mytable t
where prod_model <> ''
) t
group by Prod_Model, Risk
) t
where rn <= 10
order by rn

Each rows to column values

I'm trying to create a view that shows first table's columns plus second table's first 3 records sorted by date in 1 row.
I tried to select specific rows using offset from sub table and join to main table, but when joining query result is ordered by date, without
WHERE tblMain_id = ..
clause in joining SQL it returns wrong record.
Here is sqlfiddle example: sqlfiddle demo
tblMain
| id | fname | lname | salary |
+----+-------+-------+--------+
| 1 | John | Doe | 1000 |
| 2 | Bob | Ross | 5000 |
| 3 | Carl | Sagan | 2000 |
| 4 | Daryl | Dixon | 3000 |
tblSub
| id | email | emaildate | tblmain_id |
+----+-----------------+------------+------------+
| 1 | John#Doe1.com | 2019-01-01 | 1 |
| 2 | John#Doe2.com | 2019-01-02 | 1 |
| 3 | John#Doe3.com | 2019-01-03 | 1 |
| 4 | Bob#Ross1.com | 2019-02-01 | 2 |
| 5 | Bob#Ross2.com | 2018-12-01 | 2 |
| 6 | Carl#Sagan.com | 2019-10-01 | 3 |
| 7 | Daryl#Dixon.com | 2019-11-01 | 4 |
View I am trying to achieve:
| id | fname | lname | salary | email_1 | emaildate_1 | email_2 | emaildate_2 | email_3 | emaildate_3 |
+----+-------+-------+--------+---------------+-------------+---------------+-------------+---------------+-------------+
| 1 | John | Doe | 1000 | John#Doe1.com | 2019-01-01 | John#Doe2.com | 2019-01-02 | John#Doe3.com | 2019-01-03 |
View I have created
| id | fname | lname | salary | email_1 | emaildate_1 | email_2 | emaildate_2 | email_3 | emaildate_3 |
+----+-------+-------+--------+---------+-------------+---------------+-------------+---------------+-------------+
| 1 | John | Doe | 1000 | (null) | (null) | John#Doe1.com | 2019-01-01 | John#Doe2.com | 2019-01-02 |
You can use conditional aggregation:
select m.id, m.fname, m.lname, m.salary,
max(s.email) filter (where seqnum = 1) as email_1,
max(s.emailDate) filter (where seqnum = 1) as emailDate_1,
max(s.email) filter (where seqnum = 2) as email_2,
max(s.emailDate) filter (where seqnum = 3) as emailDate_2,
max(s.email) filter (where seqnum = 3) as email_3,
max(s.emailDate) filter (where seqnum = 3) as emailDate_3
from tblMain m left join
(select s.*,
row_number() over (partition by tblMain_id order by emailDate desc) as seqnum
from tblsub s
) s
on s.tblMain_id = m.id
where m.id = 1
group by m.id, m.fname, m.lname, m.salary;
Here is a SQL Fiddle.
Here is a solution that should get you what you expect.
This works by first ranking records within each table and joining them together. Then, the outer query uses aggregation to generate the expected output.
This solution will work even if the first record in the main table does not have id 1. Also filtering takes occurs within the JOINs, so this should be quite efficient.
SELECT
m.id,
m.fname,
m.lname,
m.salary,
MAX(CASE WHEN s.rn = 1 THEN s.email END) email_1,
MAX(CASE WHEN s.rn = 1 THEN s.emaildate END) email_date1,
MAX(CASE WHEN s.rn = 2 THEN s.email END) email_2,
MAX(CASE WHEN s.rn = 2 THEN s.emaildate END) email_date2,
MAX(CASE WHEN s.rn = 3 THEN s.email END) email_3,
MAX(CASE WHEN s.rn = 3 THEN s.emaildate END) email_date3
FROM
(
SELECT m.*, ROW_NUMBER() OVER(ORDER BY id) rn
FROM tblMain
) m
INNER JOIN (
SELECT
email,
emaildate,
ROW_NUMBER() OVER(PARTITION BY id ORDER BY emaildate) rn
FROM tblSub
) s
ON m.id = s.tblmain_id
AND m.rn = 1
AND s.rn <= 3
GROUP BY
m.id,
m.fname,
m.lname,
m.salary

Merge rows with same ID

I have the following table:
ID | variant_name | variant_color
1 | BMW 7-series | Black
2 | Volvo C60 | Gray
1 | BMW 3-series | White
3 | Subaru Forester| Orange
2 | Volvo XC90 | Green
How can I query to gain this result:
ID | variant_name_1 | variant_color_1| variant_name_2 | variant_color_2|
1 | BMW 7-series | Black | BMW 3-series | White |
2 | Volvo C60 | Gray | Volvo XC90 | Green |
3 | Subaru Forester| Orange | | |
Each ID has a maximum number of variants of 2.
Thanks!
It'll work in sql server/posgresql/oracle - use row_number()
http://sqlfiddle.com/#!18/a7540/10424
select id, max(case when rn=1 then variant_name end) as variant_name1,max(case when rn=1 then variant_color end) as variant_color1,
max(case when rn=2 then variant_name end) as variant_name2,max(case when rn=2 then variant_color end) as variant_color2
from
(
select id, variant_name, variant_color, row_number() over(partition by id order by id) as rn
from tablename)a
group by id
You can use row_number() to do conditional aggregation :
select id, max(case when seq = 1 then variant_name end) as variant_name_1,
max(case when seq = 1 then variant_color end) as variant_color_1,
max(case when seq = 2 then variant_name end) as variant_name_2,
max(case when seq = 2 then variant_color end) as variant_color_2
from (select t.*, row_number() over (partition by id order by variant_color) as seq
from table t
) t
group by id;
Try left joining with itself:
select c1.id,
c1.variant_name as variant_name_1,
c1.variant_color as variant_color_1,
c2.variant_name as variant_name_2,
c2.variant_color as variant_color_2
from cars c1
left join cars c2
on c1.id = c2.id
and c1.seq <> c2.seq
group by c1.id

Updating Rows Into Different Columns

Both of these tables already exist, so not looking for a dynamic situation. The goal is to consolidate the data rows horizontally, but have them to the leftmost "data" field available. There will never be a 4th entry.
I am using Microsoft SQL Server
Table1:
ID|Data
--------
A | 1
A | 2
B | 3
C | 4
C | 5
C | 6
Table2:
ID | Data 1 | Data 2 | Data 3
------------------------------
A | | |
B | | |
C | | |
Desired Result of Table2:
ID | Data 1 | Data 2 | Data 3
------------------------------
A | 1 | 2 |
B | 3 | |
C | 6 | 7 | 8
You can use row_number:
select id,
max(case when rn = 1 then data end) as data_1,
max(case when rn = 2 then data end) as data_2,
max(case when rn = 3 then data end) as data_3
from (
select t.*,
row_number() over (
partition by id order by data
) as rn
from your_table t
) t
group by id;