Data Operations

Master data operations including querying, filtering, aggregations, and real-time data manipulation in Qlik applications.

Data Operations Overview

The Qlik SDK provides powerful data operation capabilities including hypercube queries, field operations, selections, and real-time data updates. These operations form the foundation of interactive analytics applications.

Queries
HyperCube and ListObject queries for data retrieval
Selections
Interactive filtering and field selections
Aggregations
Real-time calculations and aggregations
Export
Data export in multiple formats

HyperCube Operations

Creating Basic HyperCubes

Fundamental hypercube operations for data analysis

typescript
import Qlik from 'qlik';

class DataOperations {
  private app: any;

  constructor(app: any) {
    this.app = app;
  }

  // Create a basic sales analysis hypercube
  async createSalesHyperCube(): Promise<any> {
    const hypercubeDef = {
      qInfo: { qType: 'sales-analysis' },
      qHyperCubeDef: {
        qDimensions: [
          {
            qDef: {
              qFieldDefs: ['Product'],
              qFieldLabels: ['Product Name']
            },
            qNullSuppression: true
          },
          {
            qDef: {
              qFieldDefs: ['Region'],
              qFieldLabels: ['Sales Region']
            }
          }
        ],
        qMeasures: [
          {
            qDef: {
              qDef: 'Sum(Sales)',
              qLabel: 'Total Sales'
            },
            qSortBy: { qSortByNumeric: -1 } // Descending
          },
          {
            qDef: {
              qDef: 'Avg(Price)',
              qLabel: 'Average Price'
            }
          },
          {
            qDef: {
              qDef: 'Count(distinct OrderID)',
              qLabel: 'Order Count'
            }
          }
        ],
        qInitialDataFetch: [
          {
            qLeft: 0,
            qTop: 0,
            qWidth: 5, // 2 dimensions + 3 measures
            qHeight: 100 // First 100 rows
          }
        ],
        qSuppressZero: true,
        qSuppressMissing: true
      }
    };

    try {
      const sessionObject = await this.app.createSessionObject(hypercubeDef);
      console.log('✅ Sales hypercube created');
      return sessionObject;
    } catch (error) {
      console.error('❌ Failed to create hypercube:', error);
      throw error;
    }
  }

  // Get data from hypercube
  async getHyperCubeData(sessionObject: any, page?: { top: number; left: number; width: number; height: number }): Promise<any> {
    try {
      const layout = await sessionObject.getLayout();
      const hypercube = layout.qHyperCube;

      // Use provided page or get first page
      const dataPage = page || {
        qLeft: 0,
        qTop: 0,
        qWidth: hypercube.qSize.qcx,
        qHeight: Math.min(hypercube.qSize.qcy, 1000) // Max 1000 rows
      };

      const data = await sessionObject.getHyperCubeData('/qHyperCubeDef', [dataPage]);
      
      return {
        headers: this.extractHeaders(hypercube),
        data: this.processHyperCubeData(data[0].qMatrix),
        totalRows: hypercube.qSize.qcy,
        totalColumns: hypercube.qSize.qcx
      };

    } catch (error) {
      console.error('Failed to get hypercube data:', error);
      throw error;
    }
  }

  private extractHeaders(hypercube: any): string[] {
    const dimensionHeaders = hypercube.qDimensionInfo.map((dim: any) => 
      dim.qFallbackTitle || dim.qFieldLabels?.[0] || 'Dimension'
    );
    
    const measureHeaders = hypercube.qMeasureInfo.map((measure: any) => 
      measure.qFallbackTitle || 'Measure'
    );
    
    return [...dimensionHeaders, ...measureHeaders];
  }

  private processHyperCubeData(matrix: any[]): any[] {
    return matrix.map(row => 
      row.map((cell: any) => ({
        text: cell.qText,
        number: cell.qNum,
        state: cell.qState,
        isNumeric: cell.qNum !== 'NaN' && cell.qNum !== null
      }))
    );
  }

  // Pagination for large datasets
  async getPaginatedData(sessionObject: any, page: number, pageSize: number = 100): Promise<any> {
    const offset = page * pageSize;
    
    return await this.getHyperCubeData(sessionObject, {
      top: offset,
      left: 0,
      width: 10, // Adjust based on your dimensions + measures
      height: pageSize
    });
  }
}

