Spießer-Zensuren in Wikimedias

Faszinierend wieviele freiwillige Autoren bei Wikipedia unentgeltlich ihre Arbeit verrichten. Die Idee einer freien Kollaborationsanwendung / eines „Gedankentempels“ (Zitat von Jimmy Wales, Gründer von Wikipedia) scheint allerdings mit der Diskussion nach Sinnhaftigkeit von Artikeln und deren Existenzberechtigung zu stehen und zu fallen. Nachdem die Administratoren ihre Rechte bekommen haben um Artikel zu löschen, entpuppen sich viele als nervöse, bewaffnete Moralapostel mit dem Finger am Abzug. Das scheint bei einer Enzyklopädie erstmal sinnvoll. Lieber kein Artikel zusätzlich, als ein schlechter Artikel zusätzlich…

Aber wieso wird bei Stupipedia und Uncyclopedia Satire zensiert? Ich saß mit einem Bekannten in der Sylvesternacht am Tisch und trank ein Gläschen Eierlikör. Dieser kam auf die Idee dem Getränk den Namen „Hoden Brandy“ zu geben, was wir zunächst witzig fanden. Die Idee, man könne einen satirischen Artikel darüber verfassen, scheiterte schon nach der Artikelanlage.

Bei Uncyclopedia haben wir uns einen festen User registriert und den Artikel „Hodenbrandy“ angelegt. Nachdem die Sektion „Definition“ verfasst wurde, in der wir lediglich einige Sätze mit Andeutungen auf „Eierlikör“ schrieben, war der Artikel bereits beim zweiten oder dritten Mal speichern verschwunden. Wir dachten uns: „Okay, da dachte wohl jdm. das wär ein Spam-Artikel“… Also versuchten wir ihn nochmals anzulegen, allerdings war der Artikel gesperrt. Wir legten einen zweiten Artikel „Hoden Brandy“ (auseinander mit Leerzeichen) an. Kurz danach wurde unser Benutzer gesperrt.

Anschließend versuchten wir unser Glück auf Stupipedia. Wir kopierten den Text, den wir mit dem Wiki-Markup bereits vorsorglich in Notepad zwischengelagert hatten, hinein. Kurz danach kam der erste Kommentar „grenzwertig“. Durch das Setzen des „In-Use“-Tags erklärten wir, dass wir noch am Arbeiten sind, und den Artikel überdenken. Wir erstellten eine lustige Visio-Grafik mit einem Kaizen-Diagramm über den Produktionsprozess des Brandys… Ca. 10 Minuten später wurde der Artikel gelöscht, da es sich angeblich um einen sexistischen Inhalt handele.

Der Artikel war im vgl. zu anderen Satiren weder sexistisch noch eklig oder hätte es gerechtfertigt unseren Benutzer zu sperren. Es wurde niemand persönlich angegriffen, da auch keine Person
angesprochen wurde. Wenn man sich die Sprüche von Michael Mittermeier oder Mario Barth anhört, vernimmt man gelegentlich tollerierte sexistische Inhalte. Aber die Beschreibung von Eierlikör unter dem Namen „Hoden Brandy“ ist an Harmlosigkeit kaum zu überbieten.

Auf der einen Seite bewundere ich das Engagement der Administratoren, auf der anderen Seite ärgere ich mich über die Spießigkeit und frage mich ob eine längere Tätigkeit auf diesem Gebiet zu Abgestumpftheit führt. Die Interpretation der Richtlinien wird häufig am eigenen Maßstab festgemacht. Was die Erhaltung von Artikeln und den Umgang mit der Arbeit von fremden Personen betrifft, wünschte ich mir hier etwas Professionalität. Auf den Löschen-Button ist schnell geklickt und die Arbeit von Stunden zerstört.

Auf der anderen Seite kann man wirklich über die Sinnhaftigkeit von Satire in Uncyclopedia/Stupipedia streiten 😉 …
Definiert Wikipedia die Satire als „eine Spottdichtung, die mangelhafte Tugend oder gesellschaftliche Missstände anklagt“, sollte sie hier gegen ihr eigenes Rahmenwerk eingesetzt werden.

Hier ein netter Versuch – ein satirischer Artikel über den Vorfall bei den Stupidedia Administratoren:
http://www.stupidedia.org/stupi/Stupidedia_Administrator

… mal schauen wie langer er sich hält, er soll ab 11. Januar gelöscht werden, unten sieht man wie sich die Administratoren aufregen XD …

Baum von mx:AdvancedDataGrid in die Zwischenablage kopieren

Aufgabe

Der Baum eine mx:AdvancedDataGrid mit mx:SummaryRows soll in die Zwischenablage kopiert werden.

Problem

Der DataProvider des mx:AdvancedDataGrid ist eigentlich flach. Der erzeugte Baum/die Hierarchie soll in die Zwischenablage kopiert werden, so dass er in Excel per Copy&Paste mit Einrückungen und Summen eingefügt werden kann.

Lösung

Eine rekursive Funktion wird erstellt, die über den erzeugten Baum iteriert:

