Table RowCount in a Common Table Expression (CTE) query? - sql-server

I am trying to get a record count in my output... but struggling with the syntax. Can't see what I'm doing wrong.... help please
SELECT
InfoSchema.table_catalog + '.' +
InfoSchema.Table_Schema + '.' +
InfoSchema.Table_Name as PhysicalName,
CONVERT(char(10), SysObj.crdate,126) as Created,
SysInx.rows Records
FROM
INFORMATION_SCHEMA.TABLES AS InfoSchema left join
SYSOBJECTS AS SysObj ON InfoSchema.TABLE_NAME = SysObj.name
WHERE
TABLE_TYPE = 'BASE TABLE'
AND SysObj.crdate is not null
AND SysObj.id = (
SELECT MIN(SysInx.id), rows
FROM SYSINDEXES AS SysInx
WHERE SysInx.id = SysObj.id
)
The desired output would be this:
PhysicalName Created RowCount
--------------+------------+-------------
tableName1 2020-01-01 127653
tableName2 2018-01-01 234098
tableName3 2019-01-01 0

Use a subquery in the result column.
SELECT
...
(
SELECT MAX(rows)
FROM SYSINDEXES AS SysInx
WHERE SysInx.id = SysObj.id
) AS Records
FROM
...
WHERE
...
AND SysObj.id IN (
SELECT SysInx.id
FROM SYSINDEXES AS SysInx
)

Related

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

TSQL to Eliminate Repetitive Query

I have pretty basic table schema.
Table A
TEMPLATE_ID TEMPLATE_NAME
Table A has the following rows
1 Procs
2 Letter
3 Retire
4 Anniversary
5 Greet
6 Event
7 Meeting
8... etc.
Table B
TEMPLATE_ID VALUE
Table B has 100K+ rows with TEMPLATE_ID connecting the two tables.
Now the execs want a sample of 20 records of types 1-5 from table A. I could do something basic...which is about my speed when it comes to TSQL.
SELECT TOP(20) B.VALUE FROM TableB
JOIN TableA ON
B.TEMPLATE_ID = A.TEMPLATE_ID
AND TableA.TEMPLATE_NAME IN ('Procs', 'Letter'...)
But that isn't quite right as I end up with 20 rows...in other words I was expecting 100 rows. 20 for each.
Is this one of those areas where partition could be used. I can see how I would break TableB into partitions for each template (tableA) but I'm not sure how I would limit it to 20 rows.
OK so I could just cut and past into Excel 20 rows from each partition...I could also write 5 very basic queries...but this is kind of an academice...improve my knowledge pursuit.
So to clarify. 20 records from each of the first r template types.
TIA
you can use ROW_NUMBER and partition the data based on the template_name and return only 20 from each partition
SELECT * FROM
(
SELECT B.VALUE,
ROW_NUMBER() OVER ( PARTITION BY TableA.TEMPLATE_NAME ORDER BY ( select NULL)) as seq
FROM
TableB
JOIN TableA ON
B.TEMPLATE_ID = A.TEMPLATE_ID
) T
where T.seq <=20
order by B.VALUE
Could you try?
SELECT B.VALUE
FROM
(
SELECT TEMPLATE_ID,VALUE, DENSE_RANK ( ) OVER (PARTITION BY TEMPLATE_ID ORDER BY VALUE DESC) AS RANK_NO
FROM TABLE_B
) B INNER JOIN TABLE_A A ON (A.TEMPLATE_ID = B.TEMPLATE_ID)
WHERE A.TEMPLATE_NAME IN ('Procs', 'Letter'...)
AND B.RANK_NO <= 20
;
You use a ranking function. You first partition your data, order each partition and apply the ranking function:
select seq = row_number() over (
partition by table_catalog , table_schema , table_name
order by column_name
) ,
*
from information_schema.COLUMNS
The above code partitions the rows in information_schame.COLUMNS on the fully-qualified table/view name to which they belong. Each partition is then ordered alphabetically and given a row_number().
That then gets wrapped in another select which makes use of it. This code pulls the first 3 columns for each table in the system based on column and provides some information about it:
select t.table_name ,
t.table_schema ,
t.table_name ,
t.table_type ,
c.seq ,
c.ordinal_position ,
c.COLUMN_NAME ,
data_type = c.data_type + coalesce('('+convert(varchar,c.character_maximum_length)+')','')
+ case c.is_nullable when 'yes' then ' is null' else ' is not null' end
from information_schema.tables t
join ( select seq = row_number() over (
partition by table_catalog , table_schema , table_name
order by column_name
) ,
*
from information_schema.COLUMNS
) c on c.table_catalog = t.table_catalog
and c.table_schema = t.table_schema
and c.table_name = t.table_name
where c.seq <= 3
order by t.table_catalog ,
t.table_schema ,
t.table_name ,
c.seq
SELECT * FROM
( SELECT B.VALUE, TableA.TEMPLATE_NAME
ROW_NUMBER() OVER ( PARTITION BY A.TEMPLATE_ID ORDER BY NEWID() ) as row
FROM TableB
JOIN TableA
ON A.TEMPLATE_ID = B.TEMPLATE_ID
AND A.TEMPLATE_ID <= 5
) T
where T.row <= 20
order by B.VALUE

