FOR Clause Issues/Combine Row into One Column - sql

Currently my goal is that if I have three rows (a, b, z), (c, d, z), (e,f,z). I will combine them to form one column so (a:b, c:d, e:f, z).
I have tried the code:
SELECT
d.engagement_id,
(SELECT cf.field + ':' + cf.custom_field_value
FROM LEFT OUTER JOIN custom_fields cf ON cf.engagement_id = d.engagement_id
FOR XML PATH('')) [SECTORS]
FROM
pseudo_table d
Currently, it says I am missing a right parenthesis before the FOR. Any ideas on why this is happening/get to my goal?

In Oracle, you would use listagg():
SELECT d.engagement_id,
LISTAGG(cf.field || ':' || cf.custom_field_value, ', ') WITHIN GROUP (ORDER BY cf.field) as sectors
FROM pseudo_table d LEFT OUTER JOIN
custom_fields cf
ON cf.engagement_id = d.engagement_id
GROUP BY d.engagement_id;

Related

Count the number of rows returned in SQL ORACLE

I have a little problem, my query look like this
select count(A.toto)
from B
inner join C
on B.tata = C.tata
inner join A
on C.tutu = A.tutu
group by A.toto, A.zaza, A.zozo;
and my result look like this :
1
2
1
6
7
4
1
1
1
But I want only the number of rows, for this example, the value that I would like to have is 9.
But I don't know how I can have this value...
Thank you in advance !!
You can use count(distinct). Unfortunately, Oracle doesn't support count(distinct) with multiple arguments, so a typical method is just to concatenate the value together:
select count(distinct A.toto || ':' || A.zaza || ':' || A.zozo)
from B inner join
C
on B.tata = C.tata inner join
A
on C.tutu = A.tutu;
This assumes that. the column values don't have the separator character (or at least in such a way that the concatenation is the same for rows with different key values).
An alternative method is to use a subquery:
select count(*)
from (select 1
from B inner join
C
on B.tata = C.tata inner join
A
on C.tutu = A.tutu
group by A.toto, A.zaza, A.zozo
) abc

Problems with using STUFF