// Usage
async function analyzeSalesData(app: any) {
  const dataOps = new DataOperations(app);
  
  // Create hypercube
  const salesCube = await dataOps.createSalesHyperCube();
  
  // Get first page of data
  const salesData = await dataOps.getHyperCubeData(salesCube);
  
  console.log('Sales Analysis Results:');
  console.log('Headers:', salesData.headers);
  console.log('Total Rows:', salesData.totalRows);
  console.log('Sample Data:', salesData.data.slice(0, 5));
  
  // Get specific page
  const page2Data = await dataOps.getPaginatedData(salesCube, 1, 50);
  console.log('Page 2 Data:', page2Data.data.length, 'rows');
}

Selections and Filtering

Interactive Selections

Implement interactive filtering and field selections

typescript
class SelectionManager {
  private app: any;
  private selectionState = new Map<string, any>();

  constructor(app: any) {
    this.app = app;
    this.setupSelectionListeners();
  }

  // Make field selections
  async selectInField(fieldName: string, values: string[]): Promise<void> {
    try {
      const field = await this.app.getField(fieldName);
      await field.selectValues(values);
      
      this.selectionState.set(fieldName, values);
      console.log(`✅ Selected in ${fieldName}:`, values);
      
      // Emit selection event
      this.emitSelectionChanged(fieldName, values);
      
    } catch (error) {
      console.error(`Selection failed for ${fieldName}:`, error);
      throw error;
    }
  }

  // Clear selections in specific field
  async clearField(fieldName: string): Promise<void> {
    try {
      const field = await this.app.getField(fieldName);
      await field.clear();
      
      this.selectionState.delete(fieldName);
      console.log(`✅ Cleared selections in ${fieldName}`);
      
      this.emitSelectionChanged(fieldName, []);
      
    } catch (error) {
      console.error(`Clear failed for ${fieldName}:`, error);
      throw error;
    }
  }

  // Clear all selections
  async clearAll(): Promise<void> {
    try {
      await this.app.clearAll();
      this.selectionState.clear();
      console.log('✅ All selections cleared');
      
      this.emitSelectionChanged('*', []);
      
    } catch (error) {
      console.error('Clear all failed:', error);
      throw error;
    }
  }

  // Get current selection state
  getSelectionState(): Map<string, any> {
    return new Map(this.selectionState);
  }

  // Search and select in field
  async searchAndSelect(fieldName: string, searchTerm: string): Promise<string[]> {
    try {
      const field = await this.app.getField(fieldName);
      
      // Get field data for search
      const listObject = await this.app.createSessionObject({
        qInfo: { qType: 'listbox' },
        qListObjectDef: {
          qDef: { qFieldDefs: [fieldName] },
          qInitialDataFetch: [{ qLeft: 0, qTop: 0, qWidth: 1, qHeight: 1000 }],
          qAutoSort: true
        }
      });

      const layout = await listObject.getLayout();
      const values = layout.qListObject.qDataPages[0].qMatrix
        .map((item: any) => item[0].qText)
        .filter((value: string) => 
          value.toLowerCase().includes(searchTerm.toLowerCase())
        );

      if (values.length > 0) {
        await this.selectInField(fieldName, values);
      }

      // Clean up
      await listObject.destroy();
      
      return values;

    } catch (error) {
      console.error(`Search and select failed for ${fieldName}:`, error);
      throw error;
    }
  }

  // Advanced selection with conditions
  async selectWithCondition(fieldName: string, condition: (value: any) => boolean): Promise<void> {
    try {
      // Create temporary listbox to get all values
      const listObject = await this.app.createSessionObject({
        qInfo: { qType: 'condition-listbox' },
        qListObjectDef: {
          qDef: { qFieldDefs: [fieldName] },
          qInitialDataFetch: [{ qLeft: 0, qTop: 0, qWidth: 1, qHeight: 10000 }]
        }
      });

      const layout = await listObject.getLayout();
      const allValues = layout.qListObject.qDataPages[0].qMatrix.map((item: any) => ({
        text: item[0].qText,
        number: item[0].qNum,
        state: item[0].qState
      }));

      // Filter values based on condition
      const selectedValues = allValues
        .filter(condition)
        .map(item => item.text);

      if (selectedValues.length > 0) {
        await this.selectInField(fieldName, selectedValues);
      }

      await listObject.destroy();

    } catch (error) {
      console.error(`Conditional selection failed for ${fieldName}:`, error);
      throw error;
    }
  }

