Wednesday, August 12, 2009

[vb6] Open Any Type Of File With Its Associated Window's Program

You simply want to be able to press a command button, which will trigger a file (e.g. "c:\media\mysong.mp3"), to be opened with by its associated application (e.g. "Media Player") just as what would happen if I went to the file in windows explorer, and double clicked it.

1. Create module modTools as following:
Attribute VB_Name = "modTools"
'*******************************************************************************
' Name : modTools
' Author : Chandra Gunawan
' Date : 22-Jul-2009
' Description : Tools Library
'
' Maintenance Log
' ==============================================================================
' Date ID Description
' ------------------------------------------------------------------------------
'*******************************************************************************

Option Explicit

'==============================================================================
' CONSTANTS & VARIABLES DEFINITION
'==============================================================================
Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA"_

(ByVal hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, _
ByVal lpParameters As String, ByVal lpDirectory As String, _
ByVal nShowCmd As Long) As Long

Private Const SW_SHOWNORMAL = 1

'------------------------------------------------------------------------------
' Name : OpenFile
' Author : Chandra Gunawan
' Date : 13-Aug-2009
' Description : Open any type of file with its associated window's program
'------------------------------------------------------------------------------
Public Function OpenFile(ByVal pForm As Form, ByVal pFilePath$) As Long

OpenFile = ShellExecute(pForm.hwnd, "open", pFilePath, vbNullString, vbNullString, SW_SHOWNORMAL)
End Function 'OpenFile

2. Execute OpenFile function inside your button's click-event
Private Sub Command1_Click()
Call OpenFile(Me, "c:\media\mysong.mp3")
End Sub

Sunday, August 9, 2009

[t-sql] Twenty tips to write a good stored procedure

By Arup Chakraborty, 2009/08/10
The writing of stored procedures is a very common task in todays database world. Not only by database developers, but also application developers are writing the procedures. DBA's also need to write procedures. In fact, I have faced a very common question in my interviews: " How many stored procedures have you written?"

Since the world is going for fat servers (a server which runs an application where most of the program code resides on it rather than on the client computers), stored procedures are gaining more and more importance. Here is my humble try to give some tips to write good procedures. I hope this will add value for novice as well as experienced programmers.

1. Keywords - Use SQL keywords in capital letters to increase readability. Also use proper indentation to increase readability.

2. SQL-92 - Always try to use ANSI 92 syntax. Till now the old syntax is working the old syntax will be deprecated in the next release of MS SQL server.
As an example, for joining, use
SELECT *
  FROM employee e1
  INNER JOIN employee _dtl e2
    ON e2.id = e1.id

Instead of
SELECT *
  FROM employee e1, employee_dtl e2
  WHERE e2.id = e1.id

3. Variables - Use as few as possible variables. It frees spaces in cache.
4. Dynamic Queries - Try to minimize the usage of dynamic queries. If you are using a dynamic query like: SELECT * FROM mydb.dbo.emp where empid = @eid then there is no problem. You can supply a value for the @eid parameter and there is no recompilation of the execution plan in the database cache. But if you are using a SQL query like SELECT * FROM emp where empid = " + @eid and supply a parameter (say 100), then the cache will keep the execution plan for the value of 100 only. If the id changes (to say 101), it will recompile the statement. Hence, this approach is slower than the previous one. (You can get the exact value of the SQL statement from Profiler)

5. Fully Qualified Names - Always use the fully qualified name when calling stored procedures. This would be the format database_name.schema_name.table_name. For example, use EXEC master.dbo.Your_Proc_name instead of EXEC Your_Proc_name This is a very common mistake, which causes an extra trip to the procedure cache to get the execution plan for execution. Also try to use the schema name while creating a procedure. Like: CREATE PROCEDURE dbo.Your_Proc_name instead of CREATE PROCEDURE Your_Proc_name

6.SET NOCOUNT OFF - This returns the message that shows number of rows affected by SQL statement. This can cause extra network traffic and can have some serious impact on performance when the procedure is called frequently.

7. The sp_ prefix - Don't use the "sp_" prefix in a stored procedure name as the "sp_" prefix is reserved for system stored procedures. Any stored procedure that has the "sp_" prefix will cause an extra lookup in the MASTER database If a stored procedure uses same name in both the user database and a system database, the stored procedure in the user database will never get executed.

