Is it possible to avoid subquery in select when concatenating columns? - sql-server

I have a "main" table containing an id (plus some other columns) and an aka table which joins to it by the [main id] column to main.id. The following query returns some columns from main along with a column of concatenated comma-separated "lastName"s from aka:
SELECT m.id, m.name,
(SELECT a.[lastname] + ',' AS [text()]
FROM aka a
WHERE a.[main id] = m.[id]
FOR xml path ('')) [akas]
FROM main m
This works fine, but I'd like to know if there is a way to avoid doing this in a subquery?

Using CROSS APPLY you could move subquery from SELECT list:
SELECT m.id, m.name,
(SELECT a.[lastname] + ',' AS [text()]
FROM aka a
WHERE a.[main id] = m.[id]
FOR xml path ('')) [akas]
FROM main m;
to:
SELECT m.id, m.name, s.akas
FROM main m
CROSS APPLY (SELECT a.[lastname] + ',' AS [text()]
FROM aka a
WHERE a.[main id] = m.[id]
FOR xml path ('')) AS s(akas)
Notes:
You could refer to s.akas multiple time
You could add WHERE s.akas ...
Long subquery in SELECT list could be less readable
If it is possbile that correlated subquery return no rows you need to use OUTER APPLY instead.

Generally spoken there's nothing against a sub-query in a technical view...
You might prefer an APPLY due to readability or multi reference.
Whenever you put a sub-query directly into the column's list like here:
SELECT Column1
,Column2
,(SELECT x FROM y) AS Column3
,[...]
... this sub-select must deliver
just one column
of just one row
Using FOR XML PATH(''),TYPE lets the result be one single value of type XML. This makes it possible to return many rows/columns "as one". Without the ,TYPE it will be the XML "as text". The concatenation trick with XML is possible due to a speciality of the generation of XML with empty tag names and return "as text". But in any case: The returned value will be just one bit of information, therefore fitting into a column list.
Whenever you expect more than one row, you'd have to force this to be one bit of data (like - often seen! - SELECT TOP 1 x FROM y ORDER BY SomeSortKey, which brings back the first or the last or ...)
All other intentions to get 1:n data needs 'JOIN' or 'APPLY'. With scalar data, as in your case, there will actually be no difference, whether you use a sub-select or an APPLY.

Since you have an arbitrary number of records combining for the final string, what you have is the best option to do this in SQL. Generally, you are expected to return one row per item, and if you want a CSV string then build that in your client code.

Related

SQL Server : finding substring using PATINDEX function

I'm writing different queries in SQL Server.
I have 2 tables, Employees and Departments.
Table Employees consists of EMPLOYEE_ID, ENAME, ID_DEP - department id. Table Departments consists of ID_DEP, DNAME.
The task is to show Employee.ENAME and his Department.DNAME where Department.DNAME has word Sales inside. I have to use functions SUBSTRING and PATINDEX.
Here is my code, but I think that it looks quite strange and it's meaningless. Nevertheless I need to use both functions in this task.
SELECT e.ENAME, d.DNAME
FROM EMPLOYEE e
JOIN DEPARTMENTS d ON d.ID_DEP = e.ID_DEP
WHERE UPPER(SUBSTRING(d.DNAME, (PATINDEX('%SALES%', d.DNAME)), 5)) = 'SALES'
Any ideas what should I change while continuing using these two functions?
The answer is just below, and BTW, using row constructor VALUES is an excellent mean to get a simple demo of what you want.
The query below provides several possible answers to your ambiguous question. Why would you need to use these functions? Is it an homework that specify this? If your SQL Server database was installed with a case insensitive collation, or the column 'name' was set to this collation, no matter how UPPER is used, it will makes no difference in match. The most you can get of UPPER is to make the data appears uppercase in the result, or turn data to uppercase if you update the column. PATINDEX/LIKE are going to perform case insensitive match. And you know, this is so useful, that most people configure their server with some case insensitive collation. To circumvent default comparison behavior that match the column/database collation, specify the collate clause, as in the outer apply of Test2.
Here are the queries. Watch the results, they show what I said.
select *
From
(Values ('très sales'), ('TRES SALES'), ('PLUTOT PROPRE')) as d(name)
outer apply (Select Test1='match' Where Substring(name, patindex('%SALES%', name), 5) = 'SALES') as test1
outer apply (Select Test2='match' Where name COLLATE Latin1_General_CS_AS like '%SALES%' ) as test2 -- CS_AS mean case sensitive
outer apply (Select Test3='match' Where name like '%SALES%') as test3
select * -- really want an upper case match ?
From
(Values ('très sales'), ('TRES SALES'), ('PLUTOT PROPRE')) as d(name)
Where name COLLATE Latin1_General_CS_AS like '%SALES%'
select * -- demo of patindex
From
(Values ('très sales'), ('TRES SALES'), ('PLUTOT PROPRE')) as d(name)
outer apply (Select ReallyUpperMatch=name Where patindex('%SALES%', name COLLATE Latin1_General_CS_AS)>0 ) as ReallyUpperMatch -- CI_AS mean case sensitive
outer apply (Select ciMatch=name Where name like '%SALES%' ) as ciMatch
outer apply (Select MakeItLookUpper=UPPER(ciMatch) ) MakeItLookUpper

