How to aggregate data in rows into several and perform other SQL operation with them? - sql

I have a table (result of selecting from some table, ordered by the Change column) like this:
ID Change
1001 4
1002 4
1003 4
1004 3
1005 3
1006 2
... ...
And I want to update another table with above data as below:
update sometable set columnA=columnA + 4 where ID in (1001, 1002, 1003)
update sometable set columnA=columnA + 3 where ID in (1004, 1005)
update sometable set columnA=columnA + 2 where ID in (1006)
...
How could I perform this with SQL command?

try this in Test sample data,
declare #t table(ID int,Change int)
insert into #t values (1001,4),(1002,4),(1003,4),(1004,3),(1005,3),(1006,2)
update s set s.columnA=s.columnA + t.change
from sometable S
inner join #t t on s.id=t.id

For your case the below is schema..
CREATE TABLE #TAB(ID INT, Change INT)
INSERT INTO #TAB
SELECT 1001 , 4
UNION ALL
SELECT 1002, 4
UNION ALL
SELECT 1003 , 4
UNION ALL
SELECT 1004, 3
UNION ALL
SELECT 1005 , 3
UNION ALL
SELECT 1006, 2
Now we can handle it using For XML Path and Stuff and a CTE as below
;WITH CTE AS(
SELECT DISTINCT CHANGE
,STUFF( (SELECT ', '+CAST(ID AS VARCHAR(100)) FROM #TAB T2
WHERE T2.Change= T.CHANGE FOR XML PATH('')),1,1,'') ID_PK FROM #TAB T
)
SELECT 'UPDATE SOMETABLE SET COLUMN_A= COLUMN_A+ '+CAST(CHANGE AS VARCHAR(10))+' WHERE ID IN ('+ID_PK +') ;' FROM CTE

Related

SQL, how can I create a lot of records for a reference join table?