8. sp_executeSQL and the KEEPFIXED PLAN options - Both sp_executesql and the KEEPFIXED PLAN option avoid the recompilation of a stored procedure. If you want to provide parameterized dynamic SQL, then go for sp_executesql instead of EXEC(proc_name). Here the execution plan for the procedure is stored with the variable name in cache memory. When the variable values are supplied, then the values are simply mapped to the query, hence no need for a recompilation.
Use the OPTION KEEPFIXED PLAN hint while selecting records from temporary tables. If the query contains this hint, then its plan is not recompiled. For more information about procedure recompilation, please go through the following article: http://technet.microsoft.com/en-us/library/cc966425.aspx
CREATE PROCEDURE my_proc
AS
  CREATE TABLE #t (a int )

  SELECT * FROM #t

  INSERT #t
    SELECT * FROM retest
   
  SELECT COUNT(*)
    FROM #t
    WHERE a = 37
    OPTION (KEEPFIXED PLAN)
GO
As an example of sp_executesql, we can write:
sp_executesql N'SELECT * FROM mydb.dbo.emp where empid = @eid', N'@eid int', @eid=40

9. SELECT vs SET - A single SELECT statement can assign values to different variables and is much faster than multiple SET statements assigning values to multiple different variables.

SELECT @Var1 = @Var1 + 1,
       @Var2 = @Var2 - 1
instead of
SET @Var1 = @Var1 + 1
SET @Var2 = @Var2 - 1

10. WHERE clauses - In a WHERE clause, the various operators used directly affect how fast a query can run. Here are the conditional operators used in the WHERE clause, ordered by their performance.
=, >, <, >=, <=, <>, !=, !>, !<

for details, refer to the article: http://msdn.microsoft.com/en-us/library/ms190276.aspx

11. More WHERE clause hints - Avoid unnecessary conditions in the WHERE Clause. You can easily use the first example instead of the second one. The second example uses one extra OR condition which can be avoided using the first example.
SELECT emp_name FROM table_name WHERE LOWER(emp_name) = 'edu'
SELECT emp_name FROM table_name WHERE emp_name = 'EDU' OR emp_name = 'edu'
Also, try to avoid IN. While checking the existence of some values, then use EXISTS instead of IN. Because IN counts the NULL values also, hence slower than EXISTS. Since EXISTS returns Boolean(Yes/No) but IN returns all values hence result set for IN is heavier than EXISTS.
SELECT * FROM employee WHERE emp_no NOT IN (SELECT emp_no from emp_detail)
SELECT * FROM employee WHERE NOT EXISTS (SELECT emp_no FROM emp_detail)
 
12. CAST and CONVERT - Try to use CAST instead of CONVERT. CAST is ANSI-92 standard but CONVERT works in MS SQL server only. Also, Convert may be deprecated in future MS SQL releases. It is better to use CONVERT only when you need to format the DATETIME datatype with the style option. CAST cannot do this.

13. Avoid DISTINCT and ORDER BY - If you don't need the DISTINCT/ORDER BY clause, then try to avoid so. Unnecessary DISTINCT or ORDER BY clauses cause extra work for the database engine. Hence making performance slower.

14. Avoid using cursors - Try to use temporary table/table variables with identity column and then iterate all the tables using WHILE loop and a looping counter, which will map with the identity column. For details, refer my previous article. http://www.sqlservercentral.com/articles/Stored+Procedures/64523/

15.SELECT statements - Try to use only the required number of columns in the SELECT clause instead of using *. Using * returns all columns, which unnecessarily create a fat recordset.

16.Subquery vs JOINs - This is a famous debate in many online forums. In fact most sub queries can be expressed as an equivalent form of JOIN. I have a good rule of thumb: subquery is faster when we have to retrieve data from large number of tables because it becomes tedious to join more tables. JOIN is faster to retrieve data from database when we have less number of tables. But try to avoid correlated sub queries because it makes the query much slower.

17. CREATE TABLE vs. SELECT INTO - Select * INTO works fine for small tables, but when dealing with large record sets or long-running queries, it creates locks on the system objects within the tempdb database. As a result, other queries and procedures that need to create objects within the tempdb database will have to wait for the long-running query to complete. This is because when an object is created, an exclusive lock is taken against the sysobjects, syscolumns, sysindexes tables.
Also, SELECT INTO not only copies data but also it copies the table structure, hence it performs slower.

