Friday, December 11, 2015

Drawing Simple Triangular Fractals in SQL Server Management Studio

At first, want to show you the end result. That is something you can draw in your SSMS and even much more:
At second, want to share the script to draw that picture:
001  /*
002  Script which draws very nice Triangle Based Fractals
003  */
004  SET NOCOUNT ON
005  GO
006  IF Exists (
007      SELECT TOP 1 1 FROM tempdb.sys.tables
008      WHERE object_id = OBJECT_ID('tempdb.dbo.#Triangles')
009  ) DROP TABLE #Triangles
010  GO
011  CREATE TABLE #Triangles( Finished BIT NULL
012      , Radius FLOAT NOT NULL
013      , Angle FLOAT NOT NULL
014      , Center_X FLOAT NOT NULL
015      , Center_Y FLOAT NOT NULL
016      , Point1_X FLOAT NOT NULL
017      , Point1_Y FLOAT NOT NULL
018      , Point2_X FLOAT NOT NULL
019      , Point2_Y FLOAT NOT NULL
020      , Point3_X FLOAT NOT NULL
021      , Point3_Y FLOAT NOT NULL
022      , TriangleLevel TINYINT NOT NULL
023      , TriangleID INT IDENTITY(1,1)
024      , Triangle as geometry::STGeomFromText('POLYGON((' +
025          CAST(Point1_X as VARCHAR) + ' ' + CAST(Point1_Y as VARCHAR) + ',' +
026          CAST(Point2_X as VARCHAR) + ' ' + CAST(Point2_Y as VARCHAR) + ',' +
027          CAST(Point3_X as VARCHAR) + ' ' + CAST(Point3_Y as VARCHAR) + ',' +
028          CAST(Point1_X as VARCHAR) + ' ' + CAST(Point1_Y as VARCHAR) + '))', 4326)
029  );
030  GO
031  DECLARE @CenterX FLOAT = 0;            -- Triangle CenterX
032  DECLARE @CenterY FLOAT = 0;            -- Triangle CenterY
033  DECLARE @Radius FLOAT = 1000;        -- Triangle Radius
034  DECLARE @Angle FLOAT = 30;            -- Triangle Orientation Angle
035  DECLARE @MaxLevel TINYINT = 7;
036  DECLARE @CurrLevel TINYINT = 1;
037  DECLARE @Rate FLOAT = 0.825;
038 
039  -- Step 1. Drawing very first Triangle
040  INSERT INTO #Triangles
041  SELECT 0, @Radius, @Angle, @CenterX, @CenterY
042      , COS(RADIANS(@Angle-60))*@Radius + @CenterX
043      , SIN(RADIANS(@Angle-60))*@Radius + @CenterY
044      , COS(RADIANS(@Angle+60))*@Radius + @CenterX
045      , SIN(RADIANS(@Angle+60))*@Radius + @CenterY
046      , COS(RADIANS(@Angle+180))*@Radius + @CenterX
047      , SIN(RADIANS(@Angle+180))*@Radius + @CenterY
048      , @CurrLevel;
049 
050  -- Step 2. Looping through Triangle Levels
051  WHILE @CurrLevel < @MaxLevel
052  BEGIN
053 
054  -- Step 2.1. Generating Next Level Triangles
055      ;WITH NextLevel as (
056          -- This section dictate how many child triangles will be created and where they will be located
057          /*Side Values: 0,1,2 */
058          /*Position Values: -1,1 */
059          SELECT 0 as Side, -1 as Position
060          UNION ALL SELECT 0, 1
061          UNION ALL SELECT 1, 1
062          UNION ALL SELECT 1, -1
063          UNION ALL SELECT 2, 1
064          UNION ALL SELECT 2, -1
065          ),
066      NextTriangle as (
067          -- Extract data for parent triangle
068          SELECT Radius, Angle, Center_X, Center_Y
069          FROM #Triangles WHERE Finished = 0
070      ),
071      Triangles as (
072          -- Calculating position of the new triangle and its dimensions
073          SELECT Radius * @Rate / 2 as Radius, Angle + 60 as Angle
074              , Center_X = COS(RADIANS(Angle + Side*120)) * (Radius / 2 + Radius * @Rate / 4) + Center_X
075              -- Adjustment of the position of new triangle on the edge of the parent
076                  + Position * (SIN(RADIANS(60.)) * (Radius - Radius * @Rate / 2)) * COS(RADIANS(Angle - 90 + Side*120))
077              , Center_Y = SIN(RADIANS(Angle + Side*120)) * (Radius / 2 + Radius * @Rate / 4) + Center_Y
078              -- Adjustment of the position of new triangle on the edge of the parent
079                  + Position * (SIN(RADIANS(60.)) * (Radius - Radius * @Rate / 2)) * SIN(RADIANS(Angle - 90 + Side*120))
080          FROM NextTriangle, NextLevel
081      )
082      -- Finak calculation of new triangle corners.
083      INSERT INTO #Triangles
084      SELECT Null, Radius, Angle, Center_X, Center_Y
085          , COS(RADIANS(Angle-60))*Radius + Center_X
086          , SIN(RADIANS(Angle-60))*Radius + Center_Y
087          , COS(RADIANS(Angle+60))*Radius + Center_X
088          , SIN(RADIANS(Angle+60))*Radius + Center_Y
089          , COS(RADIANS(Angle+180))*Radius + Center_X
090          , SIN(RADIANS(Angle+180))*Radius + Center_Y
091          , @CurrLevel
092      FROM Triangles
093 
094  -- Step 2.2. Finishing (assigning "1") previous level triangles and Initiating (assigning "0") newly created trianles
095      UPDATE #Triangles SET Finished = CASE WHEN Finished = 0 THEN 1 ELSE 0 END
096      WHERE Finished = 0 or Finished is Null
097 
098  -- Step 2.3. Incremant Level
099      SET @CurrLevel += 1
100  END
101 
102  -- Step 3. Extracting Spatial data
103  ;WITH p as (
104      -- Generating Buckets and POLYGON text string for each triangle
105      SELECT TriangleLevel,
106          Bucket = TriangleID % CAST(CEILING((SELECT SQRT(count(*)) FROM #Triangles)) as INT),
107          Polygon = 'POLYGON((' +
108              CAST(Point1_X as VARCHAR) + ' ' + CAST(Point1_Y as VARCHAR) + ',' +
109              CAST(Point2_X as VARCHAR) + ' ' + CAST(Point2_Y as VARCHAR) + ',' +
110              CAST(Point3_X as VARCHAR) + ' ' + CAST(Point3_Y as VARCHAR) + ',' +
111              CAST(Point1_X as VARCHAR) + ' ' + CAST(Point1_Y as VARCHAR) + '))'
112      FROM #Triangles
113  ), Buckets as (SELECT DISTINCT Bucket FROM p)
114  , Collections as (
115      -- Generating GEOMETRYCOLLECTION for each generated Bucket
116      SELECT b.Bucket, Polygon = geometry::STGeomFromText('GEOMETRYCOLLECTION(' +
117          SUBSTRING((
118          SELECT CAST(',' as VARCHAR(MAX)) + CAST(Polygon as VARCHAR(MAX))
119          FROM p WHERE p.Bucket = b.Bucket
120          FOR XML PATH('')),2,2147483647) + ')', 4326)
121      FROM Buckets as b
122  )
123  -- Extracting Spatial Data
124  SELECT Polygon FROM Collections

At third, if you want to learn more about how that script works keep reading.

1. Script Parameters and variables:

- @CenterX & @CenterY - Starting point of the initial triangle. It is set to (0,0), but really does not matter unless you have special requirements.

- @Radius - That is a Radius of Circumscribed circle of the Initial Triangle. It can be any, but do not make it too small because you can loose precision.

- @Angle - An Angle you want your Initial Triangle to be turned. It really does not matter, you just have ability to rotate Fractal on the screen. Try to use value "15" for better effect visibility.

- @MaxLevel - Specifies how many levels deep you want to draw your Fractal. That value can't be big. With Max Level value "9" I've got almost 2 millions of objects. So, you can try more if you want to wait. However SSMS will not be able to show you so many objects. The maximum Number I've got was little bit lower than 250K.

- @CurrLevel - Just a variable to track current Level of Fractal's Triangles

- @Rate - Rate with which triangles will be decreasing in size. With Rate = "1" child triangle's Radius of Circumscribed circle will be twice as less Radius of the Parent's triangle. You can play with that value, but pictures might be not so nice.

2. Temporary Table

 It contains all parameters of created triangles such as
- Center point;
- Coordinates of all Corners of a triangle;
- Radius of Circumscribed circle;
- Triangle Inclination Angle;
- Level of the triangle (1 is the highest);
- Processing Bit Indicator. Set to 1 if triangle already has children;
- ID Identity field;
- Calculated Spatial field. It allows you to see graphical results by just simple selecting that field from the table.

3. Triangle construction.

There is no sacred magic in the original triangle construction, so, will start from the child generation level in Step 2.1.
3.1. Lines 59-64. Here we direct how many child triangles will have each side of a parent. To demonstrate that change value of variable "@MaxLevel = 2" and replace that section with the following and run the whole script:
        SELECT 0 as Side, 0 as Position
        UNION ALL SELECT 1, 1
        UNION ALL SELECT 1, -1
        UNION ALL SELECT 2, 1
        UNION ALL SELECT 2, 0
        UNION ALL SELECT 2, -1
As the result you have to have something like this:
First side has only one child in the center, second have two children near corners and third one has all places filled. You can specify only one side by replacing what section by just "SELECT 0 as Side, 1 as Position" and changing variable "@Rate = 1.5097. Then you can get something like this:

3.2. Lines 68-69 - Simple extraction of Parent triangle parameters.
3.3. Lines 73-80 - Radius, Angles and center points of child triangles. Lines 76 & 79 are necessary to construct non-central children.
3.4. Lines 83-92 - Generation of corners of children triangles.
3.5. Lines 95-96 - Marking filled parent triangles and new children.
3.6. Line 99 - Moving to the next cycle level.

4. Triangle presentation

That is most difficult operation. At first, SSMS can't show more than 5000 separate objects at the same time. In order to show more we have to construct "MULTIPOLYGON" or "GEOMETRYCOLLECTION". That only the way to fit more objects into SSMS screen. However it is still limited.
In order to combine triangles in a single object we divide them in buckets (Line 106).
In this example I just making number of buckets approximately equal to a number of objects within each bucket. Making lower number of buckets will increase processing speed, but produce less colors. All objects in one collection will have the same color.
Also, I wrapped the last query in extra CTE to have more flexibility on results formation.

5. Playing with results

As I mentioned in #4, you can change Line 124 to produce slightly different results.
For instance you can replace it with that:
"SELECT Polygon.STBoundary() FROM Collections"
That will show ONLY the boundaries of all triangles and you might see something like this:

You also can play with number of buckets in Line 106, replacing it by following:
"Bucket = TriangleID % 5000"

6. Playing with configuration.

Besides of changing an inclination angle the biggest change is number of children triangles.
Do the following:
- Replace Lines 59-64 by following:
SELECT 0 as Side, 0 as Position
UNION ALL SELECT 1, 0
UNION ALL SELECT 2, 0
- Change "@Rate = 1.1"
- Change"@MaxLevel = 9"

You might have something like this:
If you try to explore these structures deeper and zoom it you can find beauty like this:



I understand that those fractals are not "Classical", but it is something to start with. You've got an idea and that is most important.

Tuesday, November 17, 2015

How to run your local copy of SQL Server with minimum headache.

In this post I will not describe how to download SQL Express, or where to buy SQL Developer edition.
Will not pinpoint on how to install them and what hardware/software you need to have.

Assume you already dedicated several Gigabytes of your hard drive, installed and successfully tested your local SQL Server.
So, what is next.

The very next you'll face a problem of not having enough memory, unless you only run "SELECT @@VERSION;"

Here I will discuss about how to reclaim your memory back from local SQL Server and do it very easily without spending much time on it.

Will do it in Three very easy steps:

Step 1: Make sure that SQL Server does not start automatically.

When you just start your computer or VM you usually want to do other stuff, such as browsing the Internet, Manage your Finances or even playing games, rather just than accessing your SQL Server instance. That time you do not want SQL Server to eat 200 Mb of your valuable memory.
In order to prevent that you have to set your SQL Server do not start automatically during the system startup. If you did not specified that option during the SQL Server installation I'll show you how to do it.
At first you have to start Windows Services Console. In order to do so you do following:
- Run "Control Panel" (That will be a problem in Windows 10);

- In "Control Panel" go to "Administrative Tools";

- In  "Administrative Tools" start "Services";


Actually, I've promised you the "Easy Way". So, you can just press "Win+R" on your keyboard. Copy paste the command "%windir%\system32\services.msc" and press "Enter".

When List of services opened, scroll down to SQL related services.

"Startup Type" for all of them has to be "Manual" or for some services it can be even "Disabled".
In order to get that just double click on the service, choose "Startup Type" as "Manual". Stop the service if necessary and hit "OK" button.
Please make a note of the name of your SQL Server Service. In my case it is "MSSQLSERVER".

Step 2: Easily start SQL Server Service.

Now,  when you need to play with your personal SQL Server you can just go to Services again and start it.
Not easy enough?
Here is an easy way:
- Do a right click on your desktop; Choose "New" and then click on "Shortcut"
- In the Location Bar type "NET START MSSQLSERVER", where "MSSQLSERVER" is the name of your SQL Server Service you captured in the first step.
- After hitting next, name your new shortcut like "Start SQL Server" and hit "Finish".
- That is not the End. You have to finish the configuration to have it Really Easy: Right click on your new shortcut icon and choose "Properties".
- In the Shortcut Properties in "Shortcut" tab click on "Advanced..." button.
- Inside of "Advanced Properties" check "Run as administrator" check-box and click "OK"
- Click "OK" again to close "Properties" window.

If you will click on your new shortcut it will ask you for administrator's permissions to run you "SQL Server Service". When you hit "Yes" it will open a windows shell window indicating an attempt to start your "SQL Server Service".
Now you can go to "Control Panel Services" and verify that your SQL Server Service successfully Started.
From this point you can connect your SQL Server in Management Studio.

If, by any chance, Service did not start see "Step Zero" below on how to troubleshoot the issue.

Step 3: Easily Stop SQL Server Service.

That is very possible you performed some very heavy and memory intense operation by your local SQL Server instance and it ate all your free memory. You do not need it anymore, but SQL Server won't easily give memory back.
The easiest way to claim all your memory is to stop your SQL Server.
There are some easy ways doing it:
- First way us using SQL Server Management Studio. You just have to do a right click on your local SQL Server instance and choose "Stop".
It will ask you for the confirmation. Just say "Yes" for it.

That is very smart way to stop SQL Server from SSMS, but would say you've forgot to do it. Do you want to re-start SSMS again just to stop SQL Server Service?
The second your option will be quick and dirty:
- Use "Task Manager". Just Press three buttons at once on your keyboard: "Ctrl-Shift-Esc".
Just scroll through the list of tasks and kill SQL Server Service by pressing "End Task" button.

That "quick and dirty" and now will be "quick and clean".

You probably already guessed it because it is the same way as in our "Step 2", but instead of starting the Service we want to stop it:
- First, Copy and Paste your new shortcut you created for starting the service and rename it to "Stop SQL Server"
- Open the shortcut properties and change "Target" to "C:\Windows\System32\net.exe STOP MSSQLSERVER" and hit "OK" button to save the changes:

Now you have two very nice shortcuts to Start and Stop SQL Server Service, which you can "Pin to Start" and have them available any time when you press "Win" button.

Step Zero: You are unlucky guy and your SQL Service does not start.

Lets go through a troubleshooting.
To troubleshoot your case you have to run "Windows PowerShell" in administrator's mode. In order to do this press "Win+S" and type "PowerShell". When it will be found make a right click on the "Windows PowerShell" and choose an option "Run as administrator".



Case 1. Wrong Service name. In that case you have following error:
The service name is invalid.
In this case you just have to figure out the correct Service name.

In Order to figure out correct SQL Server Service name you can run following command:
Get-Service | Where-Object {$_.displayName.StartsWith("SQL")} | Select name
In my case it returned following:

Case 2. Service is disabled. In that case you have following error:
System error 1058 has occurred.
The service cannot be started, either because it is disabled or because it has no enabled devices associated with it.


That probably means that by some reason your SQL Service is disabled. To check it out run the following command in PowerShell:
get-wmiobject win32_service -filter "name = 'MSSQLSERVER'"

Here is my result when service was disabled:

For easy fix you can go to "Control Panel Services" and change the Start mode there or simply run following command:
set-service MSSQLSERVER -startuptype manual


Case 3. Service can't start because your Shortcut is not configured to run as administrator.
Here is nothing to troubleshoot. If Shortcut does not ask you for Administrator's permission just re-do the Step 2.

Case 4. Service doesn't start at all.
Nothing to troubleshoot again. If you can't start your service even from "Control Panel Services" that means something is scudded off completely. I don't want to cover possible SQL Service Service problems in that post. You have to search solution somewhere else or simply repair SQL Server Installation on your workstation. It might be much faster than search for the solution.





Wednesday, November 4, 2015

SQL Server 2016 CTP Installation. Problems and Features.

Hi guys, I'm might be little bit late with that post, but will try to catch up.
In this post I will only stop on unusual moments, errors and interesting improvements or big changes if any.

Here we go. Step by step:

1. Downloaded 2016CTP3 version from here: https://www.microsoft.com/en-us/evalcenter/evaluate-sql-server-2016?i=1

2. Uninstalled my previous installation of CTP2. That means I had no issues on the very initial setup of new installation.

3. After choosing the evaluation version of the product I selected ALL available services and features within an installation.

4. After hitting "Next" button, I got following error:
Somehow SQL Server installation required Oracle JRE!
After I got back and unchecked "Polybase Query Service for External Data" feature everything went well.

5. I've tried to set all services on my virtual machine to start manually, to save some memory, but new feature "SQL Server Launchpad" can't be changed.

6. Collation setup looks just little different, but no surprises.

7. Very nice new feature is setting multiple TempDB files during Install.
It is not very obvious how to place different TempDB data files on different drives, only Log file has it's special folder.
I've found it wonderful that Setup program recognized virtual environment and allowed me to place TempDB files in folder on my Host machine.

8. Unfortunately, I do not have a domain setup in my virtual environment, so, I couldn't use UNC path.
When I tried to get back I couldn't move forward anymore and replace UNCs by local folders.
As a result I had to restart setup program.

9. After I restarted Setup  and choose all default options everything else was as in previous version of SQL.

10. After the full install it required me to restart my virtual machine. After restart I've got newest shiny version:

Microsoft SQL Server 2016 (CTP3.0) - 13.0.700.242 (X64)   Oct 26 2015 18:46:06   
Copyright (c) Microsoft Corporation  
Enterprise Evaluation Edition (64-bit) on 
Windows 8.1 Enterprise 6.3 <X64> (Build 9600: ) (Hypervisor) 


Please let me know if anybody have different experience or got into problems I've somehow missed.

Thursday, October 29, 2015

Cloud, Data Lake .... What Next?

At PASS Summit 2015 had to drink a lot of coffee and I've always looked at my cup bottom to see with hope to see where IT and database industry is currently moving.

The major trends are Cloud and Hadoop, but my curiosity went much further.
When all of us will accomplish going to Big Data in a Cloud what will be next?

At PASS Summit I captured new HOT word, which will be the BUZZ word for the next few years.
That is "DATA LAKE".

What is "Data Lake"?

How do we see our current infrastructure?
We have our application/database on premise. We possibly stretch our data to the Cloud.
And now we are doing collaboration of ALL OUR data in the Cloud, we doing the transformation and produce new instances of the data completely in the Cloud.
All of these databases, data warehouses and data systems in OUR Cloud for OUR company we can call OUR "Data Lake".

That is our current trend, but what next???

Now my coffee starts to play it's role.
We have a Cloud, then we have a Data Lake .... Do you see the pattern?

We have rivers, moving from Lakes to the Ocean!

"Data Ocean" - is it a buzz word from 2020's?

What that will be? Can we think about something which does not exist and completely artificially pictured by caffeinated imagination?

Why not?

What is an Ocean?
That is something, where ALL rivers go and ALL Clouds get their content.
Did you get the Idea?

Companies' Clouds and Data Lakes will collaborate with each other.
For sure it won't be free for one company to use somebody's else data, but that will create completely new servicing industry.

We see beginning of the Data Ocean era  now, companies providing data as a service right now via APIs. The perfect example is Google Maps. That is just a little run of a data. In 10-15 years it will spin if not to an entire Ocean, but at least flow as wide as Amazon river.





Wednesday, September 30, 2015

Don't Panic if you see Crazy Cost Percentage in SQL Execution Plan.

I've had situations when very complicated execution plans showed billions of percents for single operations within a query plan. Unfortunately I did not capture these extreme situations and have only pretty modest percentage example. Just up to several million percents:

That is documented bug and very well described by Aaron Bertrand on StackExchange:

http://dba.stackexchange.com/questions/40274/how-does-sql-server-generate-a-query-execution-plan-that-adds-up-to-6-000

In the theory, SUM of all tasks in Execution plan must not exceed 100%, but as you see it is not always the truth.
In this post I try to reproduce that bug in test environment, learn it's dependencies and workaround.

At first will create a test table and fill it wit 4K dummy records.
USE Tempdb
GO
CREATE TABLE tbl_Test
(
Demo_ID INT PRIMARY KEY IDENTITY(1,1) NOT NULL,
Demo_Number INT NOT NULL,
Demo_Type CHAR(1) NOT NULL,
Demo_Text NVARCHAR(450) NOT NULL
);
GO
;WITH SampleData_1 AS (SELECT CAST(0 AS TINYINT) AS SampleData UNION ALL SELECT 0 UNION ALL SELECT 0 UNION ALL SELECT 0)
    , SampleData_2 AS (SELECT sd1.SampleData FROM SampleData_1 AS sd1, SampleData_1 AS sd2)
    , SampleData_3 AS (SELECT sd1.SampleData FROM SampleData_2 AS sd1, SampleData_2 AS sd2, SampleData_2)
INSERT INTO tbl_Test(Demo_Number, Demo_Type, Demo_Text)
SELECT ROW_NUMBER() OVER(ORDER BY SampleData) rn, 'T', REPLICATE(N'A',447) FROM SampleData_3;

Then copy-paste following code in SSMS, highlight it, but do not execute. Just click "Display Estimated Execution Plan" or press "Ctrl+L"

IF EXISTS (SELECT * FROM tbl_Test WHERE Demo_Number % 33 = 0)
DELETE FROM tbl_Test WHERE Demo_Number % 33 = 0;
You will get thousands of percents in the bottom line.

Will look why did it happened.
First look at Estimated Cost:
Estimated Subtree Cost for the whole query is 0.0063008.
Estimated Cost for "Clustered Index Delete" is 0.108937, which is exactly 1728.939%  from the cost of whole query.

That means that percentage was calculated correctly, but Estimated Subtree Cost for the whole query was not.

It is obvious why that was happened. As you can see, the query contains two sub queries, but total Subtree Cost is calculated only for the first sub-query, without including costs for the second sub-query.

Now, when we know WHY and HOW the problem happens, will take a look at what is contributing to the additional percentage.

First, will double amount of records by using second part of the first query:
;WITH SampleData_1 AS (SELECT CAST(0 AS TINYINT) AS SampleData UNION ALL SELECT 0 UNION ALL SELECT 0 UNION ALL SELECT 0)
    , SampleData_2 AS (SELECT sd1.SampleData FROM SampleData_1 AS sd1, SampleData_1 AS sd2)
    , SampleData_3 AS (SELECT sd1.SampleData FROM SampleData_2 AS sd1, SampleData_2 AS sd2, SampleData_2)
INSERT INTO tbl_Test(Demo_Number, Demo_Type, Demo_Text)
SELECT ROW_NUMBER() OVER(ORDER BY SampleData) rn, 'T', REPLICATE(N'A',447) FROM SampleData_3;
Look at estimated execution plan again:
Percentages are also almost doubled.

That is easy to explain. In order to delete, SQL Server has to deal with higher volume of data, that results in higher Estimated Costs.

Now will try to add bunch of indexes to our table:

CREATE INDEX IX_tbl_Test1 ON tbl_Test(Demo_Number);
CREATE INDEX IX_tbl_Test2 ON tbl_Test(Demo_Type);
CREATE INDEX IX_tbl_Test3 ON tbl_Test(Demo_Text);
CREATE INDEX IX_tbl_Test4 ON tbl_Test(Demo_Text, Demo_Type);
CREATE INDEX IX_tbl_Test5 ON tbl_Test(Demo_Type, Demo_Text);
CREATE INDEX IX_tbl_Test6 ON tbl_Test(Demo_Type, Demo_Text, Demo_Number);
CREATE INDEX IX_tbl_Test7 ON tbl_Test(Demo_Number, Demo_Type, Demo_Text);
CREATE INDEX IX_tbl_Test8 ON tbl_Test(Demo_Type, Demo_Number, Demo_Text);
I'd expect percentages also to rise:
And they increased dramatically!


As the conclusion I'd say:
In case you see crazy percentage values in your Execution query plan:


Everything is OK, that is just SSMS does not show you correct percentages.

Tuesday, September 29, 2015

Draw American flag in SSMS using Spatial methods. Step by Step.

Yep. That is possible using SQL Server Spatial functionality.
In my few prior posts I already uncovered some Spatial drawing abilities, but now I want to draw real picture.

Let's STARt from drawing stars!


1. Draw a star.


1.1. Start in the center of a star.

   - Center will be coordinates x=0, y=0
   - Circle radius = 10

1.2. Figure out ending point of one bottom ray:

   - Angle between rays is 360 / 5 = 72 degrees.
   - To calc coordinates we need a half: 36 degrees.
   - Define coordinates of ending point of the ray as
     X = radius x SIN(36)
     Y = radius x COS(36)
   - To use SIN & COS have to use "RADIANS" function.
   - RADIANS function does not accept integer!
   "SELECT X = 100 * SIN(RADIANS(36.)), Y = - 100 * COS(RADIANS(36.))"
  
   - Middle ray angle is 90 - 72 = 18 Degrees
     X = radius x COS(18)
     Y = radius x SIN(18)

   - Top ray just has coordinates
     X = 0
     Y = radius
Here is what we get:
SELECT CAST('LINESTRING(0 0, 0 10)' as geometry)
UNION ALL
SELECT CAST('LINESTRING(0 0,' + CONVERT(VARCHAR, 10 * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, -10 * COS(RADIANS(36.))) + ')' as geometry)
UNION ALL
SELECT CAST('LINESTRING(0 0,' + CONVERT(VARCHAR, -10 * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, -10 * COS(RADIANS(36.))) + ')' as geometry)
UNION ALL
SELECT CAST('LINESTRING(0 0,' + CONVERT(VARCHAR, 10 * COS(RADIANS(18.))) + ' ' + CONVERT(VARCHAR, 10 * SIN(RADIANS(18.))) + ')' as geometry)
UNION ALL
SELECT CAST('LINESTRING(0 0,' + CONVERT(VARCHAR, -10 * COS(RADIANS(18.))) + ' ' + CONVERT(VARCHAR, 10 * SIN(RADIANS(18.))) + ')' as geometry);
And here is a result:
Not a star yet, just a skeleton.

1.3. Will connect the rays:

SELECT CAST('LINESTRING(0 10, '
+ CONVERT(VARCHAR, 10 * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, -10 * COS(RADIANS(36.))) + ','
+ CONVERT(VARCHAR, -10 * COS(RADIANS(18.))) + ' ' + CONVERT(VARCHAR, 10 * SIN(RADIANS(18.))) + ','
+ CONVERT(VARCHAR, 10 * COS(RADIANS(18.))) + ' ' + CONVERT(VARCHAR, 10 * SIN(RADIANS(18.))) + ','
+ CONVERT(VARCHAR, -10 * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, -10 * COS(RADIANS(36.))) + ','
+ '0 10)' as geometry);
That is more like a star:

1.4. Pretty, but we do not need internal lines.

Lets calculate middle Ray's bottom points:
    X = radius * SIN(18) * TAN(36)
    Y = radius * SIN(18)
Here are coordinartes of lover Ray's bottom points:
    X = radius * SIN(18) * TAN(36)
    Y = radius * SIN(18)

Also, to simplify our select, replace formulas by variables:
DECLARE @r FLOAT = 10;
DECLARE @rad18 FLOAT = RADIANS(18.);
DECLARE @rad36 FLOAT = RADIANS(36.);
DECLARE @SIN18 FLOAT = SIN(@rad18);
DECLARE @COS18 FLOAT = COS(@rad18);
DECLARE @SIN36 FLOAT = SIN(@rad36);
DECLARE @COS36 FLOAT = COS(@rad36);
DECLARE @TAN36 FLOAT = TAN(@rad36);

SELECT CAST('LINESTRING(0 0, 0 10)' as geometry)
UNION ALL
SELECT CAST('LINESTRING(0 0,' + CONVERT(VARCHAR, @r * @SIN36) + ' ' + CONVERT(VARCHAR, -@r * @COS36) + ')' as geometry)
UNION ALL
SELECT CAST('LINESTRING(0 0,' + CONVERT(VARCHAR, -@r * @SIN36) + ' ' + CONVERT(VARCHAR, -@r * @COS36) + ')' as geometry)
UNION ALL
SELECT CAST('LINESTRING(0 0,' + CONVERT(VARCHAR, @r * @COS18) + ' ' + CONVERT(VARCHAR, @r * @SIN18) + ')' as geometry)
UNION ALL
SELECT CAST('LINESTRING(0 0,' + CONVERT(VARCHAR, -@r * @COS18) + ' ' + CONVERT(VARCHAR, @r * @SIN18) + ')' as geometry)
UNION ALL
SELECT CAST('LINESTRING(0 0,' + CONVERT(VARCHAR, @r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @r * @SIN18 ) + ')' as geometry)
UNION ALL
SELECT CAST('LINESTRING(0 0,' + CONVERT(VARCHAR, -@r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @r * @SIN18 ) + ')' as geometry)
UNION ALL
SELECT CAST('LINESTRING(0 0,' + CONVERT(VARCHAR, @r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, -@r * @SIN18 * @SIN18 / @COS36 ) + ')' as geometry)
UNION ALL
SELECT CAST('LINESTRING(0 0,' + CONVERT(VARCHAR, -@r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, -@r * @SIN18 * @SIN18 / @COS36 ) + ')' as geometry)
UNION ALL
SELECT CAST('LINESTRING(0 0, 0 ' + CONVERT(VARCHAR, -@r * @SIN18 / @COS36 ) + ')' as geometry);
Again bunch of rays:

1.5. Will connect rays ones again:

DECLARE @r FLOAT = 10;
DECLARE @rad18 FLOAT = RADIANS(18.);
DECLARE @rad36 FLOAT = RADIANS(36.);
DECLARE @SIN18 FLOAT = SIN(@rad18);
DECLARE @COS18 FLOAT = COS(@rad18);
DECLARE @SIN36 FLOAT = SIN(@rad36);
DECLARE @COS36 FLOAT = COS(@rad36);
DECLARE @TAN36 FLOAT = TAN(@rad36);

SELECT CAST('LINESTRING(0 10, '
+ CONVERT(VARCHAR, @r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @r * @SIN18 ) + ','
+ CONVERT(VARCHAR, @r * COS(RADIANS(18.))) + ' ' + CONVERT(VARCHAR, @r * SIN(RADIANS(18.))) + ','
+ CONVERT(VARCHAR, @r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, -@r * @SIN18 * @SIN18 / @COS36 ) + ','
+ CONVERT(VARCHAR, @r * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, -@r * COS(RADIANS(36.))) + ','
+ '0 ' + CONVERT(VARCHAR, -@r * @SIN18 / @COS36 ) + ','
+ CONVERT(VARCHAR, -@r * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, -@r * COS(RADIANS(36.))) + ','
+ CONVERT(VARCHAR, -@r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, -@r * @SIN18 * @SIN18 / @COS36 ) + ','
+ CONVERT(VARCHAR, -@r * COS(RADIANS(18.))) + ' ' + CONVERT(VARCHAR, @r * SIN(RADIANS(18.))) + ','
+ CONVERT(VARCHAR, -@r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @r * @SIN18 ) + ','
+ '0 10)' as geometry);
Now we get a real star:

1.6. Very nice star and we will learn how to move the star on our field:


Will add extra coordinates X and Y
Now we can place our star wherever we want:
DECLARE @r FLOAT = 10;
DECLARE @rad18 FLOAT = RADIANS(18.);
DECLARE @rad36 FLOAT = RADIANS(36.);
DECLARE @SIN18 FLOAT = SIN(@rad18);
DECLARE @COS18 FLOAT = COS(@rad18);
DECLARE @SIN36 FLOAT = SIN(@rad36);
DECLARE @COS36 FLOAT = COS(@rad36);
DECLARE @TAN36 FLOAT = TAN(@rad36);

DECLARE @x FLOAT = 50;
DECLARE @y FLOAT = 50;

SELECT CAST('LINESTRING('
+ CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y + @r) + ','
+ CONVERT(VARCHAR, @x + @r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @y + @r * @SIN18 ) + ','
+ CONVERT(VARCHAR, @x + @r * COS(RADIANS(18.)) ) + ' ' + CONVERT(VARCHAR, @y + @r * SIN(RADIANS(18.))) + ','
+ CONVERT(VARCHAR, @x + @r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 * @SIN18 / @COS36 ) + ','
+ CONVERT(VARCHAR, @x + @r * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, @y - @r * COS(RADIANS(36.))) + ','
+ CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 / @COS36 ) + ','
+ CONVERT(VARCHAR, @x - @r * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, @y - @r * COS(RADIANS(36.))) + ','
+ CONVERT(VARCHAR, @x - @r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 * @SIN18 / @COS36 ) + ','
+ CONVERT(VARCHAR, @x - @r * COS(RADIANS(18.))) + ' ' + CONVERT(VARCHAR, @y + @r * SIN(RADIANS(18.))) + ','
+ CONVERT(VARCHAR, @x - @r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @y + @r * @SIN18 ) + ','
+ CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y + @r)
+ ')' AS GEOMETRY);

