简体   繁体   中英

Build SQL Query from TreeView nodes

I'm trying to build simple sql query editor, I need to be able to edit query conditions (part after WHERE keyword).
I have tree that shows conditions as shown below:

在此处输入图片说明

I have problem with transforming nodes to proper SQL code.

I can iterate over nodes, but this gives me below result:

AND
Name = John
OR
Surname = Smith
Lastname = Smith

But I need this (valid SQL):

Name = John
AND
(
    Surname = Smith
    OR
    Lastname = Smith
)

How can I do that?

Here is code that I used to extend TreeNode and to create sample from screenshot:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        var root = new OperatorNode(OperatorType.AND);
        var treeNode = new SqlTextNode("Name", ContitionType.EQUAL, "John");
        root.Nodes.Add(treeNode);
        var orNode = new OperatorNode(OperatorType.OR);
        var node2 = new SqlTextNode("Surname",ContitionType.EQUAL, "Smith");
        var node3 = new SqlTextNode("Lastname",ContitionType.EQUAL, "Smith");
        orNode.Nodes.Add(node2);
        orNode.Nodes.Add(node3);
        root.Nodes.Add(orNode);
        treeView1.Nodes.Add(root);
        treeView1.ExpandAll();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        CallRecursive(treeView1);
    }

    private void PrintRecursive(TreeNode treeNode)
    {
        Debug.WriteLine(treeNode.Text);
        foreach (TreeNode tn in treeNode.Nodes)
        {
            PrintRecursive(tn);
        }
    }

    private void CallRecursive(TreeView treeView)
    {
        TreeNodeCollection nodes = treeView.Nodes;
        foreach (TreeNode n in nodes)
        {
            PrintRecursive(n);
        }
    }
}

public class OperatorNode : TreeNode
{
    private OperatorType _operator;

    public OperatorType Operator
    {
        get { return _operator; }
        set
        {
            if(value==_operator) return;
            _operator = value;
            Text = _operator.ToString();
        }
    }

    public OperatorNode(OperatorType @operator) : base(@operator.ToString())
    {
        _operator = @operator;
    }
}

public class SqlTextNode : TreeNode
{
    private string _fieldName;
    private ContitionType _condition;
    private string _value;


    public SqlTextNode(string fieldName, ContitionType condition, string value)
    {
        _fieldName = fieldName;
        _condition = condition;
        _value = value;
        UpdateText();
    }

    public string FieldName
    {
        get { return _fieldName; }
        set
        {
            if (value == _fieldName) return;
            _fieldName = value;
            UpdateText();
        }
    }

    public ContitionType Condition
    {
        get { return _condition; }
        set
        {
            if (value == _condition) return;
            _condition = value;
            UpdateText();
        }
    }

    public string Value
    {
        get { return _value; }
        set
        {
            if (value == _value) return;
            _value = value;
            UpdateText();
        }
    }

    private void UpdateText()
    {
        Text = string.Format("{0} {1} {2}", _fieldName, Enums.GetEnumDescription(_condition), _value);
    }
}

public enum OperatorType
{
    AND,
    OR
}

public enum ContitionType
{
    EQUAL,
    NOT_EQUAL
}

public static class Enums
{
    public static string GetEnumDescription(Enum value)
    {
        FieldInfo fi = value.GetType().GetField(value.ToString());

        DescriptionAttribute[] attributes =
            (DescriptionAttribute[])fi.GetCustomAttributes(
            typeof(DescriptionAttribute),
            false);

        return attributes.Length > 0 ? attributes[0].Description : value.ToString();
    }
}

There are two different types of nodes: OperatorNode and SqlTextNode . You can easily check the type of the node and execute the corresponding actions:

  • If the node is SqlTextNode just print it as you did it before.

  • If the node is OperatorNode you need to cover all children with () and insert the operator between them.

This algorithm can be implemented in many different ways but I will add a simple example:

private void PrintRecursive(TreeNode treeNode, int indentation)
{
    var indent = new string(' ', indentation * IndentSize);
    // SqlTextNode doesn't have children
    if (treeNode is SqlTextNode)
        Debug.WriteLine(indent + treeNode.Text);
    else
    {
        Debug.WriteLine(indent + "(");
        for (int i=0; i<treeNode.Nodes.Count; i++ )
        { 
            PrintRecursive(treeNode.Nodes[i], indentation+1);
            if (i!=treeNode.Nodes.Count-1)
            {
                // print the operator
                indent = new string(' ', (indentation+1) * IndentSize);                
                Debug.WriteLine(indent + treeNode.Text);
            }
        }
        Debug.WriteLine(indent + ")");
    }
}

If you want to add indentation into the final query you could pass throuh the reqursion function ( CallRecursive ) as a parameter and increase it when you need.

This is my version of answer:
I've overridden ToString in my custom node types:

public class OperatorNode : TreeNode
{
    private OperatorType _operator;

    public OperatorType Operator
    {
        get { return _operator; }
        set
        {
            if(value==_operator) return;
            _operator = value;
            Text = _operator.ToString();
        }
    }

    public OperatorNode(OperatorType @operator) : base(@operator.ToString())
    {
        _operator = @operator;
    }

    public override string ToString()
    {
        List<string> n = (from TreeNode node in Nodes select node.ToString()).ToList();

        return " ( " + string.Join(Environment.NewLine + _operator + " ", n) + " ) ";
    }
}

public class SqlTextNode : TreeNode
{
    private string _fieldName;
    private ContitionType _condition;
    private string _value;


    public SqlTextNode(string fieldName, ContitionType condition, string value)
    {
        _fieldName = fieldName;
        _condition = condition;
        _value = value;

        UpdateText();
    }

    public override string ToString()
    {
        return string.Format("{0} {1} {2}", _fieldName, _condition.GetDescription(), _value);
    }

    public string FieldName
    {
        get { return _fieldName; }
        set
        {
            if (value == _fieldName) return;
            _fieldName = value;
            UpdateText();
        }
    }

    public ContitionType Condition
    {
        get { return _condition; }
        set
        {
            if (value == _condition) return;
            _condition = value;
            UpdateText();
        }
    }

    public string Value
    {
        get { return _value; }
        set
        {
            if (value == _value) return;
            _value = value;
            UpdateText();
        }
    }

    private void UpdateText()
    {
        Text = string.Format("{0} {1} {2}", _fieldName, _condition.GetDescription(), _value);
    }
}

This way each node has logic responsible for creating query parts.
Having that I can generate full query using below function:

private void PrintQuery(TreeView treeView)
{
    string s = string.Empty;
    TreeNodeCollection nodes = treeView.Nodes;
    s = nodes.Cast<TreeNode>().Aggregate(s, (current, n) => current + (n.ToString() + Environment.NewLine)).Trim();

    //remove unwanted brackets
    s = s.Remove(s.Length - 1).Substring(1).Trim();
    textBox1.Text = s;
}

Output doesn't have indentations, but I don't have to show final query, this is just temporary for debug.

I'm aware that I must escape conditions values with single quotes, but that's the easy part :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM