SQL How do I transpose and group data into static columns? [duplicate] - sql

This question already has answers here:
TSQL Pivot without aggregate function
(9 answers)
Closed 4 years ago.
I have a table with the following data:
UID LAST FIRST FUND AMOUNT STATUS
1 Smith John C 100 1
1 Smith John B 250 1
1 Smith John E 150 1
2 Jones Meg B 275 1
2 Jones Meg F 150 1
3 Carter Bill A 100 1
I would like to transpose the FUND, AMOUNT and STATUS values for each UID into a single row for each UID. The resulting table would have columns added for FUND_1, AMT_1, STATUS_1, FUND_2, AMT_2, STATUS_2, FUND_3, AMT_3, STATUS_3. Each UID may or may not have a total of 3 funds. If they do not, the remaining fund, amt, and status columns are left blank. The resulting table would appear as:
UID LAST FIRST FUND_1 AMT_1 STATUS_1 FUND_2 AMT_2 STATUS_2 FUND_3 AMT_3 STATUS_3
1 Smith John C 100 1 B 250 1 E 150 1
2 Jones Meg B 275 1 F 150 1
3 Carter Bill A 100 1
For clarification, this is how the data would move from the existing table to the resulting table for UID 1:
It seems I am unable to use PIVOT because FUND_1, FUND_2, FUND_3 will be different fund categories for each person. The question, TSQL Pivot without aggregate function helps but doesn't answer my question since I have multiple rows in what would be the the DBColumnName in that question.

This is a pretty common conditional aggregation. Notice how I posted consumable data as a table and insert statements. To be honest it took longer to do that portion than the actual code to select the data. You should do this in the future. Also you should avoid using keywords as column names.
declare #Something table
(
UID int
, LAST varchar(10)
, FIRST varchar(10)
, FUND char(1)
, AMOUNT int
, STATUS int
)
insert #Something values
(1, 'Smith', 'John', 'C', 100, 1)
, (1, 'Smith', 'John', 'B', 250, 1)
, (1, 'Smith', 'John', 'E', 150, 1)
, (2, 'Jones', 'Meg', 'B', 275, 1)
, (2, 'Jones', 'Meg', 'F', 150, 1)
, (3, 'Carter', 'Bill', 'A', 100, 1)
;
with SortedValues as
(
select *
, RowNum = ROW_NUMBER() over(partition by UID order by (select null))
from #Something
)
select UID
, Last
, First
, Fund_1 = max(case when RowNum = 1 then Fund end)
, Amt_1 = max(case when RowNum = 1 then Amount end)
, Status_1 = max(case when RowNum = 1 then Status end)
, Fund_2 = max(case when RowNum = 2 then Fund end)
, Amt_2 = max(case when RowNum = 2 then Amount end)
, Status_2 = max(case when RowNum = 2 then Status end)
, Fund_3 = max(case when RowNum = 3 then Fund end)
, Amt_3 = max(case when RowNum = 3 then Amount end)
, Status_3 = max(case when RowNum = 3 then Status end)
from SortedValues
group by UID
, Last
, First
order by UID
, Last
, First

Related

Filling fields with Null values [duplicate]

This question already has answers here:
Group by column and multiple Rows into One Row multiple columns
(2 answers)
Convert Rows to columns using 'Pivot' in SQL Server
(9 answers)
Closed 9 months ago.
I have two tables in SQL Server 2012
Table1
UserID
Name
1
Joe
2
Mary
Table2
UserID
Permission
1
P15
2
P5
2
P330
Each user can have between 1 and 8 Permissions.
I need to create a view that will give me the UserID with 8 permission entries with any unused entries containing null values i.e. The order of the entries does not matter.
UserID
Permit1
Permit2
Permit3
Permit4
Permit5
Permit6
Permit7
Permit8
1
P15
Null
Null
Null
Null
Null
Null
Null
2
P5
P330
Null
Null
Null
Null
Null
Null
This has me stumped, I don't even know where to start?
You can do this easily with conditional aggregation. This has been asked and answered hundreds of times but if it is a new concept it would be hard to know what search terms to use. This is complete with consumable sample data.
declare #Users table
(
UserID int
, Name varchar(10)
)
insert #Users values
(1, 'Joe')
, (2, 'Mary')
declare #Permissions table
(
UserID int
, Permission varchar(10)
)
insert #Permissions values
(1, 'P15')
, (2, 'P5')
, (2, 'P330')
select x.UserID
, Permit1 = max(case when x.RowNum = 1 then x.Permission end)
, Permit2 = max(case when x.RowNum = 2 then x.Permission end)
, Permit3 = max(case when x.RowNum = 3 then x.Permission end)
, Permit4 = max(case when x.RowNum = 4 then x.Permission end)
, Permit5 = max(case when x.RowNum = 5 then x.Permission end)
, Permit6 = max(case when x.RowNum = 6 then x.Permission end)
, Permit7 = max(case when x.RowNum = 7 then x.Permission end)
, Permit8 = max(case when x.RowNum = 8 then x.Permission end)
from
(
select u.UserID
, p.Permission
, RowNum = ROW_NUMBER() over(partition by u.UserId order by p.Permission)
from #Users u
join #Permissions p on p.UserID = u.UserID
) x
group by x.UserID