1.7. To color our star we have to build a Polygon   

MOST IMPORTANT: Polygon line must end at the same point where it started.
DECLARE @r FLOAT = 10;
DECLARE @rad18 FLOAT = RADIANS(18.);
DECLARE @rad36 FLOAT = RADIANS(36.);
DECLARE @SIN18 FLOAT = SIN(@rad18);
DECLARE @COS18 FLOAT = COS(@rad18);
DECLARE @SIN36 FLOAT = SIN(@rad36);
DECLARE @COS36 FLOAT = COS(@rad36);
DECLARE @TAN36 FLOAT = TAN(@rad36);

DECLARE @x FLOAT = 50;
DECLARE @y FLOAT = 50;

SELECT CAST('POLYGON(('
+ CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y + @r) + ','
+ CONVERT(VARCHAR, @x + @r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @y + @r * @SIN18 ) + ','
+ CONVERT(VARCHAR, @x + @r * COS(RADIANS(18.)) ) + ' ' + CONVERT(VARCHAR, @y + @r * SIN(RADIANS(18.))) + ','
+ CONVERT(VARCHAR, @x + @r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 * @SIN18 / @COS36 ) + ','
+ CONVERT(VARCHAR, @x + @r * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, @y - @r * COS(RADIANS(36.))) + ','
+ CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 / @COS36 ) + ','
+ CONVERT(VARCHAR, @x - @r * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, @y - @r * COS(RADIANS(36.))) + ','
+ CONVERT(VARCHAR, @x - @r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 * @SIN18 / @COS36 ) + ','
+ CONVERT(VARCHAR, @x - @r * COS(RADIANS(18.))) + ' ' + CONVERT(VARCHAR, @y + @r * SIN(RADIANS(18.))) + ','
+ CONVERT(VARCHAR, @x - @r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @y + @r * @SIN18 ) + ','
+ CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y + @r)
+ '))' AS GEOMETRY);
Now star is colored:

