Как эмулировать LPAD/RPAD с помощью SQLite

Мне интересно, как эмулировать функции RPAD и LPAD для SQLite, формально, в самом общем виде. Цель состоит в том, чтобы быть в состоянии сделать

LPAD(column, character, repeat)
RPAD(column, character, repeat)

Для непостоянных столбцов таблицы column, character, repeat. Если бы character и repeat были известными константами, то это было бы хорошим, жизнеспособным решением:

Но что, если вышеуказанное должно быть выполнено так:

SELECT LPAD(t.column, t.character, t.repeat) FROM t
SELECT LPAD(t.column, some_function(), some_other_function()) FROM t
SELECT LPAD(t.column, :some_bind_variable, :some_other_bind_variable) FROM t

Как вообще можно эмулировать эту функцию LPAD? Я потерялся с возможностями:

Связанный вопрос:


person Lukas Eder    schedule 04.07.2011    source источник
comment
Что вы используете для подключения к SQLite? Механизм базы данных имеет архитектуру плагинов, в которой вы можете определить дополнительные функции (UDF) и включить их в свой sql...   -  person Stobor    schedule 22.07.2012
comment
@Stobor: Хороший вопрос. Я подключаюсь с помощью неофициального драйвера JDBC. Все это должно быть реализовано в jOOQ, уровне абстракции SQL в Java. Так что, к сожалению, я не могу полагаться на возможные UDF, а только на то, что предоставляется в ядре SQLite.   -  person Lukas Eder    schedule 22.07.2012
comment
Вы разрабатываете для jOOQ или используете jOOQ API? В любом случае, я уверен, что вы можете использовать Java UDF...   -  person Stobor    schedule 22.07.2012
comment
@Stobor: я создатель jOOQ. Таким образом, я бы предпочел не создавать зависимость от этого конкретного драйвера JDBC. Но ваше решение по-прежнему неплохо для тех, кто может использовать API jOOQ. Если эти UDF можно обнаружить с помощью прагм SQLite, то jOOQ должен поддерживать их изначально!   -  person Lukas Eder    schedule 22.07.2012
comment
Нет проблем! Ваш вопрос в основном просто побудил меня понять, как это сделать самому. (Ранее я делал это на Python, поэтому подозревал, что это возможно.) jOOQ выглядит интересно, удачи с ним.   -  person Stobor    schedule 22.07.2012


Ответы (8)


Более простая версия решения @user610650, использующая hex() вместо quote() и работающая с заполнением строк в дополнение к заполнению символов:

X = padToLength
Y = padString
Z = expression

select
    Z ||
    substr(
        replace(
            hex(zeroblob(X)),
            '00',
            Y
        ),
        1,
        X - length(Z)
    );
person Steve Broberg    schedule 10.08.2018

Скопировано с http://verysimple.com/2010/01/12/sqlite-lpad-rpad-function/

-- the statement below is almost the same as
-- select lpad(mycolumn,'0',10) from mytable

select substr('0000000000' || mycolumn, -10, 10) from mytable

-- the statement below is almost the same as
-- select rpad(mycolumn,'0',10) from mytable

select substr(mycolumn || '0000000000', 1, 10) from mytable
person Sonu Kapoor    schedule 28.01.2016
comment
Это элегантное решение, за исключением (я полагаю), в случаях, когда длина значения больше, чем 10... в этом случае дополнительные левые символы усекаются. Например, SELECT SUBSTR('00000' || 'ABCDEFG', -5, 5) приводит к "CDEFG". Это может быть очень нежелательным и молчаливым поведением. - person dancow; 05.08.2020

Вы также можете PRINTF.

sqlite> SELECT PRINTF('%02d',5);
05
sqlite> SELECT PRINTF('%04d%02d',25,5);
002505
sqlite> 
person Ralph    schedule 13.06.2020
comment
Полезно только для нулевых чисел. Не работает для строк: SELECT printf('%010s', '8')' 8' - person Dmitriy Work; 29.05.2021

Вот вам еще гадости:

X = padToLength
Y = padString
Z = expression

RPAD (для LPAD вместо этого добавляется Z):

select 
    Z || 
    substr(
        replace(
            replace(
                substr(
                    quote(zeroblob(((X - length(Z) - 1 + length(Y)) / length(Y) + 1) / 2)), 
                    3
                ), 
                "'", 
                ""
            ), 
            "0", 
            Y
        ),
        1,
        (X - length(Z))
    )

Примеры:

sqlite> select "foo" || replace(replace(substr(quote(zeroblob((2 + 1) / 2)), 3, (2 - length("foo"))), "'", ""), "0", "W");
foo
sqlite> select "foo" || replace(replace(substr(quote(zeroblob((7 + 1) / 2)), 3, (7 - length("foo"))), "'", ""), "0", "W");
fooWWWW

