Pivoting , Dynamic columns in SQL Server - sql

I have tables like these
LoanPrograms
Id Name
------------
1 LP1
2 LP2
3 LP3
Channels
Id Name
----------
4 Channel1
5 Channel2
6 Channel3
LoanProgramsChannels
LoanProgramId Channelid
----------------------
1 4
1 5
2 4
I wanted to get data like these
LoanProgarmNames channel1 channel2 channel3
---------------- -------- -------- --------
LP1 y y N
LP2 y N N
LP3 N N N
I am quite new to SQL, I know I have to use PIVOT to achieve these, but not sure how can I achieve in these scenario. Can anybody help on this ?

Because you need to "fill in the gaps" the best way is to create a cartesian product using a CROSS JOIN:
;WITH CTE AS (
SELECT A.NAME AS LOANPROGRAMNAME
, B.NAME AS CHANNELNAME
, CASE WHEN C.CHANNELID IS NULL THEN 'N' ELSE 'Y' END AS LOANPROGRAMCHANNELS
FROM LOANPROGRAMS AS A
CROSS JOIN CHANNELS AS B
LEFT JOIN LOANPROGRAMSCHANNELS AS C
ON CAST(A.ID AS VARCHAR)+CAST(B.ID AS VARCHAR) =
CAST(C.LOANPROGRAMID AS VARCHAR)+CAST(C.CHANNELID AS VARCHAR))
SELECT LOANPROGRAMNAME, [CHANNEL1], [CHANNEL2], [CHANNEL3]
FROM CTE
PIVOT(MAX(LOANPROGRAMCHANNELS) FOR CHANNELNAME IN ([CHANNEL1], [CHANNEL2], [CHANNEL3])) PIV
Once you've got the condition (Y/N) you can pivot the required columns, as done here.

There might be better ways to code this, but I believe the following will provide what you want:
declare #tbLoanPrograms table (
ID int, Name varchar(12)
)
insert into #tbLoanPrograms (
ID , Name
)
values (1, 'LP1')
, (2, 'LP2')
, (3, 'LP3')
declare #tbChannels table (
ID int, Name varchar(12)
)
insert into #tbChannels (
ID , Name
)
values (4, 'Channel1')
, (5, 'Channel2')
, (6, 'Channel3')
declare #tbLoanProgramsChannels table (
LoanProgramId int, Channelid int
)
insert into #tbLoanProgramsChannels (
LoanProgramId , Channelid
)
values (1, 4)
, (1, 5)
, (2, 4)
select
t.Name
, Channel1 = max(Channel1)
, Channel2 = max(Channel2)
, Channel3 = max(Channel3)
from (
select
lp.Name
, Channel1 =
case
when lpc.LoanProgramId is not null and lpc.Channelid = 4
then 'y'
else 'N'
end
, Channel2 =
case
when lpc.LoanProgramId is not null and lpc.Channelid = 5
then 'y'
else 'N'
end
, Channel3 =
case
when lpc.LoanProgramId is not null and lpc.Channelid = 6
then 'y'
else 'N'
end
from
#tbLoanPrograms lp
left join #tbLoanProgramsChannels lpc on lpc.LoanProgramId = lp.ID
left join #tbChannels c on c.ID = lpc.Channelid
) t
group by t.Name

Related

How to get the result in below format in SQL Server?

I have a table called recipes with following data.
page_no title
-----------------
1 pancake
2 pizza
3 pasta
5 cookie
page_no 0 is always blank, and missing page_no are blank, I want output as below, for the blank page NULL values in the result.
left_title right_title
------------------------
NULL pancake
Pizza pasta
NULL cookie
I have tried this SQL statement, but it's not returning the desired output:
SELECT
CASE WHEN id % 2 = 0
THEN title
END AS left_title,
CASE WHEN id %2 != 0
THEN title
END AS right_title
FROM
recipes
You are quite close. You just need aggregation:
select max(case when id % 2 = 0 then title end) as left_title,
max(case when id % 2 = 1 then title end) as right_title
from recipes
group by id / 2
order by min(id);
SQL Server does integer division, so id / 2 is always an integer.
Using CTE.. this should be give you a good CTE overview
DECLARE #table TABLE (
pageno int,
title varchar(30)
)
INSERT INTO #table
VALUES (1, 'pancake')
, (2, 'pizza')
, (3, 'pasta')
, (5, 'cookie')
;
WITH cte_pages
AS ( -- generate page numbers
SELECT
0 n,
MAX(pageno) maxpgno
FROM #table
UNION ALL
SELECT
n + 1 n,
maxpgno
FROM cte_pages
WHERE n <= maxpgno),
cte_left
AS ( --- even
SELECT
n,
ROW_NUMBER() OVER (ORDER BY n) rn
FROM cte_pages
WHERE n % 2 = 0),
cte_right
AS ( --- odd
SELECT
n,
ROW_NUMBER() OVER (ORDER BY n) rn
FROM cte_pages
WHERE n % 2 <> 0)
SELECT
tl.title left_title,
tr.title right_title --- final output
FROM cte_left l
INNER JOIN cte_right r
ON l.rn = r.rn
LEFT OUTER JOIN #table tl
ON tl.pageno = l.n
LEFT OUTER JOIN #table tr
ON tr.pageno = r.n