At the moment, I'm using this, but its a little slow and I only end up with 1331 records. I'm thinking there must be a faster way to produce more records ?
CREATE TABLE IF NOT EXISTS util_nums (n integer primary key
autoincrement not null);
insert into util_nums values (0);
insert into util_nums(n) select null from (select 0 as n union select 1
union select 2 union select 3 union select 4 union select 5 union select 6
union select 7 union select 8 union select 9 union select 10)
a cross join (select 0 as n union select 1 union select 2 union select 3
union select 4 union select 5 union select 6 union select 7 union select 8
union select 9 union select 10) b cross join (select 0 as n union select 1
union select 2 union select 3 union select 4 union select 5 union select 6
union select 7 union select 8 union select 9 union select 10) c;
I'm using sqlite
I'm not sure if it's much faster, but you can try
--assuming table is empty, insert 2 records
insert into util_nums values (null);
insert into util_nums values (null);
insert into util_nums
select null from
util_nums,util_nums,util_nums,util_nums,util_nums,
util_nums,util_nums,util_nums,util_nums,util_nums,
util_nums,util_nums,util_nums,util_nums,util_nums,
util_nums,util_nums,util_nums,util_nums,util_nums
;
It inserts 2^20 (if you need more, just add another util_nums to from) + 2 records quite fast.
Create a file with all your data and then do a bulk insert into the table from the file.
See this
CREATE TABLE tmp_util_nums
(
n int NOT NULL,
)
go
DECLARE #SQL varchar(max)
Declare #PathFileName varchar(max)
SET #SQL = "BULK INSERT tmp_util_nums FROM '"+#PathFileName+"' WITH (FIELDTERMINATOR = ',') "
--Step 2: Execute BULK INSERT statement
EXEC (#SQL)
--Step 3: INSERT data into final table
INSERT into util_nums
SELECT * FROM tmp_util_nums
TRUNCATE TABLE tmp_util_nums
go

How to concatenate all strings from a certain column for each group

Suppose I have this table [Table1]
Name Mark
------- ------
ABC 10
DEF 10
GHI 10
JKL 20
MNO 20
PQR 30
What should be my SQL statement to retrieve a record that looks like this:
(group by [mark]).
I have done the 1 and 2 columns but don't know how to accomplish the third column (concat the [name] with the same [mark])
mark count names
---- ----- -----------
10 3 ABC,DEF,GHI
20 2 JKL,MNO
30 1 PQR
I'm using Microsoft SQL.
Please help. Thanks
If MS SQL 2005 or higher.
declare #t table([name] varchar(max), mark int)
insert #t values ('ABC', 10), ('DEF', 10), ('GHI', 10),
('JKL', 20), ('MNO', 20), ('PQR', 30)
select t.mark, COUNT(*) [count]
,STUFF((
select ',' + [name]
from #t t1
where t1.mark = t.mark
for xml path(''), type
).value('.', 'varchar(max)'), 1, 1, '') [values]
from #t t
group by t.mark
Output:
mark count values
----------- ----------- --------------
10 3 ABC,DEF,GHI
20 2 JKL,MNO
30 1 PQR
Here's a performance-related answer!
http://jerrytech.blogspot.com/2010/04/tsql-concatenate-strings-1-2-3-and.html
Using XML functions in a large query is a performance killer.
Using a CTE is a performance superstar.
Check out the link, it will explain how.
I admit the work to accomplish it is more.
But the result is milliseconds over millions of rows.
polishchuks solution is more elegant, but this is basically the same thing, we just deal with the trailing comma differently.
CREATE TABLE #Marks(Name nchar(3), Mark int)
INSERT INTO #Marks
SELECT 'ABC', 10 UNION ALL
SELECT 'DEF', 10 UNION ALL
SELECT 'GHI', 10 UNION ALL
SELECT 'JKL', 20 UNION ALL
SELECT 'MNO', 20 UNION ALL
SELECT 'PQR', 30
SELECT
mark,
[count],
CASE WHEN Len(Names) > 0 THEN LEFT(Names, LEN(Names) -1) ELSE '' END names
FROM
(
SELECT
Mark,
COUNT(Mark) AS [count],
(
SELECT DISTINCT
Name + ', '
FROM
#Marks M1
WHERE M1.Mark = M2.Mark
FOR XML PATH('')
) Names
FROM #Marks M2
GROUP BY Mark
) M
Loosely based on Itzik Ben-Gan, Inside Microsoft SQL Server 2005: T-SQL Programming, p. 215:
IF OBJECT_ID('dbo.Table1') IS NOT NULL
DROP TABLE dbo.Table1 ;
GO
CREATE TABLE dbo.Table1 ( Name VARCHAR(10), Mark INT ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'ABC', 10 ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'DEF', 10 ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'GHI', 10 ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'JKL', 20 ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'MNO', 20 ) ;
INSERT INTO dbo.Table1 ( Name, Mark ) VALUES ( 'PQR', 30 ) ;
WITH DelimitedNames AS
(
SELECT Mark, T2.Count,
( SELECT Name + ',' AS [text()]
FROM dbo.Table1 AS T1
WHERE T1.Mark = T2.Mark
ORDER BY T1.Mark
FOR XML PATH('')) AS Names
FROM ( SELECT Mark, COUNT(*) AS Count FROM dbo.Table1 GROUP BY Mark ) AS T2
)
SELECT Mark, Count, LEFT(Names, LEN(NAMES) - 1) AS Names
FROM DelimitedNames ;

SQL grouping by parent child

If I had this structure with the columns:
Primary_Key, Name, Parent_Primary_ID, DISPLAY_ORDER
1 Event NULL 1
2 News NULL 2
3 Event_List 1 1
4 Event_Detail 1 2
5 News_List 2 1
6 News_Details 2 2
how would you return data like:
1 Event
3 Event_List
4 Event_Detail
2 News
5 News_List
6 News_Detail
Thanks
Rob
If SQL Server 2005+
DECLARE #YourTable TABLE
(Primary_Key INT PRIMARY KEY,
Name VARCHAR(100),
Parent_Primary_ID INT NULL,
DISPLAY_ORDER INT)
INSERT INTO #YourTable
SELECT 1,'Event',NULL,1 UNION ALL
SELECT 2,'News',NULL,2 UNION ALL
SELECT 3,'Event_List',1,1 UNION ALL
SELECT 4,'Event_Detail',1,2 UNION ALL
SELECT 5,'News_List',2,1 UNION ALL
SELECT 6,'News_Details',2,2;
WITH Hierarchy
AS (SELECT *,
path = CAST(DISPLAY_ORDER AS VARCHAR(100))
FROM #YourTable
WHERE Parent_Primary_ID IS NULL
UNION ALL
SELECT y.Primary_Key,
y.Name,
y.Parent_Primary_ID,
y.DISPLAY_ORDER,
CAST(path + '.' + CAST(y.DISPLAY_ORDER AS VARCHAR) AS VARCHAR(100))
FROM #YourTable y
JOIN Hierarchy h
ON h.Primary_Key = y.Parent_Primary_ID)
SELECT Primary_Key,
Name
FROM Hierarchy
ORDER BY path
Try (asumming standardish sql is supported)
DECLARE #YourTable TABLE
(Primary_Key INT PRIMARY KEY,
Name VARCHAR(100),
Parent_Primary_ID INT NULL,
DISPLAY_ORDER INT)
INSERT INTO #YourTable
SELECT 1,'Event',NULL,1 UNION ALL
SELECT 2,'News',NULL,2 UNION ALL
SELECT 3,'Event_List',1,1 UNION ALL
SELECT 4,'Event_Detail',1,2 UNION ALL
SELECT 5,'News_List',2,1 UNION ALL
SELECT 6,'News_Details',2,2;
select
primary_key = t1.primary_key,
name = t1.name
from
#YourTable t1
left join #YourTable t2 on t1.parent_primary_id = t2.Primary_Key
order by
coalesce(t2.DISPLAY_ORDER,t1.DISPLAY_ORDER,0),
case
when t2.Primary_Key is null then 0
else t1.DISPLAY_ORDER
end
I don't see any grouping in your results. Unless you are trying to do something you aren't telling us I would use the query below:
SELECT Primary_Key, Name FROM YourTable
I don't see how you are ordering those results so I didn't try to order them.

Query: find rows that do not belong to a list of values

Lets consider I have a table 'Tab' which has a column 'Col'
The table 'Tab' has this data -
Col
1
2
3
4
5
If I have a set of values (2,3,6,7). I can query the values that are present in the table and the list by suing the query
Select Col from Tab where col IN (2,3,6,7)
But, if I want to return the values in the list that are not present in the table i.e. only (6,7) in this case. What query should I use?
The problem I believe is that your trying to find values from you in statement. What you need to do is turn your in statement into a table and then you can determine which values are different.
create table #temp
(
value int
)
insert into #temp values 1
insert into #temp values 2
insert into #temp values 3
insert into #temp values 4
select
id
from
#temp
where
not exists (select 1 from Tab where Col = id)
A better alternative would be to create a table-valued function to turn your comma-delimited string into a table. I don't have any code handy, but it should be easy to find on Google. In that case you would only need to use the syntax below.
select
id
from
dbo.SplitStringToTable('2,3,6,7')
where
not exists (select 1 from Tab where Col = id)
Hope this helps
A SQL Server 2008 method
SELECT N FROM (VALUES(2),(3),(6),(7)) AS D (N)
EXCEPT
Select Col from Tab
Or SQL Server 2005
DECLARE #Values XML
SET #Values =
'<r>
<v>2</v>
<v>3</v>
<v>6</v>
<v>7</v>
</r>'
SELECT
vals.item.value('.[1]', 'INT') AS Val
FROM #Values.nodes('/r/v') vals(item)
EXCEPT
Select Col from Tab
one way would be to use a temp table:
DECLARE #t1 TABLE (i INT)
INSERT #t1 VALUES(2)
INSERT #t1 VALUES(3)
INSERT #t1 VALUES(6)
INSERT #t1 VALUES(7)
SELECT i FROM #t1 WHERE i NOT IN (Select Col from Tab)
One method is
declare #table table(col int)
insert into #table
select 1 union all
select 2 union all
select 3 union all
select 4 union all
select 5
declare #t table(col int)
insert into #t
select 2 union all
select 3 union all
select 6 union all
select 7
select t1.col from #t as t1 left join #table as t2 on t1.col=t2.col
where t2.col is null
Do you have a [numbers] table in your database? (See Why should I consider using an auxiliary numbers table?)
SELECT
[Tab].*
FROM
[numbers]
LEFT JOIN [Tab]
ON [numbers].[num] = [Tab].[Col]
WHERE
[numbers].[num] IN (2, 3, 6, 7)
AND [Tab].[Col] IS NULL
I think there are many ways to achive this, here is one.
SELECT a.col
FROM
(SELECT 2 AS col UNION ALL SELECT 3 UNION ALL SELECT 6 UNION ALL SELECT 7) AS a
WHERE a.col NOT IN (SELECT col FROM Tab)
Late to the party...
SELECT
'2s' = SUM(CASE WHEN Tab.Col = 2 THEN 1 ELSE 0 END),
'3s' = SUM(CASE WHEN Tab.Col = 3 THEN 1 ELSE 0 END),
'6s' = SUM(CASE WHEN Tab.Col = 6 THEN 1 ELSE 0 END),
'7s' = SUM(CASE WHEN Tab.Col = 7 THEN 1 ELSE 0 END)
FROM
(SELECT 1 AS Col, 'Nums' = 1 UNION SELECT 2 AS Col,'Nums' = 1 UNION SELECT 3 AS Col, 'Nums' = 1 UNION SELECT 4 AS Col, 'Nums' = 1 UNION SELECT 5 AS Col, 'Nums' = 1 ) AS Tab
GROUP BY Tab.Nums
BTW, mine also gives counts of each, useful if you need it. Like if you were checking a product list against what you have in inventory. Though you can write a pivot for that better, just don't know how off the top of my head.

t-sql recursive query

Based on an existing table I used CTE recursive query to come up with following data. But failing to apply it a level further.
Data is as below
id name parentid
--------------------------
1 project 0
2 structure 1
3 path_1 2
4 path_2 2
5 path_3 2
6 path_4 3
7 path_5 4
8 path_6 5
I want to recursively form full paths from the above data. Means the recursion will give the following output.
FullPaths
-------------
Project
Project\Structure
Project\Structure\Path_1
Project\Structure\Path_2
Project\Structure\Path_3
Project\Structure\Path_1\path_4
Project\Structure\Path_2\path_5
Project\Structure\Path_3\path_6
Thanks
Here's an example CTE to do that:
declare #t table (id int, name varchar(max), parentid int)
insert into #t select 1, 'project' , 0
union all select 2, 'structure' , 1
union all select 3, 'path_1' , 2
union all select 4, 'path_2' , 2
union all select 5, 'path_3' , 2
union all select 6, 'path_4' , 3
union all select 7, 'path_5' , 4
union all select 8, 'path_6' , 5
; with CteAlias as (
select id, name, parentid
from #t t
where t.parentid = 0
union all
select t.id, parent.name + '\' + t.name, t.parentid
from #t t
inner join CteAlias parent on t.parentid = parent.id
)
select *
from CteAlias
Try something like this:
WITH Recursive AS
(
SELECT
ID,
CAST(PathName AS VARCHAR(500)) AS 'FullPaths',
1 AS 'Level'
FROM
dbo.YourTable
WHERE
ParentID = 0
UNION ALL
SELECT
tbl.ID,
CAST(r.FullPaths + '\' + tbl.PathName AS VARCHAR(500)) AS 'FullPaths',
r.Level + 1 AS 'Level'
FROM
dbo.YourTable tbl
INNER JOIN
Recursive r ON tbl.ParentID = r.ID
)
SELECT * FROM Recursive
ORDER BY Level, ID
Output:
ID FullPaths Level
1 project 1
2 project\structure 2
3 project\structure\path_1 3
4 project\structure\path_2 3
5 project\structure\path_3 3
6 project\structure\path_1\path_4 4
7 project\structure\path_2\path_5 4
8 project\structure\path_3\path_6 4
try this:
DECLARE #YourTable table (id int, nameof varchar(25), parentid int)
INSERT #YourTable VALUES (1,'project',0)
INSERT #YourTable VALUES (2,'structure',1)
INSERT #YourTable VALUES (3,'path_1',2)
INSERT #YourTable VALUES (4,'path_2',2)
INSERT #YourTable VALUES (5,'path_3',2)
INSERT #YourTable VALUES (6,'path_4',3)
INSERT #YourTable VALUES (7,'path_5',4)
INSERT #YourTable VALUES (8,'path_6',5)
;WITH Rec AS
(
SELECT
CONVERT(varchar(max),nameof) as nameof,id
FROM #YourTable
WHERE parentid=0
UNION ALL
SELECT
CONVERT(varchar(max),r.nameof+'\'+y.nameof), y.id
FROM #yourTable y
INNER jOIN Rec r ON y.parentid=r.id
)
select * from rec
output:
nameof
-----------------------------------------------
project
project\structure
project\structure\path_1
project\structure\path_2
project\structure\path_3
project\structure\path_3\path_6
project\structure\path_2\path_5
project\structure\path_1\path_4
(8 row(s) affected)
Something like
;WITH MyCTE AS
(
SELECT
name AS FullPaths, id
FROM
MyTable
WHERE
parentid = 0 /*Normally it'd be IS NULL with an FK linking the 2 columns*/
UNION ALL
SELECT
C.FullPaths + '\' + M.name, M.id
FROM
MyCTE C
JOIN
MyTable M ON M.parentid = C.id
)
SELECT FullPaths FROM MyCTE
You'll have to change the name of #test table I was using.
WITH cte(id, name, parentid) AS
(
SELECT id, convert(varchar(128), name), parentid
FROM #test
WHERE parentid = 0
UNION ALL
SELECT t.id, convert(varchar(128), c.name +'\'+t.name), t.parentid
FROM #test t
INNER JOIN cte c
ON c.id = t.parentid
)
SELECT name as FullPaths
FROM cte
order by id