Avoid duplicate values in comma delimited sql query

hello I have here a comma delimited query:
select [Product_Name]
,(select h2.Location_name + ', ' from (select distinct * from [dbo].[Product_list]) h2 where h1.Product_Name = h2.Product_Name
order by h2.Product_Name for xml path ('')) as Location_name
,(select h2.[Store name] + ', ' from [dbo].[Product_list] h2 where h1.Product_Name = h2.Product_Name
order by h2.Product_Name for xml path ('')) as store_name, sum(Quantity) as Total_Quantity from [dbo].[Product_list] h1
group by [Product_Name]
but this query shows duplicated data in comma delimited form, my problem is how will I only show the distinct values of the column in comma delimited form? can anyone please help me?
Well, if you don't SELECT DISTINCT * FROM dbo.Product_list and instead SELECT DISTINCT location_name FROM dbo.Product_list, which is anyway the only column you need, it will return only distinct values.
T-SQL supports the use of the asterisk, or “star” character (*) to
substitute for an explicit column list. This will retrieve all columns
from the source table. While the asterisk is suitable for a quick
test, avoid using it in production work, as changes made to the table
will cause the query to retrieve all current columns in the table’s
current defined order. This could cause bugs or other failures in
reports or applications expecting a known number of columns returned
in a defined order. Furthermore, returning data that is not needed can
slow down your queries and cause performance issues if the source
table contains a large number of rows. By using an explicit column
list in your SELECT clause, you will always achieve the desired
results, providing the columns exist in the table. If a column is
dropped, you will receive an error that will help identify the problem
and fix your query.
Using SELECT DISTINCT will filter out duplicates in the result set.
SELECT DISTINCT specifies that the result set must contain only unique
rows. However, it is important to understand that the DISTINCT option
operates only on the set of columns returned by the SELECT clause. It
does not take into account any other unique columns in the source
table. DISTINCT also operates on all the columns in the SELECT list,
not just the first one.
From Querying Microsoft SQL Server 2012 MCT Manual.

Summarizing count of multiple talbes in one row or column

I've designed a migration script and as the last sequence, I'm running the following two lines.
select count(*) from Origin
select count(*) from Destination
However, I'd like to present those numbers as cells in the same table. I haven't decided yet if it's most suitable to put them as separate rows in one column or adjacent columns on one row but I do want them in the same table.
How can I select stuff from those selects into vertical/horizontal line-up?
I've tried select on them both with and without parentheses but id didn't work out (probably because of the missing from)...
This questions is related to another one but differs in two aspects. Firstly, it's much more to-the-point and clearer states the issue. Secondly, it asks about both horizontal and vertical line-up of the selected values whereas the linked questions only regards the former.
select
select count(*) from Origin,
select count(*) from Destination
select(
select count(*) from Origin,
select count(*) from Destination)
You need to nest the two select statements under a main (top) SELECT in order to get one row with the counts of both tables:
SELECT
(select count(*) from Origin) AS OriginCount,
(select count(*) from Destination) AS DestinationCount
SQLFiddle for the above query
I hope this is what you are looking for, since the "same table" you are mentioning is slightly confusing. (I'm assuming you're referring to result set)
Alternatively you can use UNION ALL to return two cells with the count of both tables.
SELECT COUNT(*), 'Origin' 'Table' FROM ORIGIN
UNION ALL
SELECT COUNT(*), 'Destination' 'Table' FROM Destination
SQLFiddle with UNION ALL
SQLFiddle with UNION
I recommend adding the second text column so that you know the corresponding table for each number.
As opposed to simple UNION the UNION ALL command will return two rows everytime. The UNION command will generate a single result (single cell) if the count of rows in both tables is the same (the same number).
...or if you want vertical...
select 'OriginalCount' as Type, count(*)
from origin
union
select 'DestinationCount' as Type, count(*)
from destination

Sql Server Xml Value method -- Distinct Values for one column

