@aryacode/dsh-engineering 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +9 -8
  3. package/cordis.patch.yml +1 -1
  4. package/entries/netlogic.js +266 -0
  5. package/entries/optix.js +13 -0
  6. package/package.json +10 -10
  7. package/vendor/l5x/SKILL.md +27 -7
  8. package/vendor/l5x/lib/engine/change-set.d.ts +9 -1
  9. package/vendor/l5x/lib/engine/change-set.js +32 -7
  10. package/vendor/l5x/lib/engine/documentation.d.ts +58 -0
  11. package/vendor/l5x/lib/engine/documentation.js +237 -0
  12. package/vendor/l5x/lib/engine/instructions.d.ts +50 -0
  13. package/vendor/l5x/lib/engine/instructions.js +107 -0
  14. package/vendor/l5x/lib/engine/ladder.d.ts +74 -0
  15. package/vendor/l5x/lib/engine/ladder.js +209 -0
  16. package/vendor/l5x/lib/index.d.ts +1 -0
  17. package/vendor/l5x/lib/index.js +3 -1
  18. package/vendor/l5x/lib/model/routines.js +6 -1
  19. package/vendor/l5x/lib/session.d.ts +20 -0
  20. package/vendor/l5x/lib/session.js +36 -0
  21. package/vendor/l5x/lib/tools/document.d.ts +18 -0
  22. package/vendor/l5x/lib/tools/document.js +119 -0
  23. package/vendor/l5x/lib/tools/drafts.js +109 -3
  24. package/vendor/optix/SKILL.md +68 -0
  25. package/vendor/optix/netlogic/SOURCE.json +12 -0
  26. package/vendor/optix/netlogic/index.json +1987 -0
  27. package/vendor/optix/netlogic/pages/accessing-project-nodes.md +113 -0
  28. package/vendor/optix/netlogic/pages/advanced-svg.md +124 -0
  29. package/vendor/optix/netlogic/pages/alarming.md +489 -0
  30. package/vendor/optix/netlogic/pages/async-tasks.md +236 -0
  31. package/vendor/optix/netlogic/pages/audit-signature.md +91 -0
  32. package/vendor/optix/netlogic/pages/colors.md +122 -0
  33. package/vendor/optix/netlogic/pages/companion-specs.md +77 -0
  34. package/vendor/optix/netlogic/pages/creating-objects.md +370 -0
  35. package/vendor/optix/netlogic/pages/database-interaction.md +437 -0
  36. package/vendor/optix/netlogic/pages/datagrids.md +123 -0
  37. package/vendor/optix/netlogic/pages/debugging-tips.md +605 -0
  38. package/vendor/optix/netlogic/pages/designtime-tags-import.md +531 -0
  39. package/vendor/optix/netlogic/pages/dialog-boxes.md +150 -0
  40. package/vendor/optix/netlogic/pages/dispatch-events.md +200 -0
  41. package/vendor/optix/netlogic/pages/dynamic-links.md +825 -0
  42. package/vendor/optix/netlogic/pages/element-access.md +1208 -0
  43. package/vendor/optix/netlogic/pages/engineering-units.md +298 -0
  44. package/vendor/optix/netlogic/pages/events.md +340 -0
  45. package/vendor/optix/netlogic/pages/execute-command.md +202 -0
  46. package/vendor/optix/netlogic/pages/fto-studio-cli.md +99 -0
  47. package/vendor/optix/netlogic/pages/good-practices.md +50 -0
  48. package/vendor/optix/netlogic/pages/information-model.md +89 -0
  49. package/vendor/optix/netlogic/pages/log-output.md +22 -0
  50. package/vendor/optix/netlogic/pages/managing-aliases.md +22 -0
  51. package/vendor/optix/netlogic/pages/manipulate-nodes.md +72 -0
  52. package/vendor/optix/netlogic/pages/mqtt.md +240 -0
  53. package/vendor/optix/netlogic/pages/netlogic-lifecycle.md +270 -0
  54. package/vendor/optix/netlogic/pages/netlogic-overview.md +155 -0
  55. package/vendor/optix/netlogic/pages/opcua.md +325 -0
  56. package/vendor/optix/netlogic/pages/plc-tags.md +219 -0
  57. package/vendor/optix/netlogic/pages/queries.md +1297 -0
  58. package/vendor/optix/netlogic/pages/random-generation.md +90 -0
  59. package/vendor/optix/netlogic/pages/recipes.md +375 -0
  60. package/vendor/optix/netlogic/pages/recipex.md +2383 -0
  61. package/vendor/optix/netlogic/pages/regex.md +15 -0
  62. package/vendor/optix/netlogic/pages/register-observers.md +346 -0
  63. package/vendor/optix/netlogic/pages/reports.md +141 -0
  64. package/vendor/optix/netlogic/pages/resource-uri.md +308 -0
  65. package/vendor/optix/netlogic/pages/runtime-tags-import.md +280 -0
  66. package/vendor/optix/netlogic/pages/serial-port.md +357 -0
  67. package/vendor/optix/netlogic/pages/sessions.md +58 -0
  68. package/vendor/optix/netlogic/pages/system-node.md +153 -0
  69. package/vendor/optix/netlogic/pages/template-library.md +127 -0
  70. package/vendor/optix/netlogic/pages/threading.md +236 -0
  71. package/vendor/optix/netlogic/pages/translations.md +417 -0
  72. package/vendor/optix/netlogic/pages/trends.md +128 -0
  73. package/vendor/optix/netlogic/pages/ui-animations.md +115 -0
  74. package/vendor/optix/netlogic/pages/users-groups.md +416 -0
  75. package/vendor/optix/netlogic/pages/variables-formatting.md +16 -0
  76. package/vendor/optix/netlogic/pages/variables-interaction.md +557 -0
  77. package/vendor/optix/netlogic/pages/variables-structure.md +155 -0
