Какое волшебство SQL мне нужно, чтобы превратить один столбец в несколько? - PullRequest
5 голосов
/ 19 декабря 2010

Мне нужно распечатать несколько билетов, на каждом из которых достаточно места, чтобы вместить один набор данных клиента, а также коды до пяти товаров, заказанных этим клиентом. Клиенты, заказавшие более пяти товаров, получают несколько билетов. Таким образом, из таблицы заказов, как это,

Customer | Item
---------|------
Bob      | FTMCH
Bob      | ZORP
Bob      | KLUGE
Carol    | FTMCH
Carol    | MEEP
Carol    | ZORP
Ted      | FOON
Ted      | SMOCK
Alice    | ORGO
Carol    | SQICK
Carol    | BLECH
Carol    | KLUGE
Carol    | GLURP

Мне нужен запрос, который возвращает это:

Customer | Item1 | Item2 | Item3 | Item4 | Item5
---------|-------|-------|-------|-------|------
Alice    | ORGO  | null  | null  | null  | null
Bob      | FTMCH | ZORP  | KLUGE | null  | null
Carol    | FTMCH | MEEP  | ZORP  | SQICK | BLECH
Carol    | KLUGE | GLURP | null  | null  | null
Ted      | FOON  | SMOCK | null  | null  | null

Может ли какая-нибудь добрая душа помочь мне с SQL для этого? Встроенная база данных HSQL в OpenOffice.org Base, если это имеет значение.

Ответы [ 5 ]

2 голосов
/ 20 декабря 2010

ОК, это работает достаточно хорошо:

SELECT
    "Customer",
    MAX(CASE WHEN "Slot" = 0 THEN "Item" END) AS "Item1",
    MAX(CASE WHEN "Slot" = 1 THEN "Item" END) AS "Item2",
    MAX(CASE WHEN "Slot" = 2 THEN "Item" END) AS "Item3",
    MAX(CASE WHEN "Slot" = 3 THEN "Item" END) AS "Item4",
    MAX(CASE WHEN "Slot" = 4 THEN "Item" END) AS "Item5"
FROM (
    SELECT
        l."Customer" AS "Customer",
        l."Item" AS "Item",
        COUNT(r."Item") / 5 AS "Ticket",
        MOD(COUNT(r."Item"), 5) AS "Slot"
    FROM "Orders" AS l
    LEFT JOIN "Orders" AS r
    ON r."Customer" = l."Customer" AND r."Item" < l."Item"
    GROUP BY "Customer", "Item"
)
GROUP BY "Customer", "Ticket"
ORDER BY "Customer", "Ticket"

Это делает это:

Customer | Item1 | Item2 | Item3 | Item4 | Item5 
---------|-------|-------|-------|-------|-------
Alice    | ORGO  |       |       |       |       
Bob      | FTMCH | KLUGE | ZORP  |       |       
Carol    | BLECH | FTMCH | GLURP | KLUGE | MEEP  
Carol    | SQICK | ZORP  |       |       |       
Ted      | FOON  | SMOCK |       |       |       

Спасибо всем, кто помог, и здесь, и на Спросите Метафильтр.

(Последующее редактирование:)

