Chemicals have a trade name (which is what it is commonly referred to as) and an actual chemical name. I need to look up a trade name, find its proper chemical name, then get properties of that chemical. For example:
$tradeName = "Voranol"
if $tradeName == "Voranol" then
$productName = "Polyether Polyol"
$flare = "List I"
$bay = "1"
$listPos = 3
EndIf
I have an .au3 file that contains a ton of products. It works fine but it's kind of tedious to do if $tradeName == "Name1" or $tradeName == "Name2" or $tradeName == "Name4" or $tradeName == "Name5". I will also need to be able to edit it programmaticly. This is what I'm using now:
if $product == "dowanol pmb" or $product == "dowanol pma" or $product == "dipropylene glycol" or $product == "dowanol pnp" or $productCheck2 == "dowanol pmb" or $productCheck2 == "dowanol pma" or $productCheck2 == "dipropylene glycol" or $productCheck2 == "dowanol pnp" then
$result = "DIPROPYLENE GLYCOL"
$bay = 4
$useFlare = 1
$listPos = 2
elseif $product == "glycol blend" then
$result = "GLYCOL"
$bay = 3
$useFlare = 2
$listPos = 1
elseif $product == "isopar e" or $product == "isopar c" or $product == "isopar h" or $productCheck2 == "isopar h" then
$result = "PETROLEUM NAPTHA"
$bay = 5
$useFlare = 0
$listPos = 1
EndIf
; Note: 0 = No Flare, 1 = Normal Flare, 2 = CAS Flare
Another alternative to the database option is to use an XML document to store the product (chemical) information.
You could easily query the XML with XPath to get the product name/properties. Maintaining the file would be fairly easy too. You could even create an XML Schema to validate against to be sure your file is still valid after modifications.
The processing of the XML in AutoIt can be done a few different ways:
Creating an MSXML object
Running a command line tool like xmlstarlet
Using a XPath/XQuery/XSLT processor from the command line (i.e. java to run Saxon)
Running something like Saxon is probably overkill for what you need.
MSXML wouldn't be too bad and there should be multiple UDFs that already exist.
Xmlstarlet would be my vote. (Note: I haven't used xmlstarlet in this fashion before. I'm a huge fan of Saxon and use it almost exclusively. Specifically for AutoIt, I've used a combination of MSXML and Saxon; Saxon to do the transforms of complicated data into a smaller, simpler subset and then MSXML to do simple xpath queries on that subset. However, if I was going to do something like this, I'd give xmlstarlet a serious look.)
Also, if your data grows to the point that a single XML file does not make sense, you could always split it into a collection of smaller files; individual products maybe. You might also reach a point that it might make sense to load those files into an actual XML database (eXistdb is an option).
Here's a simple example of what your XML (schema and instance) might look like:
XSD (products.xsd)
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="products">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="product"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="product">
<xs:complexType>
<xs:sequence>
<xs:element ref="name"/>
<xs:element ref="tradenames"/>
</xs:sequence>
<xs:attribute name="bay" use="required" type="xs:integer"/>
<xs:attribute name="listpos" use="required" type="xs:integer"/>
<xs:attribute name="useflare" use="required" type="xs:integer"/>
</xs:complexType>
</xs:element>
<xs:element name="tradenames">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="name"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="name" type="xs:string"/>
</xs:schema>
XML (products.xml)
<products xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="products.xsd">
<product bay="4" useflare="1" listpos="2">
<name>DIPROPYLENE GLYCOL</name>
<tradenames>
<name>dowanol pmb</name>
<name>dowanol pma</name>
<name>dipropylene glycol</name>
<name>dowanol pnp</name>
</tradenames>
</product>
<product bay="3" useflare="2" listpos="1">
<name>GLYCOL</name>
<tradenames>
<name>glycol blend</name>
</tradenames>
</product>
<product bay="5" useflare="0" listpos="1">
<name>PETROLEUM NAPTHA</name>
<tradenames>
<name>isopar e</name>
<name>isopar c</name>
<name>isopar h</name>
</tradenames>
</product>
</products>
Here's a little bit of AutoIt that uses MSXML to demonstrate loading the XML (validates against XSD) and searching for products that have a trade name that contains "glycol".
AutoIt
;~ AutoIt Version: 3.3.14.2
;String to search on.
$searchString = "glycol"
ConsoleWrite("Search string: '" & $searchString & "'" & #CRLF)
;XPath for searching trade names. Search string is injected (code injection; escaping of strings would be a very good idea!).
$xpath_tradename = "/products/product[tradenames/name[contains(.,'" & $searchString & "')]]"
ConsoleWrite("XPath: '" & $xpath_tradename & "'" & #CRLF)
$msxml = ObjCreate('MSXML2.DOMDocument.6.0')
If IsObj($msxml) Then
$msxml.async = False
$msxml.validateOnParse = True
$msxml.resolveExternals = True
$msxml.setProperty("SelectionLanguage", "XPath")
$msxml.load('products.xml')
If $msxml.parseError.errorCode = 0 Then
$prods = $msxml.SelectNodes($xpath_tradename)
If IsObj($prods) And $prods.Length > 0 Then
ConsoleWrite("Number of products found: '" & $prods.Length & "'" & #CRLF)
For $prod In $prods
ConsoleWrite(#CRLF & "------ PRODUCT ------" & #CRLF)
ConsoleWrite("Product name: '" & $prod.SelectSingleNode('name').text & "'" & #CRLF)
ConsoleWrite("Product bay: '" & $prod.getAttribute('bay') & "'" & #CRLF)
Next
ConsoleWrite(#CRLF)
Else
ConsoleWrite("PRODUCT NOT FOUND" & #CRLF)
EndIf
Else
MsgBox(17, 'Error', 'Error opening XML file: ' & #CRLF & #CRLF & $msxml.parseError.reason)
SetError($msxml.parseError.errorCode)
EndIf
EndIf
Console Output
Search string: 'glycol'
XPath: '/products/product[tradenames/name[contains(.,'glycol')]]'
Number of products found: '2'
------ PRODUCT ------
Product name: 'DIPROPYLENE GLYCOL'
Product bay: '4'
------ PRODUCT ------
Product name: 'GLYCOL'
Product bay: '3'
The best database solution in Autoit is SQLite.
If you want to do it like a pro, you should use SQLite.
#include <SQLite.au3>
#include <SQLite.dll.au3>
Local $hQuery, $aRow
_SQLite_Startup()
ConsoleWrite("_SQLite_LibVersion=" & _SQLite_LibVersion() & #CRLF)
_SQLite_Open()
; Without $sCallback it's a resultless statement
_SQLite_Exec(-1, "Create table tblTest (a,b int,c single not null);" & _
"Insert into tblTest values ('1',2,3);" & _
"Insert into tblTest values (Null,5,6);")
Local $d = _SQLite_Exec(-1, "Select rowid,* From tblTest", "_cb") ; _cb will be called for each row
Func _cb($aRow)
For $s In $aRow
ConsoleWrite($s & #TAB)
Next
ConsoleWrite(#CRLF)
; Return $SQLITE_ABORT ; Would Abort the process and trigger an #error in _SQLite_Exec()
EndFunc ;==>_cb
_SQLite_Close()
_SQLite_Shutdown()
; Output:
; 1 1 2 3
; 2 5 6
As a simpler solution I would recommend using INI file as database.
[Voranol]
ProductName=Polyether Polyol
Flare=List I
Bay=1
ListPos=3
[Chem2]
ProductName=..
...
And then
; Read the INI section names. This will return a 1 dimensional array.
Local $aArray = IniReadSectionNames($sFilePath)
; Check if an error occurred.
If Not #error Then
; Enumerate through the array displaying the section names.
For $i = 1 To $aArray[0]
MsgBox($MB_SYSTEMMODAL, "", "Section: " & $aArray[$i])
Next
EndIf
Now there is a limit in windows on INI file size(32kb).
That means, certain Autoit INI functions will not work if that limit is breached.
This can be solved by using your own INI functions:
Func _IniReadSectionNamesEx($hFile)
Local $iSize = FileGetSize($hFile) / 1024
If $iSize <= 31 Then
Local $aNameRead = IniReadSectionNames($hFile)
If #error Then Return SetError(#error, 0, '')
Return $aNameRead
EndIf
Local $aSectionNames = StringRegExp(#CRLF & FileRead($hFile) & #CRLF, '(?s)\n\s*\[(.*?)\]s*\r', 3)
If IsArray($aSectionNames) = 0 Then Return SetError(1, 0, 0)
Local $nUbound = UBound($aSectionNames)
Local $aNameReturn[$nUbound + 1]
$aNameReturn[0] = $nUbound
For $iCC = 0 To $nUBound - 1
$aNameReturn[$iCC + 1] = $aSectionNames[$iCC]
Next
Return $aNameReturn
EndFunc
Func _IniReadSectionEx($hFile, $vSection)
Local $iSize = FileGetSize($hFile) / 1024
If $iSize <= 31 Then
Local $aSecRead = IniReadSection($hFile, $vSection)
If #error Then Return SetError(#error, 0, '')
Return $aSecRead
EndIf
Local $sFRead = #CRLF & FileRead($hFile) & #CRLF & '['
$vSection = StringStripWS($vSection, 7)
Local $aData = StringRegExp($sFRead, '(?s)(?i)\n\s*\[\s*' & $vSection & '\s*\]\s*\r\n(.*?)\[', 3)
If IsArray($aData) = 0 Then Return SetError(1, 0, 0)
Local $aKey = StringRegExp(#LF & $aData[0], '\n\s*(.*?)\s*=', 3)
Local $aValue = StringRegExp(#LF & $aData[0], '\n\s*.*?\s*=(.*?)\r', 3)
Local $nUbound = UBound($aKey)
Local $aSection[$nUBound +1][2]
$aSection[0][0] = $nUBound
For $iCC = 0 To $nUBound - 1
$aSection[$iCC + 1][0] = $aKey[$iCC]
$aSection[$iCC + 1][1] = $aValue[$iCC]
Next
Return $aSection
EndFunc
I need to look up a trade name, find its proper chemical name, then get properties of that chemical.
Relational query (SQLite)
SQL as understood by SQLite.
If requirements are limited to inserts, edits and queries, an SQLite database management tool may suffice. AutoIt SQLite functions are documented as per User-defined function-reference:
(Create and) open database using _SQLite_Open().
Create tables , indexes and view(s) (and insert records) using _SQLite_Exec().
Queries using _SQLite_GetTable2d() return results as two-dimensional array.
Structure
SQLite data types. Inserting records to product and substance table may be automated.
Table product
Defines trade names.
id
name
1
dowanol pmb
2
dowanol pma
3
dipropylene glycol
4
dowanol pnp
5
glycol blend
6
isopar e
7
isopar c
8
isopar h
9
Polyether Polyol
10
Voranol
Creation:
CREATE TABLE IF NOT EXISTS product (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE
);
Insert record:
INSERT INTO product (name) VALUES ('dowanol pmb');
Table substance
Defines chemical names (and properties).
id
name
flare
bay
1
Polyether Polyol
1
1
2
DIPROPYLENE GLYCOL
1
4
3
GLYCOL
2
3
4
PETROLEUM NAPHTA
0
5
Creation:
CREATE TABLE IF NOT EXISTS substance (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE,
flare INTEGER,
bay INTEGER
);
Insert record:
INSERT INTO substance (name, flare, bay) VALUES ('Polyether Polyol', 1, 1);
Table relation
Defines relations between product and substance table's records (using foreign keys).
id
product
substance
1
1
2
2
2
2
3
3
2
4
4
2
5
5
3
6
6
4
7
7
4
8
8
4
9
9
4
10
10
1
Creation:
CREATE TABLE IF NOT EXISTS relation (
id INTEGER PRIMARY KEY,
product INTEGER REFERENCES product(id),
substance INTEGER REFERENCES substance(id),
UNIQUE( /* Constraint applies to *combination* of columns. */
product,
substance
)
);
Insert record:
INSERT INTO relation (
product,
substance
) VALUES (
(SELECT id FROM product WHERE name = 'dowanol pmb'),
(SELECT id FROM substance WHERE name = 'DIPROPYLENE GLYCOL')
);
Or just:
INSERT INTO relation (product, substance) VALUES (0, 1);
View view_relation
View (stored query), joining (combines) product and substance tables' fields, as per relation table's records (acting as virtual table, negating need to include underlying JOIN operators every query).
name_product
name_substance
flare
bay
dowanol pmb
DIPROPYLENE GLYCOL
1
4
dowanol pma
DIPROPYLENE GLYCOL
1
4
dipropylene glycol
DIPROPYLENE GLYCOL
1
4
dowanol pnp
DIPROPYLENE GLYCOL
1
4
glycol blend
GLYCOL
2
3
isopar e
PETROLEUM NAPTHA
0
5
isopar c
PETROLEUM NAPTHA
0
5
isopar h
PETROLEUM NAPTHA
0
5
Polyether Polyol
PETROLEUM NAPTHA
0
5
Voranol
Polyether Polyol
1
1
Creation:
CREATE VIEW IF NOT EXISTS view_relation AS
SELECT
product.name AS name_product,
substance.name AS name_substance,
substance.flare,
substance.bay
FROM
relation
LEFT OUTER JOIN product ON relation.product = product.id
LEFT OUTER JOIN substance ON relation.substance = substance.id
ORDER BY
product.id ASC
;
Query by trade name
name_product
name_substance
flare
bay
Voranol
Polyether Polyol
1
1
Query:
SELECT
*
FROM
view_relation
WHERE
name_product = 'Voranol'
;
Or (without view):
SELECT
product.name AS name_product,
substance.name AS name_substance,
substance.flare,
substance.bay
FROM
relation
WHERE
product.name = 'Voranol'
LEFT OUTER JOIN product ON relation.product = product.id
LEFT OUTER JOIN substance ON relation.substance = substance.id
;
Query by substance
name_product
name_substance
flare
bay
dowanol pmb
DIPROPYLENE GLYCOL
1
4
dowanol pma
DIPROPYLENE GLYCOL
1
4
dipropylene glycol
DIPROPYLENE GLYCOL
1
4
dowanol pnp
DIPROPYLENE GLYCOL
1
4
Query:
SELECT
*
FROM
view_relation
WHERE
name_substance = 'DIPROPYLENE GLYCOL'
;
Or (without view):
SELECT
product.name AS name_product,
substance.name AS name_substance,
substance.flare,
substance.bay
FROM
relation
WHERE
substance.name = 'DIPROPYLENE GLYCOL'
LEFT OUTER JOIN product ON relation.product = product.id
LEFT OUTER JOIN substance ON relation.substance = substance.id
;
AutoIt
Example creating (and inserting records to) a single table and query it:
#include <SQLite.au3>
#include <SQLite.dll.au3>
#include <Array.au3>
Global Const $g_iRecords = 50
Global Const $g_sFileDb = #ScriptDir & '\example.sqlite'
Global $g_aCreate = [ _
'CREATE TABLE IF NOT EXISTS example (id INTEGER PRIMARY KEY, name TEXT UNIQUE);' & #LF, _
'--CREATE INDEX IF NOT EXISTS index_example_name ON example(name);' & #LF _
]
Global Const $g_sInsert = 'INSERT INTO example (name) VALUES (%i);\n'
Global Const $g_sTransact1 = 'BEGIN TRANSACTION;' & #LF
Global Const $g_sTransact2 = 'END TRANSACTION;' & #LF
Global Const $g_sQuery = 'SELECT * FROM example ORDER BY id ASC;'
Global Const $g_sMsgError = 'sqlite.dll not found.' & #LF
Global $g_hDb = 0
Main()
Func Main()
Local $sQuery = ''
Local $iResultCol = 0
Local $iResultRow = 0
Local $aResult
For $i1 = 0 To UBound($g_aCreate, 1) -1
$sQuery &= $g_aCreate[$i1]
Next
For $i1 = 1 To $g_iRecords
$sQuery &= StringFormat($g_sInsert, _SQLite_FastEscape('example' & $i1))
Next
$sQuery = $g_sTransact1 & $sQuery & $g_sTransact2
ConsoleWrite($sQuery)
_SQLite_Startup()
If #error Then
ConsoleWrite($g_sMsgError)
Exit
EndIf
_SQLite_Open($g_sFileDb)
_SQLite_Exec($g_hDb, $sQuery)
_SQLite_GetTable2d($g_hDb, $g_sQuery, $aResult, $iResultRow, $iResultCol)
_SQLite_Close($g_hDb)
_SQLite_Shutdown()
_ArrayDisplay($aResult)
Exit
EndFunc
Related
I am collecting data using a web formular and a workflow with four different status.
Started
Review by Admin
Back to User
Finished
Status 2 and 3 are repeated as long as it is needed. The application logs the history of this process and I would like to be able so see the development of the workflow over time.
The history is stored in an MS SQL Table and I can rearange it to look like this.
In the example there are two workflows (id 1 and 2) and over time the formular goes back and forth untill it is finished. I can order this by time
to derive the result I would like to have.
I dont need to have this result in wide format as long as the counting in long is correct.
I am struggling to find a way to avoid double counting status 2 and 3 and also I dont want to count status 1 later on.
I am rather looking for a strategy to obtain my desired results either in SQL or Power Query.
I tried different grouping, dense_rank and other transformations in Power Query.
Try this in PowerQuery, assumes already date sorted
let Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
#"Changed Type" = Table.TransformColumnTypes(Source,{{"Datum", type datetime}}),
#"Added Custom" = Table.AddColumn(#"Changed Type", "zStarted", each if [Status]="Started" then 1 else if [Status]="Review by Admin" then -1 else 0),
#"Added Custom1" = Table.AddColumn(#"Added Custom", "zReview", each if [Status]="Review by Admin" then 1 else if [Status]="Back to User" then -1 else if [Status]="Finished" then -1 else 0),
#"Added Custom2" = Table.AddColumn(#"Added Custom1", "zBackA", each if [Status]="Back to User" then 1 else null),
#"Filled Down" = Table.FillDown(#"Added Custom2",{"zBackA"}),
#"Added Custom3" = Table.AddColumn(#"Filled Down", "zBackB", each if [zBackA]=null then 0 else if [Status]="Back to User" then 1 else if [Status]="Review by Admin" then -1 else 0),
#"Added Custom4" = Table.AddColumn(#"Added Custom3", "zFinished", each if [Status]="Finished" then 1 else 0),
#"Added Index" = Table.AddIndexColumn(#"Added Custom4", "Index", 0, 1, Int64.Type),
// cumulative sum, converting to zero if <0
#"Cum1" = Table.AddColumn(#"Added Index", "Started", each List.Max(List.Combine({{0}, {List.Sum(List.FirstN(#"Added Index"[zStarted],[Index]+1))}}))),
#"Cum2" = Table.AddColumn(#"Cum1", "Review By Admin", each List.Max(List.Combine({{0}, {List.Sum(List.FirstN(#"Added Index"[zReview],[Index]+1))}}))),
#"Cum3" = Table.AddColumn(#"Cum2", "Back to User", each List.Max(List.Combine({{0}, {List.Sum(List.FirstN(#"Added Index"[zBackB],[Index]+1))}}))),
#"Cum4" = Table.AddColumn(#"Cum3", "Finished", each List.Max(List.Combine({{0}, {List.Sum(List.FirstN(#"Added Index"[zFinished],[Index]+1))}}))),
#"Removed Columns" = Table.RemoveColumns(Cum4,{"ID","zStarted", "zReview", "zBackA", "zBackB", "zFinished", "Index","Status"})
in #"Removed Columns"
ID
Datum
Status
1
06/24/22
Started
2
06/25/22
Started
1
06/26/22
Review by Admin
2
06/27/22
Review by Admin
1
06/28/22
Back to User
1
06/29/22
Review by Admin
2
06/30/22
Back to User
1
07/01/22
Back to User
1
07/02/22
Review by Admin
2
07/03/22
Review by Admin
1
07/04/22
Finished
2
07/05/22
Back to User
2
07/06/22
Review by Admin
2
07/07/22
Finished
Thanks to #horseyride i came up with my own solution using case and a similar approach within SQL
I follow a three step appraoch to avoid the double counting of changes in status by using the lagged status (sql columne action_nam)
With the lagged status I can then unambigously define the changes of the status and take the cumulutive sum.
I mark the first answer as accepted as this one helped me to find my own solution. I leave my solution with the Germany Status names just for completeness
Select top 600 created, form_uuid, Cum_Erstellt, Cum_Review, Cum_Korrektur, Cum_Fertig from (
Select *,
sum(Erstellt) over(Partition by form_uuid order by form_uuid, created) as Cum_Erstellt,
sum(Review) over(Partition by form_uuid order by form_uuid, created) as Cum_Review,
sum(Korrektur) over(Partition by form_uuid order by form_uuid, created) as Cum_Korrektur,
sum(Fertig) over(Partition by form_uuid order by form_uuid, created) as Cum_Fertig,
Erstellt+Review+Korrektur+Fertig as Vergleich
from (
Select created, form_uuid, encoded_key, action_name, lag_action,
CASE WHEN action_name = 'Datensatz erstellt und zur Bearbeitung an Datenlieferanten übergeben' THEN 1
When action_name = 'Datensatz zur Prüfung an Datensammler' and
lag_action='Datensatz erstellt und zur Bearbeitung an Datenlieferanten übergeben' THEN -1
Else 0 END AS Erstellt,
CASE WHEN action_name = 'Datensatz zur Prüfung an Datensammler' THEN 1
When action_name= 'Datensatz zur Korrektur an Datenlieferanten' Then -1
when Action_name= 'Datensatz erneut Öffnen' then 1
When action_name='Datensatz durch Datensammler bestätigt' Then -1
Else 0 END AS Review,
CASE
WHEN action_name = 'Datensatz zur Prüfung an Datensammler' and
lag_action!='Datensatz erstellt und zur Bearbeitung an Datenlieferanten übergeben' THEN -1
When action_name= 'Datensatz zur Korrektur an Datenlieferanten' Then 1
Else 0 END AS Korrektur,
CASE WHEN action_name = 'Datensatz durch Datensammler bestätigt' THEN 1
When action_name= 'Datensatz erneut Öffnen' Then -1
Else 0 END AS Fertig
from (
Select created, form_uuid, encoded_key, action_name, lag(action_name,1,NULL)
over( order by encoded_key, form_uuid, created) as lag_action
from process_instance_hist ) lag_table
) change_table
) cum_table
order by created, encoded_key
--group by created, form_uuid
I have the following code... How would I be able to insert values in the array list with different indexes while its looping inside of a while loop? from the 2nd function(HashMine(CarID1))
local function HistoryHash() -- This function is to print out the Hashes "Mined" using Hash.Lib
for Hashindex = 1, #HashHistory do
print("Hash "..Hashindex..":", HashHistory[Hashindex])
end
end
--Mines the BTC pending transaction
local function HashMine(CarID1)
while stringtohash:sub(1,2) ~= "00" do
STRINGTOHASH = stringtohash..HASHNUMBER
stringtohash = HASHLIBRARY.sha256(STRINGTOHASH)
HASHNUMBER = HASHNUMBER + 1
wait(1)
table.insert()
end
HashGUI.Text = stringtohash
PendingTextGui.Text = ""
local CarID1 = CarBought
if CarID1 == 1 then
ConfirmedText.Text = ("Car1 ".. game.Workspace.Cars.Car1Buy.Car1.Value .. "BTC To Malta Car Dealer from " .. Players:GetChildren()[1].Name)
AfterCarPurchase()
elseif CarID1 == 2 then
ConfirmedText.Text = ("Car2 ".. game.Workspace.Cars.Car2Buy.Car2.Value.. "BTC To Malta Car Dealer from " .. Players:GetChildren()[1].Name)
AfterCarPurchase()
elseif CarID1 == 3 then
ConfirmedText.Text = ("Car3 ".. game.Workspace.Cars.Car3Buy.Car3.Value .. "BTC To Malta Car Dealer from " .. Players:GetChildren()[1].Name)
end
AfterCarPurchase()
end
table.insert() will cause the error message
bad argument #1 to 'insert' (table expected, got no value)
According to the Lua 5.4 Reference Manual - table.insert, it is mandatory to provide the table you want to insert to and the value you want to to insert into that table.
table.insert (list, [pos,] value)
Inserts element value at position pos in list, shifting up the
elements list[pos], list[pos+1], ···, list[#list]. The default value
for pos is #list+1, so that a call table.insert(t,x) inserts x at the
end of the list t.
If you want to assign a value to a specific table index you need to use indexing assignmet t[key] = value
I have just started building a webapp in Codeigniter and i can't solve a problem with nested queries in my model.
I have three Tables:
Categories, Links and Topics. Every Link and every Topic can have just one Category, whereas every Category can have multiple Links or Topics.
Every Link can have one Topic (or no Topic at all) and every Topic can have one or more Links.
The Table-Structure looks like this (shortened):
Categories:
- ID
- DESCR
Links:
- ID
- DESCR
- ID_CATEGORY
- ID_TOPIC (either an ID from the Topics-Table or NULL)
Topics:
- ID
- DESCR
- ID_CATEGORY
In my View, i want to display them in the following manner:
- Category 1
- Link 1
- Link 2
- Topic 1
- Topiclink 1
- Topiclink 2
- Link 3
- Category 2
- Topic 2
- Topiclink 3
- Topiclink 4
- Topic 3
- Topiclink 5
- Topiclink 6
- Link 4
So there will be 3 nested queries.
My Model looks like this:
public function get_categories(){
//1. Get the categories
$this->db->select("
ec.ID_CATEGORY,
c.DESCR,
c.VISIBLE,
i.KEYVALUE");
$this->db->from("episode_categories ec");
$this->db->join("categories c", "c.ID = ec.ID_CATEGORY");
$this->db->join("ini i", "i.SETTING = c.VISIBLE");
$this->db->where("i.KEYWORD", "CATEGORY_VISIBLE");
$this->db->where("ec.ID_EPISODE", $_SESSION['cur_episode']);
$categories = $this->db->get()->result_array();
//2. Get the Entries (Topics and Links with NULL as TOPIC_ID)
foreach($categories as $key => $value)
{
$sql_links="SELECT
pf_users.USERNAME,
pf_users.NAME_SHOW,
pf_links.ID AS ID,
pf_links.URL AS URL,
pf_links.ID_USER AS ID_USER,
pf_links.ID_EPISODE,
pf_links.ID_CATEGORY,
pf_links.DESCR,
NULL AS IS_TOPIC,
pf_links.REIHENF,
pf_links.DONE,
pf_links.DONE_TS,
pf_links.INFO AS INFO,
pf_episoden.DONE AS EPISODE_DONE
from pf_links
JOIN pf_users on pf_users.ID = pf_links.ID_USER
JOIN pf_episoden on pf_episoden.ID = pf_links.ID_EPISODE
WHERE ID_EPISODE = ".$_SESSION['cur_episode']." AND ID_CATEGORY = ".$value['ID_CATEGORY']." AND ID_TOPIC IS NULL
UNION ALL SELECT pf_users.USERNAME,
pf_users.NAME_SHOW,
pf_topics.ID AS ID,
NULL AS URL,
pf_topics.ID_USER AS ID_USER,
pf_topics.ID_EPISODE,
pf_topics.ID_CATEGORY,
pf_topics.DESCR,
1 AS IS_TOPIC,
pf_topics.REIHENF,
pf_topics.DONE,
pf_topics.DONE_TS,
pf_topics.INFO AS INFO,
pf_episoden.DONE AS EPISODE_DONE
from pf_topics
JOIN pf_users on pf_users.ID = pf_topics.ID_USER
JOIN pf_episoden on pf_episoden.ID = pf_topics.ID_EPISODE
WHERE ID_EPISODE = ".$_SESSION['cur_episode']." AND ID_CATEGORY = ".$value['ID_CATEGORY']." ORDER BY REIHENF,
ID ASC";
$query_links = $this->db->query($sql_links);
$categories[$key]['link_list'] = $query_links->result_array();
//3. Get the Topiclinks (Links with an ID_TOPIC set)
foreach( $categories[$key]['link_list'] as $key2 => $value2)
{
if($value2['IS_TOPIC'] == 1)
{
$sql_topic_links = "SELECT * FROM pf_links WHERE ID_TOPIC = ".$value2['ID'];
$query_topic_links = $this->db->query($sql_topic_links);
$categories[$key2]['topic_links'] = $query_topic_links->result();
}
}
}
return $categories;
}
Everything works fine until the links of the topics (So Step 3 in the Code). The array i get is the same for every topiclink with the last one of the possible ID_TOPIC. So the links within the topics are all the same.
What do i have to change to get just the links for the specific topics in the third step of the function?
Thank you in advance!
I was able to solve the issue:
In Step 3 the following line
$categories[$key2]['topic_links'] = $query_topic_links->result();
must be changed to:
$categories[$key]['link_list'][$key2]['topic_links'] = $query_topic_links->result_array();
So in my view i can iterate trhough the result-sets.
I'm trying to build a hierarchy from two columns where by one column is a client identifier and the other is its direct parent but I have an issue because a client can have a parent which can have another parent.
At the moment I have a number of merge statements in a macro that
%macro hierarchy (level);
data sourcedata (drop = PARENT_ID);
merge sourcedata (in = a)
inputdata (in = b);
by CLIENT_ID;
if a;
length Parent_L&level $32.;
Parent_L&level = PARENT_ID;
if Parent_L&level = Parent_L%eval(&level-1) then Parent_L&level ="";
CLIENT_ID = Parent_L&level;
proc sort;
by Parent_L&level;
run;
%mend;
%hierarchy(2)
%hierarchy(3)
%hierarchy(4)
my output looks like
client_ID Parent_L1 Parent_L2 Parent_L3 Parent_L4
clientA clientB . . .
ClientE clientA clientB . .
what I'm looking for is a way to do this until the last parent_Ln is all blank as I'm not sure how many levels I need to go to
thanks
The length you need is the depth of the tree.
It is a bit cumbersome to compute that in SQL, since you really need a hash table, or at least arrays.
You could use a hash in data step or ds2 to access the parent from the child.
If you have SAS/OR, then it is easier:
you can solve a shortest path and produce your table from
the paths from the roots of the forest.
For this example I will use the dataset from the link Reeza provided:
data have;
infile datalines delimiter='|';
input subject1 subject2;
datalines;
2 | 4
2 | 5
2 | 6
2 | 8
4 | 7
4 | 11
6 | 9
6 | 10
6 | 12
10 | 15
10 | 16
13 | 14
16 | 17
;
proc optmodel printlevel=0;
set<num,num> REFERRALS;
set CLIENTS = union{<u,v> in REFERRALS} {u, v};
set ROOTS = CLIENTS diff setof{<u,v> in REFERRALS} v;
read data have into REFERRALS=[subject1 subject2];
/* source, sink, seq, tail, head. */
set<num,num,num,num,num> PATHS;
num len{ROOTS,CLIENTS} init .;
num longest = max(of len[*]);
solve with NETWORK / graph_direction = directed
links = ( include = REFERRALS )
out = ( sppaths = PATHS spweights = len )
shortpath = ( source = ROOTS )
;
num parent{ci in CLIENTS, i in 1 .. longest} init .;
for{<u, v, i, s, t> in PATHS}
parent[v, len[u,v] - i + 1] = s;
print parent;
create data want from [client]=CLIENTS
{i in 1 .. longest} <COL('Parent_L'||i) = parent[client,i]>;
quit;
I've got an ADO query (ADODB.Recordset) in VBscript that will return a result like below:
nId wstrName nParentId
0 Managed computers 2
1 Unassigned computers NULL
2 Master Server NULL
3 pseudohosts NULL
5 Server 2 0
8 Group100 5
10 Group22 5
11 Group47 5
13 Group33 5
14 Group39 5
15 Group11 5
I need to build a complete location string based on the result when I know the top level group ID.
E.g. if the top level group ID is 11 then the complete location string will be "Master Server/Managed computers/Server 2/Group47" after traversing the groups by looking at the "nId" and "nParentId" values.
The number of parent groups can vary, so I need to loop until I reach a group without a parent group. I would also like to avoid making multiple SQL queries so I'm assuming the result should get loaded into an array and then handle the information from there.
What is the best way to handle this?
Thanks in advance :)
You can work with the recordset as it is. Try this:
groupname = "..."
rs.MoveFirst
rs.Find("wstrName = '" & groupname & "'")
location = rs("wstrName")
parent = rs("nParentId")
Do Until parent = "NULL"
rs.MoveFirst
rs.Find("nId = " & parent)
location = rs("wstrName") & "/" & location
parent = rs("nParentId")
Loop
You may need to adjust the condition of the loop depending on whether the NULL values in your recordset are the string "NULL" or actual Null values.
Use a Dictionary to store your tabular data and a recursive function to build the location string.
Demo script:
Dim dicData : Set dicData = CreateObject("Scripting.Dictionary")
' nId wstrName nParentId
dicData( 0) = Array("Managed computers" , 2 )
dicData( 1) = Array("Unassigned computers", NULL)
dicData( 2) = Array("Master Server" , NULL)
dicData( 3) = Array("pseudohosts" , NULL)
dicData( 5) = Array("Server 2" , 0 )
dicData( 8) = Array("Group100" , 5 )
dicData(10) = Array("Group22" , 5 )
dicData(11) = Array("Group47" , 5 )
dicData(13) = Array("Group33" , 5 )
dicData(14) = Array("Group39" , 5 )
dicData(15) = Array("Group11" , 5 )
Dim nId
For Each nId In dicData.Keys()
WScript.Echo Right(100 + nId, 2), buildLoc(dicData, nId, Array())
Next
Function buildLoc(dicData, nId, aTmp)
ReDim Preserve aTmp(UBound(aTmp) + 1)
aTmp(UBound(aTmp)) = dicData(nId)(0)
If IsNull(dicData(nId)(1)) Then
reverseArr aTmp
buildLoc = Join(aTmp, "/")
Else
buildLoc = buildLoc(dicData, dicData(nId)(1), aTmp)
End If
End Function
Sub reverseArr(aX)
Dim nUB : nUB = UBound(aX)
Dim nUB2 : nUB2 = nUB \ 2
Dim i, vt
For i = 0 To nUB2
vt = aX(i)
aX(i) = aX(nUB - i)
aX(nUB - i) = vt
Next
End Sub
output:
00 Master Server/Managed computers
01 Unassigned computers
02 Master Server
03 pseudohosts
05 Master Server/Managed computers/Server 2
08 Master Server/Managed computers/Server 2/Group100
10 Master Server/Managed computers/Server 2/Group22
11 Master Server/Managed computers/Server 2/Group47
13 Master Server/Managed computers/Server 2/Group33
14 Master Server/Managed computers/Server 2/Group39
15 Master Server/Managed computers/Server 2/Group11