// Für das Einrücken in die Zwischenablage
public var anzahlGroupingFelder:Number=0; // Gesamttiefe der Felder
public var aktuelleTiefe:Number=0;		  // Tiefe der aktuellen Rekursion
public var rekursionsString:String='';
public var farbenFuerLevel:Object = new Object();
private function getCompleteADGRekursion(treeData:ArrayCollection):void
{
	var aktuelleGruppeFelder:String='';
		
	for (var i:Number=0; i<treeData.length; i++)
	{
		if(i==0) aktuelleTiefe++;
		var tabs:String='';
		var behindtabs:String='';
		
		for (var l:Number=0; l<=anzahlGroupingFelder-aktuelleTiefe+3; l++) 
		{
			behindtabs+='<td></td>';						
		}
					
				
		var item:Object=treeData[i];
		if(item.children)
		{
			for (var j:Number=1; j<aktuelleTiefe; j++)
			{
				tabs+='<td></td>';						
			}
						
			var properties:String='<td><i><b>'+item.actuallastyear+
			'</b></i></td><td><i><b>'+item.actualyeartillaugust+
			'</b></i></td><td><i><b>'+item.actualyearlinearprojection+
			'</b></i></td><td><i><b>'+item.nextyear+
			'</b></i></td><td><i><b>'+item.nnextyear+
			'</i></td><td><i><b>'+item.nnnextyear+
			'</b></i></td><td><i><b>'+item.nnnnextyear+
			'</b></i></td>';
						
			rekursionsString+='<tr style="background-color:'+
			farbenFuerLevel&#91;aktuelleTiefe&#93;+';">'+tabs+
			'<td>'+item.GroupLabel+behindtabs+'</td>'+
			properties+'</tr>';
			getCompleteADGRekursion(item.children);
		}
		else
		{
			for (var k:Number=0; k<anzahlGroupingFelder; k++)
			{
				tabs+='<td style="background-color:#EFEFEF"></td>';						
			}
						
			var properties2:String='<td>'+item.productcharacteristic+
			'</td><td>'+item.productcharacteristic2+
			'</td><td>'+item.productcharacteristic3+
			'</td><td>'+item.material+
			'</td><td>'+item.actuallastyear+
			'</td><td>'+item.actualyeartillaugust+
			'</td><td>'+item.actualyearlinearprojection+
			'</td><td>'+item.nextyear+
			'</td><td>'+item.nnextyear+
			'</td><td>'+item.nnnextyear+
			'</td><td>'+item.nnnnextyear+'</td>';												
			
			rekursionsString+='<tr>'+tabs+properties2+'</tr>';
		}
					
		// Beim ersten gehts eins tiefer
					
		// Beim letzten eins zurück
		if(i==treeData.length-1) aktuelleTiefe--;
	}
}

Aufruf der rekursiven Funktion:

private function copyToClipboard(dg:AdvancedDataGrid):void
{
// Farben je Interationstiefe;

farbenFuerLevel[1]=’#00B684′;
farbenFuerLevel[2]=’#31C39C‘;
farbenFuerLevel[3]=’#63D3B5′;
farbenFuerLevel[4]=’#CEF3E7′;
farbenFuerLevel[5]=’#BBBBBB‘;
farbenFuerLevel[6]=’#AAAAAA‘;
farbenFuerLevel[7]=’#999999′;
farbenFuerLevel[8]=’#888888′;

var wasIstImDataProviderDebug:Object=dg.dataProvider;
try
{
anzahlGroupingFelder=Number(wasIstImDataProviderDebug.source.
grouping.fields.length);
}
catch(e:Error)
{
anzahlGroupingFelder=0;
}

if(anzahlGroupingFelder>0)
{
rekursionsString=‘‚;
rekursionsString=‘

‚;
rekursionsString=‘

‚;

for each (var order:ReportingOrderVO in reportingOrder)
{
if(order.position!=0)
{
rekursionsString+=‘

‚;
}
}

rekursionsString+=‘

‚;
rekursionsString+=‘

‚;
rekursionsString+=‘

‚;
rekursionsString+=‘

‚;
rekursionsString+=‘

‚;
rekursionsString+=‘

‚;
rekursionsString+=‘

‚,
rekursionsString+=‘

‚;
rekursionsString+=‘

‚;
rekursionsString+=‘

‚;
rekursionsString+=‘

‚;
getCompleteADGRekursion(
ArrayCollection(dg.hierarchicalCollectionView.source
.getRoot())
);
rekursionsString+=‘

‚+order.description+
Detail 1 Detail 2 Detail 3 Detail 4 Actual last year‚;
rekursionsString+=‘
Actual year‘;
rekursionsString+=‘ till august
Actual year linear projection Next year NNext year NNNext year NNNNext year

‚;
System.setClipboard(rekursionsString);
}
else
{
var totalExport:String = new String();
var colList:Array = new Array();
for(var i:int = 0; i < dg.columnCount; i++) { colList.push(dg.columns[i].dataField); totalExport += String(dg.columns[i].headerText) .replace('\r',' ') + "\t"; } totalExport += "\r"; for(var yp:int = 0; yp < reporting.length; yp++) { for(var xp:int = 0; xp < colList.length; xp++) { var insertString:String=reporting .getItemAt(yp)[colList[xp]]; if(!insertString) insertString=''; totalExport += insertString .replace("\t","") + "\t"; } totalExport += "\r"; } System.setClipboard(totalExport); } Alert.show('Data has been copied to clipboard. Now you can paste it to '+ 'Microsoft Excel or another application of your choice.'); } [/actionscript3]

Von Microsoft Excel in den Microsoft SQL Server importieren

