Visual Studio 2010 and Query Designer SQL Problem with XML Path - sql

I have an awesome Visual Studio 2010 SQL frustration:
I found code on StackOverflow on how to concatenate strings from multiple rows onto a single row by using FOR XML PATH('').
When I write the following line in Query and View Designer (because, presumably, there is no other way to write SQL in Visual Studio and then save it as a "View"), I get results that are not what I want:
Code:
Names AS
(SELECT DISTINCT
Z.code,
(SELECT DISTINCT CAST(B.fname + ' ' + B.lname + ' ' AS VARCHAR(MAX))
FROM Records AS A LEFT OUTER JOIN Employees AS B ON A.id = B.id
WHERE (A.code = Z.code) FOR XML PATH('')) AS EmployeeNames
FROM Employees AS Z)
I get a result like:
Code EmployeeNames
---- -------------------------
1234 <Expr1>First Last</Expr1>
Why? Because Visual Studio 2010, in all it's glory, adds "as Expr1" to the following:
(SELECT DISTINCT CAST(B.fname + ' ' + B.lname + ' ' AS VARCHAR(MAX))
FROM Records AS A LEFT OUTER JOIN Employees AS B ON A.id = B.id
WHERE (A.code = Z.code) FOR XML PATH('')) *as Expr1*
Is there any work-around for this? I've noticed that the problem comes with the Query and View Designer Parser... and I can intentionally break the parser by putting a random function that the parser doesn't support (like OVER()) somewhere in my code. This fixes the problem, and removes the XML tags, but seems unnecessary.
Does anyone else have this problem? Does anyone know a work-around?
Thank you very much for your time and effort with my problem.