SQL Anywhere: find rows that are +-2 compared to another row

I have the following table:
ID User Form Depth
1 A ABC 2001
1 A XYZ 1001
1 B XYZ 1003
1 B DEF 3001
1 C XYZ 1000
If ID and Form are identical, I need to identify those rows that are +-2 from User A. Using the example above, the script would return:
ID User Form Depth
1 B XYZ 1003
1 C XYZ 1000
I already have a script which identifies rows with identical ID and Form--I just need the other part, but I'm struggling with figuring out the logic. I was hoping there was some kind of DIFF function I could use, but I can't find one for SQL Anywhere.
Does anyone have any suggestions?
Thanks!
If you're looking for the depth to be exactly +/-2 from A's depth:
select t1.*
from mytab t1,
mytab t2
where t1.id = t2.id
and t1.form = t2.form
and t1.user != 'A'
and t2.user = 'A'
and abs(t1.depth - t2.depth) = 2
go
ID User Form Depth
--- ----- ----- -----
1 B XYZ 1003
If you're looking for the depth to be within 2 of A's depth (ie, diff <= 2):
select t1.*
from mytab t1,
mytab t2
where t1.id = t2.id
and t1.form = t2.form
and t1.user != 'A'
and t2.user = 'A'
and abs(t1.depth - t2.depth) <= 2
go
ID User Form Depth
--- ----- ----- -----
1 B XYZ 1003
1 C XYZ 1000
This is pretty basic SQL so while this fiddle was done with MySQL, you should find the queries work in SQLAnywhere, too: sql fiddle
I think you want exists:
select t.*
from t
where t.user <> 'A' and
exists (select 1
from t t2
where t2.form = t.form and t2.id = t.id and
t2.depth between t.depth - 2 and t.depth + 2
);
A quick and dirty generalized method.
Replace #User with whomever you would like to remove.
DECLARE #table TABLE (
ID Int
,[User] VARCHAR(2)
,Form VARCHAR(3)
,Depth INT
)
DECLARE #User VARCHAR(2) = 'A'
INSERT INTO #table (ID , [User], Form, Depth)
VALUES
(1 , 'A' , 'ABC' , 2001),
(1 , 'A' , 'XYZ' , 1001),
(1 , 'B' , 'XYZ' , 1003),
(1 , 'B' , 'DEF' , 3001),
(1 , 'C' , 'XYZ' , 1000)
SELECT t1.ID, t1.[User], t1.Form, t1.Depth , ROW_NUMBER() OVER(ORDER BY t1.ID, t1.[User], t1.Form, t1.Depth) AS [row_number]
INTO #temp
FROM #table as t1
INNER JOIN (
SELECT t.ID, t.Form, COUNT('8') as [count]
FROM #table as t
GROUP BY ID, Form
HAVING COUNT('8') > 1
) as duplicates
ON duplicates.ID = t1.ID
AND duplicates. Form = t1.Form
ORDER BY ID, User, Form, Depth
-- SELECT * FROM #temp
SELECT [row_number] - 2 as value
INTO #range
FROM #temp as t
WHERE t.[User] = #User
--SELECT * FROM #range
INSERT INTO #range
SELECT [row_number] - 1
FROM #temp as t
WHERE t.[User] = #User
INSERT INTO #range
SELECT [row_number] + 1
FROM #temp as t
WHERE t.[User] = #User
INSERT INTO #range
SELECT [row_number] + 2
FROM #temp as t
WHERE t.[User] = #User
SELECT * FROM #temp
WHERE [row_number] IN (SELECT value FROM #range)
DROP TABLE #temp
DROP TABLE #range

i want to display data according to type in same query

i have following table "vehicle_data" :
ID ALERT_TYPE VALUE
58 2 1
58 1 1
104 1 1
104 2 1
Here alert_type = 2 is for GPS value and alert_type=1 is for engine_value .
so if alert_type=2 and its value is =1 then it means its value is correct.
when alert_type=2 and its value is =0 then it means its value is wrong.
same for alert_type=1
so now here i want the following output:
ID gps engine_value
58 1 1
104 1 1
how can i perform this query??
You can do it like this.
SELECT ID
,CASE WHEN [ALERT_TYPE]=2 and [value ]=1 THEN 1 ELSE 0 END as gps
,CASE WHEN [ALERT_TYPE]=1 and [value ]=1 THEN 1 ELSE 0 END as engine
FROM vehicle_data
SELECT ID, alert_type=2 AS gps, alert_type=1 AS [engine] FROM vehicle_data WHERE value=1;
EDITED to account for your explanation of VALUE.
Schema
CREATE TABLE table3 (id int, ALERT_TYPE int)
INSERT table3 VALUES (58, 1), (58, 2), (104, 1), (104, 2)
Query
SELECT *
FROM (
SELECT ID
,ROW_NUMBER() OVER (
PARTITION BY id ORDER BY id
) AS row_num
,gps = CASE
WHEN ALERT_TYPE = 2
THEN 1
ELSE 0
END
,engine = CASE
WHEN ALERT_TYPE = 1
THEN 1
ELSE 0
END
FROM table3
) a
WHERE a.row_num = 1
Output
ID gps engine
58 1 0
104 0 1
One possible way using subqueries :
select
Ids.ID
, gps.VALUE 'gps'
, engine_value.VALUE 'engine_value'
from (select distinct ID from vehicle_data) Ids
left join
(select ID, VALUE from vehicle_data where ALERT_TYPE = 2) gps
on gps.ID = Ids.ID
left join
(select ID, VALUE from vehicle_data where ALERT_TYPE = 1) engine_value
on engine_value.ID = Ids.ID
[SQL Fiddle demo]
I hope this should work for you,
Select ID,sum(gps) as gps ,sum(engine) as engine from
(SELECT ID
,CASE WHEN [ALERT_TYPE]=2 and [value ]=1 THEN 1 ELSE 0 END as gps
,CASE WHEN [ALERT_TYPE]=1 and [value ]=1 THEN 1 ELSE 0 END as engine
FROM vehicle_data
)a
group by id
select x.id,x.alert_type as GPS,x.value as engine_value from (
select ID,alert_type,value,ROW_NUMBER() over (partition by id order by alert_type ) as Rnk from mytable
)x
where Rnk=1
Please check this query in SQL :
create table mytable (id int, alert_type int, value int);
insert into mytable (id, alert_type, value)
values (58, 2, 1),
(58, 1, 1),
(104, 1, 1),
(104, 2, 1);
SELECT distinct ID
,(select count (id) from mytable mt where mt.id=mytable.id and mt.[ALERT_TYPE]=2 and mt.[value ]=1) as gps
,(select count (id) from mytable mt where mt.id=mytable.id and mt.[ALERT_TYPE]=1 and mt.[value ]=1) as engine
FROM mytable
BASED ON YOUR QUESTION I BELIEVE YOU WANT THE DATA IN COLUMN AND TO SUIT YOUR REQUIREMENT I HAVE MADE A SQL FIDDLE WORKING - CODE IS ALSO MENTIONED BELOW -
HERE YOU GO WITH THE WORKING FIDDLE -
WORKING DEMO
SQL CODE FOR REFERNECE -
CREATE TABLE ALERTS (ID INT, ALERT_TYPE INT, VALUE INT)
INSERT INTO ALERTS VALUES (58,2,1)
INSERT INTO ALERTS VALUES (58,1,0)
INSERT INTO ALERTS VALUES (104,1,1)
INSERT INTO ALERTS VALUES (104,2,0)
CREATE TABLE ALERTSVALUE (ID INT, gps INT,engine INT)
INSERT INTO ALERTSVALUE VALUES (58,1,0)
INSERT INTO ALERTSVALUE VALUES (104,0,1)
SELECT A.ID,
CASE A.ALERT_TYPE WHEN 2 THEN 1 ELSE 0 END AS GPS,
CASE A.ALERT_TYPE WHEN 1 THEN 1 ELSE 0 END AS ENGINE_VALUE,
A.VALUE FROM ALERTS A WHERE A.VALUE = 1
EDIT BASED ON COMMENT - TO MERGE THE ROWS FOR BOTH GPS AND ENGINE_VALUE:
SELECT X.ID,X.ALERT_TYPE as GPS,X.VALUE as ENGINE_VALUE
FROM (
SELECT ID,ALERT_TYPE ,VALUE ,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY alert_type ) AS [Rank] FROM ALERTS
)X
WHERE [Rank]=1
SQL FIDDLE DEMO