Sqlite должен быть довольно легким, поэтому я должен несколько не согласиться с вашим комментарием о том, что я «удивлен» отсутствием функциональности. Однако я согласен с тем, что должен быть более простой способ заполнения, хотя бы потому, что существуют функции trim.

person Community    schedule 18.07.2012
comment
Ха, ну, это выглядит довольно сумасшедшим! Я проверю позже, чтобы увидеть, работает ли это :-) Я знаю о легкости. Но LPAD/RPAD можно закодировать в 10 строках кода C. С другой стороны, я все еще не вижу варианта использования randomblob()... - person Lukas Eder; 18.07.2012
comment
вы можете лоббировать его по адресу [email protected] ;-) - person ; 18.07.2012
comment
Это действительно работает! quote() сделал волшебство, хотя это не сделало вещи более читабельными, когда снова пришлось удалить материал X'...'... :) Я награжу награду, если никто не придумает более короткое решение (в чем я сомневаюсь). В любом случае, ваше решение превратится в jOOQ, так что ни одному будущему пользователю SQLite не придется думать о моделировании LPAD и снова RPAD! - person Lukas Eder; 19.07.2012
comment
@LukasEder: круто; Является ли jOOQ эквивалентом EF в .Net? - person ; 19.07.2012
comment
Не совсем. EF сопоставляется с JPA в Java. Я думаю, вы могли бы сравнить jOOQ с LINQ-to-SQL - person Lukas Eder; 19.07.2012
comment
Хм, небольшим недостатком вашего решения является тот факт, что character действительно должен быть одним символом. Некоторые функции LPAD() и RPAD() поддерживают заполнение строками, например. RPAD('abc', 7, 'xy') = 'abcxyxy'. Я думаю, что это было бы последним убийством для читабельности, хотя :-) - person Lukas Eder; 20.07.2012
comment
Действительно, SQL не делает разницы между символом и строкой; но наверняка это можно сделать, наверное, будет только противнее. - person ; 20.07.2012
comment
@LukasEder: я адаптировался для поддержки заполнения строк. Дайте мне знать, как это работает для вас (однако я не обновлял примеры). - person ; 20.07.2012
comment
Краткое примечание: я вижу, что функция MySql на самом деле будет обрезать, если строка длиннее, чем padToLength. Мой ответ этого не делает, и я не думаю, что это должно быть сделано, поскольку я не думаю, что функции заполнения должны обрезаться. При необходимости обе функции должны быть оставлены на усмотрение программиста. - person ; 22.07.2012
comment
Интересно, да. Я также не думаю, что следует применять обрезку. Заполнение должно привести к минимальной длине строки, а не к точной длине строки, независимо от того, превышена ли длина из-за дополненных символов или из-за исходной строки. - person Lukas Eder; 22.07.2012

Подход JDBC/пользовательских функций (может не подходить в вашем конкретном случае, но может быть адаптирован). Использует вдохновение из пользовательских функций SqliteJDBC и правая панель и leftPad из Apache commons.lang.StringUtils:

import java.sql.*;
import org.sqlite.Function;

public class Test 
{
  public static void main(String[] args) 
  {
    Connection conn = getConnection();

    conn.createStatement().execute("SELECT LPAD(t.column, t.character, t.repeat) FROM t");
    conn.createStatement().execute("SELECT RPAD(t.column, t.character, t.repeat) FROM t");

    conn.close();
  }

  public static Connection getConnection() 
  {
    Class.forName("org.sqlite.JDBC");
    Connection conn = DriverManager.getConnection("jdbc:sqlite:");

    /* Left Padding UDF  */
    Function.create(conn, "LPAD", new Function() 
      {
        protected void xFunc() throws SQLException 
        {
            String text = value_text(0);
            /* uses first character of supplied padding */
            char paddingCharacter = value_text(1).charAt(0);
            int repeat = value_int(2);

            int padLength = repeat - text.length();
            if(padLength <= 0)
            {
               result(text);
            }

            char[] padding = new char[padLength];
            Array.fill(padding, paddingCharacter);
            result(new String(padding).append(text));
        }
    });

    /* Right Padding UDF  */
    Function.create(conn, "RPAD", new Function() 
      {
        protected void xFunc() throws SQLException 
        {
            String text = value_text(0);
            /* uses first character of supplied padding */
            char paddingCharacter = value_text(1).charAt(0);
            int repeat = value_int(2);

            int padLength = repeat - text.length();
            if(padLength <= 0)
            {
               result(text);
            }

            char[] padding = new char[padLength];
            Array.fill(padding, paddingCharacter);
            result(text.append(new String(padding)));
        }
    });
  }
}

(Непроверенный, вне манжеты, не обрабатывает нули и т. д., но должен обрисовать в общих чертах идею...)