18. Try to use table variables instead of Temporary Tables - Temp tables can cause stored procedures to recompile. But table variables were designed specifically to guard against stored procedure recompiles during execution.
If the result set is not containing a huge number of records then you should stick to table variable, otherwise temp table has its advantages. There is a misconception that temp tables always use the tembdb database but table variable do not. Table variables also use tempdb after a certain size. For more information refer to the following article : http://www.sqlservercentral.com/articles/Temporary+Tables/66720/

19.Use proper indexes - You can use the help of the data tuning advisor, but it does not gives the proper result all the time. Index scans are much faster than table scans. So identify the table scans from the execution plans. But when a table returns smaller rows, then it is better to use a table scan. You can see an excellent article on execution plans by G Vijayakumara at the following link:http://www.sqlservercentral.com/articles/Administration/executionplans/1345/

20. Use Profiler - The cachemiss event class indicates that the stored procedure is not in the cache. If the SP:Cachemiss class occurs frequently, it can indicate that more memory should be available to MS SQL server, thereby increasing the size of procedure cache. The cachehit event class indicates that a stored procedure is in the cache.
Last but not the least, again I am repeating the same advice from my previous article. Keep these guidelines in your mind, but don't hesitate to break them if needed. After all, performance is the ultimate goal. If violating the general rule gives you good performance (it may happen based on your database) then don't stick with the guidelines.

Wednesday, July 15, 2009

[vb6] How to create list of same items?

Suppose you want this result:
    abc,abc,abc,abc,abc
But you don't want to create it in the Do or For loop. So you can use the following syntax:
    Mid(Replace(5, "~"), "~", ",abc"), 2)

Sunday, June 28, 2009

[vb6] How to hide the grid from Report Designers?

Open the source code (file with .Dsr extension) using the Notepad, then update the value of _Setting property under the Designers module become 28.



Thursday, May 21, 2009

[link] Cek UserName di Banyak Situs Utama

Buat koleksi aja:

1. http://namechk.com/
2. http://checkusernames.com/

[story] Cerita-cerita Kecil Seputar Google Yang Jarang Diketahui

1. Larry Page dan Sergey Brin awalnya bermusuhan.
Sebagai orang yang sama-sama cerdas, LP dan SB adalah dua orang yang sering berdebat satu sama lain meskipun untuk urusan kecil seperti siapa yang harus menutup pintu atau siapa yang lebih dulu membaca koran. Pertengkaran kecil seperti ini sering menggangu teman-teman seasrama mereka. Ternyata mereka bertengkar bukan karena membenci satu sama lain , melainkan mereka terbiasa untuk kompetitif satu sama lain. Toh pertengkaran kecil mereka selalu diakhiri tertawa bersama :)

2. Sergey Brin lebih genit daripada Larry Page
Kalau urusan wanita, Larry Page memang lebih konservatif dibanding Sergey Brin.

3. Pendiri Google pernah frustasi
Setelah berturut-turut ditolak AltaVista, Excite, dan (terakhir) Yahoo, LP dan SB sama-sama frustasi. Buat mereka Teknologi Page Rank Google seperti layu sebelum berkembang. Mereka tidak pernah bermimpi Google akan bisa sebesar sekarang. Saat itu pikiran mereka cuma satu: menyelesaikan Phd mereka secepatnya dan menyempurnakan Google sambil jalan. Bahkan sangking frustasinya, pernah Google tidak dilirik selama 1 minggu.

4. LP dan SB menggojlok Eric Schmidt ketika pertama berkantor di Google.
LP dan SB bersekongkol mengerjai Eric dengan cara membagi-bagikan secara gratis kartu kredit perusahaan ke para karyawan. Tentu saja Eric pusing, dan dapat pekerjaan tambahan: menarik kembali kartu kredit tersebut dari para karyawan yang juga berusaha menyembunyikan. Di lain hari Eric dikerjai dengan tiba-tiba ada telepon umum di dalam ruang kerjanya. Juga Kulkas dan kursi pijat. Jika Eric tidak punya selera humor, bisa-bisa ngamuk dia. Tapi dia sadar sedang dikerjai dua orang pendiri. Jadi sing waras ngalah….