SQL sub query with complex criteria

I have a table like this:
TransId. LayerNo. AccountId.
100. 1. 2.
100. 2. 3.
120. 1. 5.
120. 2. 6.
120. 3. 12.
70. 1. 2.
I want to find transId(s) where:
(LayerNo = 1 and (accountId = 2 or 5))
and
(LayerNo = 2 and (accountId = 3 or 6))
And result set would be row no 1,2,3,4.
How could I write query to get the result?
My database is SQL server 2008 r2
Thanks in advance
Nima
SELECT TransId
FROM your_table
WHERE ( layerno = 1
AND accountid IN ( 2, 5 ) )
INTERSECT
SELECT TransId
FROM your_table
WHERE ( layerno = 2
AND accountid IN ( 3, 6 ) )
One approach is to ensure that each transID must have two records that satisfy the conditions you outlined.
SELECT * FROM
TABLE
WHERE TransID IN(
SELECT TransId
FROM table
WHERE ( layerno = 1
AND accountid IN ( 2, 5 ) )
OR ( layerno = 2
AND accountid IN( 3, 6 ) )
GROUP BY
TransId
HAVING Count(*) = 2
)
However this could be a problem if you can have multple records where layerno = 1. So you can use self joins instead to ensure the criteria.
SELECT DISTINCT a.transid
FROM table a
INNER JOIN table b
ON a.transid = b.transid
INNER JOIN table c
ON a.transid = c.transid
WHERE b.layerno = 1
AND accountid IN ( 2, 5 )
AND c.layerno = 2
AND accountid IN ( 3, 6 )
That said Martin's INTERSECT approach is probably the best
Do you mean:
SELECT
TransId,
LayerNo,
AccountId
FROM Table
WHERE (LayerNo = 1 AND AccountId IN (2, 5)) OR
(LayerNo = 2 AND AccountId IN (3, 7))
create table #temp
( rowId Int Identity(1,1), transId int)
INSERT INTO #temp(transId)
select TransId
from TableName
where (layerNo = 1 and accountID IN (2, 5))
OR (layerNo = 2 and accountId IN (3, 6))
select * from #temp
SELECT
base.TransId,
base.LayerNo,
base.AccountId
FROM TableX AS base
JOIN TableX AS a
ON a.TransId = base.TransId
AND a.LayerNo = 1 AND a.AccountId IN (2, 5)
JOIN TableX AS b
ON b.TransId = base.TransId
AND b.LayerNo = 2 AND b.AccountId IN (3, 7)
WHERE (base.LayerNo = 1 AND base.AccountId IN (2, 5))
OR (base.LayerNo = 2 AND base.AccountId IN (3, 7))
This intersection is empty. If you take the values where LayerNo = 1 and LayerNo = 2 and intersect them their intersection is empty because these events are mutually exclusive. I believe this error comes from how the question was originally stated. I might be wrong but the predicate should have been
(LayerNo = 1 and (accountId = 2 or 5)) OR (LayerNo = 2 and (accountId = 3 or 6))
Replace the AND with an OR. If the predicate was stated correctly then the intersect is correct but will always be empty.
SELECT *
FROM table
WHERE (LayerNo = 1 AND (AccountID = 2 OR AccountID = 5))
OR (LayerNo = 2 AND (AccountID = 3 OR AccountID = 6))

