Count of rows from a joined table SQL Server - sql-server

Is it possible to make a count of rows (I am using a view) with a resultset of a subquery?
Assuming I have
Table1
t1id
Table2
id, t1id_Parent, t1id_Child
Table3
t1id
I query for
select
t1.t1id as id,
stuff((select ', ' + CAST(CASE WHEN t2.t1id_Child is not null THEN t2.t1id_Child ELSE '0' END AS varchar) from Table1 t where t.t1id_Parent = t1.t1id for xml path('')),1,2,'') as ids
FROM
Table1 as t1
LEFT OUTER JOIN
Table1 AS t2 ON t1.t1id = t2.t1id_Parent
So I have the needed ids combined into a string that looks like this '1,2,5,9' in the column named "ids" that is working correctly however what I need to do now is have this query
select
t1.t1id as id,
stuff((select ', ' + CAST(CASE WHEN t2.t1id_Child is not null THEN t2.t1id_Child ELSE '0' END AS varchar) from Table1 t where t.t1id_Parent = t1.t1id for xml path('')),1,2,'') as ids
FROM
Table1 as t1
LEFT OUTER JOIN
Table2 AS t2 ON t2.t1id_Parent = t1.t1id
LEFT OUTER JOIN
(select count(t1id) as rows_count, t1id
from Table3
group by t1id) as docs ON docs.t1id in (ids)
to retrieve the row count from the 3rd table using the temporary column however I am getting an error of Invalid column name 'ids'
Is this possible or do I need to first run that query and then go back and run a new one for the count of the third table?
The result I need is
Table1.t1id, count(Table3.t1id)
Table3.t1id comes from Table1.t1id and all Table2.t1id_Child relation through Table1.t1id = Table2.t1id_Parent
I guess not possible, but got it partially working with CTE, I am now getting the list of say I have this '15673,15690,90987,45058' I can then do the query for the third column but getting the error
WITH items AS
(
select
a.ItemID,
stuff(
(select ',' +
CAST(
CASE WHEN b.[ChildItemID] is not null
THEN b.[ChildItemID] ELSE 0 END AS varchar)
from Table2 t2
where t2.[ParentItemID] = a.ItemID
for xml path('')
),1,2,'')
select
items.*,
docs.docs_count
from items
LEFT OUTER JOIN (
select count([DocID]) as docs_count from Table3
group by DocID
) as docs ON docs.ItemID in (items.ItemID)
Conversion failed when converting the nvarchar value '15673,' to data type int.
Note that the issue is not concatenating, I am getting the result expected there, a comma separated list of values, the issue comes when I try to use the comma separated values in "as docs ON docs.ItemID in (items.ItemIDs)"

Related

Find the record count in each column (exclude blank and null values) of table in entire database

I would like to find the record count of each columns of the all the tables in the entire database. The count must exclude null and blank rows of that column. I tried the following code, but it displays the row count for that table.
select
db_name() DatabaseName,
s.name + '.' + o.name TableName, c.name ColumnName, sum(p.rows) RecordCount
from sys.indexes i inner join sys.partitions p
on i.[object_id] = p.[object_id] and i.index_id = p.index_id
inner join sys.objects o
on o.[object_id] = i.[object_id]
inner join sys.columns c
on o.[object_id] = c.[object_id]
inner join sys.schemas s
on o.[schema_id] = s.[schema_id]
where i.index_id < 2
--and s.name in ('dbo', 'your_other_schema')
and o.type = 'U'
group by s.name, o.name , c.name
order by s.name, o.name;
The code output.
https://i.stack.imgur.com/kNNRZ.png
The column system_id has 5767 row count where as objectstate has only 2589 row count (excluding blank and null values)
So I expect the OBJECTSTATE column count to be 2589, SYSTEM_ID count to 5767
The output like
Table Column Count
Table1 Column1 50 (excluding null and blank row)
Table1 Column2 45 (excluding null and blank row)
Table2 Column1 100 (excluding null and blank row)
Table2 column2 69 (excluding null and blank row)
As documented here, sys.partitions.rows "Indicates the approximate number of rows in this partition."
Joining sys.partitions to sys.objects and sys.columns doesn't help because you still have the rows only on partition level.
That said, you're querying the metadata of the database. Doing this, you cannot obtain the counts you need.
What you have to do is execute a query for each table to get the counts for the table's columns. A query to do this for one particular table (e.g. Table1) might be:
SELECT
-- col1 would be a string (varchar, char, etc.) column
count(nullif(col1, '')) as col1_count,
-- col2 would not be a string column
count(col2) as col2_count
FROM Table1
The above query leaves you with a result set containing one colum for each original column and one single row containing the counts. You could change the query to use PIVOT to swap columns and rows which would give you your desired structure.
To get what you want for all tables without the manual work, you could use your original query (just the join of sys.objects and sys.columns though) and loop through that query's results using a CURSOR. In this loop you'd generate the queries for all the tables as strings or one big query string with some UNION ALL. These strings/this string would then have to be executed using EXEC()
Example:
DECLARE #MyQuery NVARCHAR(MAX)
SET #MyQuery = 'SELECT ... FROM Table1 UNION ALL SELECT ... FORM Table2 ...'
EXEC(#MyQuery)

Using sub-queries and filter in WHERE clause while joining tables

SELECT DISTINCT(t1.Ticker),t2.SecurityID,t2.ClosePrice,t2.QuoteDateTime FROM [Hub].[SecurityMaster].[SecurityMasterDetails] as t1
INNER JOIN [Hub].[SecurityMaster].[SecurityPrices] as t2
ON t2.SecurityID =t1.SecurityID
WHERE t2.QuoteDateTime IN (SELECT max(QuoteDateTime) FROM [Hub].[SecurityMaster].[SecurityPrices]) AND t1.SecurityTypeName = 'REIT'
I get an output with no data. The subquery doesn't run along with the other filter in the WHERE clause. I am not sure what I am doing wrong. Can somebody please help!
If you are trying to get the lastest row from SecurityPrices for each Ticker, one option is to use cross apply():
select --distinct /* distinct not needed if `Ticker` is unique on `smd`
smd.Ticker
, sp.SecurityID
, sp.ClosePrice
, sp.QuoteDateTime
from [Hub].[SecurityMaster].[SecurityMasterDetails] as smd
cross apply (
select top 1
i.SecurityID
, i.ClosePrice
, i.QuoteDateTime
from [Hub].[SecurityMaster].[SecurityPrices] i
where i.SecurityID = smd.SecurityID
order by i.QuoteDateTime desc
) as sp
where SecurityTypeName = 'REIT' /* which table does this column belong to? */
I think your query would be
SELECT DISTINCT TOP 1 WITH TIES
t1.Ticker,
t2.SecurityID,
t2.ClosePrice,
t2.QuoteDateTime
FROM [Hub].[SecurityMaster].[SecurityMasterDetails] as t1
INNER JOIN [Hub].[SecurityMaster].[SecurityPrices] as t2 ON t2.SecurityID =t1.SecurityID
WHERE SecurityTypeName = 'REIT'
ORDER BY t2.QuoteDateTime DESC
You aren't getting results because the max(QuoteDateTime) record doesn't have SecurityTypeName = 'REIT'. I think you want the max(QuoteDateTime) for this SecurityTypeName, so this can be done with an INNER JOIN.
SELECT DISTINCT
(t1.Ticker),
t2.SecurityID,
t2.ClosePrice,
t2.QuoteDateTime
FROM [Hub].[SecurityMaster].[SecurityMasterDetails] as t1
INNER JOIN [Hub].[SecurityMaster].[SecurityPrices] as t2
ON t2.SecurityID =t1.SecurityID
INNER JOIN
(SELECT max(QuoteDateTime) DT FROM [Hub].[SecurityMaster].[SecurityPrices]) P on P.DT = t2.QuoteDateTime
WHERE SecurityTypeName = 'REIT'
EDIT
Your data doesn't have what you think it does, I suspect. Here is how you can check...
--Find the SecurityID that matches the max date
SELECT
SecurityID ,
max(QuoteDateTime) DT
FROM [Hub].[SecurityMaster].[SecurityPrices]
GROUP BY SecurityID
--I'm betting this ID isn't in your SecurityMasterDetails where the Type is REIT
SELECT DISTINCT
SecurityID
FROM SecurityMasterDetails
WHERE SecurityTypeName = 'REIT'
Since the SecurityID returned in the first query isn't in the second query result set, you are going to get NULL results.

SQL Server 2012, exclude column

I have SQL query
; with cte as
(
SELECT
PARSENAME(REPLACE(replace(replace(replace(replace(dbo.IDENTITY_MAP.Name, 'My Company\', ''), '-VLAN2', ''), '.VLAN2\', ''), '.Instr\', '') , '\' , '.'), 1) as "Site",
Count (CASE
WHEN dbo.SEM_AGENT.AGENT_VERSION LIKE '11.%' THEN 1
END) AS 'SEP-11',
Count (CASE
WHEN dbo.SEM_AGENT.AGENT_VERSION LIKE '12.%' THEN 1
END) AS 'SEP-12',
FROM
dbo.sem_computer
INNER JOIN
[dbo].[V_SEM_COMPUTER] ON [dbo].[V_SEM_COMPUTER].COMPUTER_ID = SEM_COMPUTER.COMPUTER_ID
WHERE
dbo.IDENTITY_MAP.Name NOT LIKE '%Servers%'
GROUP BY
PARSENAME(REPLACE(replace(replace(replace(replace(dbo.IDENTITY_MAP.Name,'My Company\',''),'-VLAN2',''),'.VLAN2\',''),'.Instr\','') , '\' , '.'),1)
)
select *
from cte
join SEPM_site ss on cte.Site = ss.Site
That gives output I am looking for ------ almost i.e.
Site SEP-11 SEP-12 Rackcode Circuit Site
I only need one column for Site.
I tried recreating a temporary table with the columns, and dropping it, i.e.
; with cte as (SELECT ...)
select * into temptable
from cte
join SEPM_site ss
on cte.Site = ss.Site
alter table temptable
drop column cte.Site
select * from temptable
drop table temptable
But I get error
Incorrect syntax near '.'
And if I don't specify which table Site is from, I get error,
Column names in each table must be unique. Column name 'Site' in table 'temptable' is specified more than once.
But that's why I am trying to remove duplicate column!
Thanks!
Just specify the columns you want in your select statement:
select cte.Site, cte.[SEP-11], cte.[SEP-12], ss.Rackcode, ss.Circuit
from cte
join SEPM_site ss
on cte.Site = ss.Site
You can also select all columns in cte and just the ones you want in ss:
select cte.*, ss.Rackcode, ss.Circuit
from cte
join SEPM_site ss
on cte.Site = ss.Site

Acquiring Row index with identity column in Join Query

I need to add the results from a Left Join query to a table that does not have its index set to be an identity, but the int must still be unique. My insert query looks like this:
INSERT INTO Table1 (S.SubjectID, S.Subject, S.SubjectDescription, S.Status)
SELECT (Select MAX(SubjectID) FROM Table1) + ???? , N.Code, N.Literal, N.Trans
FROM Table2 N LEFT JOIN Table1 S ON N.Code = S.Subject
WHERE (N.Code IS NULL OR S.Subject IS NULL OR N.Trans = 'D')
Where I have the ???? is where i need to have some incrementing value so that when inserting into the table1 the ID's will be unique.
I am not allowed to change the table's structure, I just need something that can calculate his on the fly.
As always help, tips and references are much appreciated.
In most databases, you can use row_number() for this purpose. Here is an example with SQL Server syntax:
INSERT INTO Table1 (S.SubjectID, S.Subject, S.SubjectDescription, S.Status)
SELECT (Select MAX(SubjectID) FROM Table1) + row_number() over (order by (select NULL)) ,
N.Code, N.Literal, N.Trans
FROM Table2 N LEFT JOIN Table1 S ON N.Code = S.Subject
WHERE (N.Code IS NULL OR S.Subject IS NULL OR N.Trans = 'D')

Combine multiple results in a subquery into a single comma-separated value

I've got two tables:
TableA
------
ID,
Name
TableB
------
ID,
SomeColumn,
TableA_ID (FK for TableA)
The relationship is one row of TableA - many of TableB.
Now, I want to see a result like this:
ID Name SomeColumn
1. ABC X, Y, Z (these are three different rows)
2. MNO R, S
This won't work (multiple results in a subquery):
SELECT ID,
Name,
(SELECT SomeColumn FROM TableB WHERE F_ID=TableA.ID)
FROM TableA
This is a trivial problem if I do the processing on the client side. But this will mean I will have to run X queries on every page, where X is the number of results of TableA.
Note that I can't simply do a GROUP BY or something similar, as it will return multiple results for rows of TableA.
I'm not sure if a UDF, utilizing COALESCE or something similar might work?
Even this will serve the purpose
Sample data
declare #t table(id int, name varchar(20),somecolumn varchar(MAX))
insert into #t
select 1,'ABC','X' union all
select 1,'ABC','Y' union all
select 1,'ABC','Z' union all
select 2,'MNO','R' union all
select 2,'MNO','S'
Query:
SELECT ID,Name,
STUFF((SELECT ',' + CAST(T2.SomeColumn AS VARCHAR(MAX))
FROM #T T2 WHERE T1.id = T2.id AND T1.name = T2.name
FOR XML PATH('')),1,1,'') SOMECOLUMN
FROM #T T1
GROUP BY id,Name
Output:
ID Name SomeColumn
1 ABC X,Y,Z
2 MNO R,S
1. Create the UDF:
CREATE FUNCTION CombineValues
(
#FK_ID INT -- The foreign key from TableA which is used
-- to fetch corresponding records
)
RETURNS VARCHAR(8000)
AS
BEGIN
DECLARE #SomeColumnList VARCHAR(8000);
SELECT #SomeColumnList =
COALESCE(#SomeColumnList + ', ', '') + CAST(SomeColumn AS varchar(20))
FROM TableB C
WHERE C.FK_ID = #FK_ID;
RETURN
(
SELECT #SomeColumnList
)
END
2. Use in subquery:
SELECT ID, Name, dbo.CombineValues(FK_ID) FROM TableA
3. If you are using stored procedure you can do like this:
CREATE PROCEDURE GetCombinedValues
#FK_ID int
As
BEGIN
DECLARE #SomeColumnList VARCHAR(800)
SELECT #SomeColumnList =
COALESCE(#SomeColumnList + ', ', '') + CAST(SomeColumn AS varchar(20))
FROM TableB
WHERE FK_ID = #FK_ID
Select *, #SomeColumnList as SelectedIds
FROM
TableA
WHERE
FK_ID = #FK_ID
END
In MySQL there is a group_concat function that will return what you're asking for.
SELECT TableA.ID, TableA.Name, group_concat(TableB.SomeColumn)
as SomColumnGroup FROM TableA LEFT JOIN TableB ON
TableB.TableA_ID = TableA.ID
I think you are on the right track with COALESCE. See here for an example of building a comma-delimited string:
http://www.sqlteam.com/article/using-coalesce-to-build-comma-delimited-string
You may need to provide some more details for a more precise response.
Since your dataset seems kind of narrow, you might consider just using a row per result and performing the post-processing at the client.
So if you are really looking to make the server do the work return a result set like
ID Name SomeColumn
1 ABC X
1 ABC Y
1 ABC Z
2 MNO R
2 MNO S
which of course is a simple INNER JOIN on ID
Once you have the resultset back at the client, maintain a variable called CurrentName and use that as a trigger when to stop collecting SomeColumn into the useful thing you want it to do.
Assuming you only have WHERE clauses on table A create a stored procedure thus:
SELECT Id, Name From tableA WHERE ...
SELECT tableA.Id AS ParentId, Somecolumn
FROM tableA INNER JOIN tableB on TableA.Id = TableB.F_Id
WHERE ...
Then fill a DataSet ds with it. Then
ds.Relations.Add("foo", ds.Tables[0].Columns("Id"), ds.Tables[1].Columns("ParentId"));
Finally you can add a repeater in the page that puts the commas for every line
<asp:DataList ID="Subcategories" DataKeyField="ParentCatId"
DataSource='<%# Container.DataItem.CreateChildView("foo") %>' RepeatColumns="1"
RepeatDirection="Horizontal" ItemStyle-HorizontalAlign="left" ItemStyle-VerticalAlign="top"
runat="server" >
In this way you will do it client side but with only one query, passing minimal data between database and frontend
I tried the solution priyanka.sarkar mentioned and the didn't quite get it working as the OP asked. Here's the solution I ended up with:
SELECT ID,
SUBSTRING((
SELECT ',' + T2.SomeColumn
FROM #T T2
WHERE WHERE T1.id = T2.id
FOR XML PATH('')), 2, 1000000)
FROM #T T1
GROUP BY ID
Solution below:
SELECT GROUP_CONCAT(field_attr_best_weekday_value)as RAVI
FROM content_field_attr_best_weekday LEFT JOIN content_type_attraction
on content_field_attr_best_weekday.nid = content_type_attraction.nid
GROUP BY content_field_attr_best_weekday.nid
Use this, you also can change the Joins
SELECT t.ID,
t.NAME,
(SELECT t1.SOMECOLUMN
FROM TABLEB t1
WHERE t1.F_ID = T.TABLEA.ID)
FROM TABLEA t;
This will work for selecting from different table using sub query.
I have reviewed all the answers. I think in database insertion should be like:
ID Name SomeColumn
1. ABC ,X,Y Z (these are three different rows)
2. MNO ,R,S
The comma should be at previous end and do searching by like %,X,%

Resources