I ran into something simular and the fix for me was to define the Cast in each case.
Example:
Names AS
(SELECT DISTINCT
Z.code,
(SELECT DISTINCT CAST(B.fname AS varchar(10) + ' ' + CAST(B.lname AS varchar(10) + ' ')
FROM Records AS A LEFT OUTER JOIN Employees AS B ON A.id = B.id
WHERE (A.code = Z.code) FOR XML PATH('')) AS EmployeeNames
FROM Employees AS Z)

Related

How to do a cross tab where the number of columns varies? (ADO SQL Server)

I'm trying to write a query that has a variable number of columns depending on the data but I've never done this kind of thing.
We're running ADO and hope to have a single query (possibly with subqueries) but no other coding or GO statements, stored procedures, etc.
We're planning to use the results of this query in an editable grid.
Below is a sample of our data. We have a list of employees and a list of Projects. Note that this isn't a "summed" cross tab. There's only one source number per cell.
We want the query results to have one column for each project. The cells in this column would contain the hours for that employee on that project.
If we add a project, we want another column to appear in the query results.
Edit: Since we're writing the query in code and the submitting it, we can generate as the query dynamically. We need not dynamically generate the code. For example, in our data below, we'll be able to read (in our native language) the Project table and know we have 3 projects and what their names are. We can use them in a Pivot I'm seeing from reading up, but just not sure how...
How about something like this: http://sqlfiddle.com/#!6/2ded1/6
declare #pivotquery as nvarchar(max)
declare #columnname as nvarchar(max)
select #columnname= isnull(#columnname + ',','')
+ quotename(name)
from (select name from project) as t2
set #pivotquery =
N'with t1 as (
select ph.employee, e.name as empname, p.name, ph.hours
from project_hours ph
inner join project p
on p.id = ph.project
inner join employees e
on e.id = ph.employee
)
select *
from t1
pivot(sum(hours) for name in (' + #columnname + ')) as pivot_table'
exec sp_executesql #pivotquery
with significant help and further explanation here: http://sqlhints.com/2014/03/18/dynamic-pivot-in-sql-server/
EDIT: As I read your question again, I notice that you're building the query in code, in which case you probably don't need the utility above, but a simple pivot where you build the for name in clause in your program, like this: http://sqlfiddle.com/#!6/2ded1/12
with t1 as (
select ph.employee, e.name as empname, p.name, ph.hours
from project_hours ph
inner join project p
on p.id = ph.project
inner join employees e
on e.id = ph.employee
)
select *
from t1
pivot(sum(hours)
for name in ([First Floor], [Basement], [Parking Lot A]))
as hours_summary
For future readers, here is a generalizable ANSI-syntax SQL query using two nested derived table subqueries.
This query should be compliant on most RDMS (SQL Server, MySQL, SQLite, Oracle, PostgreSQL, DB2) as it uses no CTE Window functions (WITH) or database-specific functions like SQL Server's Pivot():
SELECT [Key], [Employee Name], [Age],
Max(FF) As [First Floor],
Max(BSMT) As [Basement],
Max(PRKLotA) As [Parking Lot A]
FROM (
SELECT dT.Key,
dT.[Employee Name],
dT.[Age],
CASE WHEN dT.Project = 'First Floor' THEN dT.Hours END As FF,
CASE WHEN dT.Project = 'Basement' THEN dT.Hours END As BSMT,
CASE WHEN dT.Project = 'Parking Lot A' THEN dT.Hours END As PRKLotA
FROM (
SELECT [Employees].Key,
[Employees.[Employee Name],
Employees.Age, Projects.Project,
[Project Hours].Hours
FROM [Project Hours]
INNER JOIN Employees ON [Project Hours].[Employees FK] = Employees.Key
INNER JOIN Projects ON [Project Hours].[Projects FK] = Projects.Key
) AS dT
) As dT2
GROUP BY [Key], [Employee Name], [Age]
Output:
Key Employee Name Age First Floor Basement Parking Lot A
1 Tim 40 1000 3000
2 John 5 2000 4000 5000

Sql: How to combine multiple rows into a string as an expression within an update

In sql (MS sql server specifically) is it possible to combine multiple rows into a single string as an expression which is itself part of an update that is being applied to multiple rows. I have come across the approaches of using COALESCE or FOR XML PATH (e.g. How to get multiple rows into one line as a string? ) but can't get them to work in my more complex case with the extra dimension of 'listiness'.
My problem boils down to, in words:
A Project has some Launches. A Launch has a LaunchType and a date. I have a big output table of projects ProjectOutput and I want to update a column in it with a CSV string of all the launch type names for that project that happen in the same month as the first (chronologically) launch of that project.
In sql:
UPDATE ProjectOutput
SET LaunchNamesColumn = <INSERT MAGICAL SQL STRING CONCATTING ACROSS ROWS FUNCTION HERE> of Launch.name
FROM ProjectOuput
INNER JOIN Launch ON Launch.projectId = ProjectOutput.projectId
INNER JOIN LaunchType AS lt ON LaunchType.launchTypeId = Launch.launchTypeId
OUTER APPLY (
SELECT TOP 1 Launch.month, Launch.year
FROM Launch
INNER JOIN Project ON Project.projectId = Launch.projectId
WHERE Project.projectId = ProjectOutput.projectId
--In reality there's loads more JOINS and WHERE conditions here
ORDER BY Launch.date
) firstLaunch
WHERE Launch.month = firstLaunch.month AND Launch.year = firstLaunch.year
If there were only 1 Launch per Project then the stuff would not be needed and just
SET LaunchNameColumn = Launch.name
However as there can be several Launches per Project some operation is needed to join them. I tried:
SET LaunchNamesColumn = STUFF((SELECT ', ' + lt.name FROM lt FOR XML PATH('')), 1, 2, '')
However that doesn't work (error, invalid name) because it doesn't know what the alias lt is inside that SELECT. If you just say LaunchType or dbo.LaunchType then the query runs but then you are just looping over all the possible launch types rather than only those returned by the big query below. What I really want is for that FROM in the SELECT FOR XML PATH is to be the result set of the giant query below (whereas in all the examples I've found so far it's just a simple table), but copying and pasting that in seems so wrong. Maybe there is some mental block or sql feature I'm unaware of that would make this work, or is it not possible?
The problem you have is that in the SET stage of your query you only have access to one of the matching Launches as there is no grouping applied.
You can achieve want you want by moving your Launch lookup into a sub-query over the ProjectOutput rows. A simplified example:
UPDATE ProjectOutput
SET LaunchNamesColumn = STUFF((
SELECT ', ' + Launch.name
FROM Launch
-- OUTER APPLY is not required within the sub-query.
INNER JOIN (
SELECT TOP 1 Launch.month, Launch.year
FROM Launch
-- Filter results to specific project.
WHERE Launch.projectId = ProjectOutput.projectId
ORDER BY Launch.date
) firstLaunch ON Launch.month = firstLaunch.month AND Launch.year = firstLaunch.year
-- Filter results to specific project.
WHERE Launch.projectId = ProjectOutput.projectId
FOR XML PATH('')
), 1, 2, '')
FROM ProjectOutput
Logically the sub query is run once per ProjectOutput record, allowing you to filter and group by each ProjectId.
Also nice bit of syntax that may simplify your query is SELECT TOP WITH TIES,
UPDATE ProjectOutput
SET LaunchNamesColumn = STUFF((
SELECT TOP (1) WITH TIES ', ' + Launch.name
FROM Launch
WHERE Launch.projectId = ProjectOutput.projectId
ORDER BY Launch.Year, Launch.Month
FOR XML PATH('')
), 1, 2, '')
FROM ProjectOutput
This will return all the matching Launches that have the lowest Year then Month value.
It's a little bit difficult to understand your SQL without description of the tables, but what you should do is have the query with the XML path so that it returns only those items that you want to be concatenated for that single row, so my guess is that you want actually something like this:
UPDATE O
SET LaunchNamesColumn = STUFF((SELECT ', ' + lt.Name
From Launch L
INNER JOIN Launch L ON L.projectId = O.projectId
INNER JOIN LaunchType AS lt ON lt.launchTypeId = L.launchTypeId
WHERE L.month = FL.month AND L.year = FL.year
FOR XML PATH('')), 1, 2, '')
FROM ProjectOutput O
CROSS APPLY (
SELECT TOP 1 L2.month, L2.year
FROM Launch L2
WHERE L2.projectId = O.projectId
-- Removed the other tables from here. Are they really needed?
ORDER BY L2.date
) FL
Couldn't really test this, but hopefully this helps.
Can you add the Launch and LaunchType tables into your STUFF and filter it based on the Project table or Launch table in the main query?
STUFF((SELECT ', ' + lt.name
FROM Launch l
JOIN LaunchType lt2 ON lt2.launchTypeId = l.launchTypeId
WHERE
l.projectId = Launch.projectId
FOR XML PATH('')), 1, 2, '')
Or you could maybe create a CTE and select all of the launches then use your Stuff statement on the CTE
WITH cteLaunch AS (
SELECT l.projectId,
lt.NAME
FROM Launch ON Launch.projectId = ProjectOutput.projectId
INNER JOIN LaunchType AS lt ON LaunchType.launchTypeId = Launch.launchTypeId
OUTER APPLY (SELECT TOP 1
Launch.month,
Launch.year
FROM
Launch
INNER JOIN Project ON Project.projectId = Launch.projectId
WHERE
Project.projectId = ProjectOutput.projectId
ORDER BY Launch.date
) firstLaunch
WHERE Launch.month = firstLaunch.month
AND Launch.year = firstLaunch.year
)
UPDATE
ProjectOutput
SET
LaunchNamesColumn = STUFF((SELECT ', ' + lt.name
FROM cteLaunch cte
WHERE cte.projectId = ProjectOuput.projectId
FOR XML PATH('')), 1, 2, '')
FROM
ProjectOuput
INNER JOIN cteLaunch ON cteLaunch.projectId = ProjectOutput.projectId
I think you are really close; it's the alias getting in the way:
SET LaunchNamesColumn = STUFF((SELECT ', ' + lt.name
FROM LaunchType AS lt
WHERE lt.launchTypeId = Launch.launchTypeId FOR XML PATH('')), 1, 2, '')

Convert a query of views into a single query with derived tables

Is it possible to convert (as in get the query as text) a query that consists of a lot of views within views into a query that is just based on the original tables?
The obvious is to go through all the views and then do it manually but wondered whether or not there was a quicker way?
It's not pretty, and like #David Faber commented, not sure how practical this will be, but here goes...
There's a whole lot of assumptions that one has to make for this to work, like
All of the views start with SELECT. (no CTE's)
The view is not enclosed in [ ] when referenced in another view
View names don't have spaces in them
This version only goes 1 level deep, but it should be possible to resolve any additional views you find in the output in a similar fashion.
And probably some more I didn't think of
I'm using the sys tables, and not the newer schema information objects, simply because I know the sys table structure better.
Assuming the following view
CREATE VIEW VW_DUMMY
AS
SELECT c.Name as Company, g.Name as [Group], gu.UserId
FROM VW_Company c
JOIN VW_Group g ON c.Id = g.CompanyId
JOIN VW_GroupUser gu ON g.Id = gu.GroupId AND gu.CompanyId = c.Id
Here's what I did.
1) Grab the view definition from syscomments for VW_DUMMY.
2) Strip off the CREATE VIEW part
2) Grab a list of objects that VW_DUMMY depends on from sysdepends
3) Grab the view definition from syscomments for all the dependant objects.
4) Strip off the CREATE VIEW part
5) Replace the name of the 'depends' object in the original view with the
definition...
1)
DECLARE #SQL VARCHAR(MAX);
SET #SQL = REPLACE((
SELECT c.text AS [text()]
FROM syscomments c
WHERE c.id = OBJECT_ID('VW_DUMMY')
FOR XML PATH('')), '
', '');
2)
SET #SQL = SUBSTRING(#SQL, PATINDEX('%SELECT%', #SQL), LEN(#SQL))
3), 4) and 5)
SELECT #SQL = REPLACE(#SQL, ' ' + name + ' ', '(' + SUBSTRING(text, PATINDEX('%SELECT%', text), LEN(text)) + ') ')
FROM (
SELECT DISTINCT OBJECT_NAME(depid) as name,
REPLACE((
SELECT c.text AS [text()]
FROM syscomments c
WHERE c.id = d.depid
FOR XML PATH('')), '
', '') as text
FROM sysdepends d
WHERE d.id = OBJECT_ID('VW_DUMMY')
AND exists(select 1 from sysobjects c where c.id = d.depid and c.type='V')) data
SELECT #SQL
I tried in on VW_DUMMY in my database, and the output is some of the worst formatted code that you might ever see, but the result is the same as the view.
Here's the output (bad formatting is deliberate)
SELECT c.Name as Company, g.Name as [Group], gu.UserId
FROM(SELECT *
FROM Company
) c
JOIN(SELECT *
FROM [Group]
) g ON c.Id = g.CompanyId
JOIN(SELECT *
FROM [GroupUser]
) gu ON g.Id = gu.GroupId AND gu.CompanyId = c.Id
Does that help?

SQL Server dynamically change WHERE clause in a SELECT based on returned data

I'm mainly a presentation/logic tier developer and don't mess around with SQL all that much but I have a problem and am wondering if it's impossible within SQL as it's not a full programming language.
I have a field ContactID which has an CompanyID attached to it
In another table, the CompanyID is attached to CompanyName
I am trying to create a SELECT statement that returns ONE CONTACT ID and in a seperate column, an aggregate of all the Companies attached to this contact (by name).
E.G
ContactID - CompanyID - CompanyName
***********************************
1 001 Lol
1 002 Haha
1 003 Funny
2 002 Haha
2 004 Lmao
I want to return
ContactID - Companies
*********************
1 Lol, Haha, Funny
2 Haha, Lmao
I have found the logic to do so with ONE ContactID at a time:
SELECT x.ContactID, substring(
(
SELECT ', '+y.CompanyName AS [text()]
FROM TblContactCompany x INNER JOIN TblCompany y ON x.CompanyID = y.CompanyID WHERE x.ContactID = 13963
For XML PATH (''), root('MyString'), type
).value('/MyString[1]','varchar(max)')
, 3, 1000)
[OrgNames] from TblContact x WHERE x.ContactID = 13963
As you can see here, I am hardcoding in the ContactID 13963, which is neccessary to only return the companies this individual is linked to.
The issue is when I want to return this aggregate information PER ROW on a much bigger scale SELECT (on a whole table full of ContactID's).
I want to have x.ContactID = (this.ContactID) but I can't figure out how!
Failing this, could I run one statement to return a list of ContactID's, then in the same StoredProc run another statement that LOOPS through this list of ContactID's (essentially performing the second statement x times where x = no. of ContactID's)?
Any help greatly appreciated.
You want a correlated subquery:
SELECT ct.ContactID,
stuff((SELECT ', ' + co.CompanyName AS [text()]
FROM TblContactCompany cc INNER JOIN
TblCompany co
ON cc.CompanyID = co.CompanyID
WHERE cc.ContactID = ct.ContactId
For XML PATH (''), root('MyString'), type
).value('/MyString[1]', 'varchar(max)'),
1, 2, '')
[OrgNames]
from TblContact ct;
Note the where clause on the inner subquery.
I also made two other changes:
I changed the table aliases to better represent the table names. This makes queries easier to understand. (Plus, the aliases had to be changed because you were using x in the outer query and the inner query.)
I replaced the substring() with stuff(), which does exactly what you want.
You could use a table variable to store the required x.ContactID and in your main query in the WHERE clause use IN clause like below
WHERE
...
x.ContactID IN (SELECT ContactID FROM #YourTableVariable)
I guess all you need to do is to use unique table identifiers in your subquery and join the table in subquery with outer table x:
SELECT x.ContactID, substring(
(
SELECT ', '+z.CompanyName AS [text()]
FROM TblContactCompany y, TblCompany z WHERE y.CompanyID = z.CompanyID AND y.ContactId = x.ContactId
For XML PATH (''), root('MyString'), type
).value('/MyString[1]','varchar(max)')
, 3, 1000)
[OrgNames] from TblContact x
Don't loop or you will get performance problems (row by agonising row RBAR). Instead do set based queries.
This is untested but should give you an idea of how it may work:
SELECT
x.ContactID,
substring(
(SELECT ', '+y.CompanyName AS [text()]
FROM TblContactCompany y
WHERE x.CompanyID = y.CompanyID
For XML PATH (''), root('MyString'), type).value('/MyString[1]','varchar(max)')
, 3, 1000)
[OrgNames]
FROM TblContact x
And I have a feeling you can use CONCAT instead of substring

carriage return in sql server 2012

Hey I am using the following query to display the problem list separated by commas.
SELECT tt.VrNo, STUFF((select ','+ Er1.ErrorDesc
from ( select * from CallRegErrors )as Main
left join ErrorMaster ER1 on Main.ErrorCode=ER1.ErrorCode
WHERE (main.VrNo = tt.VrNo)
FOR XML PATH('')) ,1,1,'') AS Problemlist
query is giving the output like a,b,c,d etc
But my actual requirement is I want to display each error description in a new line like,
a
b
c
d
etc
I tried the following query for it:
SELECT tt.VrNo, STUFF((select char(13)+char(10)+ Er1.ErrorDesc
from ( select * from CallRegErrors )as Main
left join ErrorMaster ER1 on Main.ErrorCode=ER1.ErrorCode
WHERE (main.VrNo = tt.VrNo)
FOR XML PATH('')) ,1,1,'') AS Problemlist
and also i have used
SELECT tt.VrNo,Replace(STUFF((select ','+ Er1.ErrorDesc as [text()] from ( select * from CallRegErrors )as Main left join ErrorMaster ER1 on Main.ErrorCode=ER1.ErrorCode
WHERE (main.VrNo = tt.VrNo)
FOR XML PATH('')),1,1,''),',',char(13)+char(10)) AS Problemlist
from (select main.VrNo, Er1.ErrorDesc from ( select * from CallRegErrors )as Main left join ErrorMaster ER1 on Main.ErrorCode=ER1.ErrorCode )as tt
group by tt.VrNo
but now get the problem list seperated by spaces instead of commas after using the above query
but its does not give the output that i want.
please help..
Thanks in advance
I think we need more information before we can help you.
I think you are trying to format the information at the child level in a parent child relationship into a list. You probably saw something like this blog on the web.
However, your query is not correctly formatted.
Is the ErrorMaster (Production.ProductCategory) the parent and CallRegErrors (SUB.ProductCategoryID) the child?
If so just change the query to those table name field names for it to work.
I used the REPLACE function on the overall result to change COMMAS to CR + LF.
-- Sample database
USE AdventureWorks2012
GO
-- Change SQL from www.sqlandme.com for this users problem
SELECT
CAT.Name AS [Category],
REPLACE(STUFF((
SELECT ',' + SUB.Name AS [text()]
FROM Production.ProductSubcategory SUB
WHERE SUB.ProductCategoryID = CAT.ProductCategoryID
FOR XML PATH('')
), 1, 1, '' ), ',', CHAR(13) + CHAR(10))
AS [Sub Categories]
FROM Production.ProductCategory CAT
You can only see carriage returns in the output window when the type is set to TEXT in SSMS.
I hope this solves your problem. If not, please write back with more information!!