1.8. And now we will make all 50 stars at once:

DECLARE @rad18 FLOAT = RADIANS(18.);
DECLARE @rad36 FLOAT = RADIANS(36.);
DECLARE @SIN18 FLOAT = SIN(@rad18);
DECLARE @COS18 FLOAT = COS(@rad18);
DECLARE @SIN36 FLOAT = SIN(@rad36);
DECLARE @COS36 FLOAT = COS(@rad36);
DECLARE @TAN36 FLOAT = TAN(@rad36);

DECLARE @x FLOAT;
DECLARE @y FLOAT;

DECLARE @r FLOAT = 6.16/2; -- Star Radius
DECLARE @hd FLOAT = 6.3; -- Horisontal distance between stars
DECLARE @vd FLOAT = 5.4; -- Vertical distance between stars

DECLARE @h TINYINT = 0, @v TINYINT = 0; -- Horisontal & Vertical Star counters

DECLARE @Stars TABLE(Star GEOMETRY);

WHILE @h < 11
BEGIN
SET @h += 1;
WHILE @v < 9
BEGIN
    SET @v += 2;
    SELECT
        @x = @hd * (@h + (@v + 1) % 2),
        @y = @vd * (@v + (@h + 1) % 2) * (-1);
    INSERT INTO @Stars(Star)
    SELECT CAST('POLYGON(('
    + CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y + @r) + ','
    + CONVERT(VARCHAR, @x + @r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @y + @r * @SIN18 ) + ','
    + CONVERT(VARCHAR, @x + @r * COS(RADIANS(18.)) ) + ' ' + CONVERT(VARCHAR, @y + @r * SIN(RADIANS(18.))) + ','
    + CONVERT(VARCHAR, @x + @r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 * @SIN18 / @COS36 ) + ','
    + CONVERT(VARCHAR, @x + @r * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, @y - @r * COS(RADIANS(36.))) + ','
    + CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 / @COS36 ) + ','
    + CONVERT(VARCHAR, @x - @r * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, @y - @r * COS(RADIANS(36.))) + ','
    + CONVERT(VARCHAR, @x - @r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 * @SIN18 / @COS36 ) + ','
    + CONVERT(VARCHAR, @x - @r * COS(RADIANS(18.))) + ' ' + CONVERT(VARCHAR, @y + @r * SIN(RADIANS(18.))) + ','
    + CONVERT(VARCHAR, @x - @r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @y + @r * @SIN18 ) + ','
    + CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y + @r)
    + '))' AS GEOMETRY);

    IF @v = 8 and @h % 2 = 0 SET @v = 9;