Господи, это только ухудшается: - (

Оказывается, бизнес-правила позволяют одному и тому же клиенту заказывать один и тот же товар несколько раз.и что все невыполненные заказы должны быть включены в один набор заявок. Поэтому мой игрушечный стол должен был выглядеть примерно так:

ID  | Customer | Item 
159 | Bob      | FTMCH
264 | Bob      | ZORP 
265 | Bob      | KLUGE
288 | Carol    | FTMCH
314 | Carol    | MEEP 
323 | Carol    | ZORP 
327 | Ted      | FOON 
338 | Ted      | SMOCK
358 | Alice    | ORGO 
419 | Carol    | SQICK
716 | Carol    | MEEP 
846 | Carol    | BLECH
939 | Carol    | MEEP 
950 | Carol    | GLURP
979 | Carol    | KLUGE

Несколько MEEP Кэрол искажают логику ранжирования в исходном решении, иУ меня получилось следующее отвратительное чудовище:

SELECT
    "Customer",
    MAX(CASE WHEN "Slot" = 0 THEN "Item" END) AS "Item0",
    MAX(CASE WHEN "Slot" = 1 THEN "Item" END) AS "Item1",
    MAX(CASE WHEN "Slot" = 2 THEN "Item" END) AS "Item2",
    MAX(CASE WHEN "Slot" = 3 THEN "Item" END) AS "Item3",
    MAX(CASE WHEN "Slot" = 4 THEN "Item" END) AS "Item4",
    MAX(CASE WHEN "Slot" = 0 THEN "Quantity" END) AS "Qty0",
    MAX(CASE WHEN "Slot" = 1 THEN "Quantity" END) AS "Qty1",
    MAX(CASE WHEN "Slot" = 2 THEN "Quantity" END) AS "Qty2",
    MAX(CASE WHEN "Slot" = 3 THEN "Quantity" END) AS "Qty3",
    MAX(CASE WHEN "Slot" = 4 THEN "Quantity" END) AS "Qty4"
FROM (
    SELECT
        "Customer",
        "Item",
        COUNT("ID") AS "Quantity",
        "Rank" / 5 AS "Ticket",
        MOD("Rank", 5) AS "Slot"
    FROM (
        SELECT
            main."ID" AS "ID",
            main."Customer" AS "Customer",
            main."Item" AS "Item",
            COUNT(less."Item") AS "Rank"
        FROM "Orders" AS main
        LEFT JOIN (
            SELECT DISTINCT
                "Customer",
                "Item"
            FROM "Orders") AS less
        ON less."Customer" = main."Customer" AND less."Item" < main."Item"
        GROUP BY "ID", "Customer", "Item"
    )
    GROUP BY "Customer", "Item", "Rank"
)
GROUP BY "Customer", "Ticket"

, которое делает это:

Customer | Item0 | Item1 | Item2 | Item3 | Item4 | Qty0 | Qty1 | Qty2 | Qty3 | Qty3 | Qty4
Bob      | FTMCH | KLUGE | ZORP  |       |       | 1    | 1    | 1    |      |      |     
Carol    | BLECH | FTMCH | GLURP | KLUGE | MEEP  | 1    | 1    | 1    | 1    | 1    | 3   
Carol    | SQICK | ZORP  |       |       |       | 1    | 1    |      |      |      |     
Ted      | FOON  | SMOCK |       |       |       | 1    | 1    |      |      |      |     
Alice    | ORGO  |       |       |       |       | 1    |      |      |      |      |     

Я думаю, это делает свою работу, но мне очень повезло, чтобаза данных всегда будет очень маленькой (несколько тысяч строк).

Духовно, я парень по встраиваемым системам, а не парень по базе данных. Может ли кто-нибудь, кто делает это для жизни, сказать мне, является ли этоерунды это обычное дело? Будет ли запрос с четырьмяsted SELECTs и LEFT JOIN заслуживают упоминания в Daily WTF?

1 голос
/ 20 декабря 2010

Я считаю, что это применимо только для T-SQL, но вы можете использовать PIVOT: http://msdn.microsoft.com/en-us/library/ms177410.aspx

Я сделал нечто подобное со списком дат, которые стали столбцами для расчетов.

0 голосов
/ 27 декабря 2010

Требование не является редкостью и может быть разумно предоставлено в SQL. Но у вас есть две проблемы, блокирующие вас.

1) Вы ввели тег SQL, что означает стандарт SQL ISO / IEC / ANSI. Правильный метод - курсор или его замена (цикл while, который делает то же самое, но быстрее). Это позволяет избежать всех этих внешних объединений и обработки массивных наборов результатов; затем превратить его в представление с помощью GROUP BY и т. д. Он также обрабатывает дубликаты, главным образом потому, что он создает их в первую очередь (через эти пять версий таблицы с псевдонимами). И да, ситуация будет ухудшаться, и когда база данных будет заполнена разумно, это приведет к снижению производительности.

2) Дубликаты не допускаются в реляционной базе данных, т.е. в ваших исходных таблицах; вам нужно сделать строки уникальными (а эти ключи / столбцы не отображаются). Нет смысла пытаться устранить дубликаты с помощью кода. Если это исправить, то все дубликаты (реальные и созданные плохим кодом) могут быть удалены.

Это требование также может быть выполнено более элегантно с использованием подзапросов; за исключением того, что здесь вам нужно два уровня вложенности: один для построения столбца «Предмет для обучения» и два для получения ранга или позиции. И это (стандартная конструкция SQL) предполагает, что у вас есть реляционная база данных (без повторяющихся строк). Высокий коэффициент Eek, если вы не привыкли к SQL. Вот почему большинство кодеров используют курсор или замену курсора.

