Sql Insert the same row multiple times - sql-server

I'm trying to write a stored procedure which will take rows from a table like the one below
and insert them as many times as the value of the Quantity column. It should also assign a unique name & number to the rows inserted.
The end result should look something like the screenshot below
I can get very close to the what I want by the SQL below
Source
INSERT INTO dbo. MyTable (....)
SELECT
t1.Name + ' (' + CAST(E.n as VARCHAR(3)) + ')',
#Prefix + ' - ' + ROW_NUMBER () OVER (ORDER BY t1.Name )
FROM
MyFirstTable t1
JOIN ....
JOIN .....
CROSS JOIN
(SELECT TOP 500 ROW_NUMBER() OVER(ORDER BY (SELECT 1)) FROM sys.columns)E(n)
WHERE
E.n <= t1.Quantity
AND....
The above statement works because I do know that quantity will never exceed 500 but I'm not a big fan of the way it is done. Is there a better way to accomplish this?
I'm not very experienced in sql.

Seems like you have already figured out what you need for the most part. As far as the top 500 not exceeding goes, you could either leave it there or remove it. I think this is what you may be looking for:
SELECT
id, --not sure where this id comes from but looks different in your output
CASE
WHEN E.n-1 > 0
THEN t1.Name + ' (' + CAST(E.n-1 as VARCHAR(3)) + ')'
ELSE t1.Name
END as Name,
#prefix + ' - ' + cast(ROW_NUMBER () OVER (ORDER BY t1.id) as varchar(10)) as Number
FROM
test t1
JOIN ...
JOIN ...
CROSS JOIN
(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) FROM sys.columns)E(n)
WHERE
E.n <= t1.Quantity
AND ....;
SQL Fiddle Demo

Related

How to present row data in JSON format in SQL Server 2014

I want to present the data which is getting after joined with another table. It is getting multiple records for one particular Id. So I want to display those multiple records in JSON format.
I have the below data.
Declare #Employees Table (EmpId int, EmpName Varchar(100))
INSERT INTO #Employees
VALUES(1,'RAM')
,(2,'RAJ')
,(3,'LAXMAN')
Declare #Subjects Table (EmpId int, Subject Varchar(100))
INSERT INTO #Subjects
VALUES(1,'Maths')
,(1,'Science')
,(1,'Physics')
,(2,'Physics')
,(3,'Maths')
,(3,'Physics')
I have tried with this query.
SELECT E.EmpId
,Subject
FROM #Employees E
LEFT JOIN (
SELECT EmpId
,'{' + STUFF((
SELECT ',"' + NULLIF(Subject, '') + '"'
FROM #Subjects AS CG1
WHERE CG1.EmpId = CG2.EmpId
FOR XML PATH('')
), 1, 1, '') + '}' AS Subject
FROM #Subjects AS CG2
GROUP BY EmpId
) SUB ON SUB.EmpId = E.EmpId
But I want the result like this.
EmpId Subject
-----------------------------------------------------
1 {"1":"Maths","2":"Science","3":"Physics"}
2 {"1":"Physics"}
3 {"1":"Maths","2":"Physics"}
Please let me know if you want any further information is needed.
I would appreciate your help.
Thanks
You were very close, you need to add a ROW_NUMBER to the subquery too. I've also switched to CONCAT for easy implicit conversions:
SELECT E.EmpId,
Subject
FROM #Employees E
LEFT JOIN (SELECT EmpId,
'{' + STUFF((SELECT CONCAT(',"', ROW_NUMBER() OVER (ORDER BY CG1.Subject), '":"', NULLIF(Subject, ''), '"')
FROM #Subjects CG1
WHERE CG1.EmpId = CG2.EmpId
ORDER BY Subject
FOR XML PATH('')),1,1,'') + '}' AS Subject
FROM #Subjects CG2
GROUP BY EmpId) SUB ON SUB.EmpId = E.EmpId;
Note that the order of the subjects in your table isn't preserved, due to that there is no way to preserve it with an ORDER BY (at least not with the data we have). Therefore 'Physics' has a number of 2 instead of 3 for Empid 1

Is it always possible to transform multiple spatial selects with while loop and variables into a single query without using temp tables in sql?