Stuck on this union / except

Trying to find the best way to proceed with this, for some reason it is really tripping me up.
I have data like this:
transaction_id(pk) decision_id(pk) accepted_ind
A 1 NULL
A 2 <blank>
A 4 Y
B 1 <blank>
B 2 Y
C 1 Y
D 1 N
D 2 O
D 3 Y
Each transaction is guaranteed to have decision 1
There can be multiple decision possibilities (what-if's) type of scenarios
Accepted can have multiple values or be blank or NULL but only one can be accepted_ind = Y
I am trying to write a query to:
Return one row for each transaction_id
Return the decision_id where the accepted_ind = Y or if the transaction has no rows accepted_ind = Y, then return the row with decision_id = 1 (regardless of value in the accepted_ind)
I have tried:
1. Using logical "or" to pull the records, kept getting duplicates.
2. Using a union and except but can not quite get the logic down correctly.
Any assistance is appreciated. I am not sure why this is tripping me up so much!
Adam
Try this. Basically the WHERE clause says:
Where Accepted = 'Y'
OR
There is no accepted row for this transaction and the decision_id = 1
SELECT Transaction_id, Decision_ID, Accepted_id
FROM MyTable t
WHERE Accepted_ind = 'Y'
OR (NOT EXISTS (SELECT 1 FROM MyTable t2
WHERE Accepted_ind = 'Y'
and t2.Transaction_id = t.transaction_id)
AND Decision_id = 1)
This approach uses ROW_NUMBER() and therefore will only work on SQL Server 2005 or later
I have modified your sample data as as it stands, all transaction_id have a Y indicator!
DECLARE #t TABLE (
transaction_id NCHAR(1),
decision_id INT,
accepted_ind NCHAR(1) NULL
)
INSERT #t VALUES
( 'A' , 1 , NULL ),
( 'A' , 2 , '' ),
( 'A' , 4 , 'Y' ),
( 'B' , 1 , '' ),
( 'B' , 2 , 'N' ), -- change from your sample data
( 'C' , 1 , 'Y' ),
( 'D' , 1 , 'N' ),
( 'D' , 2 , 'O' ),
( 'D' , 3 , 'Y' )
And here is the query itself:
SELECT transaction_id, decision_id, accepted_ind FROM (
SELECT transaction_id, decision_id, accepted_ind,
ROW_NUMBER() OVER (
PARTITION BY transaction_id
ORDER BY
CASE
WHEN accepted_ind = 'Y' THEN 1
WHEN decision_id = 1 THEN 2
ELSE 3
END
) rn
FROM #t
) Raw
WHERE rn = 1
Results:
transaction_id decision_id accepted_ind
-------------- ----------- ------------
A 4 Y
B 1
C 1 Y
D 3 Y
The ROW_NUMBER() clause gives a 'priority' to each criterion you mention; we then ORDER BY to pick the best, and take the first row.
There's probably a neater/more efficient query, but I think this will get the job done. It assumes the table name is Decision:
SELECT CASE
WHEN accepteddecision.transaction_id IS NOT NULL THEN
accepteddecision.transaction_id
ELSE firstdecision.transaction_id
END AS transaction_id,
CASE
WHEN accepteddecision.decision_id IS NOT NULL THEN
accepteddecision.decision_id
ELSE firstdecision.decision_id
END AS decision_id,
CASE
WHEN accepteddecision.accepted_ind IS NOT NULL THEN
accepteddecision.accepted_ind
ELSE firstdecision.accepted_ind
END AS accepted_ind
FROM decision
LEFT OUTER JOIN (SELECT *
FROM decision AS accepteddecision
WHERE accepteddecision.accepted_ind = 'Y') AS
accepteddecision
ON accepteddecision.transaction_id = decision.transaction_id
LEFT OUTER JOIN (SELECT *
FROM decision AS firstdecision
WHERE firstdecision.decision_id = 1) AS firstdecision
ON firstdecision.transaction_id = decision.transaction_id
GROUP BY accepteddecision.transaction_id,
firstdecision.transaction_id,
accepteddecision.decision_id,
firstdecision.decision_id,
accepteddecision.accepted_ind,
firstdecision.accepted_ind
Out of interest, the following uses UNION and EXCEPT (plus a JOIN) as specified in the question title:
WITH T AS (SELECT * FROM (
VALUES ('A', 1, NULL),
('A', 2, ''),
('A', 4, 'Y'),
('B', 1, ''),
('B', 2, 'Y'),
('C', 1, 'Y'),
('D', 1, 'N'),
('D', 2, 'O'),
('D', 3, 'Y'),
('E', 2, 'O'), -- smaple data extended
('E', 1, 'N') -- smaple data extended
) AS T (transaction_id, decision_id, accepted_ind)
)
SELECT *
FROM T
WHERE accepted_ind = 'Y'
UNION
SELECT T.*
FROM (
SELECT transaction_id
FROM T
WHERE decision_id = 1
EXCEPT
SELECT transaction_id
FROM T
WHERE accepted_ind = 'Y'
) D
JOIN T
ON T.transaction_id = D.transaction_id
AND T.decision_id = 1;