TSQL Select query that generates output similar to the ms access multiple value fields - sql

I have tables in SQL Server 2008 such as:
TopicTable
TopicID: nvarchar (Primary Key)
ProgID: nvarchar
topic1: bit
topic2: bit
topic3: bit
topic4: bit
The topic table looks something like the following:
TopicID ProgID topic1 topic2 topic3 topic4
topic001 prog001 1 1 0 0
topic002 prog002 1 0 1 1
topic003 prog003 1 0 0 0
topic004 prog004 1 1 1 1
Program table:
ProgID: nvarchar (Primary Key)
ProgramName: nvarchar
The Program table looks like this:
ProgID ProgramName
prog001 programA
prog002 programB
prog003 programC
prog004 programD
I want to create a view to get the output like:
ProgID ProgramName Topic
prog001 programA topic1,topic2
prog002 programB topic1,topic3,topic4
prog003 programB topic1
prog004 programD topic1,topic2,topic3,topic4
Please can someone help me how to get this.
Thank You.

It would look like this:
;WITH cteTopics AS (
SELECT T.ProgID
,STUFF((
SELECT T1.TopicID + ','
FROM TopicTable T1
WHERE T1.ProgID = T.ProgID
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR (MAX)')
,1,0,'') [Topics]
FROM TopicTable T
GROUP BY T.ProgID)
SELECT P.ProgID, P.ProgramName, T.Topics
FROM Program P
LEFT JOIN cteTopics T ON T.ProgID = P.ProgID
http://sqlfiddle.com/#!3/b9e4a/4

You will have some extra commas here but you can tweak the code a little bit more to eliminate the extra commas . this is what I have got for you so far
CREATE Table Topic (Topic NVARCHAR(20), Programe NVARCHAR(20), Topic1 bit, Topic2 bit,Topic3 bit,Topic4 bit)
GO
INSERT INTO Topic
VALUES
('topic001','prog001',1,1,0,0),
('topic002','prog002',1,0,1,1),
('topic003','prog003',1,0,0,0),
('topic004','prog004',1,1,1,1)
GO
CREATE TABLE Programe (P_ID NVARCHAR(20) , Name NVARCHAR(20))
GO
INSERT INTO Programe VALUES
('prog001','programA'),
('prog002','programB'),
('prog003','programC'),
('prog004','programD')
GO
View Definition
CREATE VIEW vw_ViewName
AS
SELECT P_ID, Name, ISNULL(STUFF(L1.Topic1L, 1, 1 , '') + ', ', '') + ISNULL(STUFF(L2.Topic2L, 1, 1, '') + ', ', '')
+ ISNULL( STUFF(L3.Topic3L, 1, 1, '')+ ', ', '') + ISNULL(STUFF(L4.Topic4L, 1, 1, '')+ ', ', '') AS Topics
FROM Programe P CROSS APPLY (
SELECT ' ' + CASE WHEN Topic1 = 1 THEN 'Topic1' ELSE NULL END [text()]
FROM Topic
WHERE Programe = P.P_ID
FOR XML PATH('')
)L1(Topic1L)
CROSS APPLY (
SELECT ', ' + CASE WHEN Topic2 = 1 THEN 'Topic2' ELSE NULL END [text()]
FROM Topic
WHERE Programe = P.P_ID
FOR XML PATH('')
)L2(Topic2L)
CROSS APPLY (
SELECT ', ' + CASE WHEN Topic3 = 1 THEN 'Topic3' ELSE NULL END [text()]
FROM Topic
WHERE Programe = P.P_ID
FOR XML PATH('')
)L3(Topic3L)
CROSS APPLY (
SELECT ', ' + CASE WHEN Topic4= 1 THEN 'Topic4' ELSE NULL END [text()]
FROM Topic
WHERE Programe = P.P_ID
FOR XML PATH('')
)L4(Topic4L)
RESULT SET
P_ID Name Topics
prog001 programA Topic1, Topic2,
prog002 programB Topic1, Topic3, Topic4,
prog003 programC Topic1,
prog004 programD Topic1, Topic2, Topic3, Topic4,

Related

MSSQL - GROUP_CONCAT

Here is the sample data :
IdProduit Localisation Qte_EnMain
4266864286880063006 E2-R40-B-T 13.00000
4266864286880063006 E2-R45-B-T 81.00000
4266864286880063007 E2-R45-C-T 17.00000
4266864286880063008 E2-R37-B-T 8.00000
And this is what i would like to have
IdProduit AllLocalisation
4266864286880063006 E2-R40-B-T (13), E2-R45-B-T (81)
4266864286880063007 E2-R45-C-T (17)
4266864286880063008 E2-R37-B-T (8)
I watched all the examples of GROUP_CONCAT on the forum and I tried several tests.
I don't really understand STUFF().
Here is what i would like to do :
SELECT
a.IdProduit,
GROUP_CONCAT(
CONCAT(b.Localisation, ' (', CAST(ROUND(a.Qte_EnMain, 0) AS NUMERIC(36, 0)), ')')
) AS AllLocation
FROM
ogasys.INV_InventENTLoc a
LEFT JOIN ogasys.INV_LocName b ON a.IdLoc = b.IdLoc
GROUP BY a.IdProduit, b.Localisation, a.Qte_EnMain
Now because GROUP_CONCAT is nto working with MSSQL this is the query i have created with all example on this forum.
SELECT
DISTINCT
a1.IdProduit,
STUFF((SELECT DISTINCT '' + b2.Localisation
FROM
ogasys.INV_InventENTLoc a2
LEFT JOIN ogasys.INV_LocName b2 ON a2.IdLoc = b2.IdLoc
WHERE a2.IdLoc = a1.IdLoc
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 0, '') data
FROM
ogasys.INV_InventENTLoc a1
LEFT JOIN ogasys.INV_LocName b1 ON a1.IdLoc = b1.IdLoc
ORDER BY a1.IdProduit
The query only return one localisation by row i don't understand how to make this query working.
EDIT:
Here is the solution for my situation :
SELECT
a.IdProduit,
STUFF(
(SELECT ', ' + b2.Localisation + ' (' + CAST(CAST(ROUND(a2.Qte_EnMain, 0) AS NUMERIC(36, 0)) AS VARCHAR(32)) + ')'
FROM ogasys.INV_InventENTLoc a2
LEFT JOIN ogasys.INV_LocName b2 ON a2.IdLoc = b2.IdLoc
WHERE a.IdProduit = a2.IdProduit
FOR XML PATH (''))
, 1, 1, '') AS AllLocalisation
FROM
ogasys.INV_InventENTLoc a
LEFT JOIN ogasys.INV_LocName b ON a.IdLoc = b.IdLoc
GROUP BY a.IdProduit
using STUFF
declare #table table (IdProduit varchar(100), Localisation varchar(50), Qte_EnMain float)
insert into #table
values
('4266864286880063006','E2-R40-B-T', 13.00000),
('4266864286880063006','E2-R45-B-T', 81.00000),
('4266864286880063007','E2-R45-C-T', 17.00000),
('4266864286880063008','E2-R37-B-T', 8.00000)
select IdProduit,
STUFF (
(SELECT
',' + localisation + concat(' (',cast(qte_enMain as varchar(4)),') ')
FROM #table t2
where t2.IdProduit = t1.IdProduit
FOR XML PATH('')), 1, 1, ''
)
from #table t1
group by
IdProduit

How can I join together this querys sql server?

SQL FIDDLE DEMO HERE
I have this table structure for Workers table:
CREATE TABLE Workers
(
[Name] varchar(250),
[IdWorker] varchar(250),
[work] varchar(250)
);
INSERT INTO Workers ([Name], [IdWorker], [work])
values
('Sam', '001', 'Director'),
('Julianne', '002', 'Recepcionist'),
('Jose', '003', 'Recepcionist');
What I want is to get for each job the name of workers separate by commas, like this:
Director Recepcionist
------- ------------
Sam Julianne, Jose
I tried to used this query:
DECLARE #rec VARCHAR(MAX)
SELECT #rec = COALESCE(#rec + ', ', '') + Name from
Workers where job = 'Recepcionist' SELECT #dir AS Recepcionist
And I got this result:
Recepcionist
------------
Julianne, Jose
This works only for one job, but I need to add more, so I tried then to use this query:
SELECT [Director] , [Recepcionist]
FROM
(SELECT [job], [Name],RANK() OVER (PARTITION BY [job] ORDER BY [job],[Name]) as rnk
FROM Workers ) p
PIVOT(
Min([Name])
FOR [job] IN
( [Director] , [Recepcionist] )
) AS pvt
And I get this result:
Director Recepcionist
-------- ------------
Sam Julianne
Jose
I need to get the results in the same row separate by commas, how can I combine the two querys?
I accept suggestions, thanks.
I am assuming in your example query you meant job to reference the work column. The following query should do the job as per your sql fiddle.
SELECT STUFF(
(
SELECT ', ' + cast([Name] as varchar(max))
FROM Workers
WHERE [work] = 'Recepcionist'
FOR XML PATH('')
), 1, 2, ''
) AS Recepcionist
,STUFF(
(
SELECT ', ' + cast([Name] as varchar(max))
FROM Workers
WHERE [work] = 'Director'
FOR XML PATH('')
), 1, 2, '') AS Director;
the way you would do this using PIVOT would be like this.
SELECT *
FROM (SELECT [work],
STUFF((SELECT ', ' + [Name]
FROM Workers s
WHERE s.WORK = w.WORK
FOR XML PATH('')),
1, 2, '') AS [workers]
FROM Workers w) t
PIVOT (
MAX([workers])
FOR [work] IN ([Director], [Recepcionist])
) p
another alternative to PIVOT is MAX(CASE)
SELECT MAX(CASE WHEN [work] = 'Director' THEN [workers] END) AS [Director],
MAX(CASE WHEN [work] = 'Recepcionist' THEN [workers] END) AS [Recepcionist]
FROM (SELECT [work],
STUFF((SELECT ', ' + [Name]
FROM Workers s
WHERE s.WORK = w.WORK
FOR XML PATH('')),
1, 2, '') AS [workers]
FROM Workers w) t
both of these allow you to separate the data by other fields like Company or Department
you can do it like this
SELECT
t1.job
,STUFF(
(SELECT
', ' + t2.Name
FROM Workers t2
WHERE t1.job =t2.job
ORDER BY t2.Name
FOR XML PATH(''), TYPE
).value('.','varchar(max)')
,1,2, ''
) AS ChildValues
FROM Workers t1
GROUP BY t1.job

How to read value inside a cursor?

There are two tables.
One table contains:
Name value
A 1
B 2
C 3
D 4
another table contains
City value
aa 1
bb 2,3
cc 3
dd 1,2,4
I want an output which contains:
City value Name
aa 1 A
bb 2,3 B,C
cc 3 C
dd 1,2,4 A,B,D
How can i do this using cursor?
Thanks. Your question really made me appreciate normal forms.
Anyhow, I am going to go out on a limb and assume you asked for a cursor-based solution because you assumed the non-normalized data could not be handled.
Once you have the function to materialize the rows into a value list, you can solve this with a simple query.
Given:
CREATE TABLE dbo.NV (Name CHAR(1), Value INT)
CREATE TABLE dbo.CV (City varchar(88), ValueList VARCHAR(88))
loaded with the data you indicated.
And this SQL script:
GO
CREATE FUNCTION dbo.f_NVList(#VList VARCHAR(MAX)) RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #VAL VARCHAR(928)='',
#FIDescr VARCHAR(55)
SELECT #VAL = COALESCE(#VAL + LTRIM(map.name),'') + ','
FROM dbo.nv Map
WHERE CHARINDEX(','+LTRIM(STR(map.value)) + ',', ','+#VList + ',' ) > 0
SET #VAL = SUBSTRING(#VAL,1,len(#VAL)-1)
RETURN(#VAL)
END
GO -- end of function
-- this generates the output, using the function to materialize the name-values
SELECT cv.* , dbo.f_NVList(cv.ValueList ) as NameList FROM dbo.CV cv;
producing your output:
PLEASE DON'T - but If you really need the cursor for some reason, instead of
SELECT cv.* , dbo.f_NVList(cv.ValueList ) as NameList FROM dbo.CV cv;
use this
OPEN BadIdea;
FETCH NEXT FROM BadIdea INTO #C, #VList
WHILE ##FETCH_STATUS = 0
BEGIN
SET #NameList = dbo.f_NVList(#Vlist)
INSERT INTO #OUT VALUES( #C, #VLIST , #NameList )
FETCH NEXT FROM BadIdea INTO #C, #VList
END
CLOSE BadIdea
DEALLOCATE BadIdea
select * from #OUT ;
Please give a try on this:
;with nv as (
select *
from (values ('A', '1'), ('B', '2'), ('C', '3'), ('D', '4')) a (Name, value))
, cv as (
select *
from (values ('aa', '1'), ('bb', '2,3'), ('cc', '3'), ('dd', '1,2,4')) a(City, value)
)
, cv2 as (
select cv.City
, case when charindex(',',cv.value)>0 then LEFT(cv.value, charindex(',',cv.value)-1) else cv.value end value
, case when charindex(',',cv.value)>0 then right(cv.value, LEN(cv.value)-len(LEFT(cv.value, charindex(',',cv.value)-1)+',')) end leftover
from cv
union all
select cv.City
, case when charindex(',',cv.leftover)>0 then LEFT(cv.leftover, charindex(',',cv.leftover)-1) else cv.leftover end value
, case when charindex(',',cv.leftover)>0 then right(cv.leftover, LEN(cv.leftover)-len(LEFT(cv.leftover, charindex(',',cv.leftover)-1)+',')) end leftover
from cv2 cv
where cv.leftover is not null
)
select *
, stuff((
select ','+nv.Name
from cv2
join nv on nv.value=cv2.value
where cv2.City=cv.City
for xml path('')
), 1, 1, '') Name
from cv
With cv2 I split the values to City, with a recursive CTE. After that I calculate the new Name for each City.
I don't know how fast is on a big table, but I think it is better then cursor.
using CROSS APPLY we will initially delimit all the values and then we can acheieve using XML path () and CTE's
DECLARE #Name table (name varchar(5),value int)
INSERT INTO #Name (name,value)values ('A',1),('B',2),('C',3),('D',4)
DECLARE #City table (city varchar(10),value varchar(10))
INSERT INTO #City (city,value)values ('aa','1'),('bb','2,3'),('cc','3'),('dd','1,2,4')
Code :
;with CTE AS (
SELECT A.city,
Split.a.value('.', 'VARCHAR(100)') AS Data
FROM
(
SELECT city,
CAST ('<M>' + REPLACE(value, ',', '</M><M>') + '</M>' AS XML) AS Data
FROM #City
) AS A CROSS APPLY Data.nodes ('/M') AS Split(a)
),CTE2 AS (
Select c.city,t.value,STUFF((SELECT ', ' + CAST(name AS VARCHAR(10)) [text()]
FROM #Name
WHERE value = c.Data
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') List_Output
from CTE C
INNER JOIN #Name t
ON c.Data = t.value
)
select DISTINCT c.city,STUFF((SELECT ', ' + CAST(value AS VARCHAR(10)) [text()]
FROM CTE2
WHERE city = C.city
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') As Value ,STUFF((SELECT ', ' + CAST(List_Output AS VARCHAR(10)) [text()]
FROM CTE2
WHERE city = C.city
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ')As Name from CTE2 C
First, you need a function to split your comma-delimited values. Here is the DelimitedSplit8K written by Jeff Moden and improved by the community. This is regarded as one of the fastest SQL-based string splitter.
You should also read on FOR XML PATH(''), a method to concatenate strings. Check this article by Aaron Bertrand for more information.
SELECT
*
FROM Table2 t2
CROSS APPLY(
SELECT STUFF((
SELECT ',' + Name
FROM Table1
WHERE Value IN(
SELECT CAST(s.Item AS INT) FROM dbo.DelimitedSplit8K(t2.Value, ',') s
)
FOR XML PATH(''), type).value('.', 'VARCHAR(MAX)'
), 1, 1, '')
)x(Name)
SQL Fiddle
Notes:
Make sure to get the latest version of the DelimitedSplit8K.
For other splitter functions, check out this article by Aaron Bertrand.

How to count in SQL all fields with null values in one record?

Is there any way to count all fields with null values for specific record excluding PrimaryKey column?
Example:
ID Name Age City Zip
1 Alex 32 Miami NULL
2 NULL 24 NULL NULL
As output I need to get 1 and 3. Without explicitly specifying column names.
declare #T table
(
ID int,
Name varchar(10),
Age int,
City varchar(10),
Zip varchar(10)
)
insert into #T values
(1, 'Alex', 32, 'Miami', NULL),
(2, NULL, 24, NULL, NULL)
;with xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as ns)
select ID,
(
select *
from #T as T2
where T1.ID = T2.ID
for xml path('row'), elements xsinil, type
).value('count(/row/*[#ns:nil = "true"])', 'int') as NullCount
from #T as T1
Result:
ID NullCount
----------- -----------
1 1
2 3
Update:
Here is a better version. Thanks to Martin Smith.
;with xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as ns)
select ID,
(
select T1.*
for xml path('row'), elements xsinil, type
).value('count(/row/*[#ns:nil = "true"])', 'int') as NullCount
from #T as T1
Update:
And with a bit faster XQuery expression.
;with xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as ns)
select ID,
(
select T1.*
for xml path('row'), elements xsinil, type
).value('count(//*/#ns:nil)', 'int') as NullCount
from #T as T1
SELECT id,
CASE WHEN Name IS NULL THEN 1 ELSE 0 END +
CASE WHEN City IS NULL THEN 1 ELSE 0 END +
CASE WHEN Zip IS NULL THEN 1 ELSE 0 END
FROM YourTable
If you do not want explicit column names in query, welcome to dynamic querying
DECLARE #sql NVARCHAR(MAX) = ''
SELECT #sql = #sql + N' CASE WHEN '+QUOTENAME(c.name)+N' IS NULL THEN 1 ELSE 0 END +'
FROM sys.tables t
JOIN sys.columns c
ON t.object_id = c.object_id
WHERE
c.is_nullable = 1
AND t.object_id = OBJECT_ID('YourTableName')
SET #sql = N'SELECT id, '+#sql +N'+0 AS Cnt FROM [YourTableName]'
EXEC(#sql)
This should solve your problem:
select count (id)
where ( isnull(Name,"") = "" or isnull(City,"") = "" or isnull(Zip,"") = "" )
Not a smart solution, but it should do the work.
DECLARE #tempSQL nvarchar(max)
SET #tempSQL = N'SELECT '
SELECT #tempSQL = #tempSQL + 'sum(case when ' + cols.name + ' is null then 1 else 0 end) "Null Values for ' + cols.name + '",
sum(case when ' + cols.name + ' is null then 0 else 1 end) "Non-Null Values for ' + cols.name + '",' FROM sys.columns cols WHERE cols.object_id = object_id('TABLE1');
SET #tempSQL = SUBSTRING(#tempSQL, 1, LEN(#tempSQL) - 1) + ' FROM TABLE1;'
EXEC sp_executesql #tempSQL

Flatten SQL Server Table to a string

I have a table like:
ID | Value
----------------
1 | One
2 | Two
3 | Three
What I need to do is create a single string from these values, in the format:
'1: One, 2: Two, 3: Three'
I know how to do this using cursors, but it will be used as a column in a view, so it's not really a performant option.
Any pointers?
WITH T(ID,Value) AS
(
SELECT 1, 'One' UNION ALL
SELECT 2, 'Two' UNION ALL
SELECT 3, 'Three'
)
SELECT STUFF(
(SELECT ', ' + CAST(ID as varchar(11)) + ': ' + Value
FROM T
FOR XML PATH (''))
, 1, 2, '')
Have a look at something like
DECLARE #Table TABLE(
ID INT,
Value VARCHAR(20)
)
INSERT INTO #Table SELECT 1,'One'
INSERT INTO #Table SELECT 2,'Two'
INSERT INTO #Table SELECT 3,'Three'
SELECT STUFF(
(
SELECT ', ' + CAST(ID AS VARCHAR(MAX)) + ': ' + Value
FROM #Table
FOR XML PATH(''), TYPE
).value('.','varchar(max)')
,1,2, ''
)
SELECT STUFF((
SELECT ' ' + CAST(ID AS VARCHAR(2)) + ': '+ Value
FROM dbo.Table
FOR XML PATH('')
), 1, 1, ''
) As concatenated_string
DECLARE #ans VARCHAR(max)
SET #ans=''
SELECT #ans = #ans + str(id)+':'+value FROM table
SELECT #ans
Change the max to 8000 if your version of SQL doesn't support it
I would simply not do this in the database if at all possible. It's not designed for formatting data in a certain way; let the calling application handle that.
In C# (assuming data is an instance of SqlDataReader):
var l = new List<string>();
while (reader.Read())
l.Add(string.Format("{0}: {1}", reader[0], reader[1]));
var s = string.Join(", ", l.ToArray());