5. Cerita Palsu untuk nama perusahaan
Nama asli Google dipublikasikan oleh perusahaan sebagai cerita salah tulis dari kata Googol yang artinya angka 1 diikuti 100 angka nol dibelakangnya menjadi Google. Padahal, menurut bocoran salah seorang karyawan awal Google, sebenarnya kata Google berasal dari kata “Go Girl !”, dimana Sergey Brin suatu saat memperoleh ide tersebut ketika sedang menonton pertandingan olahraga dengan para Cherleader yang meneriakkan kata Go Girl ! Go Girl ! (gogel) Cerita meragukan ini didukung 2 fakta: Sergey Brin suka menonton para Cherleader, dan kedua: Google mensponsori para Cherleader dengan memasang tulisan Google di kaos para Cherleader. Mengapa perlu dibuat cerita palsu yang masuk akal, karena konotasi GoGirl yang negatif tidak sesuai dengan citra perusahaan jika suatu saat ingin Go Public.

6. Larry Page ingin membuat Alat Transportasi otomatis, Sergey Brin ingin membuat koloni di Mars
Ini di kemudian hari memang benar-benar diwujudkan. Setidaknya, berusaha diwujudkan. Ide Larry dimulai dengan project mobil listrik, ide Sergey diwujudkan dengan Project Virgle.

7. Tanda ~ yang lebih powerfull daripada tanda * yang jarang dipakai
Dahulu di jaman DOS masih jaya, tanda * sangat berguna untuk menemukan semua file yang bernama tertentu. Sekarang di jaman DOS-nya Internet (Google) maka tanda ~ menggantikan fungsi wildcard dengan sedikir perbedaan penting: lebih cerdas. Tanda ~ jika diketikkan didepan kata tertentu, berarti kita menginginkan Google untuk mencari semua data yang berkaitan dengan keyword tertentu dan sinonimnya. Misalnya ~motor, maka semua halaman yang mengandung kata motor dan sinonimnya, seperti motor show, sepeda motor, atau motor boat akan ditampilkan.

Sumber: http://tech19.wordpress.com/2008/10/27/cerita-cerita-kecil-seputar-google-yang-jarang-diketahui/

Tuesday, April 7, 2009

[t-sql] What is the difference between SET and SELECT when assigning values to variables?

Traditionally, SQL Server database developers are accustomed to using SELECT for assigning values to variables. This was fine and a perfectly valid practice right until SQL Server 6.5. Microsoft released SQL Server 7.0 in 1999. SQL Server 7.0 introduced the new SET statement for initializing and assigning values to variables. SQL Server 7.0 Books Online also stated: "It is recommended that SET @local_variable be used for variable assignment rather than SELECT @local_variable."

This caused some confusion in the database developer community, as Microsoft never mentioned, why SET is recommended over SELECT for assigning values to variables. In this article, I will highlight all the major differences between SET and SELECT, and things you should be aware of, when using either SET or SELECT.

If you are completely new to T-SQL, then the following examples give you an idea of what I am talking about:
    /* Declaring variables */
DECLARE @Variable1 AS int, @Variable2 AS int

/* Setting @Variable1 to a value of 1 using SELECT */
SELECT @Variable1 = 1

/* Setting @Variable2 to a value of 2 using SET */
SET @Variable2 = 2

Now coming to the differences between SET and SELECT! Are standards important to you? If your answer is 'yes', then you should be using SET. This is because, SET is the ANSI standard way of assigning values to variables, and SELECT is not.

Another fundamental difference between SET and SELECT is that, you can use SELECT to assign values to more than one variable at a time. SET allows you to assign data to only one variable at a time. Here's how:
    /* Declaring variables */
DECLARE @Variable1 AS int, @Variable2 AS int

/* Initializing two variables at once */
SELECT @Variable1 = 1, @Variable2 = 2

/* The same can be done using SET, but two SET statements are needed */
SET @Variable1 = 1
SET @Variable2 = 2

So far so good. But if you ever wrote error handling code in T-SQL, you most probably are aware that, the system variables @@ERROR and @@ROWCOUNT must be captured in one statement, immediately after a data manipulation (DML) statement like INSERT, UPDATE, DELETE, or else, these system variables get reset to 0. So, if you want to stick to the standards and use SET in this scenario, you are out of luck. The following example demonstrates the problem:
    DECLARE @Error int, @RowCount int
