1

I did a research showing that there's no such thing as list or array to rely on in SQL. The only non-scalar data structure is a table. I'm more of a C# than SQL developer so to me, the following seems very bad. However, I sense there should be a way to make the code more tidy.

declare @Url01 as varchar(max) = 'aaa.domain.com/some-some'
...
declare @Url24 as varchar(max) = 'zzz.domain.com/some-some'

insert into UrlTable (Url,...) values (concat('https://',@Url01),...)
...
insert into UrlTable (Url,...) values (concat('https://',@Url24),...)

Coming from the background of mine, I'd expect an array of the values, instead of declaring them in individual variables, then loopify in them into the target table. I can imagine that concept is resolved using a temporary table instead of a variable with multiple values but that's where my SQL-foo ends. Googling the issue led me nowhere good.

Is it doable at all (reasonably speaking)? What approach should I take (so I know what to google for)?

Konrad Viltersten
  • 36,151
  • 76
  • 250
  • 438
  • Not sure what you are trying to do. If you want to pass multiple parameters to sql-server you can use [table-valued-parameters](https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/table-valued-parameters). I've asked a question about it some time ago which you might find helpful because i have shown my approach: https://stackoverflow.com/questions/39467251/is-the-sort-order-of-table-valued-parameters-guaranteed-to-remain-the-same – Tim Schmelter Jan 29 '22 at 13:05
  • Where are you running this SQL from (database procedure, SQL sent from client code etc) and what else is part of your query - you indicate "..." there are other columns which makes it hard to suggest anything suitable. Where is the loop you allude to? – Stu Jan 29 '22 at 13:08
  • Erland's [website](https://www.sommarskog.se/index.html) has a collection of articles that cover "arrays" in sql server and how to implement them - along with some other very useful discussions – SMor Jan 29 '22 at 13:21
  • @AaronBertrand Good remark. I'm typing those in. It's a set of URLs that need to be inserted on occasion. It seemed as a nooby thing to declare a new variable for each and I'd rather have the payload of raw strings at the top and then, do the magic on them, preferably as a loop or bulk operation. – Konrad Viltersten Jan 29 '22 at 17:29
  • @TimSchmelter Not exactly. Sorry for being insufficiently clear. I want to insert the multiple values into the DB and I get a set of strings. I'd like to know if it's possible to declare a single object containing all the strings and insert from it. As opposed to the current solution of declaring a new variable for each string and then inserting each such variable individually. – Konrad Viltersten Jan 29 '22 at 17:31
  • @Stu The ellipsis indicated rows of the same content except a minor change in the string being declared/inserted and with a differnet index on the variable. There's no loopy thingy at the moment. I actually declare 50+ variables and perform 50+ insertions. I'd like to remodel the query, if possible, so that I declare a single element with an array/list/set of strings and then loop them in with a single insert statement. – Konrad Viltersten Jan 29 '22 at 17:33
  • @AaronBertrand Copying from a chat window. So, in your set of alternatives, copying form a piece of paper. Most of the links will be small derivations from the others, so e.g. having *users.domdom.com* may also lead to *users.domdom.net*. It's a manual job and I want to automate it as much as possible. The only thing that needs to be altered are the strings. As for the version - latest Express. Why? – Konrad Viltersten Jan 29 '22 at 17:36
  • @AaronBertrand The version is the latest SQL Express. The other values are irrelevant, I believe, since it's only an integer, which is constant and the same for the whole operation. The only part of each insert that varies are the strings. The integer will be different one say in the future but the query I'm running has no need to change it. – Konrad Viltersten Jan 29 '22 at 17:42
  • Ok, see [this fiddle](https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=6765b189c9ec79cc8047bfc7c52817e4) (which I also added to my answer, and addresses the comment you left on the accepted answer). – Aaron Bertrand Jan 29 '22 at 17:47

2 Answers2

3

You can reduce the bulk of the code by doing:

DECLARE @Url01 varchar(max) = 'aaa.domain.com/some-some',
        ...
        @Url24 as varchar(max) = 'zzz.domain.com/some-some';

INSERT dbo.UrlTable (Url,...) VALUES
        (concat('https://',@Url01),...),
        ...
        (concat('https://',@Url24),...);

But if you have thousands, you'll need to break the inserts up into 1,000 sets of VALUES() at a time (this is an arbitrary limit set by SQL Server).

Another way:

DECLARE @urls varchar(max) = '
url1.com
url2.com
url3.com
url4.com';

--INSERT dbo.table(URL, othercol1, othercol2)
SELECT value, 
       othercol1 = 5, 
       othercol2 = 'some constant string'
  FROM STRING_SPLIT(TRIM(char(13) FROM @urls), char(10))
  WHERE value > '';

Ideally, if this data is coming from some kind of structured type outside of SQL Server (like a DataTable in C#), you can simply use a table-valued parameter.

CREATE TYPE dbo.URLs AS TABLE(URL varchar(max));
GO

CREATE PROCEDURE dbo.GiveMeAllTheURLs
  @urls dbo.URLs READONLY
AS
BEGIN
  SET NOCOUNT ON;

  INSERT dbo.UrlTable(Url, othercol1, othercol2)
  SELECT URL, 5, 'some constant string' FROM @urls;
END
GO

Then from C#, assuming you already have a command object set to call this stored procedure:

SqlParameter tvp1 = cmd.Parameters.AddWithValue("@urls", DataTableName);
tvp1.SqlDbType = SqlDbType.Structured;
tvp1.TypeName = "dbo.URLs";
cmd.ExecuteNonQuery();

I don't know if you have to use AddWithValue, but it's the only example I had handy, so please don't roast me for it.

Aaron Bertrand
  • 272,866
  • 37
  • 466
  • 490
2

Taking into account that you can easily form a JSON string in C#, you also can use this approach to pass data. Depending on what is best suited for your task.

Declare @JsonData As VarChar(max) = '[{"Url":"aaa.domain.com/some-some", "OtherProp":"Val0"},
                                      {"Url":"zzz.domain.com/some-some", "OtherProp":"Val1"}]'

Insert Into UrlTable ([Url], OtherProp)
Select T.[Url], T.OtherProp
From OPENJSON(@JsonData, '$') Cross Apply OPENJSON(value, '$')
WITH (
[Url] VarChar(255) '$.Url',
OtherProp VarChar(255) '$.OtherProp') As T

db<>fiddle

If only the URL values differ in the input data, you can use a simpler structure like this:

Declare @JsonData As VarChar(max) = '["aaa.domain.com/some-some", 
                                      "zzz.domain.com/some-some"]'
Declare @OtherProp As VarChar(255) = 'Val'

Insert Into UrlTable ([Url], OtherProp)
Select value, @OtherProp
From OPENJSON(@JsonData, '$')

Newtonsoft.Json NuGet package can be used to form this JSON array in C# app as follows:

var urls = new List<string>();
urls.Add("aaa.domain.com/some-some");
urls.Add("zzz.domain.com/some-some");

var jsonData = JsonConvert.SerializeObject(urls);

db<>fiddle

Anton Grig
  • 1,640
  • 7
  • 11
  • That's actually pretty close to what I'm aiming at. I'll only have to figure out how to concat the strings so that the first part of the query only contains the strings that are being altered (i.e. URLs, as the rest is sort of the same for all of them). – Konrad Viltersten Jan 29 '22 at 17:40