Another SQL 'Rows to Columns' Question - sql

Here is my data:
ID
Model
Year
1
Civic
2008
1
Accord
2010
2
Mustang
2011
3
Tahoe
2011
I would like to get this result:
ID
Model1
Year1
Model2
Year2
1
Civic
2008
Accord
2010
2
Mustang
2011
3
Tahoe
2011
Up to 4 cars can be present under each ID and no more. I have spent a lot of time researching this but have not found a good solution that fits my example exactly. Perhaps because I don't know how exactly to word my search. Thanks...

You should use PIVOT tables. It's ugly, but it works:
if object_id('tempdb..#RepeatingGroup') is not null drop table #RepeatingGroup
select 1 as ID, 'Civic' as Model, '2008' as [Year] into #RepeatingGroup union all
select 1, 'Accord', '2010' union all
select 2, 'Mustang', '2011' union all
select 3, 'Tahoe', '2011'
if object_id('tempdb..#tmp') is not null drop table #tmp
select
ID,
Model,
Year,
row_number() over (partition by x.ID order by x.Model) as Ordinal
into
#tmp
from
#RepeatingGroup x
select
pvtMd.ID,
pvtMd.[1] as Model1,
pvtYr.[1] as Year1,
pvtMd.[2] as Model2,
pvtYr.[2] as Year2,
pvtMd.[3] as Model3,
pvtYr.[3] as Year3,
pvtMd.[4] as Model4,
pvtYr.[4] as Year4
from
(select ID, Model, Ordinal from #tmp t) t
pivot (
min(Model) for Ordinal in ([1], [2], [3], [4])
) as pvtMd,
(select ID, Year, Ordinal from #tmp t) t2
pivot (
min([Year]) for Ordinal in ([1], [2], [3], [4])
) as pvtYr
where
pvtMd.ID = pvtYr.ID
order by
1

There's not a really great way to do this in SQL. You might try and struggle with a pivot table, but each entity would need a sequence.
Your best bet would be to arrange it how you want in your output language which will have much better tools available for this sort of thing.
Add a column to each row called sequence (which has 1, 2, 3, and 4)
SELECT
id,
Max(case when seq = 1 then model end) as model1,
max(case when seq = 1 then year end) as year1,
Max(case when seq = 2 then model end) as model2,
max(case when seq = 2 then year end) as year2,
Max(case when seq = 3 then model end) as model3,
max(case when seq = 3 then year end) as year3,
Max(case when seq = 4 then model end) as model4,
max(case when seq = 4 then year end) as year4
group by id

Related

Convert rows to columns in SQL Server 2008

We have one table called Licenses. This is what it looks like:
CustNum
LicenseAddress
License
ExpiryDate
155
123
Y32CA
12/31/2018
155
998
Y32CB
12/31/2020
155
568
Y32CC
12/31/2022
Here is what I want it to look like:
LicAddr1
Lic1
ExpiryDate1
LicAddr2
Lic2
ExpiryDate2
LicAddr3
Lic3
ExpiryDate3
123
Y32CA
12/31/2018
998
Y32CB
12/31/2020
568
Y32CC
12/31/2022
Here is the query I have currently, however it’s only returning NULLs:
SELECT LicAddr1,
Lic1,
ExpiryDate1,
LicAddr2,
Lic2,
ExpiryDate2,
LicAddr3,
Lic3,
ExpiryDate3
FROM (SELECT CustNum, LicenseAddress, License, ExpiryDate FROM Licenses) d
PIVOT (
MAX(ExpiryDate)
FOR CustNum IN (LicAddr1, Lic1, ExpiryDate1, LicAddr2, Lic2, ExpiryDate2, LicAddr3, Lic3, ExpiryDate3)
) piv
What am I doing wrong?
PIVOT isn't really what you're after, especially since you're trying to get multiple values from each row (which doesn't work very well with the aggregation PIVOT tries to offer).
I'm assuming here you want the most recent three licenses, in which case we can apply a row number per CustNum, ordered by ExpiryDate (newest first), then flip them so they are left-to-right oldest-to-newest:
;WITH cte AS
(
SELECT CustNum, LicenseAddress, License, ExpiryDate,
rn = ROW_NUMBER() OVER (PARTITION BY CustNum ORDER BY ExpiryDate DESC)
FROM dbo.Licenses
)
SELECT CustNum,
LicAddr1 = MAX(CASE WHEN rn = 3 THEN LicenseAddress END),
Lic1 = MAX(CASE WHEN rn = 3 THEN License END),
ExpiryDate1 = MAX(CASE WHEN rn = 3 THEN ExpiryDate END),
LicAddr2 = MAX(CASE WHEN rn = 2 THEN LicenseAddress END),
Lic2 = MAX(CASE WHEN rn = 2 THEN License END),
ExpiryDate2 = MAX(CASE WHEN rn = 2 THEN ExpiryDate END),
LicAddr3 = MAX(CASE WHEN rn = 1 THEN LicenseAddress END),
Lic3 = MAX(CASE WHEN rn = 1 THEN License END),
ExpiryDate3 = MAX(CASE WHEN rn = 1 THEN ExpiryDate END)
FROM cte
GROUP BY CustNum;
Results:
CustNum
LicAddr1
Lic1
ExpiryDate1
LicAddr2
Lic2
ExpiryDate2
LicAddr3
Lic3
ExpiryDate3
155
123
Y32CA
2018-12-31
998
Y32CB
2020-12-31
568
Y32CC
2022-12-31
Example db<>fiddle

How do I get the count of distinct values in the first row of the result

I want to check if the colour and city are multiple for a document for the highest amount. if yes, I want to set a bit as 1 and if not, it should be 0
Sample data:
Code doc year amount colour city
AB 123 2021 485 Red Paris
AB 123 2021 416 Red Paris
AB 123 2021 729 Red London
AB 123 2021 645 Red Bengaluru
Expected output:
I want the output in one row
Code Doc Year Amount Colour City Col_Mul City_Mul
AB 123 2021 729 Red London 0 1
Amount, Colour and city should be the maximum one.
What I tried:
To get the data in one row, I used the row number and ordered by the maximum amount and selected the data where the row number is one. But after that I used dense rank for the Colour and City column. But I didn't get the expected output.
You can use CROSS APPLY and get data as given below:
Thanks #Gayani for test data.
select TOPROW.*,case when T1.colorcount > 1 THEN 1 else 0 end as Multi_color,
case when T2.citycount > 1 THEN 1 else 0 end as Multi_city
from
(SELECT TOP 1 * FROM tes_firstRow
order by amount desc) as toprow
cross apply
(
SELECT count(distinct color) from tes_firstrow WHERE doc = toprow.doc
) as t1(colorcount)
cross apply
(
SELECT count(distinct city) from tes_firstrow WHERE doc = toprow.doc
) as t2(citycount)
Code
doc
year
amount
color
City
Multi_color
Multi_city
AB
123
2021
729
RED
LONDON
0
1
There you go. Weird requirement though...
SELECT T.Code, Doc, Year, MAX(T.Amount) Amount,
(SELECT TOP 1 Colour FROM T as X WHERE Amount = MAX(T.Amount)) Colour,
(SELECT TOP 1 City FROM T as X WHERE Amount = MAX(T.Amount)) City,
CASE WHEN COUNT(DISTINCT T.Colour) > 1 THEN 1 ELSE 0 END as Col_Mul,
CASE WHEN COUNT(DISTINCT T.City) > 1 THEN 1 ELSE 0 END as City_Mul
FROM T
GROUP BY T.Code, Doc, Year
I think you just want window functions combined with conditional aggregation:
select code, doc, year, max(amount),
max(case when seqnum = 1 then color end) as color,
max(case when seqnum = 1 then city end) as city,
(case when seqnum = 1 and color_count > 1 then 1 else 0 end) as color_dup,
(case when seqnum = 1 and city_count > 1 then 1 else 0 end) as city_dup,
from (select t.*,
row_number() over (partition by code, doc, year order by amount desc) as seqnum,
count(*) over (partition by code, doc, year, color) as color_count,
count(*) over (partition by code, doc, year, city) as city_count
from t
) t
group by code, doc, year;
I'm not actually sure if you want 1 when the value is duplicated or not, so those values might be backwards.
I hope this code sample will help you with this.
Please try the below code and let me know if it helps with what you need. I used temporary tables here. You can use any of the technology to build the logic. CTE (Common table expressions) or derived tables.
CREATE TABLE tes_firstRow
(
Code varchar(100)
, doc int
, [year] int
, amount int
, color varchar(100)
, City varchar(100)
)
insert into tes_firstRow values ('AB', 123,2021,485,'RED','PARIS')
insert into tes_firstRow values ('AB', 123,2021,416,'RED','PARIS')
insert into tes_firstRow values ('AB', 123,2021,729,'RED','LONDON')
insert into tes_firstRow values ('AB', 123,2021,645,'RED','BENGALURU')
SELECT
RANK() OVER (PARTITION BY Code, doc,[year] ORDER BY amount DESC) AS [rank]
,Code
,doc
,[year]
,amount
,color
,City
INTO #temp_1
FROM tes_firstRow
SELECT
[#temp_1].[Code]
,[#temp_1].[doc]
,[#temp_1].[year]
,[#temp_1].[amount]
,[#temp_1].[color]
,[#temp_1].[City]
, (Select COUNT(Distinct [#temp_1].[color] ) where [#temp_1].[rank] = 1 ) as Col_Mul
, (Select COUNT(Distinct [#temp_1].[City]) where [#temp_1].[rank] = 1) as City_Mul
,1 as City_Mul
FROM #temp_1
WHERE #temp_1.[rank] = 1
group by [#temp_1].[Code]
,[#temp_1].[doc]
,[#temp_1].[year]
,[#temp_1].[amount]
,[#temp_1].[color]
,[#temp_1].[City]
,[#temp_1].[rank]
DROP TABLE #temp_1
Result:

SQL - First n non null columns

I have 30 columns like p1, p2, p3,......,p29, p30.
Out of them, any 6 consecutive values will be non-null and the rest are all none.
I need to write an SQL query (preferably Redshift) to get all of them into 6 columns. Say a1,a2,a3,a4,a5,a6
Eg. If I have 50 rows of data with 30 columns with a lot of nulls. I'll be turning it into 50 rows of data with those 6 non-null values of a row.
There is no simple way to do this. One method is to unpivot and then re-aggregate -- assuming your table has a primary key:
select pk,
max(case when seqnum = 1 then p end) as q1,
max(case when seqnum = 2 then p end) as q2,
max(case when seqnum = 3 then p end) as q3,
max(case when seqnum = 4 then p end) as q4,
max(case when seqnum = 5 then p end) as q5,
max(case when seqnum = 6 then p end) as q6
from (select pk, p, row_number() over (partition by pk order by which) as seqnum
from ((select pk, 1 as which, p1 as p from t) union all
(select pk, 2 as which, p2 as p from t) union all
. . .
) t
where p is not null
) t
group by pk

How can I calculate the quotient from the same column of two seperate rows?

I have the folowing table test:
MonthNumber Fees FeesName
1 10 A
2 22 A
3 25 A
1 11 B
2 25 B
3 28 B
3 8 C
3 2 D
3 3 E
I am looking Fees(A) / Fees(B), for each MonthNumber.
Use Pivot:
;WITH CTE AS
(
SELECT MonthNumber, Fees, Feesname
FROM test
WHERE FeesName in ('A', 'B')
)
SELECT MonthNumber, [A]/[B]
FROM cte
PIVOT (SUM(Fees) FOR [Feesname] IN ([A], [B])) AS pvt
ORDER BY MonthNumber
And if you don't like pivot or use an sqlserver 2005 or older
SELECT
MonthNumber,
SUM(CASE WHEN FeesName = 'A' THEN Fees END)/
SUM(CASE WHEN FeesName = 'B' THEN Fees END) result
FROM test
WHERE FeesName in ('A', 'B')
GROUP BY MonthNumber
ORDER BY MonthNumber
Your question is pretty sparse, but if I'm understanding you correctly, then you want something like this:
SELECT A.Fees / B.Fees
FROM test A
INNER JOIN test B ON A.MonthNumber = B.MonthNumber
WHERE A.FeesName = 'A' AND B.FeesName = 'B'
Basically, join the table against itself and run your division as you generally would.

SQL Server 2008: Converting rows into columns

I have two tables:
CREATE TABLE #A (id int, cond_id int)
INSERT INTO #A (id, cond_id)
VALUES (101,20),
(101,22),
(101,24),
(102,23),
(102,22)
Now, each id can have max of 4 cond_ids. I want to populate table #B so that there is one id and all cond_ids will be populated in the columns as one row according to cond_id ascending.
like for id 102, cond_id 22 goes in cond_id and 23 goes in cond_id2.
create table #B (id int, cond_id1 int, cond_id2 int, cond_id3 int, cond_id4 int)
Desired result:
Table #B
id cond_id1 cond_id2 cond_id3 cond_id4
101 20 22 24 null
102 22 23 null null
Thanks in advance!
Because you know the maximum number of columns, one option is to use row_number, max and case:
with cte as (
select row_number() over (partition by id order by cond_id) rn, id, cond_id
from a)
select id,
max(case when rn = 1 then cond_id end) cond_id1,
max(case when rn = 2 then cond_id end) cond_id2,
max(case when rn = 3 then cond_id end) cond_id3,
max(case when rn = 4 then cond_id end) cond_id4
from cte
group by id
SQL Fiddle Demo
Or you could look at Pivot:
select id, [1] cond_id1, [2] cond_id2, [3] cond_id3, [4] cond_id4
from
(select row_number() over (partition by id order by cond_id) rn, id, cond_id
from a) t
pivot
(
max(cond_id)
for rn in ([1], [2], [3], [4])
) p
More Fiddle