@@ -0,0 +1,1297 @@
1
+ # Database queries
2
+
3
+ > [!NOTE]
4
+ > As databases are only accessible at runtime, all the queries described in this page can only be used at runtime.
5
+
6
+ ## Table of Contents
7
+
8
+ - [Acknowledgements](#acknowledgements)
9
+ - [Guidance and Best Practices](#guidance-and-best-practices)
10
+ - [SELECT queries](#select-queries)
11
+ - [UPDATE Queries](#update-queries)
12
+ - [DELETE Queries](#delete-queries)
13
+ - [INSERT Queries](#insert-queries)
14
+ - [Temporary Tables](#temporary-tables)
15
+ - [Dynamic queries using variables](#dynamic-queries-using-variables)
16
+ - [Known Limitations and Workarounds](#known-limitations-and-workarounds)
17
+ - [Queries on UI objects](#queries-on-ui-objects)
18
+
19
+ ## Acknowledgements
20
+
21
+ FactoryTalk Optix does not support the full set of SQL queries, but has an own subset based on the SQL ANSI standard. The reason for this is that FactoryTalk Optix will automatically act as translation layer between the UI (where the custom queries are written) and the underlying database, which may be different based on the user selection (SQLite, SQL Server, MySQL, InfluxDB, etc).
22
+
23
+ This means that if the user later switches to a different database, the queries will still work without any change, as FactoryTalk Optix will take care of translating them to the right dialect. This is a great advantage for portability, but it also means that some queries that are valid in a specific SQL dialect may not be supported by FactoryTalk Optix if they are not compatible with all the supported databases.
24
+
25
+ ## Guidance and Best Practices
26
+
27
+ ### Best practices and gotchas
28
+
29
+ When writing queries for use in FactoryTalk Optix or similar embedded database contexts, keep these recommendations in mind:
30
+
31
+ - Prefer explicit column lists instead of `SELECT *` in production code to avoid unexpected schema changes and reduce bandwidth.
32
+ - When using `DISTINCT`, be explicit about the columns you need - `DISTINCT *` removes duplicate entire rows which can be expensive.
33
+ - Avoid updating temporary tables when portability is a concern some backends restrict updates on temporary objects.
34
+ - Use table aliases (for example `t1`, `t2`) when joining multiple tables to prevent ambiguity and improve readability.
35
+ - Be cautious with deep subqueries or multiple nested levels - they can impact performance and readability.
36
+
37
+ ### SQL compatibility and versions
38
+
39
+ Some features documented here are available starting from specific versions of FactoryTalk Optix (noted inline). Always test queries on the target database before deploying to production.
40
+
41
+ ### Security & parameterization
42
+
43
+ FactoryTalk Optix always provides parameterized queries when inserting data to the database using the dedicated `Insert` method of the store. This helps prevent SQL injection attacks and ensures data integrity.
44
+
45
+ ## SELECT queries
46
+
47
+ ### Select All Columns
48
+
49
+ Retrieves all columns from a table.
50
+
51
+ ```sql
52
+ SELECT * FROM TestTable1
53
+ ```
54
+
55
+ ### DISTINCT Qualifier
56
+
57
+ > [!NOTE]
58
+ > This feature is available starting from FactoryTalk Optix version 1.7.x.
59
+
60
+ The DISTINCT keyword eliminates duplicate rows from the result set. The ALL qualifier includes all rows, including duplicates (which is the default behavior).
61
+
62
+ ```sql
63
+ SELECT DISTINCT Column1 FROM Table1
64
+ SELECT DISTINCT Column1, Column2 FROM Table1
65
+ SELECT DISTINCT * FROM Table1
66
+ ```
67
+
68
+ ### Select Specific Columns
69
+
70
+ Retrieves specific columns from a table.
71
+
72
+ ```sql
73
+ SELECT ID, Name FROM TestTable1
74
+ ```
75
+
76
+ ### Column Aliases
77
+
78
+ Uses aliases to rename columns in the result set.
79
+
80
+ ```sql
81
+ SELECT Name AS EmployeeName, Salary AS EmployeeSalary FROM TestTable1
82
+ ```
83
+
84
+ ### Filter Rows with Conditions
85
+
86
+ Filters rows based on conditions using WHERE clause.
87
+
88
+ ```sql
89
+ SELECT * FROM TestTable1 WHERE Salary > 60000
90
+ SELECT * FROM TestTable1 WHERE Salary > 60000 AND DepartmentID = 101
91
+ SELECT * FROM TestTable1 WHERE DepartmentID = 101 OR DepartmentID = 102
92
+ SELECT * FROM TestTable1 WHERE NOT DepartmentID = 103
93
+ SELECT * FROM TestTable2 WHERE Location = 'Chicago'
94
+ SELECT Username FROM Users WHERE PrivateKey = 'test1234' AND Username <> 'test-user'
95
+ SELECT Username FROM Users WHERE PrivateKey = 'test1234' AND NOT (A = 6)
96
+ ```
97
+
98
+ ### IN
99
+
100
+ Filters rows where a column value matches any value in a specified list.
101
+
102
+ ```sql
103
+ SELECT * FROM Table1 WHERE Column1 IN (10, 20, 30)
104
+ ```
105
+
106
+ ### BETWEEN
107
+
108
+ Filters rows where a column value falls within a specified range, inclusive of the boundaries.
109
+
110
+ ```sql
111
+ SELECT * FROM Table1 WHERE Column1 BETWEEN 100 AND 200
112
+ ```
113
+
114
+ ### LIKE
115
+
116
+ Filters rows using pattern matching with wildcards. Supports escape characters for literal matching.
117
+
118
+ ```sql
119
+ SELECT * FROM Table1 WHERE column1 LIKE '%a'
120
+ SELECT * FROM Table1 WHERE column1 LIKE '%a%'
121
+ SELECT * FROM Table1 WHERE column1 LIKE '%bbpi!%ppo%' ESCAPE '!'
122
+ ```
123
+
124
+ ### Sorting Results
125
+
126
+ Orders the result set by specified columns.
127
+
128
+ ```sql
129
+ SELECT * FROM TestTable1 ORDER BY Salary ASC
130
+ SELECT * FROM TestTable1 ORDER BY Salary DESC
131
+ ```
132
+
133
+ ### Fully Qualified Identifiers in ORDER BY
134
+
135
+ > [!NOTE]
136
+ > This feature is available starting from FactoryTalk Optix version 1.7.x.
137
+
138
+ Supports fully qualified table.column references in the ORDER BY clause.
139
+
140
+ ```sql
141
+ SELECT * FROM MyTable ORDER BY MyTable.MyColumn
142
+
143
+ SELECT Recipes.Name, RecipeMetadata_MyRecipeSchema.MyMetadata1
144
+ FROM Recipes JOIN RecipeMetadata_MyRecipeSchema ON Recipes.Id = RecipeMetadata_MyRecipeSchema.RecipeId
145
+ WHERE RecipeMetadata_MyRecipeSchema.MyMetadata1 = 'BB'
146
+ ORDER BY Recipes.Name
147
+ ```
148
+
149
+ ### Pagination
150
+
151
+ Limits the number of rows returned and supports offset.
152
+
153
+ ```sql
154
+ SELECT * FROM TestTable1 LIMIT 5
155
+ SELECT * FROM TestTable1 LIMIT 5 OFFSET 5
156
+ ```
157
+
158
+ ### Complex Filtering with Multiple Conditions
159
+
160
+ Combines multiple conditions with AND and OR operators.
161
+
162
+ ```sql
163
+ SELECT * FROM TestTable1
164
+ WHERE (Salary > 60000 AND DepartmentID = 101) OR
165
+ (Salary < 50000 AND DepartmentID = 102)
166
+ ```
167
+
168
+ ### Combining * with Standard Column
169
+
170
+ Adds additional columns to the result set alongside all columns.
171
+
172
+ ```sql
173
+ SELECT *, Timestamp FROM TestTable4
174
+ ```
175
+
176
+ ### Asterisked Identifiers
177
+
178
+ > [!NOTE]
179
+ > This feature is available starting from FactoryTalk Optix version 1.7.x.
180
+
181
+ Supports table.* syntax to select all columns from a specific table in JOIN queries.
182
+
183
+ ```sql
184
+ SELECT A.*, B.* FROM Table1 AS A JOIN Table2 AS B on A.Id = B.Table1Id WHERE ...
185
+ ```
186
+
187
+ ### Combining * with Literal Values
188
+
189
+ Adds literal values as new columns alongside all existing columns.
190
+
191
+ ```sql
192
+ SELECT *, 'StaticValue' AS LiteralColumn FROM TestTable4
193
+ ```
194
+
195
+ ### Special Characters in Identifiers
196
+
197
+ Handles column names with special characters using double quotes.
198
+
199
+ ```sql
200
+ SELECT "Water level", ID FROM TestTable1
201
+ ```
202
+
203
+ ### CASE WHEN Expression
204
+
205
+ > [!NOTE]
206
+ > This feature is available starting from FactoryTalk Optix version 1.7.x.
207
+
208
+ The CASE WHEN expression allows conditional logic in SQL statements, supporting both SELECT and UPDATE operations.
209
+
210
+ ```sql
211
+ SELECT
212
+ CASE
213
+ WHEN Id = 1 THEN 'A'
214
+ WHEN Id = 2 THEN 'B'
215
+ END
216
+ FROM Table1
217
+ WHERE Id IN (1, 2, 3)
218
+ ```
219
+
220
+ ```sql
221
+ SELECT
222
+ CASE
223
+ WHEN Id = 1 THEN 'A'
224
+ WHEN Id = 2 THEN 'B'
225
+ ELSE 'X'
226
+ END
227
+ FROM Table1
228
+ ```
229
+
230
+ ```sql
231
+ UPDATE Table1 SET Value =
232
+ CASE
233
+ WHEN Id = 1 THEN 'A'
234
+ WHEN Id = 2 THEN 'B'
235
+ ELSE 'X'
236
+ END
237
+ ```
238
+
239
+ ```sql
240
+ UPDATE Table1 SET Value =
241
+ CASE
242
+ WHEN Id = 1 THEN 'A'
243
+ WHEN Id = 2 THEN 'B'
244
+ END
245
+ WHERE Id IN (1, 2, 3)
246
+ ```
247
+
248
+ ### IS NULL and IS NOT NULL
249
+
250
+ Filters rows based on NULL values.
251
+
252
+ ```sql
253
+ SELECT * FROM TestTable1 WHERE DepartmentID IS NULL
254
+ SELECT * FROM TestTable1 WHERE DepartmentID IS NOT NULL
255
+ ```
256
+
257
+ ### NOT
258
+
259
+ The NOT operator negates conditions and applies to all other operators, such as IN, BETWEEN, EXISTS, IS NULL, etc.
260
+
261
+ ```sql
262
+ SELECT * FROM Table1 WHERE column1 IS NOT NULL
263
+ SELECT * FROM Table1 WHERE column1 NOT IN (10, 20)
264
+ SELECT * FROM Table1 WHERE column1 NOT BETWEEN 100 AND 200
265
+ ```
266
+
267
+ ### Basic Aggregates
268
+
269
+ Performs basic aggregation operations on columns.
270
+
271
+ ```sql
272
+ SELECT COUNT(*) AS TotalCount FROM TestTable1
273
+ SELECT AVG(Salary) AS AverageSalary FROM TestTable1
274
+ SELECT SUM(Salary) AS TotalSalary FROM TestTable1
275
+ SELECT MAX(Salary) AS HighestSalary FROM TestTable1
276
+ SELECT MIN(Salary) AS LowestSalary FROM TestTable1
277
+ ```
278
+
279
+ ### Group Aggregates with Multiple Conditions
280
+
281
+ Groups rows by DepartmentID, counts employees and sums salaries for those with salary > 50000.
282
+
283
+ **Example Output:**
284
+
285
+ | DepartmentID | EmployeeCount | TotalSalary |
286
+ |--------------|---------------|-------------|
287
+ | 101 | 3 | 210000 |
288
+ | 102 | 2 | 105000 |
289
+
290
+ ```sql
291
+ SELECT DepartmentID, COUNT(*) AS EmployeeCount, SUM(Salary) AS TotalSalary
292
+ FROM TestTable1
293
+ WHERE Salary > 50000
294
+ GROUP BY DepartmentID
295
+ SELECT DepartmentID, AVG(Salary) AS AvgSalary
296
+ FROM TestTable1
297
+ GROUP BY DepartmentID
298
+ ```
299
+
300
+ ### Filter Aggregates with HAVING
301
+
302
+ Groups by department and calculates average salary, then filters groups where average salary > 65000.
303
+
304
+ **Example Output:**
305
+
306
+ | DepartmentID | AvgSalary |
307
+ |--------------|-----------|
308
+ | 101 | 67000 |
309
+ | 103 | 79000 |
310
+
311
+ ```sql
312
+ SELECT DepartmentID, AVG(Salary) AS AvgSalary
313
+ FROM TestTable1
314
+ GROUP BY DepartmentID
315
+ HAVING AVG(Salary) > 65000
316
+ ```
317
+
318
+ ### Combining * with Aggregate Functions
319
+
320
+ Adds a total row count column to all columns from TestTable4 using window function.
321
+
322
+ **Example Output:**
323
+
324
+ | ID | Timestamp | EventName | Duration | TotalCount |
325
+ |-----|---------------------|----------------|----------|------------|
326
+ | 1 | 1/1/2025 12:00 PM | Maintenance | 2.5 | 13 |
327
+ | 2 | 1/1/2025 12:15 PM | Adjustment | 1.5 | 13 |
328
+
329
+ ```sql
330
+ SELECT *, COUNT(*) OVER () AS TotalCount FROM TestTable4
331
+ ```
332
+
333
+ ### INNER JOIN
334
+
335
+ Joins TestTable1 and TestTable2 on DepartmentID, returning employees with their department names.
336
+
337
+ **Input Tables:**
338
+
339
+ TestTable1 (Employees):
340
+
341
+ | ID | Name | DepartmentID |
342
+ |----|-------|--------------|
343
+ | 1 | Alice | 101 |
344
+ | 2 | Bob | 102 |
345
+
346
+ TestTable2 (Departments):
347
+
348
+ | DepartmentID | DepartmentName |
349
+ |--------------|----------------|
350
+ | 101 | Engineering |
351
+ | 102 | Sales |
352
+
353
+ **Output:**
354
+
355
+ | Name | DepartmentName |
356
+ |-------|----------------|
357
+ | Alice | Engineering |
358
+ | Bob | Sales |
359
+
360
+ ```sql
361
+ SELECT t1.Name, t2.DepartmentName
362
+ FROM TestTable1 AS t1
363
+ INNER JOIN TestTable2 AS t2
364
+ ON t1.DepartmentID = t2.DepartmentID
365
+ ```
366
+
367
+ ### INNER JOIN Without Table Name Aliases
368
+
369
+ Joins tables without using aliases.
370
+
371
+ ```sql
372
+ SELECT TestTable1.Name, TestTable2.DepartmentName
373
+ FROM TestTable1
374
+ INNER JOIN TestTable2
375
+ ON TestTable1.DepartmentID = TestTable2.DepartmentID
376
+ ```
377
+
378
+ ### Aggregates with INNER JOIN
379
+
380
+ Joins employees and departments, then groups by department name to count employees and average salary.
381
+
382
+ **Example Output:**
383
+
384
+ | DepartmentName | EmployeeCount | AvgSalary |
385
+ |----------------|---------------|-----------|
386
+ | Engineering | 3 | 67000 |
387
+ | HR | 2 | 79000 |
388
+ | IT | 2 | 64000 |
389
+
390
+ ```sql
391
+ SELECT t2.DepartmentName, COUNT(t1.ID) AS EmployeeCount, AVG(t1.Salary) AS AvgSalary
392
+ FROM TestTable1 AS t1
393
+ INNER JOIN TestTable2 AS t2
394
+ ON t1.DepartmentID = t2.DepartmentID
395
+ GROUP BY t2.DepartmentName
396
+ ```
397
+
398
+ ### LEFT JOIN
399
+
400
+ Includes all rows from left table and matching from right.
401
+
402
+ ```sql
403
+ SELECT t1.Name, t2.Location FROM TestTable1 AS t1 LEFT JOIN TestTable2 AS t2 ON t1.DepartmentID = t2.DepartmentID
404
+ ```
405
+
406
+ ### LEFT JOIN with Filtering
407
+
408
+ Performs LEFT JOIN between employees and departments, then filters out rows where department name is NULL.
409
+
410
+ **Example Output:**
411
+
412
+ | Name | DepartmentName |
413
+ |---------|----------------|
414
+ | Alice | Engineering |
415
+ | Bob | Sales |
416
+
417
+ ```sql
418
+ SELECT t1.Name, t2.DepartmentName
419
+ FROM TestTable1 AS t1
420
+ LEFT JOIN TestTable2 AS t2
421
+ ON t1.DepartmentID = t2.DepartmentID
422
+ WHERE t2.DepartmentName IS NOT NULL
423
+ ```
424
+
425
+ ### Aggregates with LEFT JOIN
426
+
427
+ LEFT JOIN employees and departments, group by department name, count employees and average salary. Includes departments with no employees (NULL counts).
428
+
429
+ **Explanation:** A LEFT JOIN returns all rows from the left table (TestTable1), along with matching rows from the right table (TestTable2). When no match is found, NULL values are included for columns from the right table.
430
+
431
+ ```sql
432
+ SELECT t2.DepartmentName,
433
+ COUNT(t1.ID) AS EmployeeCount,
434
+ AVG(t1.Salary) AS AvgSalary
435
+ FROM TestTable1 AS t1
436
+ LEFT JOIN TestTable2 AS t2
437
+ ON t1.DepartmentID = t2.DepartmentID
438
+ GROUP BY t2.DepartmentName
439
+ ```
440
+
441
+ ### CROSS JOIN
442
+
443
+ Creates Cartesian product of employees and departments, pairing each employee with every department.
444
+
445
+ **Example Output (Partial):**
446
+
447
+ | Name | DepartmentName |
448
+ |-------|----------------|
449
+ | Alice | Engineering |
450
+ | Alice | Sales |
451
+ | Bob | Engineering |
452
+ | Bob | Sales |
453
+
454
+ **Explanation:** A CROSS JOIN produces a Cartesian product, where each row from the first table is paired with every row from the second table. This can result in a large dataset.
455
+
456
+ ```sql
457
+ SELECT t1.Name, t2.DepartmentName
458
+ FROM TestTable1 AS t1
459
+ CROSS JOIN TestTable2 AS t2
460
+ ```
461
+
462
+ ### Subquery in FROM Clause
463
+
464
+ Uses a subquery to calculate average salaries per department, then filters departments with avg salary > 65000.
465
+
466
+ **Example Output:**
467
+
468
+ | DepartmentID | AvgSalary |
469
+ |--------------|-----------|
470
+ | 101 | 67000 |
471
+ | 103 | 79000 |
472
+
473
+ **Explanation:** The subquery calculates the average salary grouped by department, and the outer query filters the results to include only departments where the average salary exceeds 65,000.
474
+
475
+ ```sql
476
+ SELECT *
477
+ FROM (
478
+ SELECT DepartmentID, AVG(Salary) AS AvgSalary
479
+ FROM TestTable1
480
+ GROUP BY DepartmentID
481
+ ) AS SubQuery
482
+ WHERE AvgSalary > 65000
483
+ ```
484
+
485
+ ### Subquery with JOIN
486
+
487
+ Joins employees with a subquery that calculates average salary per department.
488
+
489
+ **Example Output:**
490
+
491
+ | Name | AvgSalary |
492
+ |-------|-----------|
493
+ | Alice | 67000 |
494
+ | Bob | 55500 |
495
+
496
+ **Explanation:** The subquery calculates the average salary for each department. The main query then joins this result with TestTable1 to associate employee names with their respective department's average salary.
497
+
498
+ ```sql
499
+ SELECT t1.Name, SubQuery.AvgSalary
500
+ FROM TestTable1 AS t1
501
+ INNER JOIN (
502
+ SELECT DepartmentID, AVG(Salary) AS AvgSalary
503
+ FROM TestTable1
504
+ GROUP BY DepartmentID
505
+ ) AS SubQuery
506
+ ON t1.DepartmentID = SubQuery.DepartmentID
507
+ ```
508
+
509
+ ### Subquery in WHERE Clause for IN Condition
510
+
511
+ Filters employees whose salary is in the list of salaries from department 101.
512
+
513
+ **Example Output:**
514
+
515
+ | Name |
516
+ |----------|
517
+ | Alice |
518
+ | Charlie |
519
+ | Grace |
520
+
521
+ **Description:** Subqueries in the WHERE clause with the IN operator are supported. This query correctly filtered names where Salary matches the subquery result.
522
+
523
+ ```sql
524
+ SELECT Name FROM TestTable1 WHERE Salary IN (SELECT Salary FROM TestTable1 WHERE DepartmentID = 101)
525
+ ```
526
+
527
+ ### Subquery in WHERE Clause with Comparison
528
+
529
+ > [!NOTE]
530
+ > This feature is available starting from FactoryTalk Optix version 1.7.x.
531
+
532
+ Supports subqueries in WHERE clause using comparison operators.
533
+
534
+ ```sql
535
+ SELECT * FROM Table1 WHERE Column > (SELECT Value FROM Table2)
536
+ ```
537
+
538
+ ### EXISTS Operator
539
+
540
+ #### Simple EXISTS Subquery
541
+
542
+ Finds employees in departments located in 'New York' using EXISTS subquery.
543
+
544
+ **Example Output:**
545
+
546
+ | Name |
547
+ |----------|
548
+ | Alice |
549
+ | Charlie |
550
+ | Grace |
551
+
552
+ **Description:** Queries with EXISTS are supported, correctly identifying employees in departments located in "New York."
553
+
554
+ ```sql
555
+ SELECT Name FROM TestTable1 WHERE EXISTS (SELECT 1 FROM TestTable2 WHERE TestTable2.DepartmentID = TestTable1.DepartmentID AND TestTable2.Location = 'New York')
556
+ ```
557
+
558
+ #### Only extracting the recipes with the highest version for each recipe name:
559
+
560
+ When using the new [RecipeX](./recipex.md) module, multiple recipes with same name and different versions can be created. This query will extract only the recipes with the highest version for each recipe name, by using a subquery in the WHERE clause to filter out recipes that have a higher version available.
561
+
562
+ ```sql
563
+ SELECT
564
+ R.*,
565
+ M.*
566
+ FROM Recipes AS R
567
+ JOIN RecipeMetadata_RecipeSchema1 AS M
568
+ ON R.Id = M.RecipeId
569
+ WHERE R.RecipeSchemaName = {#RecipeSchemaName:sql_literal}
570
+ AND NOT EXISTS (
571
+ SELECT 1
572
+ FROM Recipes AS R2
573
+ WHERE R2.RecipeSchemaName = R.RecipeSchemaName
574
+ AND R2.Name = R.Name
575
+ AND R2.Version > R.Version
576
+ )
577
+ ORDER BY R.Name
578
+ ```
579
+
580
+ **NOTE:** This query should be placed in a `string formatter` and the parameter `RecipeSchemaName` should be passed with a DynamicLink to retrieve the list of recipes for a given recipe schema name.
581
+
582
+ ### Subquery in FROM Clause (Aggregation)
583
+
584
+ Uses subquery for aggregation in FROM.
585
+
586
+ ```sql
587
+ SELECT AVG(Salary) AS AvgSalary FROM (SELECT * FROM TestTable1 WHERE DepartmentID = 101) AS SubQuery
588
+ ```
589
+
590
+ ### Multiple Subqueries with IN
591
+
592
+ Combines multiple IN subqueries.
593
+
594
+ ```sql
595
+ SELECT Name FROM TestTable1 WHERE DepartmentID IN (SELECT DepartmentID FROM TestTable2 WHERE Location = 'New York') AND Salary IN (SELECT Salary FROM TestTable1 WHERE DepartmentID = 101)
596
+ ```
597
+
598
+ ### Multiple Subqueries with EXISTS
599
+
600
+ Combines multiple EXISTS subqueries.
601
+
602
+ ```sql
603
+ SELECT Name FROM TestTable1 WHERE EXISTS (SELECT 1 FROM TestTable2 WHERE TestTable1.DepartmentID = TestTable2.DepartmentID AND TestTable2.Location = 'Chicago') AND EXISTS (SELECT 1 FROM TestTable1 AS Sub WHERE Sub.Salary > 60000 AND Sub.DepartmentID = 101)
604
+ ```
605
+
606
+ ### Mixed IN and EXISTS with Correlation
607
+
608
+ Mixes IN and EXISTS with correlation.
609
+
610
+ ```sql
611
+ SELECT Name FROM TestTable1 WHERE DepartmentID IN (SELECT DepartmentID FROM TestTable2 WHERE Location = 'Chicago') AND EXISTS (SELECT 1 FROM TestTable2 WHERE TestTable2.DepartmentID = TestTable1.DepartmentID AND TestTable2.Location = 'New York')
612
+ ```
613
+
614
+ ### Subquery with Nested IN Clauses
615
+
616
+ Nested IN conditions in subquery.
617
+
618
+ ```sql
619
+ SELECT Name FROM TestTable1 WHERE DepartmentID IN (SELECT DepartmentID FROM TestTable2 WHERE Location IN (SELECT Location FROM TestTable2 WHERE DepartmentID = 101))
620
+ ```
621
+
622
+ ### Subquery with Nested EXISTS Clauses
623
+
624
+ Nested EXISTS conditions.
625
+
626
+ ```sql
627
+ SELECT Name FROM TestTable1 WHERE EXISTS (SELECT 1 FROM TestTable2 WHERE TestTable1.DepartmentID = TestTable2.DepartmentID AND EXISTS (SELECT 1 FROM TestTable1 AS Sub WHERE Sub.Salary > 70000))
628
+ ```
629
+
630
+ ### Deeper Nested Subquery with IN
631
+
632
+ Deeper nesting with IN.
633
+
634
+ ```sql
635
+ SELECT Name FROM TestTable1 WHERE DepartmentID IN (SELECT DepartmentID FROM TestTable2 WHERE Location IN (SELECT Location FROM TestTable2 WHERE Location IN (SELECT Location FROM TestTable2 WHERE DepartmentID = 101)))
636
+ ```
637
+
638
+ ### Deeper Nested Subquery with EXISTS
639
+
640
+ Deeper nesting with EXISTS.
641
+
642
+ ```sql
643
+ SELECT Name FROM TestTable1 WHERE EXISTS (SELECT 1 FROM TestTable2 WHERE TestTable1.DepartmentID = TestTable2.DepartmentID AND EXISTS (SELECT 1 FROM TestTable1 WHERE EXISTS (SELECT 1 FROM TestTable2 WHERE TestTable2.DepartmentID = TestTable1.DepartmentID)))
644
+ ```
645
+
646
+ ### Subquery with Nested IN Clauses and Conditions
647
+
648
+ Complex nested conditions.
649
+
650
+ ```sql
651
+ SELECT Name FROM TestTable1 WHERE DepartmentID IN (SELECT DepartmentID FROM TestTable2 WHERE Location IN (SELECT Location FROM TestTable2 WHERE EXISTS (SELECT 1 FROM TestTable1 WHERE Salary > 70000)))
652
+ ```
653
+
654
+ ### Subquery in JOIN Condition with Explicit Alias
655
+
656
+ Subquery in JOIN ON clause.
657
+
658
+ ```sql
659
+ SELECT t1.Name, t2.DepartmentName FROM TestTable1 AS t1 INNER JOIN (SELECT * FROM TestTable2 WHERE Location = 'Chicago') AS t2 ON t1.DepartmentID = t2.DepartmentID
660
+ ```
661
+
662
+ ### Validate Joins with Nested Subqueries and Alias
663
+
664
+ Nested subqueries in JOIN.
665
+
666
+ ```sql
667
+ SELECT t1.Name FROM TestTable1 AS t1 INNER JOIN (SELECT DepartmentID FROM TestTable2 WHERE EXISTS (SELECT 1 FROM TestTable1 WHERE TestTable1.DepartmentID = TestTable2.DepartmentID)) AS t2 ON t1.DepartmentID = t2.DepartmentID
668
+ ```
669
+
670
+ ### Combined Subqueries and Joins with Explicit Alias
671
+
672
+ Combines subqueries and joins.
673
+
674
+ ```sql
675
+ SELECT t1.Name, t2.DepartmentName FROM TestTable1 AS t1 LEFT JOIN (SELECT DepartmentID, DepartmentName FROM TestTable2 WHERE EXISTS (SELECT 1 FROM TestTable1 WHERE Salary > 60000)) AS t2 ON t1.DepartmentID = t2.DepartmentID
676
+ ```
677
+
678
+ ### Subqueries in FROM Clause (Filtered)
679
+
680
+ Filtered subquery in FROM.
681
+
682
+ ```sql
683
+ SELECT * FROM (SELECT * FROM TestTable4 WHERE Duration > 2) AS SubQuery
684
+ ```
685
+
686
+ ### ROW_NUMBER Window Function
687
+
688
+ Assigns unique sequential numbers to rows within each department partition, ordered by salary descending.
689
+
690
+ **Example Output:**
691
+
692
+ | Name | DepartmentID | Rank |
693
+ |---------|--------------|------|
694
+ | Grace | 101 | 1 |
695
+ | Charlie | 101 | 2 |
696
+ | Alice | 101 | 3 |
697
+
698
+ **Explanation:** The ROW_NUMBER function assigns a unique rank to each row within a partition (grouped by DepartmentID), ordered by Salary in descending order.
699
+
700
+ ```sql
701
+ SELECT Name, DepartmentID,
702
+ ROW_NUMBER() OVER (PARTITION BY DepartmentID ORDER BY Salary DESC) AS Rank
703
+ FROM TestTable1
704
+ ```
705
+
706
+ ### RANK Window Function
707
+
708
+ Assigns ranks within department partitions, handling ties by giving same rank to equal salaries.
709
+
710
+ **Explanation:** The RANK function assigns a rank to each row within a partition, ordered by Salary in descending order. Tied rows receive the same rank, with subsequent ranks skipping as needed.
711
+
712
+ ```sql
713
+ SELECT Name, DepartmentID,
714
+ RANK() OVER (PARTITION BY DepartmentID ORDER BY Salary DESC) AS Rank
715
+ FROM TestTable1
716
+ ```
717
+
718
+ ### SUM with PARTITION BY
719
+
720
+ Calculates total salary for each department and adds it to every row in that department.
721
+
722
+ **Example Output:**
723
+
724
+ | Name | DepartmentID | TotalDeptSalary |
725
+ |---------|--------------|-----------------|
726
+ | Alice | 101 | 201000 |
727
+ | Charlie | 101 | 201000 |
728
+
729
+ **Explanation:** The SUM function calculates the total Salary for all employees within each DepartmentID. This value is applied to every row in the respective partition.
730
+
731
+ ```sql
732
+ SELECT Name, DepartmentID,
733
+ SUM(Salary) OVER (PARTITION BY DepartmentID) AS TotalDeptSalary
734
+ FROM TestTable1
735
+ ```
736
+
737
+ ### AVG with PARTITION BY
738
+
739
+ Calculates averages within partitions.
740
+
741
+ ```sql
742
+ SELECT Name, DepartmentID,
743
+ AVG(Salary) OVER (PARTITION BY DepartmentID) AS AvgDeptSalary
744
+ FROM TestTable1
745
+ ```
746
+
747
+ ### Global Ranking Using ROW_NUMBER
748
+
749
+ Ranks all employees globally by salary in descending order.
750
+
751
+ **Example Output:**
752
+
753
+ | Name | Salary | DepartmentID | GlobalRank |
754
+ |-------|--------|--------------|------------|
755
+ | Diana | 80000 | 103 | 1 |
756
+ | Ivy | 78000 | 103 | 2 |
757
+ | Grace | 71000 | 101 | 3 |
758
+
759
+ **Explanation:** The ROW_NUMBER function assigns a unique global rank to each employee, ordered by salary in descending order. No partitioning is applied, so the ranking is across all rows.
760
+
761
+ ```sql
762
+ SELECT Name, Salary, DepartmentID,
763
+ ROW_NUMBER() OVER (ORDER BY Salary DESC) AS GlobalRank
764
+ FROM TestTable1
765
+ ```
766
+
767
+ ### Ranking Within Department: Lowest to Highest Salary
768
+
769
+ Ranks within department by ascending salary.
770
+
771
+ ```sql
772
+ SELECT Name, DepartmentID,
773
+ ROW_NUMBER() OVER (PARTITION BY DepartmentID ORDER BY Salary ASC) AS RankLowToHigh
774
+ FROM TestTable1
775
+ ```
776
+
777
+ ### ROW_NUMBER with Multiple Columns in ORDER BY
778
+
779
+ Ranks with tie-breaking by multiple columns.
780
+
781
+ ```sql
782
+ SELECT Name, DepartmentID,
783
+ ROW_NUMBER() OVER (PARTITION BY DepartmentID ORDER BY Salary DESC, Name ASC) AS Rank
784
+ FROM TestTable1
785
+ ```
786
+
787
+ ### Combining * with a Window Function
788
+
789
+ Adds window function column to all columns.
790
+
791
+ ```sql
792
+ SELECT *, ROW_NUMBER() OVER (ORDER BY Timestamp) AS RowNum FROM TestTable4
793
+ ```
794
+
795
+ ### Extract Year, Month, Day, Hour, Minute, and Second
796
+
797
+ Extracts all datetime components from Timestamp column.
798
+
799
+ **Example Output (Partial):**
800
+
801
+ | ID | Year | Month | Day | Hour | Minute | Second |
802
+ |-----|------|-------|-----|------|--------|--------|
803
+ | 1 | 2025 | 1 | 1 | 12 | 0 | 0 |
804
+ | 12 | 2025 | 2 | 28 | 23 | 59 | 59 |
805
+ | 13 | 2024 | 2 | 29 | 12 | 0 | 0 |
806
+
807
+ **Explanation:** The query uses the EXTRACT operator multiple times within the same SELECT statement to retrieve and display year, month, day, hour, minute, and second for each record in TestTable4.
808
+
809
+ ```sql
810
+ SELECT ID,
811
+ EXTRACT(YEAR FROM Timestamp) AS Year,
812
+ EXTRACT(MONTH FROM Timestamp) AS Month,
813
+ EXTRACT(DAY FROM Timestamp) AS Day,
814
+ EXTRACT(HOUR FROM Timestamp) AS Hour,
815
+ EXTRACT(MINUTE FROM Timestamp) AS Minute,
816
+ EXTRACT(SECOND FROM Timestamp) AS Second
817
+ FROM TestTable4
818
+ ```
819
+
820
+ ### Average Duration for Each Hour
821
+
822
+ Groups events by hour and calculates average duration for each hour.
823
+
824
+ **Example Output:**
825
+
826
+ | Hour | AvgDuration |
827
+ |------|-------------|
828
+ | 00 | 2.8 |
829
+ | 08 | 3.75 |
830
+ | 12 | 2.5625 |
831
+ | 23 | 2.93 |
832
+
833
+ **Explanation:** The AVG function calculates the average Duration for each hour extracted from the Timestamp. Grouping by Hour ensures the calculation is performed for each unique hour.
834
+
835
+ ```sql
836
+ SELECT EXTRACT(HOUR FROM Timestamp) AS Hour,
837
+ AVG(Duration) AS AvgDuration
838
+ FROM TestTable4
839
+ GROUP BY Hour
840
+ ORDER BY Hour
841
+ ```
842
+
843
+ ### Maximum Duration for Each Day
844
+
845
+ Groups events by day and finds maximum duration for each day.
846
+
847
+ **Example Output:**
848
+
849
+ | Day | MaxDuration |
850
+ |-----|-------------|
851
+ | 01 | 2.5 |
852
+ | 02 | 4.0 |
853
+ | 31 | 3.0 |
854
+
855
+ **Explanation:** The MAX function identifies the maximum Duration for each day extracted from the Timestamp. Grouping by Day ensures the calculation is performed for each unique day.
856
+
857
+ ```sql
858
+ SELECT EXTRACT(DAY FROM Timestamp) AS Day,
859
+ MAX(Duration) AS MaxDuration
860
+ FROM TestTable4
861
+ GROUP BY Day
862
+ ORDER BY Day
863
+ ```
864
+
865
+ ### Event Count by Month
866
+
867
+ Groups events by month and counts total events per month.
868
+
869
+ **Example Output:**
870
+
871
+ | Month | EventCount |
872
+ |-------|------------|
873
+ | 01 | 11 |
874
+ | 02 | 2 |
875
+
876
+ **Explanation:** The COUNT(*) function calculates the total number of events for each month extracted from the Timestamp. Grouping by Month ensures events are counted for each unique month.
877
+
878
+ ```sql
879
+ SELECT EXTRACT(MONTH FROM Timestamp) AS Month,
880
+ COUNT(*) AS EventCount
881
+ FROM TestTable4
882
+ GROUP BY Month
883
+ ORDER BY Month
884
+ ```
885
+
886
+ ### Grouping and Summing by Hourly Periods
887
+
888
+ Groups by year, month, day, hour and sums duration.
889
+
890
+ ```sql
891
+ SELECT
892
+ EXTRACT(YEAR FROM Timestamp) AS Year,
893
+ EXTRACT(MONTH FROM Timestamp) AS Month,
894
+ EXTRACT(DAY FROM Timestamp) AS Day,
895
+ EXTRACT(HOUR FROM Timestamp) AS Hour,
896
+ SUM(Duration) AS TotalDuration
897
+ FROM TestTable4
898
+ GROUP BY Year, Month, Day, Hour
899
+ ORDER BY Year, Month, Day, Hour
900
+ ```
901
+
902
+ ### Grouping by Hour and Summing Duration
903
+
904
+ Groups by hour and sums duration.
905
+
906
+ ```sql
907
+ SELECT EXTRACT(HOUR FROM Timestamp) AS Hour,
908
+ SUM(Duration) AS TotalDuration
909
+ FROM TestTable4
910
+ GROUP BY Hour
911
+ ```
912
+
913
+ ### Grouping by Full Hour Periods
914
+
915
+ Groups by full hour periods using subquery.
916
+
917
+ ```sql
918
+ SELECT Year, Month, Day, Hour,
919
+ SUM(Duration) AS TotalDuration
920
+ FROM (
921
+ SELECT
922
+ EXTRACT(YEAR FROM Timestamp) AS Year,
923
+ EXTRACT(MONTH FROM Timestamp) AS Month,
924
+ EXTRACT(DAY FROM Timestamp) AS Day,
925
+ EXTRACT(HOUR FROM Timestamp) AS Hour,
926
+ Duration
927
+ FROM TestTable4
928
+ ) AS SubQuery
929
+ GROUP BY Year, Month, Day, Hour
930
+ ORDER BY Year, Month, Day, Hour
931
+ ```
932
+
933
+ ### Verifying Grouping and Ordering by Extracted Datetime Data in a Subquery with Non-Selected Columns
934
+
935
+ Groups by extracted data with additional columns.
936
+
937
+ ```sql
938
+ SELECT EventName,
939
+ SUM(Duration) AS TotalDuration
940
+ FROM (
941
+ SELECT
942
+ EXTRACT(YEAR FROM Timestamp) AS Year,
943
+ EXTRACT(MONTH FROM Timestamp) AS Month,
944
+ EXTRACT(DAY FROM Timestamp) AS Day,
945
+ EXTRACT(HOUR FROM Timestamp) AS Hour,
946
+ Duration,
947
+ EventName
948
+ FROM TestTable4
949
+ ) AS SubQuery
950
+ GROUP BY Year, Month, Day, Hour
951
+ ORDER BY Year, Month, Day, Hour
952
+ ```
953
+
954
+ ### Grouping with Subquery and Extracted Values
955
+
956
+ Groups using subquery with extracted values.
957
+
958
+ ```sql
959
+ SELECT Hour,
960
+ SUM(Duration) AS TotalDuration
961
+ FROM (
962
+ SELECT EXTRACT(HOUR FROM Timestamp) AS Hour,
963
+ Duration
964
+ FROM TestTable4
965
+ ) AS SubQuery
966
+ GROUP BY Hour
967
+ ORDER BY Hour
968
+ ```
969
+
970
+ ### Filtering by EXTRACT in WHERE Clause (Year)
971
+
972
+ Filters by year using string comparison.
973
+
974
+ ```sql
975
+ SELECT EXTRACT(HOUR FROM Timestamp) AS Hour, SUM(Duration) AS TotalDuration
976
+ FROM TestTable4
977
+ WHERE EXTRACT(YEAR FROM Timestamp) = '2025'
978
+ GROUP BY Hour
979
+ ORDER BY Hour
980
+ ```
981
+
982
+ ### Filtering by EXTRACT in WHERE Clause (Month)
983
+
984
+ Filters by month using string comparison.
985
+
986
+ ```sql
987
+ SELECT EXTRACT(DAY FROM Timestamp) AS Day, AVG(Duration) AS AvgDuration
988
+ FROM TestTable4
989
+ WHERE EXTRACT(MONTH FROM Timestamp) = '01'
990
+ GROUP BY Day
991
+ ORDER BY Day
992
+ ```
993
+
994
+ ## UPDATE Queries
995
+
996
+ ### Basic UPDATE
997
+
998
+ Simple single-row update (set Salary for a single Id):
999
+
1000
+ ```sql
1001
+ UPDATE UpdateTest SET Salary = 56000 WHERE Id = 2
1002
+ ```
1003
+
1004
+ Update rows by matching text column (rename a person):
1005
+
1006
+ ```sql
1007
+ UPDATE UpdateTest SET Name = 'Eve-Updated' WHERE Name = 'Eve'
1008
+ ```
1009
+
1010
+ Update multiple rows using IN:
1011
+
1012
+ ```sql
1013
+ UPDATE UpdateTest SET Salary = Salary + 1000 WHERE Id IN (1, 3)
1014
+ ```
1015
+
1016
+ ### Grouped UPDATE queries
1017
+
1018
+ These are used as a CASE/branching workaround when CASE expressions inside UPDATE may not be accepted by the store (FactoryTalk Optix versions prior to 1.7.x):
1019
+
1020
+ ```sql
1021
+ -- set High for very large salaries
1022
+ UPDATE UpdateTest SET Status = 'High' WHERE Salary >= 80000
1023
+
1024
+ -- set Medium for mid-range salaries
1025
+ UPDATE UpdateTest SET Status = 'Medium' WHERE Salary >= 65000 AND Salary < 80000
1026
+
1027
+ -- set Low for lower salaries
1028
+ UPDATE UpdateTest SET Status = 'Low' WHERE Salary < 65000
1029
+ ```
1030
+
1031
+ ### Update with CASE WHEN:
1032
+
1033
+ > [!NOTE]
1034
+ > This feature is available starting from FactoryTalk Optix version 1.7.x.
1035
+
1036
+ ```sql
1037
+ UPDATE UpdateTest
1038
+ SET Status = CASE
1039
+ WHEN Salary >= 80000 THEN 'High'
1040
+ WHEN Salary >= 65000 THEN 'Medium'
1041
+ ELSE 'Low'
1042
+ END
1043
+ ```
1044
+
1045
+ If that raises a syntax error on your target (FactoryTalk Optix versions prior to 1.7.x), use the grouped-update workaround demonstrated earlier.
1046
+
1047
+ ### UPDATE with schema-qualified table names
1048
+
1049
+ > [!NOTE]
1050
+ > This feature is available starting from FactoryTalk Optix version 1.7.x.
1051
+
1052
+ Updates values in a table, supporting schema-qualified table names.
1053
+
1054
+ ```sql
1055
+ UPDATE schema1.table1 SET column1 = 3, ...
1056
+ ```
1057
+
1058
+ ### Notes on unsupported or dialect-specific UPDATE forms
1059
+
1060
+ The following UPDATE forms were tested against the embedded database. Results are summarized and example workarounds are provided.
1061
+
1062
+ #### Subquery in SET (UPDATE ... SET col = (SELECT ...))
1063
+
1064
+ Attempting to set a column using a scalar subquery (for example, mapping values from another table in a single UPDATE statement) is currently not supported.
1065
+
1066
+ Example of unsupported query (will fail):
1067
+
1068
+ ```sql
1069
+ UPDATE UpdateTest
1070
+ SET Status = (SELECT NewStatus FROM StatusMap WHERE StatusMap.OldStatus = UpdateTest.Status)
1071
+ WHERE Status IN (SELECT OldStatus FROM StatusMap)
1072
+ ```
1073
+
1074
+ Workaround: perform one simple UPDATE per mapping row (this is compatible and fast for small mapping tables):
1075
+
1076
+ ```sql
1077
+ -- mapping table has rows (OldStatus, NewStatus) = ('Active','Enabled'), ('Inactive','Disabled')
1078
+ UPDATE UpdateTest SET Status = 'Enabled' WHERE Status = 'Active'
1079
+ UPDATE UpdateTest SET Status = 'Disabled' WHERE Status = 'Inactive'
1080
+ ```
1081
+
1082
+ #### UPDATE ... FROM / join-style UPDATE
1083
+
1084
+ Some SQL dialects support `UPDATE ... FROM <join>` to update rows using a join with another table. These queries are not supported and will produce a syntax error.
1085
+
1086
+ Example of unsupported query (will fail):
1087
+
1088
+ ```sql
1089
+ UPDATE UpdateTest SET Status = StatusMap.NewStatus FROM StatusMap WHERE UpdateTest.Status = StatusMap.OldStatus
1090
+ ```
1091
+
1092
+ Workaround: use per-mapping UPDATE statements as shown above, or run a small client-side loop that queries mapping rows and issues single UPDATE statements for each mapping.
1093
+
1094
+ #### Correlated EXISTS in UPDATE WHERE
1095
+
1096
+ Running `UPDATE ... WHERE EXISTS (SELECT 1 FROM Other WHERE Other.key = Target.key ...)` is currently not supported. If you rely on correlated `EXISTS` for updates, translate to an explicit set of `WHERE` conditions or use per-row updates retrieved by a separate `SELECT`.
1097
+
1098
+ FactoryTalk Optix reliably accepts straightforward `UPDATE ... SET ... WHERE <condition>` forms. More advanced forms that embed subqueries inside the `SET` expression, use correlated `EXISTS` in `WHERE`, or use `UPDATE ... FROM` may fail depending on FactoryTalk Optix version. When unsupported, the recommended approach is to break the update into multiple simple `UPDATE` statements or perform the logic in the client and apply targeted `UPDATE`s.
1099
+
1100
+ ## DELETE Queries
1101
+
1102
+ ### Basic DELETE
1103
+
1104
+ Deletes rows from a table based on a condition.
1105
+
1106
+ ```sql
1107
+ DELETE FROM DeleteTestDemo WHERE id = 1
1108
+ ```
1109
+
1110
+ ### Delete multiple rows using IN
1111
+
1112
+ ```sql
1113
+ DELETE FROM DeleteTestDemo WHERE id IN (2,3,4)
1114
+ ```
1115
+
1116
+ ### Delete using a subquery
1117
+
1118
+ ```sql
1119
+ DELETE FROM DeleteTestDemo WHERE id IN (SELECT id FROM OtherTable WHERE Flag = 1)
1120
+ ```
1121
+
1122
+ ### Delete all rows (use with caution)
1123
+
1124
+ ```sql
1125
+ DELETE FROM DeleteTestDemo
1126
+ ```
1127
+
1128
+ ### DELETE using a subquery (non-correlated example)
1129
+
1130
+ ```sql
1131
+ DELETE FROM DeleteTestDemo WHERE GroupID IN (SELECT GroupID FROM OtherTable WHERE Flag = 1)
1132
+ ```
1133
+
1134
+ ### Pattern and range deletes
1135
+
1136
+ ```sql
1137
+ DELETE FROM DeleteTestExtra2 WHERE Val LIKE 'a%'
1138
+ DELETE FROM DeleteTestExtra2 WHERE Salary BETWEEN 50000 AND 53000
1139
+ ```
1140
+
1141
+ ### Notes on unsupported or dialect-specific DELETE forms
1142
+
1143
+ During testing the server did not accept several DELETE variants:
1144
+
1145
+ - Correlated EXISTS deletes in the form `DELETE ... WHERE EXISTS (SELECT ... WHERE o.col = target.col ...)` are not supported.
1146
+ - Multi-table DELETE / DELETE with JOIN (MySQL/SQL Server style) are not supported.
1147
+ - CTE-based DELETE (`WITH ... DELETE ...`) are not supported.
1148
+ - `DELETE ... RETURNING` and explicit transaction control (BEGIN/ROLLBACK) are not supported.
1149
+
1150
+ If you depend on any of the unsupported forms, ask for help translating to an equivalent supported pattern (for example, using `IN` with a subquery instead of a correlated `EXISTS`).
1151
+
1152
+ ## INSERT Queries
1153
+
1154
+ `INSERT` queries are only allowed using the `Insert` method of the store. See the [Database interactions](./database-interaction.md) for details.
1155
+
1156
+ ## Temporary Tables
1157
+
1158
+ Temporary tables provide a way to store intermediate results during query process. Key characteristics include:
1159
+
1160
+ - Temporary tables, identified by the `##` prefix, can be created and accessed using double-quoted identifiers.
1161
+ - Supported operations include querying, joining, and aggregations however, update operations are not permitted.
1162
+ - Temporary tables can be dropped successfully, facilitating proper resource management.
1163
+
1164
+ ### Temporary Tables with Quoted Names
1165
+
1166
+ Creates temporary table with quoted name.
1167
+
1168
+ ```sql
1169
+ CREATE TEMPORARY TABLE "##TempTable" AS SELECT DepartmentID, AVG(Salary) AS AvgSalary FROM TestTable1 GROUP BY DepartmentID
1170
+ ```
1171
+
1172
+ ### Accessing Temporary Tables
1173
+
1174
+ Queries data from the temporary table created with department average salaries.
1175
+
1176
+ **Example Output:**
1177
+
1178
+ | DepartmentID | AvgSalary |
1179
+ |--------------|-----------|
1180
+ | 101 | 67000 |
1181
+ | 102 | 55500 |
1182
+ | 103 | 79000 |
1183
+ | 104 | 64000 |
1184
+ | 105 | 62000 |
1185
+
1186
+ ```sql
1187
+ SELECT * FROM "##TempTable"
1188
+ ```
1189
+
1190
+ ### Joining Temporary Tables
1191
+
1192
+ Joins employees table with temporary table containing department average salaries.
1193
+
1194
+ **Example Output:**
1195
+
1196
+ | Name | AvgSalary |
1197
+ |---------|-----------|
1198
+ | Alice | 67000 |
1199
+ | Bob | 55500 |
1200
+ | Charlie | 67000 |
1201
+ | Diana | 79000 |
1202
+ | Eve | 64000 |
1203
+
1204
+ ```sql
1205
+ SELECT t1.Name, t2.AvgSalary FROM TestTable1 AS t1 INNER JOIN "##TempTable" AS t2 ON t1.DepartmentID = t2.DepartmentID
1206
+ ```
1207
+
1208
+ ### Dropping Temporary Tables
1209
+
1210
+ Removes temporary table.
1211
+
1212
+ ```sql
1213
+ DROP TABLE "##TempTable"
1214
+ ```
1215
+
1216
+ ### Filtered Temporary Tables
1217
+
1218
+ Creates temporary table with filtered data.
1219
+
1220
+ ```sql
1221
+ CREATE TEMPORARY TABLE "##FilteredTemp" AS SELECT ID, Name, Salary FROM TestTable1 WHERE Salary > 60000
1222
+ ```
1223
+
1224
+ ### Accessing Filtered Temporary Tables
1225
+
1226
+ Queries the temporary table filtered to employees with salary > 60000.
1227
+
1228
+ **Example Output:**
1229
+
1230
+ | ID | Name | Salary |
1231
+ |----|---------|--------|
1232
+ | 3 | Charlie | 70000 |
1233
+ | 4 | Diana | 80000 |
1234
+ | 5 | Eve | 65000 |
1235
+ | 7 | Grace | 71000 |
1236
+
1237
+ ```sql
1238
+ SELECT * FROM "##FilteredTemp"
1239
+ ```
1240
+
1241
+ ### Aggregations on Temporary Tables
1242
+
1243
+ Counts high-salary employees from the filtered temporary table.
1244
+
1245
+ **Example Output:**
1246
+
1247
+ | HighSalaryCount |
1248
+ |-----------------|
1249
+ | 7 |
1250
+
1251
+ ```sql
1252
+ SELECT COUNT(*) AS HighSalaryCount FROM "##FilteredTemp"
1253
+ ```
1254
+
1255
+ ## Dynamic queries using variables
1256
+
1257
+ FactoryTalk Optix supports user-defined variables in SQL queries by using string formatting in your application code to construct the SQL query with the desired values before executing it against the database.
1258
+
1259
+ Variables has to be escaped or formatted using two dedicated formatters:
1260
+
1261
+ - `sql_literal` for string values, which adds single quotes, escapes internal quotes and formats dates appropriately.
1262
+ - `sql_identifier` for identifiers (like table or column names), which adds double quotes and escapes internal quotes.
1263
+
1264
+ For example:
1265
+
1266
+ ```sql
1267
+ SELECT * FROM {#TableName:sql_identifier} WHERE Name = {#NameValue:sql_literal}
1268
+ ```
1269
+
1270
+ ## Known Limitations and Workarounds
1271
+
1272
+ As FactoryTalk Optix does not support all SQL features, some queries may fail due to syntax errors or unsupported constructs. If you encounter issues, consider the following workarounds:
1273
+
1274
+ - Use a StringFormatter to build dynamic queries that adjust based on supported features.
1275
+ - `CONCAT` and the `||` operator are not supported, but you can concatenate a string with something like `SELECT {#Param1:sql_literal} AS NewColumnName FROM Table` and compose the query dynamically.
1276
+
1277
+ ## Queries on UI objects
1278
+
1279
+ ### Populate a PieChart with the count of unique values in a column
1280
+
1281
+ This query returns the count of unique values in the column `Code` of the table `SQLiteStoreTable1` to populate a pie chard. Each slice of the pie chart will represent a unique value in the column `Code` and the size of the slice will be the count of the occurrences of that value.
1282
+
1283
+ The PieChard object should be populated with:
1284
+
1285
+ - Model: `EmbeddedDatabase1`
1286
+ - Query: `SELECT Code, COUNT(*) AS Count FROM SQLiteStoreTable1 GROUP BY Code ORDER BY Count DESC`
1287
+ - Label: `{Item}/Code`
1288
+ - Value: `{Item}/Count`
1289
+
1290
+ ### Populate a Histogram chart with the count of unique values in a column
1291
+
1292
+ This query returns the count of unique values in the column `StatusMachine` of the table `SQLiteStoreTable1` to populate a histogram chart. Each bar of the histogram will represent a unique value in the column `StatusMachine` and the height of the bar will be the count of the occurrences of that value.
1293
+
1294
+ - Model: `EmbeddedDatabase1`
1295
+ - Query: `SELECT StatusMachine, COUNT(*) AS Occurrences FROM Machine_state WHERE StatusMachine >= 0 GROUP BY StatusMachine ORDER BY Occurrences DESC`
1296
+ - Label: `{Item}/StatusMachine`
1297
+ - Value: `{Item}/Occurrences`