END
SET @v = 0;
END

SELECT * FROM @Stars;
Here are our 50 stars:

1.9. Let's make all 50 stars as one POLYGON:

DECLARE @rad18 FLOAT = RADIANS(18.);
DECLARE @rad36 FLOAT = RADIANS(36.);
DECLARE @SIN18 FLOAT = SIN(@rad18);
DECLARE @COS18 FLOAT = COS(@rad18);
DECLARE @SIN36 FLOAT = SIN(@rad36);
DECLARE @COS36 FLOAT = COS(@rad36);
DECLARE @TAN36 FLOAT = TAN(@rad36);

DECLARE @x FLOAT;
DECLARE @y FLOAT;

DECLARE @r FLOAT = 6.16/2; -- Star Radius
DECLARE @hd FLOAT = 6.3; -- Horisontal distance between stars
DECLARE @vd FLOAT = 5.4; -- Vertical distance between stars

DECLARE @h TINYINT = 0, @v TINYINT = 0; -- Horisontal & Vertical Star counters

DECLARE @StarPolygon VARCHAR(MAX) = '';

WHILE @h < 11
BEGIN
SET @h += 1;
WHILE @v < 9
BEGIN
    SET @v += 2;
    SELECT
        @x = @hd * (@h - 1 + (@v + 1) % 2),
        @y = @vd * (@v - 1 + (@h + 1) % 2) * (-1);

    SET @StarPolygon += ',('
    + CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y + @r) + ','
    + CONVERT(VARCHAR, @x + @r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @y + @r * @SIN18 ) + ','
    + CONVERT(VARCHAR, @x + @r * COS(RADIANS(18.)) ) + ' ' + CONVERT(VARCHAR, @y + @r * SIN(RADIANS(18.))) + ','
    + CONVERT(VARCHAR, @x + @r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 * @SIN18 / @COS36 ) + ','
    + CONVERT(VARCHAR, @x + @r * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, @y - @r * COS(RADIANS(36.))) + ','
    + CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 / @COS36 ) + ','
    + CONVERT(VARCHAR, @x - @r * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, @y - @r * COS(RADIANS(36.))) + ','
    + CONVERT(VARCHAR, @x - @r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 * @SIN18 / @COS36 ) + ','
    + CONVERT(VARCHAR, @x - @r * COS(RADIANS(18.))) + ' ' + CONVERT(VARCHAR, @y + @r * SIN(RADIANS(18.))) + ','
    + CONVERT(VARCHAR, @x - @r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @y + @r * @SIN18 ) + ','
    + CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y + @r)
    + ')';

    IF @v = 8 and @h % 2 = 0 SET @v = 9;