This problem can be solved with temp table, however, I don't want to use Temp table or var table, this question is mostly for my personal educational purposes.
I inherited the following SQL:
DECLARE #i int = 993
while #i <=1000
begin
declare #lat nvarchar(20)
select top 1 #lat = SUBSTRING(Address,0,CHARINDEX(',',Address,0)) from dbo.rent
where id = #i;
declare #lon nvarchar(20)
select top 1 #lon = SUBSTRING(Address,CHARINDEX(',',Address)+1,LEN(Address)) from dbo.rent
where id = #i
declare #p GEOGRAPHY = GEOGRAPHY::STGeomFromText('POINT('+ #lat +' '+#lon+')', 4326)
select price/LivingArea sq_m, (price/LivingArea)/avg_sq_m, * from
(select (sum(price)/sum(LivingArea)) avg_sq_m, count(1) cnt, #i id from
(select *, GEOGRAPHY::STGeomFromText('POINT('+
convert(nvarchar(20), SUBSTRING(Address,0,CHARINDEX(',',Address,0)))+' '+
convert( nvarchar(20), SUBSTRING(Address,CHARINDEX(',',Address)+1,LEN(Address)))+')', 4326)
.STBuffer(500).STIntersects(#p) as [Intersects]
from dbo.rent
where Address is not null
) s
where [Intersects] = 1) prox
inner join dbo.rent r on prox.id = r.id
set #i = #i+1
end
it is used to analyze property prices per square meter that are in proximity and compare them to see which ones are cheaper...
Problem: a mechanism for calling has to be moved from C# to SQL and all queries have to be combined into a single result (now you get one row per one while run), i.e #i and #p has to go and become while id < x and id > y or somehow magically joined,
the procedure is a cut down version of actual thing but having a solution to the above I will have no problem making the whole thing work...
I am of the opinion that any SQL mechanism with variables and loops can be transformed to a single SQL statement, hence the question.
SqlFiddle
If I understand your question properly (Remove the need for loops and return one data set) then you can use CTE (Common Table Expressions) for the Lats, Lons and Geog variables.
You;re SQLFIddle was referencing a database called "webanalyser" so I removed that from the query below
However, the query will not return anything as the sample data has wrong data for address column.
;WITH cteLatsLongs
AS(
SELECT
lat = SUBSTRING(Address, 0, CHARINDEX(',', Address, 0))
,lon = SUBSTRING(Address, CHARINDEX(',', Address) + 1, LEN(Address))
FROM dbo.rent
)
,cteGeogs
AS(
SELECT
Geog = GEOGRAPHY ::STGeomFromText('POINT(' + LL.lat + ' ' + LL.lon + ')', 4326)
FROM cteLatsLongs LL
),cteIntersects
AS(
SELECT *,
GEOGRAPHY::STGeomFromText('POINT(' + CONVERT(NVARCHAR(20), SUBSTRING(Address, 0, CHARINDEX(',', Address, 0))) + ' ' + CONVERT(NVARCHAR(20), SUBSTRING(Address, CHARINDEX(',', Address) + 1, LEN(Address))) + ')', 4326).STBuffer(500).STIntersects(G.Geog) AS [Intersects]
FROM dbo.rent
CROSS APPLY cteGeogs G
)
SELECT avg_sq_m = (SUM(price) / SUM(LivingArea)), COUNT(1) cnt
FROM
cteIntersects I
WHERE I.[Intersects] = 1
It can be done, in this specific case 'discovery' that was necessary was the ability to perform JOINs on Point e.g ability to join tables on proximity (another a small cheat was to aggregate point-strings to actual points, but it's just an optimization). Once this is done, a query could be rewritten as follows:
SELECT adds.Url,
adds.Price/adds.LivingArea Sqm,
(adds.Price/adds.LivingArea)/k1.sale1Avg ratio,
*
FROM
(SELECT baseid,
count(k1Rent.rentid) rent1kCount,
sum(k1Rent.RperSqM)/(count(k1Rent.rentid)) AS rent1kAvgSqM,
count(around1k.SaleId) sale1kCount,
(sum(k1sale.price)/sum(k1Sale.LivingArea)) sale1Avg,
(sum(k1sale.price)/sum(k1Sale.LivingArea))/((sum(k1Rent.RperSqM)/(count(k1Rent.rentid)))*12) years --*
FROM
(SELECT sa.id baseid,
s.id saleid,
s.RoomCount,
POINT
FROM SpatialAnalysis sa
INNER JOIN Sale s ON s.Id = SaleId
WHERE sa.SalesIn1kRadiusCount IS NULL) AS base
JOIN SpatialAnalysis around1k ON base.Point.STBuffer(1000).STIntersects(around1k.Point) = 1
LEFT OUTER JOIN
(SELECT id rentid,
rc,
Price/avgRoomSize RperSqM
FROM
(SELECT *
FROM
(SELECT rc,
sum(avgArea*c)/sum(c) avgRoomSize
FROM
(SELECT roomcount rc,
avg(livingarea) avgArea,
count(1) c
FROM Rent
WHERE url LIKE '%systemname%'
AND LivingArea IS NOT NULL
GROUP BY RoomCount
UNION
(SELECT roomcount rc,
avg(livingarea) avgArea,
count(1) c
FROM sale
WHERE url LIKE '%systemname%'
AND LivingArea IS NOT NULL
GROUP BY RoomCount))uni
GROUP BY rc) avgRoom) avgrents
JOIN rent r ON r.RoomCount = avgrents.rc) k1Rent ON k1Rent.rentid =around1k.RentId
AND base.RoomCount = k1Rent.rc
LEFT OUTER JOIN Sale k1Sale ON k1Sale.Id = around1k.SaleId
AND base.RoomCount = k1Sale.RoomCount
GROUP BY baseid) k1
left outer join SpatialAnalysis sp on sp.Id = baseid
left outer join Sale adds on adds.Id = sp.SaleId
where adds.Price < 250000
order by years, ratio

SQL Server : efficient way to find missing Ids

I am using SQL Server to store tens of millions of records. I need to be able to query its tables to find missing rows where there are gaps in the Id column, as there should be none.
I am currently using a solution that I have found here on StackOverflow:
CREATE PROCEDURE [dbo].[find_missing_ids]
#Table NVARCHAR(128)
AS
BEGIN
DECLARE #query NVARCHAR(MAX)
SET #query = 'WITH Missing (missnum, maxid) '
+ N'AS '
+ N'('
+ N' SELECT 1 AS missnum, (select max(Id) from ' + #Table + ') '
+ N' UNION ALL '
+ N' SELECT missnum + 1, maxid FROM Missing '
+ N' WHERE missnum < maxid '
+ N') '
+ N'SELECT missnum '
+ N'FROM Missing '
+ N'LEFT OUTER JOIN ' + #Table + ' tt on tt.Id = Missing.missnum '
+ N'WHERE tt.Id is NULL '
+ N'OPTION (MAXRECURSION 0);';
EXEC sp_executesql #query
END;
This solution has been working very well, but it has been getting slower and more resource intensive as the tables have grown. Now, running the procedure on a table of 38 million rows is taking about 3.5 minutes and lots of CPU.
Is there a more efficient way to perform this? After a certain range has been found to not contain any missing Ids, I no longer need to check that range again.
JBJ's answer is almost complete. The query needs to return the From and Through for each range of missing values.
select B+1 as [From],A-1 as[Through]from
(select StuffID as A,
lag(StuffID)over(order by StuffID)as B from Stuff)z
where A<>B+1
order by A
I created a test table with 50 million records, then deleted a few. The first row of the result is:
From Through
33 35
This indicates that all IDs in the range from 33 through 35 are missing, i.e. 33, 34 and 35.
On my machine the query took 37 seconds.
try
select pId
from (select Id, lag(Id) over (order by Id) pId from yourschema.yourtable) e
where pId <> (Id-1)
order by Id
replacing yourschema.yourtable with the appropriate table information
Try this solution, it will be faster than CTE.
;WITH CTE AS
(
SELECT ROW_NUMBER()
OVER (
ORDER BY (SELECT NULL)) RN
FROM ( values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) v(id) --10 ROWS
CROSS JOIN ( values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) v1(id)--100 ROWS
CROSS JOIN ( values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) v2(id) --1000 ROWS
CROSS JOIN ( values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) v3(id) --10000 ROWS
CROSS JOIN ( values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) v4(id)--100000 ROWS
CROSS JOIN ( values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) v5(id)--1000000 ROWS
)
SELECT RN AS Missing
FROM CTE C
LEFT JOIN YOURABLE T ON T.ID=R.ID
WHERE T.ID IS NULL
If you want you can use master..[spt_values] also to generate the number like following.
SELECT (ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) RN
FROM master..[spt_values] T1
CROSS JOIN (select top 500 * from master..[spt_values]) T2
Above query will generate 1268500 numbers
Note: You need to add the CROSS JOIN as per your requirement.

SQL Subqueries concatenation issue

I have a simple subquery that works fine
SELECT id, Name, subset
, (select count (1) from anotherTable where qid = someTable.id )
FROM someTable
the value "subset" is a string ie:
"and (PreQ1 like '%A%') and ( PreQ2 like '%A%' or PreQ2 like '%C%')"
And so I'd like to ConCatenate the subquery with the value subset like this:
SELECT id, Name, subset
, exec ( 'select count (1) from anotherTable where qid = someTable.id ' + subset)
FROM someTable
.. but get a "Conversion failed" error
Is this what you want?
SELECT s.id, s.Name, s.subset,
(select cast(count(1) as varchar(255)) + s.subset
from anotherTable a
where a.qid = s.id
)
FROM someTable s;
This does the concatenation within the subquery. You can also put the + s.subset outside the subquery.
Also note that table aliases make the query easier to write and to read.

Dynamic sql to convert column names with one row into table with 2 columns and several rows

After searching for several ways of converting columns to rows using PIVOT, cross join etc my question still goes unanswered
I have a Result set which returns 1 row and 147 columns
ID | Name | DOB | BloodGroup | ...... 147 columns
1 XYZ 17MAY A+ ......
My aim is to convert this result set into 2 columns and 147 rows
Column_Name | Value
ID 1
NAME XYZ
: :
How should I go about it ? I appreciate your feedback
I took the second approach Gordon mentioned in his post, but built dynamic SQL from it. I CROSS JOINED the result of a few sys table JOINs and a source table, then built a CASE statement off the column names. I UNION it all together as dynamic SQL then EXECUTE it. To make it easy, I've made all the variable items into variables which you fill out at the beginning of the routine. Here's the code:
USE AdventureWorks2012;
GO
DECLARE #MySchema VARCHAR(100),
#MyTable VARCHAR(100),
#MyUIDColumn VARCHAR(100),
#MyFieldsMaxLength VARCHAR(10),
#SQL AS VARCHAR(MAX);
SET #MySchema = 'Person';
SET #MyTable = 'Person';
-- Unique ID which routine will identify unique entities by. Will also sort on this value in the end result dataset.
SET #MyUIDColumn = 'BusinessEntityID';
-- This determines the max length of the fields you will cast in your Value column.
SET #MyFieldsMaxLength = 'MAX';
WITH cteSQL
AS
(
SELECT 1 AS Sorter, 'SELECT c.name AS ColumnName,' AS SQL
UNION ALL
SELECT 2, 'CASE' AS Statement
UNION ALL
SELECT 3, 'WHEN c.name = ''' + c.name + ''' THEN CAST(mt.' + c.name + ' AS VARCHAR(' + #MyFieldsMaxLength + ')) '
FROM sys.tables t INNER JOIN sys.columns c
ON t.object_id = c.object_id
WHERE t.name = #MyTable
UNION ALL
SELECT 4, 'END AS Value' AS Statement
UNION ALL
SELECT 5, 'FROM sys.tables t INNER JOIN sys.columns c ON t.object_id = c.object_id INNER JOIN sys.schemas s ON t.schema_id = s.schema_id, ' + #MySchema + '.' + #MyTable + ' mt WHERE t.name = ''' + #MyTable + ''' AND s.name = ''' + #MySchema + ''' ORDER BY mt. ' + #MyUIDColumn + ', c.name;'
)
SELECT #SQL =
(
SELECT SQL + ' '
FROM cteSQL
ORDER BY Sorter
FOR XML PATH ('')
);
EXEC(#SQL);
I really can't say what execution time will be like. I ran it against AdventureWorks2012, Person.Person table (~20k rows, 13 columns) on my local machine and it brought back ~2.5 million rows in about 8 seconds, if that means anything. The good thing is that its flexible to take any table seamlessly. Anyway, just thought it was a fun puzzle so decided to play with it a bit. Hope it helps.
EDIT: Thinking about it, this is probably even slower than Gordon's proposed method, but I did it aready. Oh well. (Yeah, his method works in about half the time. Getting fancy didn't help me much.)
This is called unpivot. The easiest way, conceptually, is to do:
select 'id' as column_name, cast(id as varchar(255)) as column_value
from Result
union all
select 'name', name
from Result
union all
. . .
This can be cumbersome to type. If result is a table, you can use information_schema.columns to create the SQL, something like:
select 'select ''' + column_name + ''' as column_name, cast(' + column_name + ' as varchar(255)) as column_value from result union all'
from information_schema.columns
where table_name = 'Result'
This method is not the most efficient approach, because it requires reading the table for each column. For that unpivot is the better approach. The syntax is described here.
Thanks for the response.
I figured out a way of doing it.
I got all the column names in a comma separated string variable. 2. Passed the same string to the UNPIVOT object. By this approach, hard coding of the 140 column names was completely avoided.

Resources