How to return all records from table A , if any one of the column has a specific value in oracle sql?

Below is the sample data
If I pass lot name as a parameter, I want to return employees who has greater than 0 records in The specific Lot . Not just the one record but all the records of that employee.
Table A
Empid lotname itemcount
1 A 1
1 B 1
2 B 0
3 B 1
3 C 0
Parameter - B
Result :
Empid lotname itemcount
1 A 1
1 B 1
3 B 1
3 C 0
Because employee 3 and 1 has count in B lot. All the employee lot details should be returned.
select data.* from A data,
(select Empid,count(lotname)
from A
group by Empid
having count(lotname)>1) MulLotEmp
where data.lotname='B'
and data.Empid=MulLotEmp.Empid;
Check if this query solves your problem. In this I created a inner table first for your first requirement that emp with multiple lot, then I mapped this table with actual table with condition of input lot name.
If I understand correctly, you want all "1" and then only "0" if there is no "1".
One method is:
select a.*
from a
where itemcount = 1 or
not exists (select 1 from a a2 where a2.empid = a.empid and a2.itemcount = 1);
In Oracle, you can use the MAX analytic function:
SELECT Empid,
lotname,
itemcount
FROM (
SELECT t.*,
MAX( itemcount ) OVER ( PARTITION BY Empid ) AS max_itemcount
FROM table_name t
)
WHERE max_itemcount = 1;
So, for you sample data:
CREATE TABLE table_name ( Empid, lotname, itemcount ) AS
SELECT 1, 'A', 1 FROM DUAL UNION ALL
SELECT 1, 'B', 1 FROM DUAL UNION ALL
SELECT 2, 'B', 0 FROM DUAL UNION ALL
SELECT 3, 'B', 1 FROM DUAL UNION ALL
SELECT 3, 'C', 0 FROM DUAL;
This outputs:
EMPID | LOTNAME | ITEMCOUNT
----: | :------ | --------:
1 | A | 1
1 | B | 1
3 | B | 1
3 | C | 0
db<>fiddle here
The analytic function
sum(case when LOTNAME = 'B' /* parameter */ then ITEMCOUNT end) over (partition by EMPID) as lot_itemcnt
calculates for each customer the total number of items with the selected lot.
Feel free to use it as a bind variable, e.g.
sum(case when LOTNAME = ? /* parameter */ then ITEMCOUNT end) over (partition by EMPID) as lot_itemcnt
The whole query is than as follows
with cust as (
select
EMPID, LOTNAME, ITEMCOUNT,
sum(case when LOTNAME = 'B' /* parameter */ then ITEMCOUNT end) over (partition by EMPID) as lot_itemcnt
from tab)
select
EMPID, LOTNAME, ITEMCOUNT
from cust
where lot_itemcnt >= 1;

SQL- Cumulative sum based on condition