person Stobor    schedule 22.07.2012
comment
Очень красиво, не знала, что так можно! В моем конкретном случае я не уверен, хочу ли я создавать UDF, которые зависят от возможностей этого драйвера JDBC. А в целом неплохое решение - person Lukas Eder; 22.07.2012
comment
@LukasEder: да, в многоцелевой библиотеке это немного менее практично, но есть некоторые возможности. Если это нормально как зависимость от сборки, но не зависимость во время выполнения, тогда у вас может быть класс инициализатора для конкретной базы данных, который загружается (через отражение), если строка класса соединения соответствует org.sqlite или что-то в этом роде. Но да, это не единственный способ, и если у вас есть лучшие методы, вы всегда можете использовать их вместо этого. - person Stobor; 22.07.2012

Это простое решение для заполнения 0-9 начальным нулем с использованием CASE.

sqlite> select id,week,year from bulletin where id = 67;
67|2|2014

select id,CASE WHEN length(week) = 2 THEN week 
               ELSE '0'||week 
          END AS week,year from bulletin where id = 67;
67|02|2014
person aberpaul    schedule 27.11.2013
comment
Да, это работает для дополнения чего-либо до фиксированной длины. Но даже если эта фиксированная длина равна, скажем, 10, это решение будет слишком многословным... - person Lukas Eder; 27.11.2013

Может быть так:

  • LPAD(@orig_text, @padding_char, @padding_length):

    SELECT
      SUBSTR(
        REPLACE(
          CAST(ZEROBLOB(@padding_length) AS TEXT),
          CAST(ZEROBLOB(1) AS TEXT),
          @padding_char
        ) + @orig_text,
        -@padding_length,
        @paadding_length
      )
    
  • RPAD(@orig_text, @padding_char, @padding_length):

    SELECT
      SUBSTR(
        @orig_text + REPLACE(
          CAST(ZEROBLOB(@padding_length) AS TEXT),
          CAST(ZEROBLOB(1) AS TEXT),
          @padding_char
        ),
        1,
        @padding_length
      )
    
person Andriy M    schedule 04.07.2011
comment
Это хорошая идея, но, похоже, она не работает. Похоже, что приведение значения zeroblob(N) к TEXT приводит к пустой строке. В C строки заканчиваются символом 0x00, который содержится в zeroblob :-( - person Lukas Eder; 05.07.2011
comment
@ Лукас Эдер: Жаль. Это единственная параметризуемая вещь, которую мне удалось найти до сих пор. Извините, что разочаровал вас этим. - person Andriy M; 05.07.2011
comment
Не беспокойтесь, вы меня не разочаруете :) На самом деле это довольно творческое решение. Я просто снова и снова удивляюсь отсутствию функциональности в SQLite. Как у них может быть бесполезная функция randomblob(), но нет функций заполнения...? Ну что ж - person Lukas Eder; 05.07.2011

У меня абсолютно нет опыта работы с SQLite, на самом деле мое время взаимодействия с базой данных SQLite3 составляет менее трех дней. Поэтому я не уверен, что мои выводы могут чем-то помочь вашему требованию.

Я играю с каким-то забавным проектом, в котором есть все возможные 11-значные номера телефонов (3-значный префикс оператора + 8-значный номер абонента). Моя цель состояла в том, чтобы создать какую-то базу данных с минимально возможным ресурсом хранения, но она должна охватывать все возможные числа в базе данных. Поэтому я создал одну таблицу для 8-значного абонента, а другая таблица содержит 3-значный префикс компании. Окончательное число появится при просмотре соединения двух данных таблицы. Позвольте мне сосредоточиться на проблеме НАГРУЗКИ. Поскольку столбец таблицы подписчиков имеет тип INT, это отдельная запись от 0 до 99999999. Ошибка простого присоединения для абонента с номером менее 10000000 ; любой идентификатор подписки подписчиков со значением ниже 10000000 показывает XXXprefix+11, где ожидается XXX000000+11.

После сбоя с LPAD/RPAD на SQLite я нашел «SUBSTR»!

Посмотрите на запрос ниже:

CREATE TABLE subs_num (
subscriber_num integer PRIMARY KEY
);

INSERT INTO subs_num values ('1');
INSERT INTO subs_num values ('10');
INSERT INTO subs_num values ('100');
INSERT INTO subs_num values ('1000');

SELECT subscriber_num from subs_num;

SELECT SUBSTR('00000000' || subscriber_num, -8, 8) AS SUBSCRIB_ID FROM subs_num;

Теперь я думаю, что вы можете использовать SUBSTR для своих нужд LPAD/RPAD.

Ваше здоровье!

person A. Bauani    schedule 30.09.2018
comment
Это уже предлагалось здесь - person Lukas Eder; 01.10.2018