Why the heck isn't this working??? Seems to follow everything I've found around here. I'm getting the error:
Column '#TempTable.clientId' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
If I add the tt.clientId to the group by, then it doesn't do the stuff, and combine them all into 1 line, they come up as separate rows. Did I make a typo or something?
SELECT tt.Station, STUFF((SELECT ', ' + c.client_code
FROM client c
WHERE tt.clientId = c.ID
FOR XML PATH('')),1,1,'') [Values]
FROM #TempTable tt
GROUP BY tt.Station
SELECT ... FOR XML PATH should be a function of GROUP BY column[s]
tt.station in your case.
Something like that
SELECT tt.Station, STUFF((SELECT ', ' + c.client_code
FROM client c
JOIN #TempTable tt2
ON tt2.clientId = c.ID
AND tt2.Station = tt.Station
FOR XML PATH('')),1,1,'') [Values]
FROM
GROUP BY tt.Station
You have to add tt.ClientId to the GROUP BY because you are using it to correlate the subquery here: WHERE tt.clientId = c.ID
Otherwise, how does SQL Server know which ClientId to use for each Station?
If you don't want to group by ClientId, then you have to correlate by tt.Station.
There are a couple alternatives here, but here are some things to consider:
Determine what you want your left-most item to be -- in this case, it looks like it's supposed to be station. If that is the case, one way to approach it is to first get the distinct set of stations. This can be done using a group by or a distinct.
Determine the level of the items for which you wish to generate a list -- in this case it's client_code. Therefore, you want to get you inner select to be at that level. One way to do that is to resolve the distinct set of client codes prior to attempting to use for xml.
One critique -- it's always good to provide a simple set of data. Makes providing an answer much easier and faster.
Again, there are alternatives here, but here's a possible solution.
The test data
select top (100)
client_id = abs(checksum(newid())) % 100,
client_code = char(abs(checksum(newid())) % 10 + 65)
into #client
from sys.all_columns a
cross join sys.all_columns b;
select top (100)
station = abs(checksum(newid())) % 10,
client_id = abs(checksum(newid())) % 50 -- just a subset
into #temp
from sys.all_columns a
cross join sys.all_columns b;
The query
select station, client_codes = stuff((
select ', ' + cc.client_code
from (
select distinct c.client_code -- distinct client codes
from #temp t
join #client c
on t.client_id = c.client_id
where t.station = s.station) cc
order by client_code
for xml path('')), 1, 2, '')
from (
select distinct station
from #temp) s; -- distinct stations
The results
station client_codes
----------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
0 B, C, D, E, G, J
1 A, B, D, G, H, J
2 A, C, E, F, G, H, J
3 B, C, H, J
4 A, B, C, D, F, H, J
5 H, J
6 D, E, F, G, I
7 A, C, D, F, G, H, J
8 A, E, G
9 C, E, F, G, I
Hope this helps.

trying to concatenate a column into a comma delimited list

i have 3 tables, 1 for products and one for categories the products are assigned to. what IM trying to do is concatenate the column called stCategoryName to a single column in a comma delimited list.
Basically I have the products table containing the primary key for each product and im trying to figure out how to concatenate all the stcategoryName column next to each product so i can have a simplified return
what im trying to get is the following.
stProductID stCategoryName
123 category1,category2,category3
SELECT
dbo.StoreItemTracking.StCategoryID,
dbo.StoreItemTracking.StProductID,
dbo.StoreItemTracking.viewOrder,
dbo.StoreCategories.StCategoryName,
dbo.Store_Products.PartNumber
FROM
dbo.StoreItemTracking
INNER JOIN dbo.StoreCategories
ON dbo.StoreItemTracking.StCategoryID = dbo.StoreCategories.StCategoryID
INNER JOIN dbo.Store_Products
ON dbo.StoreItemTracking.StProductID = dbo.Store_Products.ID
Im stuck as to how to concatenate a column where the query contains 3 tables to select from.
any help greatly appreciated
Look at using coalesce to turn category into a CSV:
See example:
DECLARE #EmployeeList varchar(100)
SELECT #EmployeeList = COALESCE(#EmployeeList + ', ', '')
+ CAST(Emp_UniqueID AS varchar(5))
FROM SalesCallsEmployees
WHERE SalCal_UniqueID = 1
SELECT #EmployeeList
You can also use CTE's or Subqueries. See:
http://archive.msdn.microsoft.com/SQLExamples/Wiki/View.aspx?title=createacommadelimitedlist
Another nice and easy example:
http://www.codeproject.com/Articles/21082/Concatenate-Field-Values-in-One-String-Using-CTE-i
This:
FId FName
--- ----
2 A
4 B
5 C
6 D
8 E
with:
;WITH ABC (FId, FName) AS
(
SELECT 1, CAST('' AS VARCHAR(8000))
UNION ALL
SELECT B.FId + 1, B.FName + A.FName + ', '
FROM (And the above query will return
SELECT Row_Number() OVER (ORDER BY FId) AS RN, FName FROM tblTest) A
INNER JOIN ABC B ON A.RN = B.FId
)
SELECT TOP 1 FName FROM ABC ORDER BY FId DESC
becomes:
FName
----------------------------
A, B, C, D, E,
Don't understand how your products and categories are connected but in general I do like this to create comma separated lists.
SELECT table1.Id
,Csv
FROM table1
CROSS APPLY (
-- Double select so we can have an alias for the csv column
SELECT (SELECT ',' + table2.Name
FROM table2
WHERE table2.Id = table1.Id
FOR XML PATH('')
) AS RawCsv
) AS CA1
CROSS APPLY (
-- Trim the first comma
SELECT RIGHT(RawCsv, LEN(RawCsv) - 1) AS Csv
) AS CA2

Combining Multiple SQL Views ON Year & Month

I have a SQL Server database (2012 express) with many tables.
I have produced three different VIEWS based on different combinations of the underlying tables.
Each of these views consists of three columns, Year, Month & Total
The Total column in each of the 3 Views is of a different measure.
What I want to be able to do is to combine the three Totals into a single View
I have attempted this with the following script -
SELECT b.[Year], b.[Month], b.Fees AS [Billing],
f.Estimate AS [Estimate],
w.Fees AS [WIP]
FROM MonthlyBillingTotals AS b
FULL JOIN MonthlyFeeEstimates AS f
ON (b.[Year] = f.[Year] AND b.[Month] = f.[Month])
FULL JOIN MonthlyInstructionsWIP AS w
ON (b.[Year] = w.[Year] AND b.[Month] = w.[Month])
ORDER BY b.[Year], b.[Month]
Originally I tried INNER JOINS but of course unless the Year / Month combo existed in the first view (MonthlyBillingTotals) then it did not appear in the combined query. I therefore tried FULL JOINS, but the problem here is that I get some NULLS in the Year and Month columns, when they do not exist in the first view (MonthlyBillingTotals).
If the data in the three Views is as follows -
Then what I want is -
And even better (if it is possible) -
with the missing months filled in
You could try building the full list of Months/Years from your tables using a UNION subquery, and then use that to drive your joins.. Something like this:
SELECT a.[Year], a.[Month], b.Fees AS [Billing],
f.Estimate AS [Estimate],
w.Fees AS [WIP]
FROM (SELECT a.[Year], a.[Month] FROM MonthlyBillingTotals AS a
UNION
SELECT b.[Year], b.[Month] FROM MonthlyFeeEstimates AS b
UNION
SELECT c.[Year], c.[Month] FROM MonthlyInstructionsWIP AS c) AS a
LEFT OUTER JOIN MonthlyBillingTotals AS b
ON (a.[Year] = b.[Year] AND a.[Month] = b.[Month])
LEFT OUTER JOIN MonthlyFeeEstimates AS f
ON (a.[Year] = f.[Year] AND a.[Month] = f.[Month])
LEFT OUTER JOIN MonthlyInstructionsWIP AS w
ON (a.[Year] = w.[Year] AND a.[Month] = w.[Month])
ORDER BY a.[Year], a.[Month]
This is completely untested, but see if this solves your problems:
SELECT b.[Year], b.[Month], Coalesce(b.Fees, '0') AS [Billing],
Coalesce(f.Estimate,'0') AS [Estimate],
Coalesce(w.Fees,'0') AS [WIP]
FROM MonthlyBillingTotals AS b
LEFT JOIN MonthlyFeeEstimates AS f
ON (b.[Year] = f.[Year] AND b.[Month] = f.[Month])
LEFT JOIN MonthlyInstructionsWIP AS w
ON (b.[Year] = w.[Year] AND b.[Month] = w.[Month])
ORDER BY b.[Year], b.[Month]
The Coalesce function puts in a '0' value if nothing is found, and left joins should only join parts of MonthlyFeeEstimates and MonthlyInstructionsWIP when the year and month match.
You could set up a small date table with year and month and left join the views with that, and use the ISNULL(variable,0) function to replace NULL with 0. Another option instead of a date table would be to use a common table expression to generate a date range to join with. In any case I suggest you look up the date table (or numbers table), it can be a really useful tool.
Edit: added an example on how a date table can be created (for reference):
declare #year_month table (y int, m int)
;with cte as (
select cast('2000-01-01' as datetime) date_value
union all
select date_value + 1
from cte
where date_value + 1 < '2010-12-31'
)
insert #year_month (y, m)
select distinct year(date_value), month(date_value)
from cte
order by 1, 2
option (maxrecursion 0)
select * from #year_month

How to rewrite this query (PostgreSQL) in SQL Server?

Few days ago I have asked a question about 1,2 and 3. degree connections. Question Link and #Snoopy gave an article link which can fix all my problems. Article Link
I have carefully examined this article but I was unable to use With Recursive query with SQL Server.
PostgreSQL Query:
SELECT a AS you,
b AS mightknow,
shared_connection,
CASE
WHEN (n1.feat1 = n2.feat1 AND n1.feat1 = n3.feat1) THEN 'feat1 in common'
WHEN (n1.feat2 = n2.feat2 AND n1.feat2 = n3.feat2) THEN 'feat2 in common'
ELSE 'nothing in common'
END AS reason
FROM (
WITH RECURSIVE transitive_closure(a, b, distance, path_string) AS
( SELECT a, b, 1 AS distance,
a || '.' || b || '.' AS path_string,
b AS direct_connection
FROM edges2
WHERE a = 1 -- set the starting node
UNION ALL
SELECT tc.a, e.b, tc.distance + 1,
tc.path_string || e.b || '.' AS path_string,
tc.direct_connection
FROM edges2 AS e
JOIN transitive_closure AS tc ON e.a = tc.b
WHERE tc.path_string NOT LIKE '%' || e.b || '.%'
AND tc.distance < 2
)
SELECT a,
b,
direct_connection AS shared_connection
FROM transitive_closure
WHERE distance = 2
) AS youmightknow
LEFT JOIN nodes AS n1 ON youmightknow.a = n1.id
LEFT JOIN nodes AS n2 ON youmightknow.b = n2.id
LEFT JOIN nodes AS n3 ON youmightknow.shared_connection = n3.id
WHERE (n1.feat1 = n2.feat1 AND n1.feat1 = n3.feat1)
OR (n1.feat2 = n2.feat2 AND n1.feat2 = n3.feat2);
or just
WITH RECURSIVE transitive_closure(a, b, distance, path_string) AS
( SELECT a, b, 1 AS distance,
a || '.' || b || '.' AS path_string
FROM edges
WHERE a = 1 -- source
UNION ALL
SELECT tc.a, e.b, tc.distance + 1,
tc.path_string || e.b || '.' AS path_string
FROM edges AS e
JOIN transitive_closure AS tc ON e.a = tc.b
WHERE tc.path_string NOT LIKE '%' || e.b || '.%'
)
SELECT * FROM transitive_closure
WHERE b=6 -- destination
ORDER BY a, b, distance;
As I said, I don't know how to write a recursive query with SQL Server using CTEs. Made a search and examined this page but still no luck. I couldn't run the query.
If someone interested, here is the answer;
I managed to convert the query in question to SQL by;
converting integer values to varchar(MAX). If you don't specify the length of varchar as MAX, you'll get "Types don't match between the anchor and the recursive part in column..."
I replaced || to +
I added ; to the beginning of query
Finally as #a_horse_with_no_name proposed I removed RECURSIVE from query.
Result;
;WITH transitive_closure(a, b, distance, path_string) AS
( SELECT a, b, 1 AS distance,
CAST(a as varchar(MAX)) + '.' + CAST(b as varchar(MAX)) + '.' AS path_string
FROM edges
WHERE a = 1 -- source
UNION ALL
SELECT tc.a, e.b, tc.distance + 1,
CAST(tc.path_string as varchar(MAX)) + CAST(e.b as varchar(MAX)) + '.' AS path_string
FROM edges AS e
JOIN transitive_closure AS tc ON e.a = tc.b
WHERE tc.path_string NOT LIKE '%' + CAST(e.b as varchar(MAX)) + '.%'
)
SELECT * FROM transitive_closure
WHERE b=6 -- destination
ORDER BY a, b, distance;
The recursive CTE should be the same on SQL Server (at least on a recent version, this was introduced with SQL Server 2005 if I'm not mistaken), just leave out the recursive keyword.
Note that SQL Server does not comply with the SQL standard and therefor you need to replace the || concatenation with +