I have the below query I am trying to return distinct value from the second .value method.
Here is what I have tried. I tried adding 'distinct-values(.)' to return only distinct but it is still returning the same results as a normal '.' How can I select distinct values from just one column?
;WITH XMLNAMESPACES (default 'http://www.w3.org/2001/XMLSchema')
SELECT
a.value('.', 'NVARCHAR(50)') AS Visitor
, b.value('distinct-values(.)', 'NVARCHAR(50)') AS Sender
FROM XmlTable AS X
CROSS APPLY xmlDocument.nodes('Root/Visitors/Visitor') AS aa(a)
CROSS APPLY xmlDocument.nodes('Root/Senders/Sender') AS bb(b)
Here is the normal result
Here is whay I am trying to get
Xml Like this
<upx:Root xmlns:upx="http://www.w3.org/2001/XMLSchema">
<upx:Visitors>
<upx:Visitor>Visitor1</upx:Visitor>
<upx:Visitor>Visitor2</upx:Visitor>
</upx:Visitors>
<upx:Senders>
<upx:Sender>Sender1</upx:Sender>
</upx:Senders>
</upx:Root>
It is your cross apply with your nodes statement listed twice that is showing this problem. Do what you are doing with the 'nodes' syntax with a 'query' extension instead followed up by a 'value' extension to show what is in the xml directly from extension instead of relying on the nodes with a cross apply. The problem is you are not displaying to the audience where you get that Id from? Are you determining that at run time from the xml itself or joining yet to another table or having another part of the xml not present? What in essence that is happening with the nodes is it is cross applying and saying: "I have two vales in that node heirarchy here they are." Then you are cross applying again a different node and it is returning the same thing twice. You must be careful when using cross apply twice exactly what it is doing. I can show the differentiation but without how I know you are relating back to 1 (are you just hunting for it somehow for the int after visitor?) I don't know how to represent exactly what you are wanting.
EDIT: Okay it is what I thought then. Now my code may be longer than some and I will admit there may be an easier way to do this however I would do three things:
Keep your cross apply with nodes because nodes is useful in that it will repeat rows you need to count on. However I would add an artificial flag for the name you use for the node. Then I would union together two select statements using the nodes.
I would then use a nested select as a from statement and then determine row number with a windowed function based on the flags I just set.
I would then nest that again and then use the very same row number as the Id of the row number and then I would do some syntactic pivoting based on a max(case when) based on the flags I arbitrarily set.
I usually prefer cte's but since your XML namespace has a 'with' beginning and the first cte does as well I forgot how the syntax is to work around that. Nested Selects IMHO can get hairy when there are multiple so I choose CTE's usually but in this case I did a nested select inside of another nested select. I hope this helps:
declare #xml xml = '<upx:Root xmlns:upx="http://www.w3.org/2001/XMLSchema">
<upx:Visitors>
<upx:Visitor>Visitor1</upx:Visitor>
<upx:Visitor>Visitor2</upx:Visitor>
</upx:Visitors>
<upx:Senders>
<upx:Sender>Sender1</upx:Sender>
</upx:Senders>
</upx:Root>'
;
declare #Xmltable table ( xmlDocument xml);
insert into #XmlTable values (#xml);
WITH XMLNAMESPACES (default 'http://www.w3.org/2001/XMLSchema')
select
pos as Id
, max(case when Listing = 'Visitors' then Value end) as Visitors
, max(case when Listing = 'Senders' then Value end) as Senders
from
(
select
*
, row_number() over(partition by Listing order by Value) as pos
from
(
SELECT
'Visitors' as Listing
, a.value('.', 'NVARCHAR(50)') AS Value
FROM #XmlTable AS X
CROSS APPLY xmlDocument.nodes('Root/Visitors/Visitor') AS aa(a)
union
SELECT
'Senders'
, b.value('distinct-values(.)', 'NVARCHAR(50)') AS Sender
FROM #XmlTable AS X
CROSS APPLY xmlDocument.nodes('Root/Senders/Sender') AS bb(b)
) as u
) as listing
group by pos

How to get comma-separated values in SQL Server?

I have two tables called tblEmployee and tblWorkPlace. The second table consists of multiple rows for each employee. I want to get the result as follows
EmployeeName WorkPlace
Gopi India,Pakistan,...
I.e. if tblWorkPlace consists of multiple rows for an employee, I want the result in a single row, with data being separated by commas.
How will I get this result ????
You'll need to have code on the client side for this. It is possible to make this happen in sql server, but it's not trivial, the performance is horrible, and this kind of thing does not belong on the database anyway.
You're not very clear on how the tables tblWorkplace and tblEmployee are connected - I'm assuming by means of a many-to-many link table or something.
If that's the case, you can use something like this:
SELECT
e.EmpName,
(SELECT STUFF(
(SELECT ',' + w.WPName
FROM #Workplace w
INNER JOIN #WorkplaceEmployeesLink we ON w.WorkplaceID = we.WorkplaceID
WHERE we.EmpID = e.EmpID
FOR XML PATH('')
), 1, 1, '')
) AS Workplaces
FROM #Employee e
(I've replaced your tables with my own table variables #Employee and #Workplace etc. for testing)
This gives me an output something like:
EmpName Workplaces
Gopi India,Pakistan
for that one row.
Basically, the internal FOR XML PATH('') selects the list of workplaces for each employee, and prepends each workplace with a ',' so you get something like ,India,Pakistan.
The STUFF method then stuff an empty string into that resulting string, at position one, for a length of 1 - essentially wiping out the first comma, thus you get the list of workplaces as you desired.
You can assign multiple values to a variable in a select statement. I call this "flattening" the records.
declare #WorkPlace varchar(max)
select #WorkPlace = '' -- it is important to be NOT null
select #WorkPlace = #WorkPlace + WorkPlace + ', '
from YourTable
where EmployeeName = 'Gopi'
You may want to add some code to remove the final ',' from the string.
Here's a link to different ways to get comma separated values.

Resources