SELECT price/0 FROM dbo.titles
SET @RowCount = @@ROWCOUNT
SET @Error = @@ERROR
SELECT @Error AS Error
GO

If you run the above piece of code in pubs database, the value of @@ERROR system variable will be displayed as 0, even though the 'division by zero' resulted in error 8134. So, in this particular scenario, forget about standards and use SELECT, as shown below:
    DECLARE @Error int, @RowCount int
SELECT price/0 FROM dbo.titles
SELECT @RowCount = @@ROWCOUNT, @Error = @@ERROR
SELECT @Error AS Error

But if you insist on using SET even in this scenario, there's always a way out. Here's one example, though not readable and recommended:
    DECLARE @ErrorAndRowcount AS varchar(25), @Error int, @RowCount int
SELECT price/0 FROM dbo.titles

/* Capturing @@ERROR and @@ROWCOUNT into a dot separated string */
SET @ErrorAndRowcount = CAST(@@ERROR AS varchar(12)) + '.' + CAST(@@ROWCOUNT AS varchar(12))

/* One way to separate the string into error and rowcount variables */
SET @Error = CAST(PARSENAME(@ErrorAndRowcount, 2) AS int)
SET @RowCount = CAST(PARSENAME(@ErrorAndRowcount, 1) AS int)
SELECT @Error AS Error, @RowCount AS Row_Count

/* Another way of splitting the string into error and rowcount variables */
SET @Error = CAST(LEFT(@ErrorAndRowcount, CHARINDEX('.', @ErrorAndRowcount)-1) AS int)
SET @RowCount = CAST(RIGHT(@ErrorAndRowcount, CHARINDEX('.', REVERSE(@ErrorAndRowcount))-1) AS int)
SELECT @Error AS Error, @RowCount AS Row_Count
GO

Moving on to other differences between SET and SELECT: When using a query to populate a variable, SET will fail with an error, if the query returns more than one value. But SELECT will assign one of the returned rows and mask the fact that the query returned more than one row. As a result, bugs in your code could go unnoticed with SELECT, and this type of bugs are hard to track down too. Here is an example:
    /* Consider the following table with two rows */
SET NOCOUNT ON
CREATE TABLE #Test (i int, j varchar(10))
INSERT INTO #Test (i, j) VALUES (1, 'First Row')
INSERT INTO #Test (i, j) VALUES (1, 'Second Row')
GO

/* Following SELECT will return two rows, but the variable
gets its value from one of those rows, without an error.
This may not be what you were expecting. Since no error is returned,
you will never know that two rows existed for the condition, WHERE i = 1 */
DECLARE @j varchar(10)
SELECT @j = j FROM #Test WHERE i = 1
SELECT @j
GO