I have a scenario in which I have to calculate the counter based on below data. If the status is A, B,C than counter should be 0 which is working fine.
If STATUS is D counter should do a cumulative sum with the exception that if status is changed in between(like in 201907) , the counter should reset again and sum should start again with 1,2,3 and so on. Any possible help is appreciated on same.
Input - 3 columns - Customer_No, Date, Status
CUSTOMER_NO Date STATUS
1234 201901 A
1234 201902 B
1234 201903 C
1234 201904 D
1234 201905 D
1234 201906 D
1234 201907 C
1234 201908 D
1234 201910 D
1234 201911 D
1234 201912 D
expected Output - Input columns + Counter Column
CUSTOMER_NO Date STATUS COUNTER
----------------------------------------
1234 201901 A 0
1234 201902 B 0
1234 201903 C 0
1234 201904 D 1
1234 201905 D 2
1234 201906 D 3
1234 201907 C 0
1234 201908 D 1
1234 201910 D 2
1234 201911 D 3
1234 201912 D 4
Sample data
Thanks
You can create a numbering like a serial number for the ordering purpose using the ROW_NUMBER() function as shown below.
create table SampleData(CUSTOMER_NO int
, STATUS char(1)
, COUNTER int)
insert into SampleData Values
(1234, 'A', 0),
(1234, 'B', 0),
(1234, 'C', 0),
(1234, 'D', 1),
(1234, 'D', 2),
(1234, 'D', 3),
(1234, 'C', 0),
(1234, 'D', 1),
(1234, 'D', 2),
(1234, 'D', 3),
(1234, 'D', 4)
;with cte as(
Select *
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS RN
from SampleData
)
select CUSTOMER_NO
, STATUS
, COUNTER
, (SELECT SUM(case STATUS when 'D' then Counter else 0 end) FROM cte t2 WHERE t2.RN <= cte.RN) AS Needed
from cte
Live db<>fiddle demo.
This is a similar approach this Gordon's, however, uses CTEs and ROW_NUMBER to make the islands first, and then 0's if there is only 1 row in that island using a windowed COUNT and a CASE expression:
WITH Grps AS(
SELECT ID,
CUSTOMER_NO,
[STATUS],
ROW_NUMBER() OVER (PARTITION BY CUSTOMER_NO ORDER BY ID) -
ROW_NUMBER() OVER (PARTITION BY CUSTOMER_NO, [STATUS] ORDER BY ID) AS Grp
FROM (VALUES(1,1234,'A'),
(2,1234,'B'),
(3,1234,'C'),
(4,1234,'D'),
(5,1234,'D'),
(6,1234,'D'),
(7,1234,'C'),
(8,1234,'D'),
(9,1234,'D'),
(10,1234,'D'),
(11,1234,'D'))V(ID,CUSTOMER_NO,[STATUS]))
SELECT ID,
CUSTOMER_NO,
[STATUS],
Grp,
CASE WHEN COUNT(ID) OVER (PARTITION BY CUSTOMER_NO, [STATUS], Grp) = 1 THEN 0
ELSE ROW_NUMBER() OVER (PARTITION BY CUSTOMER_NO, [STATUS], Grp ORDER BY ID) - 1
END AS [COUNTER]
FROM Grps;
As Gordon mentioned, as well, if you don't have some kind of sequential ID/Key, you can't do this with your data. You will need to implement some kind of sequential ID, and hope that your data retains it's "insert order".
This is a variant of a gaps-and-islands problem. For this particular incarnation, you can identify the islands by counting the number of non-D statuses before a given row.
After identifying the groups, use case and row_number():
select t.*,
(case when status = 'D'
then row_number() over (partition by customer_no, grp, status order by date)
else 0
end) as counter
from (select t.*,
sum(case when status <> 'D' then 1 else 0 end) over (partition by customer_no order by date) as grp
from t
) t

Issue with Row_Number() Over Partition