Но если у вас нет SQL, его базовых возможностей (HSQL - не совсем стандартная реализация), тогда мы не будем использовать один и тот же набор инструментов. Код SQL, который я могу предоставить, не будет работать для вас, и мы будем продолжать идти вперед и назад.

(Может быть, у нас должен быть тег "psuedo-SQL".)

ID Колонка предотвращает дублирование ???

Существует миф, который распространен в некоторых частях отрасли, в связи с этим из-за книг, написанных начинающими базами данных. Как обычно, мифы не имеют научной основы. Давайте попробуем простой тест.

<code>    CREATE TABLE Person (
    PersonId  IDENTITY NOT NULL
        PRIMARY KEY,
    FirstName CHAR(30) NOT NULL,
    LastName  CHAR(30) NOT NULL
    )<br>
    INSERT Person VALUES ("Fred", "Astaire")
1 row(s) affected<br>
    INSERT Person VALUES ("Ginger", "Rogers")
1 row(s) affected<br>
    INSERT Person VALUES ("Fred", "Astaire")
1 row(s) affected<br>
    SELECT * FROM Person<br>
PersonId FirstName                      LastName
======== ============================== ==============================
       1 Fred                           Astaire
       2 Ginger                         Rogers
       3 Fred                           Astaire<br>
3 row(s) affected
Это чистый, неоспоримый дубликат строки. Простой факт есть. столбец Id содержит номер строки, но ничего не делает для предотвращения дублирования строк. Для этого вам понадобится Уникальный Индекс для столбцов, которые определяют уникальность, как определено в модели данных, для каждой реляционной таблицы в базе данных (по определению, если строки не уникальны, это не Реляционная Таблица). В противном случае это просто система хранения файлов.
<code>    CREATE <strong>UNIQUE</strong> NONCLUSTERED INDEX U_Name
       ON Person (LastName, FirstName)

Существует еще одна форма целостности данных (дублирование), которую я мог бы определить, пока я на ней.

<code>    INSERT Person VALUES ("Fred", "Astair")
1 row(s) affected<br>
    INSERT Person VALUES ("Astaire", "Fred")
1 row(s) affected
Все можно предотвратить в SQL.
0 голосов
/ 20 декабря 2010

Это дает вам большую часть пути, но не обрабатывает дубликат заказа для Carol.Это было бы легко сделать, если бы было что-то еще для группы, например OrderID или OrderDate.Вы можете опубликовать полную схему?

select m1.Customer, 
    min(m1.Item) as Item1, 
    min(m2.item) as Item2, 
    min(m3.item) as Item3, 
    min(m4.item) as Item4, 
    min(m5.item) as Item5
from CustomerOrder m1
left outer join CustomerOrder m2 on m1.Customer = m2.Customer 
    and m2.item > m1.item
left outer join CustomerOrder m3 on m1.Customer = m3.Customer 
    and m3.item > m2.item
left outer join CustomerOrder m4 on m1.Customer = m4.Customer 
    and m4.item > m3.item
left outer join CustomerOrder m5 on m1.Customer = m5.Customer 
    and m5.item > m4.item
group by m1.Customer

Вывод:

Customer       Item1      Item2      Item3      Item4      Item5
-------------- ---------- ---------- ---------- ---------- ----------
Alice          ORGO       NULL       NULL       NULL       NULL
Bob            FTMCH      KLUGE      ZORP       NULL       NULL
Carol          BLECH      FTMCH      GLURP      KLUGE      MEEP
Ted            FOON       SMOCK      NULL       NULL       NULL
0 голосов
/ 19 декабря 2010

Не совсем то, что вы просили, и MySQL, а не OpenOffice, но может дать вам идею, или кто-то другой может поработать над этим:

select
    u.Customer,
    group_concat(u.Item) items
from
    (select
        t.Item,
        @n:=if(@c=t.Customer and @n<4,@n+1,0) c1,
        @m:=if(@n,@m,@m+1) g,
        @c:=t.Customer as Customer
    from
        t1 t, (select @m:=0) init
    order
        by t.Customer
    ) u
group by
    u.g

Выход:

+----------+------------------------------+
| Customer | items                        |
+----------+------------------------------+
| Alice    | ORGO                         | 
| Bob      | FTMCH,ZORP,KLUGE             | 
| Carol    | KLUGE,ZORP,BLECH,SQICK,GLURP | 
| Carol    | MEEP,FTMCH                   | 
| Ted      | FOON,SMOCK                   | 
+----------+------------------------------+
...