  // Set up selection change listeners
  private setupSelectionListeners(): void {
    // Listen for selection changes from other sources
    // This would typically be set up through Qlik's event system
  }

  private emitSelectionChanged(field: string, values: any[]): void {
    const event = new CustomEvent('qlik:selection-changed', {
      detail: { field, values, timestamp: Date.now() }
    });
    window.dispatchEvent(event);
  }
}

// Usage examples
async function implementInteractiveFiltering(app: any) {
  const selectionManager = new SelectionManager(app);

  // Listen for selection events
  window.addEventListener('qlik:selection-changed', (event: any) => {
    console.log('Selection changed:', event.detail);
    // Update your UI components here
  });

  // Example: Product filter
  await selectionManager.selectInField('Product', ['Laptop', 'Desktop']);

  // Example: Date range (requires date field)
  await selectionManager.selectWithCondition('OrderDate', (value) => {
    const date = new Date(value.text);
    const startDate = new Date('2024-01-01');
    const endDate = new Date('2024-12-31');
    return date >= startDate && date <= endDate;
  });

  // Example: Top performers (numeric condition)
  await selectionManager.selectWithCondition('SalesAmount', (value) => {
    return value.number > 10000; // Sales > 10k
  });

  // Example: Search functionality
  const searchResults = await selectionManager.searchAndSelect('Customer', 'Corp');
  console.log('Found customers:', searchResults);
}

Data Export Operations

Export to Multiple Formats

Export Qlik data to CSV, JSON, Excel, and other formats

typescript
class DataExporter {
  private app: any;

  constructor(app: any) {
    this.app = app;
  }

  // Export hypercube data to CSV
  async exportToCSV(sessionObject: any, filename: string = 'qlik_data.csv'): Promise<void> {
    try {
      const layout = await sessionObject.getLayout();
      const hypercube = layout.qHyperCube;

      // Get all data (handle large datasets with pagination)
      const allData = await this.getAllHyperCubeData(sessionObject);
      
      // Create CSV content
      const headers = this.extractHeaders(hypercube);
      const csvRows = [headers.join(',')];

      allData.forEach(row => {
        const csvRow = row.map(cell => {
          const value = cell.text || cell.number || '';
          // Escape commas and quotes
          return `"${String(value).replace(/"/g, '""')}"`;
        });
        csvRows.push(csvRow.join(','));
      });

      const csvContent = csvRows.join('\n');
      
      // Download file
      this.downloadFile(csvContent, filename, 'text/csv');
      console.log(`✅ Data exported to ${filename}`);

    } catch (error) {
      console.error('CSV export failed:', error);
      throw error;
    }
  }

  // Export to JSON with metadata
  async exportToJSON(sessionObject: any, filename: string = 'qlik_data.json'): Promise<void> {
    try {
      const layout = await sessionObject.getLayout();
      const hypercube = layout.qHyperCube;
      const data = await this.getAllHyperCubeData(sessionObject);

      const exportData = {
        metadata: {
          exportTime: new Date().toISOString(),
          title: layout.title || 'Qlik Data Export',
          dimensions: hypercube.qDimensionInfo.length,
          measures: hypercube.qMeasureInfo.length,
          totalRows: hypercube.qSize.qcy,
          selectionState: await this.getSelectionState()
        },
        headers: this.extractHeaders(hypercube),
        dimensionInfo: hypercube.qDimensionInfo,
        measureInfo: hypercube.qMeasureInfo,
        data: data.map(row => row.map(cell => ({
          text: cell.text,
          number: cell.number,
          state: cell.state
        })))
      };

      const jsonContent = JSON.stringify(exportData, null, 2);
      this.downloadFile(jsonContent, filename, 'application/json');
      console.log(`✅ Data exported to ${filename}`);

    } catch (error) {
      console.error('JSON export failed:', error);
      throw error;
    }
  }