END
SET @v = 0;
END

SELECT CAST('POLYGON(' + SUBSTRING(@StarPolygon, 2, @@TEXTSIZE) + ')' AS GEOMETRY);
Here are all stars as one POLYGON:

1.10. Final step with stars is putting them in a box:

DECLARE @rad18 FLOAT = RADIANS(18.);
DECLARE @rad36 FLOAT = RADIANS(36.);
DECLARE @SIN18 FLOAT = SIN(@rad18);
DECLARE @COS18 FLOAT = COS(@rad18);
DECLARE @SIN36 FLOAT = SIN(@rad36);
DECLARE @COS36 FLOAT = COS(@rad36);
DECLARE @TAN36 FLOAT = TAN(@rad36);

DECLARE @x FLOAT;
DECLARE @y FLOAT;

DECLARE @r FLOAT = 6.16/2; -- Star Radius
DECLARE @hd FLOAT = 6.3; -- Horisontal distance between stars
DECLARE @vd FLOAT = 5.4; -- Vertical distance between stars

DECLARE @h TINYINT = 0, @v TINYINT = 0; -- Horisontal & Vertical Star counters

DECLARE @StarPolygon VARCHAR(MAX) = '';
-- DECLARE @Stars TABLE(Star GEOMETRY);

WHILE @h < 11
BEGIN
SET @h += 1;
WHILE @v < 9
BEGIN
    SET @v += 2;
    SELECT
        @x = @hd * (@h - 1 + (@v + 1) % 2),
        @y = @vd * (@v - 1 + (@h + 1) % 2) * (-1);

    SET @StarPolygon += ',('
    + CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y + @r) + ','
    + CONVERT(VARCHAR, @x + @r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @y + @r * @SIN18 ) + ','
    + CONVERT(VARCHAR, @x + @r * COS(RADIANS(18.)) ) + ' ' + CONVERT(VARCHAR, @y + @r * SIN(RADIANS(18.))) + ','
    + CONVERT(VARCHAR, @x + @r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 * @SIN18 / @COS36 ) + ','
    + CONVERT(VARCHAR, @x + @r * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, @y - @r * COS(RADIANS(36.))) + ','
    + CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 / @COS36 ) + ','
    + CONVERT(VARCHAR, @x - @r * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, @y - @r * COS(RADIANS(36.))) + ','
    + CONVERT(VARCHAR, @x - @r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 * @SIN18 / @COS36 ) + ','
    + CONVERT(VARCHAR, @x - @r * COS(RADIANS(18.))) + ' ' + CONVERT(VARCHAR, @y + @r * SIN(RADIANS(18.))) + ','
    + CONVERT(VARCHAR, @x - @r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @y + @r * @SIN18 ) + ','
    + CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y + @r)
    + ')';

    IF @v = 8 and @h % 2 = 0 SET @v = 9;