I've been trying to reset the row_number when the value changes on Column Value and I have no idea on how should i do this.
This is my SQL snippet:
WITH Sch(SubjectID, VisitID, Scheduled,Actual,UserId,RLev,SubjectTransactionID,SubjectTransactionTypeID,TransactionDateUTC,MissedVisit,FieldId,Value) as
(
select
svs.*,
CASE WHEN stdp.FieldID = 'FrequencyRegime' and svs.SubjectTransactionTypeID in (2,3) THEN
stdp.FieldID
WHEN stdp.FieldID is NULL and svs.SubjectTransactionTypeID = 1
THEN NULL
WHEN stdp.FieldID is NULL
THEN 'FrequencyRegime'
ELSE stdp.FieldID
END AS [FieldID],
CASE WHEN stdp.Value is NULL and svs.SubjectTransactionTypeID = 1
THEN NULL
WHEN stdp.Value IS NULL THEN
(SELECT TOP 1 stdp.Value from SubjectTransaction st
JOIN SubjectTransactionDataPoint STDP on stdp.SubjectTransactionID = st.SubjectTransactionID and stdp.FieldID = 'FrequencyRegime'
where st.SubjectID = svs.SubjectID
order by st.ServerDateST desc)
ELSE stdp.Value END AS [Value]
from SubjectVisitSchedule svs
left join SubjectTransactionDataPoint stdp on svs.SubjectTransactionID = stdp.SubjectTransactionID and stdp.FieldID = 'FrequencyRegime'
)
select
Sch.*,
CASE WHEN sch.Value is not NULL THEN
ROW_NUMBER() over(partition by Sch.Value, Sch.SubjectID order by Sch.SubjectID, Sch.VisitID)
ELSE NULL
END as [FrequencyCounter],
CASE WHEN Sch.Value = 1 THEN 1--v.Quantity
WHEN Sch.Value = 2 and (ROW_NUMBER() over(partition by Sch.Value, Sch.SubjectID order by Sch.SubjectID, Sch.VisitID) % 2) <> 0
THEN 0
WHEN Sch.Value = 2 and (ROW_NUMBER() over(partition by Sch.Value, Sch.SubjectID order by Sch.SubjectID, Sch.VisitID) % 2) = 0
THEN 1
ELSE NULL
END AS [DispenseQuantity]
from Sch
--left join VisitDrugAssignment v on v.VisitID = Sch.VisitID
where SubjectID = '4E80718E-D0D8-4250-B5CF-02B7A259CAC4'
order by SubjectID, VisitID
This is my Dataset:
Based on the Dataset, I am trying to reset the FrequencyCounter to 1 every time the value changes for each subject, Right now it does 50% of what I want, It is counting when the value 1 or 2 is found, but when value 1 comes again after value 2 it continues the count from where it left. I want every time the value is changes the count to also start from the beginning.
It's difficult to reproduce and test without sample data, but if you want to know how to number rows based on change in column value, next approach may help. It's probably not the best one, but at least will give you a good start. Of course, I hope I understand your question correctly.
Data:
CREATE TABLE #Data (
[Id] int,
[Subject] varchar(3),
[Value] int
)
INSERT INTO #Data
([Id], [Subject], [Value])
VALUES
(1, '801', 1),
(2, '801', 2),
(3, '801', 2),
(4, '801', 2),
(5, '801', 1),
(6, '801', 2),
(7, '801', 2),
(8, '801', 2)
Statement:
;WITH ChangesCTE AS (
SELECT
*,
CASE
WHEN LAG([Value]) OVER (PARTITION BY [Subject] ORDER BY [Id]) <> [Value] THEN 1
ELSE 0
END AS [Change]
FROM #Data
), GroupsCTE AS (
SELECT
*,
SUM([Change]) OVER (PARTITION BY [Subject] ORDER BY [Id]) AS [GroupID]
FROM ChangesCTE
)
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY [GroupID] ORDER BY [Id]) AS Rn
FROM GroupsCTE
Result:
--------------------------------------
Id Subject Value Change GroupID Rn
--------------------------------------
1 801 1 0 0 1
2 801 2 1 1 1
3 801 2 0 1 2
4 801 2 0 1 3
5 801 1 1 2 1
6 801 2 1 3 1
7 801 2 0 3 2
8 801 2 0 3 3
As per my understanding, you need DENSE_RANK as you are looking for the row number will only change when value changed. The syntax will be as below-
WITH your_table(your_column)
AS
(
SELECT 2 UNION ALL
SELECT 10 UNION ALL
SELECT 2 UNION ALL
SELECT 11
)
SELECT *,DENSE_RANK() OVER (ORDER BY your_column)
FROM your_table

pivot multi column and mutil row

this is my origin data:
ID New LikeNew Old Warehouse
1 10 100 20 LA
1 12 130 40 CA
2 90 200 10 LA
2 103 230 30 CA
i want to get the following format:
ID LA_new LA_likeNew LA_Old CA_New CA_LikeNew CA_Old
1 10 100 20 12 130 40
2 90 200 10 103 230 30
I can only pivot on each column but can not do on all 3 columns(New, LikeNew, Old) , so how can I do that?
You can accomplish this by using conditional logic to create your fields and grouping:
select id,
max(case when warehouse = 'LA' then new else null end) LA_New,
max(case when warehouse = 'LA' then likenew else null end) LA_likeNew,
max(case when warehouse = 'LA' then old else null end) LA_Old,
max(case when warehouse = 'CA' then new else null end) CA_New,
max(case when warehouse = 'CA' then likenew else null end) CA_likeNew,
max(case when warehouse = 'CA' then old else null end) CA_Old
from yourtable
group by id
Alternatively, you can use WITH Common Table Expression
DECLARE #T AS TABLE
(
id INT ,
New INT ,
likeNew INT ,
old INT ,
Warehouse VARCHAR(50)
)
INSERT INTO #T
( id, New, likeNew, old, Warehouse )
VALUES ( 1, 10, 100, 20, 'LA' ),
( 1, 12, 130, 40, 'CA' ),
( 2, 90, 200, 10, 'LA' ),
( 2, 103, 230, 30, 'CA' );
WITH cte
AS ( SELECT *
FROM #T
WHERE Warehouse = 'LA'
),
cte1
AS ( SELECT *
FROM #T
WHERE Warehouse = 'CA'
)
SELECT a.id ,
a.new [LA_New] ,
a.likeNew [LA_LikeNew] ,
a.Old [LA_Old] ,
b.new [CA_New] ,
b.likeNew [CA_LikeNew] ,
b.Old [CA_Old]
FROM cte A
JOIN cte1 B ON a.id = b.id
Result:
id LA_New LA_LikeNew LA_Old CA_New CA_LikeNew CA_Old
----------- ----------- ----------- ----------- ----------- ----------- -----------
1 10 100 20 12 130 40
2 90 200 10 103 230 30