  // Export formatted data for Excel
  async exportToExcel(sessionObject: any, filename: string = 'qlik_data.xlsx'): Promise<void> {
    try {
      // Note: This is a simplified version. For full Excel support, 
      // you would use a library like 'xlsx' or 'exceljs'
      
      const layout = await sessionObject.getLayout();
      const hypercube = layout.qHyperCube;
      const data = await this.getAllHyperCubeData(sessionObject);

      // Create tab-separated values (TSV) for Excel compatibility
      const headers = this.extractHeaders(hypercube);
      const tsvRows = [headers.join('\t')];

      data.forEach(row => {
        const tsvRow = row.map(cell => {
          const value = cell.text || cell.number || '';
          return String(value).replace(/\t/g, ' '); // Replace tabs with spaces
        });
        tsvRows.push(tsvRow.join('\t'));
      });

      const tsvContent = tsvRows.join('\n');
      this.downloadFile(tsvContent, filename.replace('.xlsx', '.txt'), 'text/tab-separated-values');
      
      console.log('✅ Data exported in Excel-compatible format');

    } catch (error) {
      console.error('Excel export failed:', error);
      throw error;
    }
  }

  // Get all data with pagination for large datasets
  private async getAllHyperCubeData(sessionObject: any): Promise<any[]> {
    const layout = await sessionObject.getLayout();
    const hypercube = layout.qHyperCube;
    const totalRows = hypercube.qSize.qcy;
    const pageSize = 1000; // Process 1000 rows at a time
    
    let allData: any[] = [];
    let currentRow = 0;

    while (currentRow < totalRows) {
      const remainingRows = Math.min(pageSize, totalRows - currentRow);
      
      const pageData = await sessionObject.getHyperCubeData('/qHyperCubeDef', [{
        qLeft: 0,
        qTop: currentRow,
        qWidth: hypercube.qSize.qcx,
        qHeight: remainingRows
      }]);

      allData = allData.concat(pageData[0].qMatrix);
      currentRow += remainingRows;

      // Progress callback
      console.log(`Exported ${currentRow}/${totalRows} rows (${Math.round(currentRow/totalRows*100)}%)`);
    }

    return allData;
  }

  private extractHeaders(hypercube: any): string[] {
    const dimensionHeaders = hypercube.qDimensionInfo.map((dim: any) => 
      dim.qFallbackTitle || 'Dimension'
    );
    
    const measureHeaders = hypercube.qMeasureInfo.map((measure: any) => 
      measure.qFallbackTitle || 'Measure'
    );
    
    return [...dimensionHeaders, ...measureHeaders];
  }

  private async getSelectionState(): Promise<any> {
    try {
      const selectionState = await this.app.getSelectionState();
      return selectionState;
    } catch {
      return {};
    }
  }

  private downloadFile(content: string, filename: string, mimeType: string): void {
    const blob = new Blob([content], { type: mimeType });
    const url = URL.createObjectURL(blob);
    
    const link = document.createElement('a');
    link.href = url;
    link.download = filename;
    link.style.display = 'none';
    
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    
    URL.revokeObjectURL(url);
  }
}

// Usage
async function exportAnalysisResults(app: any) {
  const exporter = new DataExporter(app);
  
  // Create analysis hypercube
  const analysisObject = await app.createSessionObject({
    qInfo: { qType: 'export-analysis' },
    qHyperCubeDef: {
      qDimensions: [
        { qDef: { qFieldDefs: ['Product'] } },
        { qDef: { qFieldDefs: ['Region'] } }
      ],
      qMeasures: [
        { qDef: { qDef: 'Sum(Sales)', qLabel: 'Total Sales' } },
        { qDef: { qDef: 'Count(OrderID)', qLabel: 'Order Count' } }
      ],
      qInitialDataFetch: [{ qLeft: 0, qTop: 0, qWidth: 4, qHeight: 10000 }]
    }
  });

  // Export in different formats
  await exporter.exportToCSV(analysisObject, 'sales_analysis.csv');
  await exporter.exportToJSON(analysisObject, 'sales_analysis.json');
  await exporter.exportToExcel(analysisObject, 'sales_analysis.xlsx');
}

⚡ Data Operations Best Practices

Pagination: Use pagination for large datasets to avoid memory issues
Error Handling: Implement comprehensive error handling for all data operations
Performance: Limit initial data fetch sizes and load data on demand
State Management: Keep track of selections and filters across operations
Memory Management: Destroy session objects when no longer needed
Real-time Updates: Implement efficient polling or event-based updates
Export Optimization: Stream large exports to avoid blocking the UI
User Experience: Provide progress indicators for long-running operations