END
SET @v = 0;
END

SELECT CAST('POLYGON('
+ '(0 0,0 ' + CONVERT(VARCHAR, @vd * -10)
+ ',' + CONVERT(VARCHAR, @hd * 12) + ' ' + CONVERT(VARCHAR, @vd * -10)
+ ',' + CONVERT(VARCHAR, @hd * 12) + ' 0,0 0),'
+ SUBSTRING(@StarPolygon, 2, @@TEXTSIZE) + ')' AS GEOMETRY);
We are done with most difficult part of the American Flag:

2. Stripes.

2.1. Stripes are pretty easy:

DECLARE @A FLOAT = 100; -- Flag Width
DECLARE @B FLOAT = 190; -- Flag Length
DECLARE @UA FLOAT = @A * 7 / 13; -- UNION Length
DECLARE @UB FLOAT = 76; -- UNION Length
DECLARE @s TINYINT = 1; -- Stripe Count
DECLARE @StripePolygon VARCHAR(MAX) = '';

WHILE @s <= 13
BEGIN
SELECT @StripePolygon += ',('
+ CASE WHEN @s < 9 THEN CONVERT(VARCHAR, @UB) ELSE '0' END + ' '
+ CONVERT(VARCHAR, (@s - 1) * @A / -13) + ','

+ CASE WHEN @s < 9 THEN CONVERT(VARCHAR, @UB) ELSE '0' END + ' '
+ CONVERT(VARCHAR, @s * @A / -13) + ','

+ CONVERT(VARCHAR, @B) + ' '
+ CONVERT(VARCHAR, @s * @A / -13) + ','

+ CONVERT(VARCHAR, @B) + ' '
+ CONVERT(VARCHAR, (@s - 1) * @A / -13) + ','

+ CASE WHEN @s < 9 THEN CONVERT(VARCHAR, @UB) ELSE '0' END + ' '
+ CONVERT(VARCHAR, (@s - 1) * @A / -13) + ')', @s += 2;
END

SELECT CAST('POLYGON(' + SUBSTRING(@StripePolygon, 2, @@TEXTSIZE) + ')' AS GEOMETRY);
Here they are.Remind me flag of Greece.

2.2. Will finish with stripes by framing them:

DECLARE @A FLOAT = 100; -- Flag Width
DECLARE @B FLOAT = 190; -- Flag Length
DECLARE @UA FLOAT = @A * 7 / 13; -- UNION Length
DECLARE @UB FLOAT = 76; -- UNION Length
DECLARE @s TINYINT = 1; -- Stripe Count
DECLARE @StripePolygon VARCHAR(MAX) = '';

WHILE @s <= 13
BEGIN
SELECT @StripePolygon += ',('
+ CASE WHEN @s < 9 THEN CONVERT(VARCHAR, @UB) ELSE '0' END + ' '
+ CONVERT(VARCHAR, (@s - 1) * @A / -13) + ','

+ CASE WHEN @s < 9 THEN CONVERT(VARCHAR, @UB) ELSE '0' END + ' '
+ CONVERT(VARCHAR, @s * @A / -13) + ','