Dies ist ein VBA Skript um Dateien von einer Excel-Tabelle in den Microsoft SQL Server zu importieren. Bevor das Skript läuft muss im VBA-Editor unter Extras -> Verweise „Microsoft ActiveX Data Objects“ als Bibliothek ausgewählt werden, damit ADODB.* zur Verfügung steht.

Private Sub CommandButton1_Click()
    ' Die Datenbankverbindung
    Dim Cn As New ADODB.Connection
    Dim Rs As New ADODB.Recordset
    Dim letzteZeile As Integer
    Dim i As Integer
    
    ' Die beiden Felder material, prdha
    Dim materialnr As String
    Dim prdha As String
    
    ' Finde die letzte Zeile
    letzteZeile = Cells.Find("*", [A1], , , xlByRows, xlPrevious).Row + 1
    
    ' Provider OLEDB Provider
    Cn.Provider = "SQLOLEDB.1"
    
    ' Connectionstring
    Cn.ConnectionString = "Password=meinpasswort;" & _
    "Persist Security Info=True;" & _
    "User ID=meinuser;" & _
    "Initial Catalog=meinedatenbank;" & _
    "Data Source=SERERNAMEODERIPADRESSE"
    
    ' Verbindung öffnen
    Cn.Open
            
    For i = 1 To letzteZeile
        materialnr = Tabelle1.Cells(i, 1)
        prdha = Tabelle1.Cells(i, 2)
        Cn.Execute ("INSERT INTO prdha(material, prdha) " & _
                         "VALUES('" + materialnr + "','" + prdha + "')")
    Next i
    
    ' Verbindung schließen
    Cn.Close
    
End Sub

Security Event Log auslesen bei Microsoft Windows Server

Das nachfolgende Konsolenprogramm gibt eine Liste des Security Event Logs beim Windows Server aus. Hier sieht man welche Person zu welchem Zeitpunkt eine Windows-Authentifizierung über eine Windows/ASP.NET-Anwendung am IIS Server getan hat.

namespace APITester
{
class Program
{
static void Main(string[] args)
{
EventLog test = new EventLog();
test.Log = „Security“;

for (int i = 0; i < test.Entries.Count; i++) { try { Console.WriteLine(test.Entries[i].TimeGenerated + " " + test.Entries[i].UserName); } catch (Exception e) { } } Console.ReadLine(); } } }[/javascript]

Zugriff von VBA auf Microsoft SQL Server

Zunächst wird (z.B. in Excel) im VBA-Editor im Menü „Verweise“ der Verweis „Microsoft ActiveX data Object“ hinzugefügt, damit man Zugriff auf die ADO-Klassen bekommt.

Private Sub CommandButton1_Click()
    Dim Cn As New ADODB.Connection
    Dim Rs As New ADODB.Recordset
    
    Cn.Provider = "SQLOLEDB.1"
    
    Cn.ConnectionString = "Password=daspasswort;" & _
    "Persist Security Info=True;" & _
    "User ID=deruser;" & _
    "Initial Catalog=datenbankname;" & _
    "Data Source=rechnername"
    
    Cn.Open
    
    Rs.CursorType = adOpenKeyset
    Rs.LockType = adLockPessimistic
    Rs.Open "SELECT TOP 10 * FROM globe2010juni_converted", Cn, adOpenStatic
   
    Do Until Rs.EOF
      Debug.Print Rs.Fields("compcode").Value
      Rs.MoveNext
    Loop
   
    Cn.Close
End Sub

MS SQL Server-Tabelle mit Pivot drehen (z.B. für Zeitreihenanalyse)

Problemfeldbeschreibung

Beim Arbeiten mit Daten im Microsoft SQL Server kommt man gelegentlich an den Punkt, an dem man sich das „Drehen“ von Tabellendaten als nützlich entpuppt. Anwendungsgebiete sind u.a. die Prognoserechnung, Zeitreihenanalyse oder einfach Matrizenmultiplikation.

Nehmen wir z.B. den folgenden Datensatz, er soll gedreht werden:

200901     1    2009    0    30
200902     2    2009    58    16
200903     3    2009    520    15
200906     6    2009    780    15
200809     9    2008    0    7
200909     9    2009    260    8
200810    10    2008    0    

Der unerfahrene Datenbankentwickler beginnt dann häufig mit der Implementierung von langsamen Cursorskripten á la

DECLARE @compcode varchar(255)
DECLARE @market varchar(255)
DECLARE @salesdiv varchar(255)
DECLARE @material varchar(255)
DECLARE @month int
DECLARE @year int
DECLARE @bruttoumsatz varchar(255)
DECLARE @amount varchar(255)
DECLARE @spalte varchar(255)

DECLARE mmg_cursor CURSOR FOR
SELECT compcode, market, salesdiv, material, month, year, bruttoumsatz, amount
FROM cube WHERE CAST(CAST(year as varchar)+(CASE WHEN month<10 THEN ('0'+CAST(month as varchar)) ELSE CAST(@month as varchar) END ) as int) >=200610

OPEN mmg_cursor

FETCH NEXT FROM mmg_cursor INTO @compcode,@market, @salesdiv, @material,
@month, @year, @bruttoumsatz, @amount

WHILE @@FETCH_STATUS = 0
BEGIN