/* If you rewrite the same query, but use SET instead,
for variable initialization, you will see the following error */
DECLARE @j varchar(10)
SET @j = (SELECT j FROM #Test WHERE i = 1)
SELECT @j

Server: Msg 512, Level 16, State 1, Line -1074284106
Subquery returned more than 1 value.
This is not permitted when the subquery follows =, !=, <, <= , >, >= or
when the subquery is used as an expression.

Based on the above results, when using a query to populate variables, I suggest you always use SET, if you want to be sure that only one row is returned. If you hate SET for some reason, you could get the same behavior of SET, using SELECT, as shown below:
    DECLARE @j varchar(10)
SELECT @j = (SELECT j FROM #Test WHERE i = 1)
SELECT @j

Here is another difference with respect to assigning values based on a query, especially when the query doesn't return any rows. Run the following example in the pubs database, and you will see what I mean:
    /* Returns NULL */
DECLARE @Title varchar(80)
SET @Title = 'Not Found'

SET @Title = (SELECT title
FROM dbo.titles
WHERE title_id = 'InvalitTitleID')

SELECT @Title
GO

/* Returns the string literal 'Not Found' */
DECLARE @Title varchar(80)
SET @Title = 'Not Found'

SELECT @Title = title
FROM dbo.titles
WHERE title_id = 'InvalitTitleID'

SELECT @Title
GO

Last, but not the least! Is there any performance difference between SET and SELECT? Is one faster or slower than the other? This is one question most database developers and DBAs are not so sure about. So I decided to conduct a test and come up with some conclusive results. I picked a development SQL Server for this test. Closed all applications, and stopped all unnecessary services running on that machine. Stopped SQL Server agent service, to make sure, no jobs kick in during the performance test. Also, unplugged the machine from the network. So, this is one isolated SQL Server box, with nothing but just SQL Server service running on it. Then I created a test script, that continuously assigns values to variables inside a loop (of configurable iterations) using SET, SELECT and measures the time taken to complete each loop.

Here are the results:

There is hardly any performance difference between SET and SELECT, when initializing/assigning values to variables. BUT, I made one startling discovery. As you all know, one single SELECT statement can be used to assign values to multiple variables. This very feature of SELECT makes it a winner over SET, when assigning values to multiple variables. A single SELECT statement assigning values to 3 different variables, is much faster than 3 different SET statements assigning values to 3 different variables. In this scenario, using a SELECT is at least twice as fast, compared to SET. So, the conclusion is, if you have a loop in your stored procedure that manipulates the values of several variables, and if you want to squeeze as much performance as possible out of this loop, then do all variable manipulations in one single SELECT statement (or group the related variables into few SELECT statements) as show below:
    SELECT @TestVar1 = @TestVar1 + 1, @TestVar2 = @TestVar2 - 1, @CTR = @CTR + 1

I ran this test on SQL Server versions 7.0, 2000 and SQL Server 2005 (Yukon), and the results were consistent. I even tested this on single and multi-processor boxes, and the results were the same. If you want to test this yourself, feel free to use the following test script. A word of caution though, do not run this script on a production SQL Server, as it could lead to 100% CPU utilization for the duration of the test. Also, if you think the test is taking too long, reduce the value of the variable @TimesToLoop2, to reduce the number of iterations. At the end of the test, the script displays how much time (in Seconds) it took to assign values to variables using SET, SELECT and SELECT with multiple assignments. Here's the script:
    DECLARE @Test1 int,  @Test2 int, @Test3 int, @TestVar1 int, @TestVar2 int
DECLARE @Loop int, @Start datetime, @CTR int, @TimesToLoop1 int, @TimesToLoop2 int

SET @Test1 = 0
SET @Test2 = 0
SET @Test3 = 0
SET @Loop = 0
SET @TestVar2 = 0
SET @TimesToLoop1 = 10
SET @TimesToLoop2 = 50000
WHILE @Loop < @TimesToLoop1
BEGIN
SET @Start = CURRENT_TIMESTAMP
SET @CTR = 0

/* Testing the performance of SET */
WHILE @CTR < @TimesToLoop2
BEGIN
SET @TestVar1 = 1
SET @TestVar2 = @TestVar2 - @TestVar1
SET @CTR = @CTR + 1
END

SET @Loop = @Loop + 1
SET @Test1 = @Test1 + DATEDIFF(ms, @Start, CURRENT_TIMESTAMP)
END

SET @Loop = 0
SET @TestVar2 = 0
WHILE @Loop < @TimesToLoop1
BEGIN
SELECT @Start = CURRENT_TIMESTAMP
SELECT @CTR = 0

/* Testing the performance of SELECT */
WHILE @CTR < @TimesToLoop2
BEGIN
SELECT @TestVar1 = 1
SELECT @TestVar2 = @TestVar2 - @TestVar1
SELECT @CTR = @CTR + 1
END

SELECT @Loop = @Loop + 1
SELECT @Test2 = @Test2 + DATEDIFF(ms, @Start, CURRENT_TIMESTAMP)
END

SET @Loop = 0
SET @TestVar2 = 0
WHILE @Loop < @TimesToLoop1
BEGIN
SELECT @Start = CURRENT_TIMESTAMP, @CTR = 0

/* Testing the performance of SELECT with multiple variable assignments */
WHILE @CTR < @TimesToLoop2
BEGIN
SELECT @TestVar1 = 1, @TestVar2 = @TestVar2 - @TestVar1, @CTR = @CTR + 1
END

SELECT @Loop = @Loop + 1, @Test3 = @Test3 + DATEDIFF(ms, @Start, CURRENT_TIMESTAMP)
END

SELECT
(@Test1/CAST(@TimesToLoop1 AS decimal(7,2)))/1000.00 AS [SET],
(@Test2/CAST(@TimesToLoop1 AS decimal(7,2)))/1000.00 AS [SELECT],
(@Test3/CAST(@TimesToLoop1 AS decimal(7,2)))/1000.00 AS [SELECT with Multiple Assignments]