+ CONVERT(VARCHAR, @B) + ' '
+ CONVERT(VARCHAR, @s * @A / -13) + ','

+ CONVERT(VARCHAR, @B) + ' '
+ CONVERT(VARCHAR, (@s - 1) * @A / -13) + ','

+ CASE WHEN @s < 9 THEN CONVERT(VARCHAR, @UB) ELSE '0' END + ' '
+ CONVERT(VARCHAR, (@s - 1) * @A / -13) + ')', @s += 2;
END

SELECT CAST('POLYGON((0 0, 0 '
+ CONVERT(VARCHAR, -@A) + ', '
+ CONVERT(VARCHAR, @B) + ' ' + CONVERT(VARCHAR, -@A) + ', '
+ CONVERT(VARCHAR, @B) + ' 0,0 0),'
+ '(0.1 -0.1,0.1 '
+ CONVERT(VARCHAR, 0.1 - @A ) + ', '
+ CONVERT(VARCHAR, @B - 0.1) + ' ' + CONVERT(VARCHAR, 0.1 - @A) + ', '
+ CONVERT(VARCHAR, @B - 0.1) + ' -0.1,0.1 -0.1)'
+ ')' AS GEOMETRY)
UNION ALL
SELECT CAST('POLYGON(' + SUBSTRING(@StripePolygon, 2, @@TEXTSIZE) + ')' AS GEOMETRY);
Here how they look in frame:

3. Now will combine Stars and Stripes:

DECLARE @A FLOAT = 100; -- Flag Width
DECLARE @B FLOAT = 190; -- Flag Length
DECLARE @UA FLOAT = @A * 7 / 13; -- UNION Length
DECLARE @UB FLOAT = 76; -- UNION Length
DECLARE @s TINYINT = 1; -- Stripe Count

DECLARE @StripePolygon VARCHAR(MAX) = '';
DECLARE @r FLOAT = 6.16/2; -- Star Radius
DECLARE @hd FLOAT = @UB/12; -- Horisontal distance between stars
DECLARE @vd FLOAT = 5.4; -- Vertical distance between stars

DECLARE @h TINYINT = 0, @v TINYINT = 0; -- Horisontal & Vertical Star counters
DECLARE @x FLOAT;
DECLARE @y FLOAT;

DECLARE @rad18 FLOAT = RADIANS(18.);
DECLARE @rad36 FLOAT = RADIANS(36.);
DECLARE @SIN18 FLOAT = SIN(@rad18);
DECLARE @COS18 FLOAT = COS(@rad18);
DECLARE @SIN36 FLOAT = SIN(@rad36);
DECLARE @COS36 FLOAT = COS(@rad36);
DECLARE @TAN36 FLOAT = TAN(@rad36);

DECLARE @StarPolygon VARCHAR(MAX) = '';

DECLARE @Flag TABLE(Polygon GEOMETRY, ID INT IDENTITY(1,1))

WHILE @h < 11
BEGIN
SET @h += 1;
WHILE @v < 9
BEGIN
    SET @v += 2;
    SELECT
        @x = @hd * (@h - 1 + (@v + 1) % 2),
        @y = @vd * (@v - 1 + (@h + 1) % 2) * (-1);

    SET @StarPolygon += ',('
    + CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y + @r) + ','
    + CONVERT(VARCHAR, @x + @r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @y + @r * @SIN18 ) + ','
    + CONVERT(VARCHAR, @x + @r * COS(RADIANS(18.)) ) + ' ' + CONVERT(VARCHAR, @y + @r * SIN(RADIANS(18.))) + ','
    + CONVERT(VARCHAR, @x + @r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 * @SIN18 / @COS36 ) + ','
    + CONVERT(VARCHAR, @x + @r * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, @y - @r * COS(RADIANS(36.))) + ','
    + CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 / @COS36 ) + ','
    + CONVERT(VARCHAR, @x - @r * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, @y - @r * COS(RADIANS(36.))) + ','
    + CONVERT(VARCHAR, @x - @r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 * @SIN18 / @COS36 ) + ','
    + CONVERT(VARCHAR, @x - @r * COS(RADIANS(18.))) + ' ' + CONVERT(VARCHAR, @y + @r * SIN(RADIANS(18.))) + ','
    + CONVERT(VARCHAR, @x - @r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @y + @r * @SIN18 ) + ','
    + CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y + @r)
    + ')';

    IF @v = 8 and @h % 2 = 0 SET @v = 9;
END
SET @v = 0;
END

INSERT INTO @Flag(Polygon)
SELECT CAST('POLYGON('
+ '(0 0,0 ' + CONVERT(VARCHAR, @vd * -10)
+ ',' + CONVERT(VARCHAR, @hd * 12) + ' ' + CONVERT(VARCHAR, @vd * -10)
+ ',' + CONVERT(VARCHAR, @hd * 12) + ' 0,0 0),'
+ SUBSTRING(@StarPolygon, 2, @@TEXTSIZE) + ')' AS GEOMETRY);

WHILE @s <= 13
BEGIN
SELECT @StripePolygon += ',('
+ CASE WHEN @s < 9 THEN CONVERT(VARCHAR, @UB) ELSE '0' END + ' '
+ CONVERT(VARCHAR, (@s - 1) * @A / -13) + ','

+ CASE WHEN @s < 9 THEN CONVERT(VARCHAR, @UB) ELSE '0' END + ' '
+ CONVERT(VARCHAR, @s * @A / -13) + ','

+ CONVERT(VARCHAR, @B) + ' '
+ CONVERT(VARCHAR, @s * @A / -13) + ','

+ CONVERT(VARCHAR, @B) + ' '
+ CONVERT(VARCHAR, (@s - 1) * @A / -13) + ','

+ CASE WHEN @s < 9 THEN CONVERT(VARCHAR, @UB) ELSE '0' END + ' '
+ CONVERT(VARCHAR, (@s - 1) * @A / -13) + ')', @s += 2;
END

INSERT INTO @Flag(Polygon)
SELECT CAST('POLYGON((0 0, 0 '
+ CONVERT(VARCHAR, -@A) + ', '
+ CONVERT(VARCHAR, @B) + ' ' + CONVERT(VARCHAR, -@A) + ', '
+ CONVERT(VARCHAR, @B) + ' 0,0 0),'
+ '(0.1 -0.1,0.1 '
+ CONVERT(VARCHAR, 0.1 - @A ) + ', '
+ CONVERT(VARCHAR, @B - 0.1) + ' ' + CONVERT(VARCHAR, 0.1 - @A) + ', '
+ CONVERT(VARCHAR, @B - 0.1) + ' -0.1,0.1 -0.1)'
+ ')' AS GEOMETRY)

INSERT INTO @Flag(Polygon)
SELECT CAST('POLYGON(' + SUBSTRING(@StripePolygon, 2, @@TEXTSIZE) + ')' AS GEOMETRY);

SELECT * FROM @Flag;
Picture is good, but colors are ugly:

4. Coloring. Very important step.

As you noticed, on all prior pics all colors are random. That is because we CAN'T control them!
However, even though they are random, but they are random in the certain order and we can use that order for our needs.

Let's Look at colors we  can use:
DECLARE @SSMSColourPalette table (id INT IDENTITY(1,1), geom geometry)

DECLARE @x INT = 0, @y INT = 0;
WHILE @y < 100
BEGIN
WHILE @x < 20
BEGIN
INSERT INTO @SSMSColourPalette VALUES(
'POLYGON((' + CAST(@x AS VARCHAR(32)) + ' ' + CAST(@y AS VARCHAR(32)) + ','
+ CAST(@x + 1 AS VARCHAR(32)) + ' ' + CAST(@y AS VARCHAR(32)) + ','
+ CAST(@x + 1 AS VARCHAR(32)) + ' ' + CAST(@y + 1 AS VARCHAR(32)) + ','
+ CAST(@x AS VARCHAR(32)) + ' ' + CAST(@y + 1 AS VARCHAR(32)) + ','
+ CAST(@x AS VARCHAR(32)) + ' ' + CAST(@y AS VARCHAR(32)) + '))'
)
SET @x = @x + 1;
END
SET @x = 0;
SET @y = @y + 1;
END