How to concatenate strings from multiple rows in one column + inner join in one query

I have a query with the following result:
query:
SELECT Tasks.TaskId, Comments.Comment, comments.timespent
FROM comments
INNER JOIN tasks ON comments.entityid = tasks.taskid
WHERE ( comments.entity = 1 )
GROUP BY Tasks.TaskId, Comments.Comment, comments.timespent
Result:
TaskID Comment TimeSpent
__________________________
111754 C1 4
111754 C2 1
111754 C3 79
Please tell me how should I write my query to get the result as follows:
TaskID Comment TimeSpent
__________________________________
111754 ,C1,C2,C3 84
Thanks in advance.
Here's the working SQL Fiddle: http://sqlfiddle.com/#!3/3597a/3
Here's the actual working SQL.
SELECT Tasks.TaskId, SUBSTRING(
(SELECT ',' + Comments.Comment
FROM Comments
INNER JOIN tasks ON comments.entityid = tasks.taskid
FOR XML PATH('')),2,200000) AS Comments
, SUM(comments.timespent) AS TimeSpent
FROM comments
INNER JOIN tasks ON comments.entityid = tasks.taskid
WHERE ( comments.entity = 1 )
GROUP BY Tasks.TaskId
Create Table and Populate Data
CREATE TABLE Tasks
(
TaskID NVARCHAR(20) NOT NULL,
);
CREATE TABLE Comments
(
Entity INT NOT NULL,
EntityID NVARCHAR(20) NOT NULL,
Comment NVARCHAR(50) NOT NULL,
TimeSpent INT NOT NULL
);
INSERT INTO Tasks VALUES
( '111754' );
INSERT INTO Comments VALUES
(1,'111754', 'C1',4 ),
(1,'111754', 'C2',1 ),
(1,'111754', 'C3',79 );
Execute SQL
SELECT Tasks.TaskId, SUBSTRING(
(SELECT ',' + Comments.Comment
FROM Comments
INNER JOIN tasks ON comments.entityid = tasks.taskid
FOR XML PATH('')),2,200000) AS Comments
, SUM(comments.timespent) AS TimeSpent
FROM comments
INNER JOIN tasks ON comments.entityid = tasks.taskid
WHERE comments.entity = 1
GROUP BY Tasks.TaskId
View Results.
TASKID COMMENTS TIMESPENT
111754 C1,C2,C3 84
You can do with CROSS APPLY with XML Path such as:
`
Select *
from table1 t1
CROSS APPLY
(
SELECT
[text()] = t.[Name] + '; '
FROM table2 t2
WHERE t1.[Id] = t2.[Id]
ORDER BY t2.name
FOR XML PATH('')
) a (Type)
`
You should look into FOR XML PATH.
Ok, this is a bit more complicated but it doesn't use xml, and it can be used with other databases than sql server:
WITH orig
AS (SELECT 1 AS f1, 'C11' AS f2
UNION ALL
SELECT 1 AS f1, 'C12' AS f2
UNION ALL
SELECT 1 AS f1, 'C13' AS f2
UNION ALL
SELECT 2 AS f1, 'C21' AS f2
UNION ALL
SELECT 2 AS f1, 'C22' AS f2
UNION ALL
SELECT 2 AS f1, 'C23' AS f2
UNION ALL
SELECT 3 AS f1, 'C31' AS f2)
, orig2 AS (SELECT DISTINCT f1, f2 FROM orig)
, orig3 AS (SELECT f1, f2, row_number() OVER(PARTITION BY f1 ORDER BY f2) AS RowNum FROM orig2)
, orig4
-- Use recursion to concatenate the fields
AS (SELECT f1, CONVERT(VARCHAR(MAX), f2) AS val, rownum
FROM orig3
WHERE RowNum = 1
UNION ALL
SELECT orig4.f1, orig4.val + ', ' + orig3.f2 AS val, orig3.rownum
FROM orig4
INNER JOIN orig3
ON orig4.RowNum + 1 = orig3.RowNum
AND orig4.f1 = orig3.f1)
SELECT *
FROM orig4
-- select only the rows that match the maximum rownum
WHERE NOT EXISTS
(SELECT 1
FROM orig4 o44
WHERE o44.f1 = orig4.f1
AND o44.rownum > orig4.rownum)
Another approach that works only for sql server would be to build an aggregate CLR function that concatenates the values: http://msdn.microsoft.com/en-us/library/91e6taax%28v=vs.90%29.aspx.
If you came across this article but you use oracle, you have the option to use the query above or define a custom aggregate function in pl/sql (http://docs.oracle.com/cd/B28359_01/appdev.111/b28425/aggr_functions.htm).
You should use the STRING_AGG function. It is a sql aggregator function for string. Your final query I think will be:
SELECT Tasks.TaskId, STRING_AGG(Comments.Comment, ', '), SUM(comments.timespent)
FROM comments
INNER JOIN tasks ON comments.entityid = tasks.taskid
WHERE ( comments.entity = 1 )
GROUP BY Tasks.TaskId

Concatenating columns using CTE in SQL Server 2008

I have a table like
TABLEX -
+------+------------+
| NAME | TABLE_NAME |
+------+------------+
| X1 | X001 |
| X2 | X002 |
+------+------------+
This table contains a name column which is nothing but description and a table_name column which is actually a table already present in the database.
X001 Table has columns like X1_A, X1_B
X002 Table has columns like X2_A, X2_B
Now I want to concatenate all columns in the actual table present in the TABLE_NAME column in a comma separated string and display that as a column.
+------+------------+------------+
| NAME | TABLE_NAME | COLUMNS |
+------+------------+------------+
| X1 | X001 | X1_A, X1_B |
| X2 | X002 | X2_A, X2_B |
+------+------------+------------+
Now can this be achieved using CTE. I've already successfully created the query using STUFF with XML PATH, but I'm having performance issues because there are like 200 odd rows in the table that I've show above and each subsequent tables linked have like 100 columns each.
EDIT -
SELECT
P.NAME,
P.TABLE_NAME,
[COLUMNS]=(SELECT STUFF((SELECT ',' + NAME FROM sys.syscolumns WHERE ID = OBJECT_ID(P.TABLE_NAME) ORDER BY colorder FOR XML PATH('') ), 1, 1,''))
FROM TABLEX P
Where TABLEX is the table posted above.
Try this one -
DDL:
IF OBJECT_ID (N'dbo.TABLEX') IS NOT NULL
DROP TABLE TABLEX
IF OBJECT_ID (N'dbo.X001') IS NOT NULL
DROP TABLE X001
IF OBJECT_ID (N'dbo.X002') IS NOT NULL
DROP TABLE X002
CREATE TABLE dbo.TABLEX (NAME VARCHAR(50), TABLE_NAME VARCHAR(50))
INSERT INTO dbo.TABLEX (NAME, TABLE_NAME)
VALUES ('X1', 'X001'), ('X2', 'X002')
CREATE TABLE dbo.X001 (X1_A VARCHAR(50), X1_B VARCHAR(50))
CREATE TABLE dbo.X002 (X2_A VARCHAR(50), X2_B VARCHAR(50))
Query:
;WITH cte AS
(
SELECT
NAME
, TABLE_NAME
, [COLUMN] = CAST('' AS VARCHAR(1024))
, POS = 1
FROM TABLEX t
UNION ALL
SELECT
t.NAME
, t.TABLE_NAME
, CAST([COLUMN] + ', ' + c.name AS VARCHAR(1024))
, POS + 1
FROM cte t
JOIN sys.columns c ON
OBJECT_ID('dbo.' + t.TABLE_NAME) = c.[object_id]
AND
t.POS = c.column_id
)
SELECT
NAME
, TABLE_NAME
, [COLUMNS] = STUFF([COLUMN], 1, 2, '')
FROM (
SELECT *, rn = ROW_NUMBER() OVER (PARTITION BY NAME ORDER BY POS DESC)
FROM cte
) t
WHERE t.rn = 1
Results:
NAME TABLE_NAME COLUMNS
------ ------------- -------------
X1 X001 X1_A, X1_B
X2 X002 X2_A, X2_B
Query cost:
Statistic:
Query Presenter Scans Logical Reads
------------------- ----- -------------
XML 5 9
CTE 3 48

SQL Server - Include NULL using UNPIVOT

UNPIVOT will not return NULLs, but I need them in a comparison query. I am trying to avoid using ISNULL the following example (Because in the real sql there are over 100 fields):
Select ID, theValue, column_name
From
(select ID,
ISNULL(CAST([TheColumnToCompare] AS VarChar(1000)), '') as TheColumnToCompare
from MyView
where The_Date = '04/30/2009'
) MA
UNPIVOT
(theValue FOR column_name IN
([TheColumnToCompare])
) AS unpvt
Any alternatives?
To preserve NULLs, use CROSS JOIN ... CASE:
select a.ID, b.column_name
, column_value =
case b.column_name
when 'col1' then a.col1
when 'col2' then a.col2
when 'col3' then a.col3
when 'col4' then a.col4
end
from (
select ID, col1, col2, col3, col4
from table1
) a
cross join (
select 'col1' union all
select 'col2' union all
select 'col3' union all
select 'col4'
) b (column_name)
Instead of:
select ID, column_name, column_value
From (
select ID, col1, col2, col3, col4
from table1
) a
unpivot (
column_value FOR column_name IN (
col1, col2, col3, col4)
) b
A text editor with column mode makes such queries easier to write. UltraEdit has it, so does Emacs. In Emacs it's called rectangular edit.
You might need to script it for 100 columns.
It's a real pain. You have to switch them out before the UNPIVOT, because there is no row produced for ISNULL() to operate on - code generation is your friend here.
I have the problem on PIVOT as well. Missing rows turn into NULL, which you have to wrap in ISNULL() all the way across the row if missing values are the same as 0.0 for example.
I ran into the same problem. Using CROSS APPLY (SQL Server 2005 and later) instead of Unpivot solved the problem. I found the solution based on this article An Alternative (Better?) Method to UNPIVOT
and I made the following example to demonstrate that CROSS APPLY will NOT Ignore NULLs like Unpivot.
create table #Orders (OrderDate datetime, product nvarchar(100), ItemsCount float, GrossAmount float, employee nvarchar(100))
insert into #Orders
select getutcdate(),'Windows',10,10.32,'Me'
union
select getutcdate(),'Office',31,21.23,'you'
union
select getutcdate(),'Office',31,55.45,'me'
union
select getutcdate(),'Windows',10,null,'You'
SELECT OrderDate, product,employee,Measure,MeasureType
from #Orders orders
CROSS APPLY (
VALUES ('ItemsCount',ItemsCount),('GrossAmount',GrossAmount)
)
x(MeasureType, Measure)
SELECT OrderDate, product,employee,Measure,MeasureType
from #Orders orders
UNPIVOT
(Measure FOR MeasureType IN
(ItemsCount,GrossAmount)
)AS unpvt;
drop table #Orders
or, in SQLServer 2008 in shorter way:
...
cross join
(values('col1'), ('col2'), ('col3'), ('col4')) column_names(column_name)
Using dynamic SQL and COALESCE, I solved the problem like this:
DECLARE #SQL NVARCHAR(MAX)
DECLARE #cols NVARCHAR(MAX)
DECLARE #dataCols NVARCHAR(MAX)
SELECT
#dataCols = COALESCE(#dataCols + ', ' + 'ISNULL(' + Name + ',0) ' + Name , 'ISNULL(' + Name + ',0) ' + Name )
FROM Metric WITH (NOLOCK)
ORDER BY ID
SELECT
#cols = COALESCE(#cols + ', ' + Name , Name )
FROM Metric WITH (NOLOCK)
ORDER BY ID
SET #SQL = 'SELECT ArchiveID, MetricDate, BoxID, GroupID, ID MetricID, MetricName, Value
FROM
(SELECT ArchiveID, [Date] MetricDate, BoxID, GroupID, ' + #dataCols + '
FROM MetricData WITH (NOLOCK)
INNER JOIN Archive WITH (NOLOCK)
ON ArchiveID = ID
WHERE BoxID = ' + CONVERT(VARCHAR(40), #BoxID) + '
AND GroupID = ' + CONVERT(VARCHAR(40), #GroupID) + ') p
UNPIVOT
(Value FOR MetricName IN
(' + #cols + ')
)AS unpvt
INNER JOIN Metric WITH (NOLOCK)
ON MetricName = Name
ORDER BY MetricID, MetricDate'
EXECUTE( #SQL )
I've found left outer joining the UNPIVOT result to the full list of fields, conveniently pulled from INFORMATION_SCHEMA, to be a practical answer to this problem in some contexts.
-- test data
CREATE TABLE _t1(name varchar(20),object_id varchar(20),principal_id varchar(20),schema_id varchar(20),parent_object_id varchar(20),type varchar(20),type_desc varchar(20),create_date varchar(20),modify_date varchar(20),is_ms_shipped varchar(20),is_published varchar(20),is_schema_published varchar(20))
INSERT INTO _t1 SELECT 'blah1', 3, NULL, 4, 0, 'blah2', 'blah3', '20100402 16:59:23.267', NULL, 1, 0, 0
-- example
select c.COLUMN_NAME, Value
from INFORMATION_SCHEMA.COLUMNS c
left join (
select * from _t1
) q1
unpivot (Value for COLUMN_NAME in (name,object_id,principal_id,schema_id,parent_object_id,type,type_desc,create_date,modify_date,is_ms_shipped,is_published,is_schema_published)
) t on t.COLUMN_NAME = c.COLUMN_NAME
where c.TABLE_NAME = '_t1'
</pre>
output looks like:
+----------------------+-----------------------+
| COLUMN_NAME | Value |
+----------------------+-----------------------+
| name | blah1 |
| object_id | 3 |
| principal_id | NULL | <======
| schema_id | 4 |
| parent_object_id | 0 |
| type | blah2 |
| type_desc | blah3 |
| create_date | 20100402 16:59:23.26 |
| modify_date | NULL | <======
| is_ms_shipped | 1 |
| is_published | 0 |
| is_schema_published | 0 |
+----------------------+-----------------------+
Writing in May'22 with testing it on AWS Redshift.
You can use a with clause where you can coalesce the columns where nulls are expected. Alternatively, you can use coalesce in the select statement prior to the UNPIVOT block.
And don't forget to alias with the original column name (Not following won't break or violate the rule but would save some time for coffee).
Select ID, theValue, column_name
From
(select ID,
coalesce(CAST([TheColumnToCompare] AS VarChar(1000)), '') as TheColumnToCompare
from MyView
where The_Date = '04/30/2009'
) MA
UNPIVOT
(theValue FOR column_name IN
([TheColumnToCompare])
) AS unpvt
OR
WITH TEMP1 as (
select ID,
coalesce(CAST([TheColumnToCompare] AS VarChar(1000)), '') as TheColumnToCompare
from MyView
where The_Date = '04/30/2009'
)
Select ID, theValue, column_name
From
(select ID, TheColumnToCompare
from MyView
where The_Date = '04/30/2009'
) MA
UNPIVOT
(theValue FOR column_name IN
([TheColumnToCompare])
) AS unpvt
I had your same problem and this is
my quick and dirty solution :
your query :
select
Month,Name,value
from TableName
unpivot
(
Value for Name in (Col_1,Col_2,Col_3,Col_4,Col_5
)
) u
replace with :
select Month,Name,value from
( select
isnull(Month,'no-data') as Month,
isnull(Name,'no-data') as Name,
isnull(value,'no-data') as value from TableName
) as T1
unpivot
(
Value
for Name in (Col_1,Col_2,Col_3,Col_4,Col_5)
) u
ok the null value is replaced with a string, but all rows will be returned !!
ISNULL is half the answer. Use NULLIF to translate back to NULL. E.g.
DECLARE #temp TABLE(
Foo varchar(50),
Bar varchar(50) NULL
);
INSERT INTO #temp( Foo,Bar )VALUES( 'licious',NULL );
SELECT * FROM #temp;
SELECT
Col,
NULLIF( Val,'0Null' ) AS Val
FROM(
SELECT
Foo,
ISNULL( Bar,'0Null' ) AS Bar
FROM
#temp
) AS t
UNPIVOT(
Val FOR Col IN(
Foo,
Bar
)
) up;
Here I use "0Null" as my intermediate value. You can use anything you like. However, you risk collision with user input if you choose something real-world like "Null". Garbage works fine "!##34())0" but may be more confusing to future coders. I am sure you get the picture.

Resources