Related
I have the following table contents:-
And I need to produce the following output:-
P1 C1
P1 C3
P2 C1
P2 C4
P3 C2
P3 C3
P3 C4
How can I create that list from my table?
Thanks
P
You have to use UnPivot to get the desired result
DECLARE #MyTable TABLE
(Attribute VARCHAR(10) , C1 VARCHAR(10), C2 VARCHAR(10), C3 VARCHAR(10), C4 VARCHAR(10))
INSERT INTO #MyTable VALUES
('P1','X', NULL,'X',NULL), ('P2','X',NULL,NULL,'X'),('P3',NULL,'X','X','X')
SELECT * FROM #MyTable
SELECT uPivot.Attribute, uPivot.Quatr, uPivot.IsMarked
FROM #MyTable Tab1
UNPIVOT
(
IsMarked
for Quatr in (C1, C2, C3, C4)
) uPivot;
SQL FIDDLER EXAMPLE
This should work based off the information provided.
select u.P, -- whatever the column containing p1, p2, etc is called
u.C,
u.Contents -- what is stored in each cell
from xtable
unpivot
(
Contents
for C in (c1, c2, c3)
) u
where u.Contents like 'x'
http://sqlfiddle.com/#!3/4d908/1/0
I pretty much slightly modified the example given in what Ako linked in the comments.
This was the solution I implemented in the end
DECLARE #SQL NVARCHAR(MAX)
DECLARE #ColName TABLE (Name VARCHAR(500))
DECLARE #ColCSV VARCHAR(MAX)
INSERT INTO #ColName
SELECT
Column_Name
FROM
information_schema.columns
WHERE
Table_Name = 'SMC_PatientCondition'
AND Column_Name NOT IN
('ReportingCode',
'P_PatientId',
'P_SysId',
'Condition')
SET #ColCSV = (SELECT SUBSTRING((SELECT ',' + Name FROM #ColName FOR XML PATH ('')),2,2000) AS CSV)
SET #SQL =
('
SELECT
P_SysID,
Conditions
FROM
(
SELECT
P_SysId,
RIGHT(Conditions, LEN(Conditions)-2) Conditions,
HasCondition
FROM
SMC_PatientCondition
UNPIVOT
(
HasCondition FOR Conditions IN ('+ #ColCSV +')
)Con
) UserConditions
WHERE
HasCondition = ''Yes''
')
EXEC sp_executesql #SQL
I have two tables with identical columns, in an identical order. I have a desire to join across one of the two tables, depending on a subquery condition. For example, assume I have the following schema:
CREATE TABLE b (
bid SERIAL PRIMARY KEY,
cid INT NOT NULL
);
CREATE TABLE a1 (
aid SERIAL PRIMARY KEY,
bid INT NOT NULL REFERENCES b
);
CREATE TABLE a2 (
aid SERIAL PRIMARY KEY,
bid INT NOT NULL REFERENCES b
);
I would like a query, that performs a join across either a1 or a2 based on some condition. Something like:
WITH z AS (
SELECT cid, someCondition FROM someTable
)
SELECT *
FROM CASE z.someCondition THEN a1 ELSE a2 END
JOIN b USING (bid)
WHERE cid = (SELECT cid FROM z);
However, the above doesn't work. Is there some way to conditionally join across a1 or a2, depending on some boolean condition stored in table z?
If the conditions are exclusive (I expect they are): just do both queries and UNION ALL them, with the smart union construct:
WITH z AS (
SELECT cid
, (cid %3) AS some_condition -- Fake ...
FROM b
)
SELECT *
FROM a1
JOIN b USING (bid)
WHERE EXISTS( SELECT * FROM z
WHERE some_condition = 1 AND cid = b.cid )
UNION ALL
SELECT *
FROM a2
JOIN b USING (bid)
WHERE EXISTS( SELECT * FROM z
WHERE some_condition = 2 AND cid = b.cid )
;
A somewhat different syntax to do the same:
WITH z AS (
SELECT cid
, (cid %3) AS some_condition
FROM b
)
SELECT *
FROM a1
JOIN b ON a1.bid = b.bid
AND EXISTS( SELECT * FROM z
WHERE some_condition = 1 AND cid = b.cid )
UNION ALL
SELECT *
FROM a2
JOIN b ON a2.bid = b.bid
AND EXISTS( SELECT * FROM z
WHERE some_condition = 2 AND cid = b.cid )
;
SQL syntax does not allow conditional joins.
Probably the simplest way to achieve a similar effect is to use a dynamic query in a plpgsql function, which may look like this:
create function conditional_select(acid int, some_condition boolean)
returns table (aid int, bid int, cid int)
language plpgsql as $$
declare
tname text;
begin
if some_condition then tname = 'a1';
else tname = 'a2';
end if;
return query execute format ($fmt$
select a.aid, b.bid, b.cid
from %s a
join b using(bid)
where cid = %s;
$fmt$, tname, acid);
end $$;
select * from conditional_select(1, true)
If, like in your example, you have only a few columns that you want to output, you can use the CASE statement for every column:
SELECT CASE z.someCondition THEN a1.aid ELSE a2.aid END AS aid,
CASE z.someCondition THEN a1.bid ELSE a2.bid END AS bid
FROM b
JOIN a1 ON a1.bid = b.bid
JOIN a2 ON a2.bid = b.bid
JOIN someTable z USING (cid);
Depending on the size of tables a1 and a2 and how many columns you have to output, this may or my not be faster than Klin's solution with a function, which is inherently slower than plain SQL and even more so because of the dynamic query. Given that z.someCondition is a boolean value already, the CASE evaluation will be very fast. Small tables + few columns = this solution; large tables + many columns = Klin's solution.
This is my T-SQL
select Id,Profile,Type ,
case Profile
when 'Soft' then 'SID'
when 'Hard' then 'HID'
end as [Profile]
from ProductDetail p1
inner join [tableA or tableB] on xxxxxxxx
I want join tableA when Profile = Soft and join tableB when Profile = Hard, how can I do just only using T-SQL in one batch?
Thanks
You can't directly do it, but could achieve the same effect with outer joins
select Id,Profile,Type ,
case Profile
when 'Soft' then 'SID'
when 'Hard' then 'HID'
end as [Profile]
from ProductDetail p1
left outer join tableA ON tableA.x = p1.x AND p1.Profile = 'Soft'
left outer join tableB ON tableB.x = p1.x AND p1.Profile = 'Hard'
where
where
(tableA.x IS NOT NULL and p1.Profile = 'Soft')
or (tableB.x IS NOT NULL and p1.Profile = 'Hard')
Of course, you can choose different tables for inner join operation, but it must be based on some condition or variable.
For Example:
select Id,Profile,Type ,
case Profile
when 'Soft' then 'SID'
when 'Hard' then 'HID'
end as [Profile]
from ProductDetail p1
inner join tableA A
on Profile='Soft'
AND <any other Condition>
UNION
select Id,Profile,Type ,
case Profile
when 'Soft' then 'SID'
when 'Hard' then 'HID'
end as [Profile]
from ProductDetail p1
inner join tableB B
on Profile='Hard'
AND <any other Condition>
You can do this in a single statement with the same or similar case statement in your join. Below is sample code using temp tables that joins to 2 different reference tables merged into a single result set using a UNION
DECLARE #ProductDetail TABLE (Id INT, sProfile VARCHAR(100), StID INT, HdID INT)
DECLARE #TableA TABLE (StId INT, Field1 VARCHAR(100))
DECLARE #TableB TABLE (HdId INT, Field1 VARCHAR(100))
INSERT INTO #ProductDetail (Id, sProfile, StID , HdID ) VALUES (1,'Soft',1,1)
INSERT INTO #ProductDetail (Id, sProfile, StID , HdID ) VALUES (2,'Hard',2,2)
INSERT INTO #TableA (StId,Field1) VALUES (1,'Soft 1')
INSERT INTO #TableA (StId,Field1) VALUES (2,'Soft 2')
INSERT INTO #TableB (HdId,Field1) VALUES (1,'Hard 1')
INSERT INTO #TableB (HdId,Field1) VALUES (2,'Hard 2')
SELECT
p1.Id,p1.sProfile,
CASE
WHEN p1.sProfile = 'Soft' THEN StID
WHEN p1.sProfile = 'Hard' THEN HdId
END AS [Profile]
,ReferenceTable.FieldName
FROM
#ProductDetail p1
INNER JOIN
(
SELECT StID AS id, 'Soft' AS sProfile, Field1 AS FieldName
FROM #TableA AS tableA
UNION ALL
SELECT HdID AS id, 'Hard' AS sProfile, Field1 AS FieldName
FROM #TableB AS tableB
)
AS ReferenceTable
ON
CASE
WHEN p1.sProfile = 'Soft' THEN StID
WHEN p1.sProfile = 'Hard' THEN HdID
END = ReferenceTable.Id
AND p1.sProfile = ReferenceTable.sProfile
This will return the following result set:
Id sProfile Profile FieldName
1 Soft 1 Soft 1
2 Hard 2 Hard 2
MyTable has four columns.
Condition1 | Condition2 | CondEquation | EquationResult
---------------------------------------------------------
1 | 0 | C1 & C2 | 0
1 | 1 | C1 & C2 | 1
EquationResult data is select C1 & C2. It is evaluated expression of CondEquation.
How to update the 4th column using SQL statements.
Is there anyway I can write function for this?
SQL Server 2008 R2
Thank you,
Smith
Sure. But I can only deliver you a solution that is based on a cursor, I hope that's not a problem.
use testing
-- create table test_01 (c1 int, c2 int, ce nvarchar(100), result int)
insert into test_01 (c1, c2, ce) values (1, 0, 'c1 & c2')
insert into test_01 (c1, c2, ce) values (1, 1, 'c1 & c2')
insert into test_01 (c1, c2, ce) values (7, 3, 'c1 & c2')
insert into test_01 (c1, c2, ce) values (2, 4, 'c1 | c2')
declare #eq nvarchar(100)
declare #sql_statement nvarchar(500)
declare c cursor for select ce from test_01
open c
fetch next from c into #eq
while ##fetch_status = 0
begin
-- update test_01 set result = (c1 & c2) where current of c
set #sql_statement = 'update test_01 set result = (' + #eq + ') where current of c'
exec sp_executesql #sql_statement
fetch next from c into #eq
end
close c
deallocate c
select * from test_01
This gives the following results:
c1 c2 ce result
1 0 c1 & c2 0
1 1 c1 & c2 1
7 3 c1 & c2 3
2 4 c1 | c2 6
Here is a script that will show the cEquationResult even when data change in the table, it can only handle the bit operators & and |
Table to represent your table:
create table t_test(condition1 bit, condition2 bit, condition3 bit, CondEquation varchar(20))
insert t_test values(1,0, 0, 'c1&c2|c3')
insert t_test values(1,1, 1, 'c1&c2 | c3')
go
Function to calculate the calculated bit. Yes it IS a mean one:
create function f_t(#condition1 bit, #condition2 bit, #condition3 bit, #CondEquation varchar(10))
returns bit
as
begin
declare #result bit
;with a as
(
select replace(replace(replace(replace(#CondEquation, 'c1',#condition1), 'c2',#condition2), 'c3',#condition3), ' ','') n
),
b as
(
select n, 1 rn from a
union all
select stuff(n, patindex('%&%', n) - 1, 3 , case when substring(n, patindex('%&%', n) - 1, 3) like '%0%' then 0 else 1 end), rn+1
from b
where patindex('%&%', n)> 0
), c as
(
select n from (
select n, row_number() over (order by rn desc) rn2 from b
) a where rn2 = 1
), d as
(
select n, 1 rn from c
union all
select stuff(n, patindex('%|%', n) - 1, 3 , case when substring(n, patindex('%|%', n) - 1, 3) like '%1%' then 1 else 0 end), rn+1
from d
where patindex('%|%', n)> 0
), e as
(
select n from (
select n, row_number() over (order by rn desc) rn2 from d
) a where rn2 = 1
)
select #result=n from e
return #result
end
go
adding the extra field to show the calculated bit
ALTER TABLE t_test ADD cEquationResult AS
dbo.f_t(condition1, condition2, condition3, CondEquation)
Testing the scripts:
select * from t_test
From the table below, how can I convert the Values column into multiple columns, populated with individual values that are currently separated by commas? Before the conversion:
Name Values
---- ------
John val,val2,val3
Peter val5,val7,val9,val14
Lesli val8,val34,val36,val65,val71,val
Amy val3,val5,val99
The result of the conversion should look like:
Name Col1 Col2 Col3 Col4 Col5 Col6
---- ---- ---- ---- ---- ---- ----
John val val2 val3
Peter val5 val7 val9 val14
Lesli val8 val34 val36 val65 val71 val
Amy val3 val5 val99
First, what database product and version are you using? If you are using SQL Server 2005 and later, you can write a Split user-defined function like so:
CREATE FUNCTION [dbo].[Split]
(
#DelimitedList nvarchar(max)
, #Delimiter varchar(2) = ','
)
RETURNS TABLE
AS
RETURN
(
With CorrectedList As
(
Select Case When Left(#DelimitedList, DataLength(#Delimiter)) <> #Delimiter Then #Delimiter Else '' End
+ #DelimitedList
+ Case When Right(#DelimitedList, DataLength(#Delimiter)) <> #Delimiter Then #Delimiter Else '' End
As List
, DataLength(#Delimiter) As DelimiterLen
)
, Numbers As
(
Select TOP (Coalesce(Len(#DelimitedList),1)) Row_Number() Over ( Order By c1.object_id ) As Value
From sys.objects As c1
Cross Join sys.columns As c2
)
Select CharIndex(#Delimiter, CL.list, N.Value) + CL.DelimiterLen As Position
, Substring (
CL.List
, CharIndex(#Delimiter, CL.list, N.Value) + CL.DelimiterLen
, CharIndex(#Delimiter, CL.list, N.Value + 1)
- ( CharIndex(#Delimiter, CL.list, N.Value) + CL.DelimiterLen )
) As Value
From CorrectedList As CL
Cross Join Numbers As N
Where N.Value < Len(CL.List)
And Substring(CL.List, N.Value, CL.DelimiterLen) = #Delimiter
)
You can then split out the values in you want using something akin to:
Select Name, Values
From Table1 As T1
Where Exists (
Select 1
From Table2 As T2
Cross Apply dbo.Split (T1.Values, ',') As T1Values
Cross Apply dbo.Split (T2.Values, ',') As T2Values
Where T2.Values.Value = T1Values.Value
And T1.Name = T2.Name
)
Here is a solution that uses a recursive cte to generate a "table of numbers" (courtesy of Itzik Ben-Gan), which is useful for all manner of problems including string splitting, and PIVOT. SQL Server 2005 onwards. Full table create, insert and select script included.
CREATE TABLE dbo.Table1
(
Name VARCHAR(30),
[Values] VARCHAR(128)
)
GO
INSERT INTO dbo.Table1 VALUES ('John', 'val,val2,val3')
INSERT INTO dbo.Table1 VALUES ('Peter', 'val5,val7,val9,val14')
INSERT INTO dbo.Table1 VALUES ('Lesli', 'val8,val34,val36,val65,val71,val')
INSERT INTO dbo.Table1 VALUES ('Amy', 'val3,val5,val99')
GO
SELECT * FROM dbo.Table1;
GO
WITH
L0 AS(SELECT 1 AS c UNION ALL SELECT 1),
L1 AS(SELECT 1 AS c FROM L0 AS A, L0 AS B),
L2 AS(SELECT 1 AS c FROM L1 AS A, L1 AS B),
L3 AS(SELECT 1 AS c FROM L2 AS A, L2 AS B),
Numbers AS(SELECT ROW_NUMBER() OVER(ORDER BY c) AS n FROM L3)
SELECT Name, [1] AS Column1, [2] AS Column2, [3] AS Column3, [4] AS Column4, [5] AS Column5, [6] AS Column6, [7] AS Column7
FROM
(SELECT Name,
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY nums.n) AS PositionInList,
LTRIM(RTRIM(SUBSTRING(valueTable.[Values], nums.n, charindex(N',', valueTable.[Values] + N',', nums.n) - nums.n))) AS [Value]
FROM Numbers AS nums INNER JOIN dbo.Table1 AS valueTable ON nums.n <= CONVERT(int, LEN(valueTable.[Values])) AND SUBSTRING(N',' + valueTable.[Values], n, 1) = N',') AS SourceTable
PIVOT
(
MAX([VALUE]) FOR PositionInList IN ([1], [2], [3], [4], [5], [6], [7])
) AS Table2
GO
--DROP TABLE dbo.Table1
Which converts this output
Name Values
John val,val2,val3
Peter val5,val7,val9,val14
Lesli val8,val34,val36,val65,val71,val
Amy val3,val5,val99
to
Name Column1 Column2 Column3 Column4 Column5 Column6 Column7
Amy val3 val5 val99 NULL NULL NULL NULL
John val val2 val3 NULL NULL NULL NULL
Lesli val8 val34 val36 val65 val71 val NULL
Peter val5 val7 val9 val14 NULL NULL NULL