SELECT * FROM @SSMSColourPalette
ORDER BY id;
Here are the firs couple of hundred colors fro selected 2000:
In order to get necessary color we have to do draw some empty objects, which will hold colors we want to skip using following script:

SELECT TOP 140
CAST('POLYGON((0 0, 0 0.0000001, 0.0000001 0.0000001, 0 0))' AS GEOMETRY)  
FROM sys.messages;


5. And Now our Final Step and the Final script of drawing the Flag:


GO

DECLARE @A FLOAT = 100; -- Flag Width
DECLARE @B FLOAT = 190; -- Flag Length
DECLARE @UA FLOAT = @A * 7 / 13; -- UNION Length
DECLARE @UB FLOAT = 76; -- UNION Length
DECLARE @s TINYINT = 1; -- Stripe Count

DECLARE @StripePolygon VARCHAR(MAX) = '';
DECLARE @r FLOAT = 6.16/2; -- Star Radius
DECLARE @hd FLOAT = @UB/12; -- Horisontal distance between stars
DECLARE @vd FLOAT = 5.4; -- Vertical distance between stars

DECLARE @h TINYINT = 0, @v TINYINT = 0; -- Horisontal & Vertical Star counters
DECLARE @x FLOAT;
DECLARE @y FLOAT;

DECLARE @rad18 FLOAT = RADIANS(18.);
DECLARE @rad36 FLOAT = RADIANS(36.);
DECLARE @SIN18 FLOAT = SIN(@rad18);
DECLARE @COS18 FLOAT = COS(@rad18);
DECLARE @SIN36 FLOAT = SIN(@rad36);
DECLARE @COS36 FLOAT = COS(@rad36);
DECLARE @TAN36 FLOAT = TAN(@rad36);

DECLARE @StarPolygon VARCHAR(MAX) = '';

DECLARE @Flag TABLE(Polygon GEOMETRY, ID INT IDENTITY(1,1))

-- Building SET of 50 stars
WHILE @h < 11
BEGIN
SET @h += 1;
WHILE @v < 9
BEGIN
    SET @v += 2;
    SELECT
        @x = @hd * (@h - 1 + (@v + 1) % 2),
        @y = @vd * (@v - 1 + (@h + 1) % 2) * (-1);

    -- Building Individual star
    SET @StarPolygon += ',('
    + CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y + @r) + ','
    + CONVERT(VARCHAR, @x + @r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @y + @r * @SIN18 ) + ','
    + CONVERT(VARCHAR, @x + @r * COS(RADIANS(18.)) ) + ' ' + CONVERT(VARCHAR, @y + @r * SIN(RADIANS(18.))) + ','
    + CONVERT(VARCHAR, @x + @r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 * @SIN18 / @COS36 ) + ','
    + CONVERT(VARCHAR, @x + @r * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, @y - @r * COS(RADIANS(36.))) + ','
    + CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 / @COS36 ) + ','
    + CONVERT(VARCHAR, @x - @r * SIN(RADIANS(36.))) + ' ' + CONVERT(VARCHAR, @y - @r * COS(RADIANS(36.))) + ','
    + CONVERT(VARCHAR, @x - @r * @SIN18 * @COS18 / @COS36 ) + ' ' + CONVERT(VARCHAR, @y - @r * @SIN18 * @SIN18 / @COS36 ) + ','
    + CONVERT(VARCHAR, @x - @r * COS(RADIANS(18.))) + ' ' + CONVERT(VARCHAR, @y + @r * SIN(RADIANS(18.))) + ','
    + CONVERT(VARCHAR, @x - @r * @SIN18 * @TAN36 ) + ' ' + CONVERT(VARCHAR, @y + @r * @SIN18 ) + ','
    + CONVERT(VARCHAR, @x) + ' ' + CONVERT(VARCHAR, @y + @r)
    + ')';

    IF @v = 8 and @h % 2 = 0 SET @v = 9;
END
SET @v = 0;
END

-- Building SET of 13 stripes
WHILE @s <= 13
BEGIN
-- Building Individual stripe
SELECT @StripePolygon += ',('
+ CASE WHEN @s < 9 THEN CONVERT(VARCHAR, @UB) ELSE '0' END + ' ' + CONVERT(VARCHAR, (@s - 1) * @A / -13) + ','
+ CASE WHEN @s < 9 THEN CONVERT(VARCHAR, @UB) ELSE '0' END + ' ' + CONVERT(VARCHAR, @s * @A / -13) + ','
+ CONVERT(VARCHAR, @B) + ' ' + CONVERT(VARCHAR, @s * @A / -13) + ','
+ CONVERT(VARCHAR, @B) + ' ' + CONVERT(VARCHAR, (@s - 1) * @A / -13) + ','
+ CASE WHEN @s < 9 THEN CONVERT(VARCHAR, @UB) ELSE '0' END + ' ' + CONVERT(VARCHAR, (@s - 1) * @A / -13) + ')'
, @s += 2;
END

---------------------------------------------------------------------------------------------------------------

-- Adjust Frame Color
INSERT INTO @Flag(Polygon)
SELECT TOP 149 CAST('POLYGON((0 0, 0 0.0000001, 0.0000001 0.0000001, 0 0))' AS GEOMETRY) FROM sys.messages

-- Adding Frame
INSERT INTO @Flag(Polygon)
SELECT CAST('POLYGON((0 0, 0 '
+ CONVERT(VARCHAR, -@A) + ', '
+ CONVERT(VARCHAR, @B) + ' ' + CONVERT(VARCHAR, -@A) + ', '
+ CONVERT(VARCHAR, @B) + ' 0,0 0),'
+ '(0.1 -0.1,0.1 '
+ CONVERT(VARCHAR, 0.1 - @A ) + ', '
+ CONVERT(VARCHAR, @B - 0.1) + ' ' + CONVERT(VARCHAR, 0.1 - @A) + ', '
+ CONVERT(VARCHAR, @B - 0.1) + ' -0.1,0.1 -0.1)'
+ ')' AS GEOMETRY)

-- Adjust UNION Color
INSERT INTO @Flag(Polygon)
SELECT TOP 140 CAST('POLYGON((0 0, 0 0.0000001, 0.0000001 0.0000001, 0 0))' AS GEOMETRY) FROM sys.messages

-- Adding UNION
INSERT INTO @Flag(Polygon)
SELECT CAST('POLYGON('
+ '(0 0,0 ' + CONVERT(VARCHAR, @vd * -10)
+ ',' + CONVERT(VARCHAR, @hd * 12) + ' ' + CONVERT(VARCHAR, @vd * -10)
+ ',' + CONVERT(VARCHAR, @hd * 12) + ' 0,0 0),'
+ SUBSTRING(@StarPolygon, 2, @@TEXTSIZE) + ')' AS GEOMETRY);

-- Adjust Stripe's Color
INSERT INTO @Flag(Polygon)
SELECT TOP 4 CAST('POLYGON((0 0, 0 0.0000001, 0.0000001 0.0000001, 0 0))' AS GEOMETRY) FROM sys.messages

-- Adding Strips
INSERT INTO @Flag(Polygon)
SELECT CAST('POLYGON(' + SUBSTRING(@StripePolygon, 2, @@TEXTSIZE) + ')' AS GEOMETRY);

-- Adjust Stars' Color
INSERT INTO @Flag(Polygon)
SELECT TOP 28 CAST('POLYGON((0 0, 0 0.0000001, 0.0000001 0.0000001, 0 0))' AS GEOMETRY) FROM sys.messages

-- Adding Stars
INSERT INTO @Flag(Polygon)
SELECT CAST('POLYGON(' + SUBSTRING(@StarPolygon, 2, @@TEXTSIZE) + ')' AS GEOMETRY);

SELECT * FROM @Flag;

And here is the beautiful result:

Actually, American flag is easy to draw. I wonder if somebody woud draw Mexican Flag.