SET @spalte=CAST(@year as varchar)+(CASE WHEN @month<10 THEN ('0'+CAST(@month as varchar)) ELSE CAST(@month as varchar) END ) EXEC('UPDATE hypercube SET ['+@spalte+']='+@amount+' WHERE [compcode]='''+@compcode+''' AND [salesdiv]='''+@salesdiv+''' AND [market]='''+@market+''' AND [material]='''+@material+''' ') FETCH NEXT FROM mmg_cursor INTO @compcode,@market, @salesdiv, @material, @month, @year, @bruttoumsatz, @amount END DEALLOCATE mmg_cursor [/javascript]

Ansatz

Es bedarf keines Benchmarks oder einer Messung um festzustellen, dass diese Methode sehr zeitaufwändig und unzeitgemäß ist. Microsoft rät von solchen Cursor-Skripten ab und rät von der Nutzung der PIVOT-Funktion, die an Performance und Handhabung kaum zu überbieten ist.

Lösung

Das folgende SQL-Skript dreht die Tabelle mit PIVOT (Die MS SQL Server-Datenbank muss hierzu mind. auf MSSQL2005-Kompatibilität gestellt werden.

declare @columns varchar(max)
declare @columnsoben varchar(max)
declare @count int
declare @max int
declare @current int
declare @sql nvarchar(max)
declare @t table(id int identity(1,1), datekey int)
insert into @t select distinct datekey from vglobe2 order by 1
set @columns = “
set @columnsoben = “
set @count = 1
set @max = (select max(id) from @t)
while (@count <= @max) begin set @current = (select datekey from @t where id = @count) set @columns = @columns + '[' + cast(@current as varchar(6)) +'], ' set @columnsOben = @columnsOben + '[' + cast(@current as varchar(6)) +'],' + '0 as [' + cast(@current as varchar(6)) +'stat],' set @count = @count+1 end set @columns = substring(@columns, 1, len(@columns)-1) -- letztes komma set @columnsOben = substring(@columnsOben, 1, len(@columnsOben)-1) -- letztes komma set @sql = 'SELECT Artikel, ' + @columnsoben + N' FROM (SELECT artikel, bruttoumsatz , datekey FROM vglobe2) p PIVOT ( sum(bruttoumsatz) FOR datekey in (' + @columns + N') ) AS pvt ORDER BY 1' print @sql exec sp_executesql @sql [/sql]

Kostenloses Tool um SAP Rollen zu vergleichen / SAP Role Compare

Problemfeldbeschreibung

In großen Unternehmen sind die einzelnen Businessabteilungen so organisiert, dass die IT-Schnittstelle über eine Person mit IT-Background realisiert wird, diese jedoch nicht zwangsläufig von der IT gestellt wird. Hierbei kann es sich um eine „ehrenamtliche“ Tätigkeit handeln, die nichts mit der Hauptaufgabe des IT-Koordinators zu tun hat.

Die Koordinatoren besitzen besondere Rechte für die Vergabe von Rollen in der Transaktion SU01, welche auf bestimmte Länder oder Anwendungen begrenzt wird und den jeweiligen Businessbereich abdeckt.

Bei einem großem unübersichtlichen Rollenaufkommen bleibt den IT-Koordinatoren nur der Vergleich von Rollen, um herauszufinden welche Berechtigungen jemand benötigt oder welche ihm weggenommen werden sollen.

Aufgabenstellungen

Die Sachbearbeiter kommen zu den IT-Koordinatoren und diese bekommen dann die folgenden Frage- und Aufgabenstellungen

  • Die Person x benötigt die gleichen Berechtigungen auf Transaktion y wie die Person z hat.
  • Die Person x benötigt zusätzlich (alte Rechte bleiben erhalten) alle Berechtigungen im Testsystem, die sie im Produktivsystem hat
  • Die Person x braucht zusätzlich (alte Rechte bleiben erhalten) die gleichen Berechtigungen wie Person z.
  • u.v.m….

Anhand der Vielfalt von Fragestellungen stellen sich jetzt dem IT-Koordinator die folgenden Fragen

  • Welche Rollen hat die Person z an der Transaktion y, die Person x nicht hat?
  • Welche Rollen hat die Person x im Testsystem, die sie nicht im Produktivsystem hat?
  • Welche Rollen hat Person x die Person z nicht hat?
  • u.v.m. …

Ansatz

Die einzige Möglichkeit die dem Koordinator bleibt ist der Vergleich von Rollen zu Transaktionen über die Transaktion SUIM (Infosystem). Damit lassen sich die o.g. Anwendungsfälle alleine nicht abdecken. Vielmehr bietet die Transaktion die Möglichkeit eine Liste von Rollen

  • … nach Transaktionszuordnung
  • … nach Benutzerzuordnung
  • u.v.m. …

abzufragen. Diese Rollen müssten „händisch“ verglichen werden. Bei kryptischen Rollennamen nach dem Prinzip „CA_XXXX_LE44_MB“ und einer Anzahl von 200 Rollen pro Person oder Transaktion, artet das für eine „ehrenamtliche“ Tätigkeit in viel Arbeit aus.

Lösung

Vorgehensweise

Um diesen Vorgang wesentlich effizienter und effektiver zu gestalten, kann man die kostenlose Web-Anwendung (Flash) „Sap Role Compare“ benutzen. Hierbei ist die Vorgehensweise die folgende.

1.) Transkation SUIM aufrufen
2.) Die gewünschte Liste (z.B. Rollen nach Transaktionszuordnung) wählen
3.) Benutzer/Transaktion auswählen und auf die Uhr mit dem grünen Häkchen links oben klicken
4.) Wichtig: In der Ergebnisliste mit der Scrollleiste rechts GANZ RUNTER scrollen (sonst kommen die Daten nur teilweise in die Zwischenablage)
5.) Auf die Kopfzeile der Spalte klicken, in der die Rollennamen stehen, so dass ALLE Rollennamen selektiert sind.
6.) Rechte Maustaste->kopieren und im ersten Feld links der Webanwendung (Rollen (x) ) mit rechte Maustaste->einfügen (jetzt hätten wir alle Rollen der Transaktion, falls nach Transaktionname ausgewählt wurde).
7.) Wieder in SUIM den Benutzer/ die Transaktion auswählen, mit der verglichen werden soll (wir hätten jetzt die Rollen der Transaktion im ersten Feld (Rollen (x)) und kopieren ins zweite Feld (Rollen (z) )  die Rollen des Benutzers)
8.) Wieder ganz runter scrollen (nie vergessen), Spalte markieren, Rechte Maustaste->Kopieren und in Rollen(z) einfügen
9.) Den Button mit dem GLEICH-Zeichen klicken

Ergebnis

Wir erhalten jetzt 3 Ergebnismengen:

1.) Die Rollen, die in Rollen (x) und in Rollen (z) gleich sind
2.) Die Rollen, die in Rollen (x) und NICHT in Rollen (z)  sind
3.) Die Rollen, die in Rollen (z) und NICHT in Rollen (x)  sind

Mit diesen Mengen lassen sich ALLE Anwendungsfälle abdecken, die nicht durch die Transaktion SUIM unterstützt werden.

Download

Die Flash-Anwendung lässt sich hier herunterladen (rechte Maustaste -> Ziel speichern unter) …

Massenimport von Daten über MS SQL Server Management Studio

Problem

Eine große CSV Datei soll in eine Tabelle importiert werden. Hierbei sollen nicht alle Felder importiert werden.

Ansatz

Die Verwendung des Kommandos BULK INSERT wurde von Microsoft für sehr große Dateien vorgesehen, um den SQL Server ohne die Verwendung von langsamen Cursor-Iterationen zu füllen

Lösung

Vorgehensweise

Erstellen einer Tabelle mit CREATE TABLE in die importiert wird
Zunächst erstellt man eine Tabelle in die importiert werden soll, hier wird eine fiktive Tabelle RAWBEIL erstellt.

CREATE TABLE [dbo].[rawbeil](
	[gruppe] [varchar](255) NULL,
	[bezeichnung] [varchar](255) NULL,
	[feldname] [varchar](255) NULL,
	[sapcolumn] [varchar](50) NULL,
	[saptable] [varchar](50) NULL
)

Erstellen einer IMPORT-Datei für die Feldzuweisung

Der RECORD-Tag muss den kompletten Aufbau der CSV-Datei enthalten; der ROW-Teil darf enthält die Tabellenspalten, in die importiert wird. FIELD ID wird auf COLUMN SOURCE gemappt. ROW/COLUMN SOURCE kann auch Lücken enthalten, wenn man Felder aus der CSV Datei weglassen möchte..

&lt;?xml version="1.0" ?>
&lt;BCPFORMAT xmlns="http://schemas.microsoft.com/sqlserver/2004/bulkload/format" 
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	&lt;RECORD>
		&lt;FIELD ID="1" xsi:type="CharTerm" TERMINATOR=";" MAX_LENGTH="255"/>
		&lt;FIELD ID="2" xsi:type="CharTerm" TERMINATOR=";" MAX_LENGTH="255" />
		&lt;FIELD ID="3" xsi:type="CharTerm" TERMINATOR=";" MAX_LENGTH="255" />
		&lt;FIELD ID="4" xsi:type="CharTerm" TERMINATOR=";" MAX_LENGTH="255" />
		&lt;FIELD ID="5" xsi:type="CharTerm" TERMINATOR=";" MAX_LENGTH="255" />
		&lt;FIELD ID="6" xsi:type="CharTerm" TERMINATOR=";" MAX_LENGTH="255" />
		&lt;FIELD ID="7" xsi:type="CharTerm" TERMINATOR=";" MAX_LENGTH="255" />
		&lt;FIELD ID="8" xsi:type="CharTerm" TERMINATOR=";" MAX_LENGTH="255" />
		&lt;FIELD ID="9" xsi:type="CharTerm" TERMINATOR=";" MAX_LENGTH="255" />
		&lt;FIELD ID="10" xsi:type="CharTerm" TERMINATOR=";" MAX_LENGTH="255" />
		&lt;FIELD ID="11" xsi:type="CharTerm" TERMINATOR=";" MAX_LENGTH="255" />
		&lt;FIELD ID="12" xsi:type="CharTerm" TERMINATOR=";" MAX_LENGTH="255" />
		&lt;FIELD ID="13" xsi:type="CharTerm" TERMINATOR=";" MAX_LENGTH="255" />
		&lt;FIELD ID="14" xsi:type="CharTerm" TERMINATOR="\r\n" 
                  MAX_LENGTH="255" />
	&lt;/RECORD>
	&lt;ROW>
		&lt;COLUMN SOURCE="1" NAME="gruppe" xsi:type="SQLVARYCHAR" />
		&lt;COLUMN SOURCE="2" NAME="bezeichnung" xsi:type="SQLVARYCHAR" />
		&lt;COLUMN SOURCE="3" NAME="feldname" xsi:type="SQLVARYCHAR" />
		&lt;COLUMN SOURCE="4" NAME="sapcolumn" xsi:type="SQLVARYCHAR" />
		&lt;COLUMN SOURCE="5" NAME="saptable" xsi:type="SQLVARYCHAR" />
	&lt;/ROW>
&lt;/BCPFORMAT>

SQL Anweisung

CODEPAGE wird verwendet um das Umlaute-Problem zu beheben, FIRSTROW ist die erste Spalte ab der aus CSV importiert wird (meistens will man den Header ausschließen).

DECLARE @sql nvarchar(4000)
SET @sql =
'BULK INSERT rawbeil FROM ''D:\web\karpbjde\import\db.csv''
WITH (
FORMATFILE = ''D:\web\karpbjde\import\beilmade.fmt'',
CODEPAGE=''raw'',
FIRSTROW = 2
)'
EXEC(@sql)

BULK INSERT mit Typecast in einen bestimmten Datentyp

/* Bulk-Import von einer .CSV-Datei in eine Tabelle incl. der Datentypen
* es werden Zahlen im Vormat
* xxx,yy
* xxx.yy
* -xxx,yy
* -xxx.yy
* xxx,yy-
* xxx.yy
* unterstützt. Diese werden als Money-Datentyp convertiert
* Bulk-Import
*
* gewidmet an Björn K.von Nico Gerbig
*/ 

-- Zuerst als Varchar lesen, dann richtig
create table ordersbulk (col1 varchar(50), col2 varchar(50), col3 varchar(50));
create table ordersbulk2 (col1 varchar(50), col2 money, col3 money) 

-- Eintragen in Tabelle mit Varchars
BULK INSERT OrdersBulk
    FROM 'c:\test.csv.txt'
    WITH
    (
        --FIRSTROW = 2,
        FIELDTERMINATOR = ';',
        ROWTERMINATOR = '\n'
    ) 

-- und Kopieren in die Tabelle mit den richtigen Datentypen
insert into ordersbulk2 (col1, col2, col3)
    select col1,
          dbo.ConvertVarcharToMoney(col2),
          dbo.ConvertVarcharToMoney(col3)
    from ordersbulk
go
-- testen
select * from ordersbulk2
go 

-- dafür notwendige funktion (c)  -=e!ninio=-
create function ConvertVarcharToMoney(@abc varchar(255))
    returns money
as begin
    declare @invert money;
    declare @index int;
    declare @newabc money;
    set @invert = 1;
    set @abc = Replace(@abc,',','.')
    set @abc = Replace(@abc,' ','')
    set @index = CHARINDEX('-',@abc)
    if @index &gt; 0
    begin
        set @invert = -1;
        set @abc = Replace(@abc, '-', '')
    end
    set @newabc = CAST(@abc as money) * @invert
    return @newabc
end
go 

-- Test für die Funktion selbst
select dbo.ConvertVarcharToMoney('13,41')
select dbo.ConvertVarcharToMoney('13.41')
select dbo.ConvertVarcharToMoney('-13,41')
select dbo.ConvertVarcharToMoney('13,41-')
select dbo.ConvertVarcharToMoney('- 13,41')
select dbo.ConvertVarcharToMoney('13,41 -')

Kostenloses Tool um Daten aus der SAP Transaktion SE16 per BULK INSERT in den Microsoft SQL Server importieren

Für schnelle Instant-Reportings, die praktisch auf „Knopfdruck“ benötigt werden, besteht in großen Unternehmen durch Restriktionen der IT oft nicht die Möglichkeit über SAP Standardschnittstellen auf Funktionsbausteine oder BAPIs zuzugreifen. Benötigt man die Versorgung von SAP Stammdaten einer Nicht-SAP-Lösung, welche mit vorrübergehenden Inkonsistenzen betrieben werden kann, lassen sich Stammdaten über die Transaktion SE16 besorgen. Diese können anschließend in den Microsoft SQL Server übertragen werden.

Die Vorteile von replizierten Daten liegen auf der Hand:

  • Keine Belastung des SAP Systems. Der SQL Server übernimmt die Anfragelast.
  • Höhere Performance (vorhersehbare Leistungseinbrüche führen nicht zum Wegbrechen der Datenquelle, deshalb…)
  • Höhere Verfügbarkeit der Daten
  • Unsichere Daten (z.B. Personalinformationen) können von Stammdaten getrennt werden
  • Ein fachkundiger Anwender kann entscheiden ob bestimmte Daten, die z.B. für Kanbanzwecke/Lieferantenreportings (VMI)/… benötigt werden, im Internet verfügbar sein dürfen. Diese Daten könnten auf einen einfachen MS SQL Server in der DMZ repliziert werden.
  • Die Daten können über eine Web-Anwendung und eine beliebige Web-Technologie eingelesen werden. Ein Web-Entwickler hat die volle Kontrolle auf relationaler Ebene und kann genau bestimmen, was  mit den Daten geschieht (keine Seiteneffekte durch unbekannte Logik in den SAP-Funktionsbausteinen).

Herunterladen von SAP Stammdaten aus der Transaktion SE16

Als Anmeldesprache bitte Sicherheitshalber „DE“ benutzen

Schritt 1: Tabelle wählen
Schritt 1: Tabelle wählen
Zweiter Bildschirm SE16
Zweites Bild: Tabellen anzeigen mit SE16 (hier bitte die maximale Trefferzahl auf 9999999 erhöhen)
Schritt 3: Download im Kontextmenü
Schritt 3: Download im Kontextmenü
Schritt 4: Unkovertiert auswählen und Dateiname angeben
Schritt 4: Unkovertiert auswählen und Dateiname angeben

Hierfür wird die Transaktion SE16 zunächst aufgerufen (Berechtigungen müssen vorhanden sein) und die entsprechende Stammdatentabelle selektiert.

Interessante Tabellen für den Materialstamm sind:

  • MAKT (Materialtexte)
  • MARA
  • MARC
  • MARD
  • MPOP
  • ….

Nachdem die Liste angezeigt wird, öffnet man mit einem Rechtsklick auf der Tabelle das Kontext-Menü und wählt die Option „Download“.

Für einen schnellen Download wählen wir die Option „unkonvertiert“. Die Option „Tabellenkalkulation“ ist aufgrund älterer Office-Versionen nur für eine Datensatzmenge von 65000 Einträgen interessant.

Als Download-Ort nehmen wir C:\Temp\mattexte.txt . Öffnet man die Heruntergeladenen Daten erhält man ein scheinbar unmögliches Format der Form:

Tabelle:        MAKT
Angezeigte Felder:  5 von  5  Feststehende Führungsspalten:  3  Listbreite 0250
----------------------------------------------------------------------------------------------------------------------
| |MANDT|MATNR               |SPRAS|MAKTX                                   |MAKTG                                   |
----------------------------------------------------------------------------------------------------------------------
| |100  |00022837            |E    |Stoßdämpfer      3.8.01758              |Stossdaempfer    3.8.01758              |
| |100  |0-275-W-5-0-NAG     |E    |Wagenständer      275CM                 |Wagenstaender 275CM                     |

Durchforstet man die runtergeladene Datei, stellt man fest das der Header-Anteil öfter vorkommt, je mehr Datensätze man runtergeladen hat. Bei einer Menge ab 1000000 Stück wiederholt sich der Header regelmäßig und scheinbar willkürlich, so dass es schwer erscheint ein Import-Tool dafür zu implementieren.

Doch dieses Format lässt sich ohne Probleme mit dem vorgestelltem .NET-Programm in einer hohen Geschwindigkeit (1.000.000 Datensätze in 1 Minute)  in den Microsoft SQL Server importieren. Hierbei wird das „BULK INSERT“-Verfahren verwendet. Dabei wird nicht 1.000.000 Mal ein INSERT-Statement ausgeführt, sondern die Datei binär zum Server übertragen.

Hochladen der SAP-Stammdaten in den MS SQL Server

Schritt 1: Unkovertierte Datei aus SE16 wählen^
Schritt 1: Unkovertierte Datei aus SE16 wählen
Schritt 2: Datenbankverbindung einrichten, testen und Zieltabellenname wählen (Create Statement wird automatisch anhand des Headers in der Textdatei generiert)
Schritt 2: Datenbankverbindung einrichten, testen und Zieltabellenname wählen (Create Statement wird automatisch anhand des Headers in der Textdatei generiert)
Schritt 3: Weiter klicken für hochladen
Schritt 3: Weiter klicken für hochladen

DOWNLOAD DES KOSTENLOSES IMPORTERS VON SE16 ZU MS SQL SERVER

FluorineFX RTMP/Messaging konfigurieren

In einer Microsoft IIS Umgebung greift der Flex-Entwickler auf die kostenlose Middleware FluorineFX zurück. Hier gibt es 4 wichtige Dateien, die für die Konfiguration des Frameworks zuständig sind.

  • data-management-config.xml
  • messaging-config.xml
  • remoting-config.xml
  • services-config.xml

Dem Java-Entwickler sind diese Dateien ebenfalls aus der Java-Bibliothek BlazeDS bekannt.

Bei einem Kompiliervorgang wird die Datei services-config.xml in den Compilersettings angegeben.
[Rechtsklick auf Projekt in Workspace -> Properties -> Flex Compiler -> Additional compiler arguments]

-locale en_US -services services-config.xml -context-root /

Es gibt die Möglichkeit einen direkten Pfad mit einer Netzwerkfreigabe auf die services-config.xml des IIS-Servers anzugeben oder diese Datei lokal zu kopieren und die URL’s

  • RTMP – Real Time Messaging Protocol – was für Messaging benötigt wird und..
  • die normalen AMF Services (Actionscript Messaging Format – dient lediglich der RPC-Kommunikation wie GWT RPC oder SOAP Webservices, hierauf wird in diesem Artikel nicht eingegangen)

anzupassen. Hierbei kann man eine absolute Pfadangabe machen, wenn man den IIS nicht lokal auf dem Entwicklerrechner installiert hat (dies ist das Vorgehen in diesem Artikel). Auf dem Server, auf dem sich redundant (in der deployten FluorineFX-Anwendung im WEB-INF-Verzeichnis) diese Dateien befinden, werden allerdings entgegen den nachfolgenden Beispielen anstelle der absoluten Pfade die Platzhalter rtmp://{server.name}:2037 und http://{server.name}:{server.port}/{context.root}/Gateway.aspx verwendet.

services-config.xml:

<?xml version="1.0" encoding="utf-8" ?> 
<services-config>
    <services>
      <service-include file-path="remoting-config.xml" />
      <service-include file-path="messaging-config.xml" />
    </services>
       
    <channels>
        <channel-definition id="my-amf" class="mx.messaging.channels.AMFChannel">
	        <endpoint uri="http://rechner3j.asag.asap.com:4321/Gateway.aspx" 
                 class="flex.messaging.endpoints.AMFEndpoint"/> 
        </channel-definition>

        <channel-definition id="my-rtmp" class="mx.messaging.channels.RTMPChannel">
            <endpoint uri="rtmp://rechner3j.asag.asap.com:2037" 
             class="flex.messaging.endpoints.RTMPEndpoint"/>
            <properties>
                <idle-timeout-minutes>0</idle-timeout-minutes>
            </properties>
        </channel-definition>

    </channels>
</services-config>

remoting-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<service id="remoting-service" class="flex.messaging.services.RemotingService" 
 messageTypes="flex.messaging.messages.RemotingMessage">
    <adapters>
        <adapter-definition id="dotnet" class="FluorineFx.Remoting.RemotingAdapter" 
	default="true"/>
    </adapters>

    <0default-channels>
        <channel ref="my-amf"/>
    </default-channels>

  <destination id="GenericDestination">
    <properties>
      <source>*</source>
    </properties>
  </destination>  

  <destination id="fluorine">
    <properties>
       <source>*</source>
    </properties>      
  </destination>
</service>

messaging-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<service id="message-service" class="flex.messaging.services.MessageService" 
 messageTypes="flex.messaging.messages.AsyncMessage">
     <!-- DO NOT CHANGE <adapters> SECTION-->
    <adapters>
	<adapter-definition id="messagingAdapter" 
        class="FluorineFx.Messaging.Services.Messaging.MessagingAdapter" 
        default="true"/>
    </adapters>

    <destination id="chat">
		<adapter ref="messagingAdapter"/>
    	<properties>
	        <network>
            	<session-timeout>0</session-timeout>
        	</network>
    	</properties>
    	<channels>
         	<channel ref="my-rtmp"/>
    	</channels>
    </destination>
</service>

FluorineFX geht folgendermaßen vor:
Sobald die Server-Seite (also nicht die Dateien mit den absoluten Pfaden wie obenstehend) mit der Definition für einen RTMP-Channel versehen wurde, wird ein Treffer auf einer beliebigen ASPX-Seite innerhalb der FluorineFX-Webanwendung dazu führen, das ein Socket auf dem definierten Port gestartet wird (oben wäre dass dann Port 2037 für RTMP). Ob es funktioniert hat, lässt sich mit einem telnet-Login prüfen. Hierzu einfach mal in der Eingabeaufforderung (CMD)

telnet hostname 2037

eingeben. Sollte der RTMP-Server nicht laufen, bitte mal auf dem Server per Remotekonsole einloggen und in der Eingabeaufforderung mit

telnet 127.0.0.1 2037

den Login versuchen.
Der Login funktioniert, wenn der Bildschirm gelöscht wird und ein Cursor in der Ecke links oben steht. Dieser Modus kann meistens mit STRG+C abgebrochen werden, sollte dies nicht funktionieren kann man auch die Eingabeaufforderung schließen.

Wichtig!!! Damit sie die Flex-Anwendung überhaupt mit der Messaging-Funktionalität und den obengenannten Konfigurationsdateien kompilieren können benötigen sie 2 Dateien, die sie in der Installation der Adobe Livecycle Services (LSDS) finden.

  • fds_rb.swc
  • fds.swc

Sie müssen einen Account bei Adobe registrieren und die LCDS runterladen, anschließend kopieren sie die o.g. Dateien in das LIB-Verzeichnis ihrer Flex-Anwendung und es kompiliert!!! Glückwunsch. So nun ein kleines Beispiel für einen gut funktionierenden Chat:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application creationComplete="{creationComplete()}" 
    xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
	<mx:Script>
	<![CDATA[
	    import mx.controls.Alert;
	    import mx.rpc.events.FaultEvent;
            import mx.rpc.events.ResultEvent;
    	    import mx.messaging.*;
    	    import mx.messaging.messages.*;
    	    import mx.messaging.events.*;
    	    import mx.core.Application; 
    		
    	    public function creationComplete():void
    	    {
    		consumer.subscribe();
    	    }
   
            private function messageHandler(event:MessageEvent):void
            {
    		txtLog.text += event.message.body.toString()+'\r';
    	    }
   
            private function messagefaultHandler(event:MessageFaultEvent):void
    	    { 
        	Alert.show(event.faultString, "Error"); 
    	    }
   
            public function sendMessage():void
	    {
        	var message:AsyncMessage = new AsyncMessage();
       		message.body = txtOutput.text;
       		producer.send(message);
       		txtOutput.text="";
    	    }
	]]>
       </mx:Script>
       <mx:Consumer id="consumer" destination="chat" 
	   message="messageHandler(event)" fault="messagefaultHandler(event)"/>
       <mx:Producer id="producer" destination="chat" fault="messagefaultHandler(event)"/>
       <mx:VBox width="100%" height="100%">
    	
       <mx:HBox width="100%">
         <mx:TextInput width="100%" id="txtOutput"/>
         <mx:Button label="Skicka" click="sendMessage()"/>
       </mx:HBox>
    	<mx:TextArea height="100%" width="100%" editable="true" id="txtLog"/>
    </mx:VBox>
</mx:Application>

by Björn Karpenstein