Jak mogę zwrócić dane wyjściowe tabeli przestawnej w MySQL?

311

Jeśli mam tabelę MySQL wyglądającą mniej więcej tak:

nazwa_działania akcja liczba stron
-------------------------------
Firma A DRUKUJ 3
Firma A DRUKUJ 2
Firma A DRUKUJ 3
Firma B EMAIL   
Firma B DRUKUJ 2
Firma B DRUKUJ 2
Firma B WYDRUK 1
Firma A DRUKUJ 3

Czy można uruchomić zapytanie MySQL, aby uzyskać dane wyjściowe w następujący sposób:

nazwa firmy EMAIL DRUKUJ 1 strony DRUKUJ 2 strony DRUKUJ 3 strony
-------------------------------------------------- -----------
Firma A 0 0 1 3
Firma B 1 1 2 0

Chodzi o to, że pagecountmoże się zmieniać, więc kwota wyjściowa kolumna powinna odzwierciedlać, że jedna kolumna dla każdego action/ pagecountpary, a następnie liczba odsłon company_name. Nie jestem pewien, czy nazywa się to tabelą przestawną, ale ktoś to sugerował?

peku
źródło
3
Nazywa się to przestawianiem i znacznie, dużo szybciej jest wykonać tę transformację poza SQL.
NB
1
Excel rozdziera takie rzeczy, w MySQL jest to naprawdę trudne, ponieważ nie ma operatora „CROSSTAB” :(
Dave Rix,
Tak, obecnie odbywa się to ręcznie w programie Excel i staramy się to zautomatyzować.
peku
3
Tutaj znalazłem przykład krok po kroku: jak zautomatyzować tabele przestawne . a to
Devid G
1
@giannischristofakis - to naprawdę zależy od tego, co ty i twoi współpracownicy uważacie za prostsze. Technologia trochę nadrobiła zaległości, odkąd opublikowałem komentarz (4 lata), więc zależy to od tego, co uważasz za lepsze - czy to w aplikacji, czy w SQL. Na przykład w mojej pracy mamy do czynienia z podobnym problemem, ale łączymy zarówno podejście SQL, jak i podejście w aplikacji. Zasadniczo nie mogę ci pomóc, poza udzieleniem opinii i to nie jest to, czego potrzebujesz :)
NB

Odpowiedzi:

235

Zasadniczo jest to tabela przestawna.

Miły samouczek, jak to osiągnąć, można znaleźć tutaj: http://www.artfulsoftware.com/infotree/qrytip.php?id=78

Radzę przeczytać ten post i dostosować to rozwiązanie do swoich potrzeb.

Aktualizacja

Ponieważ powyższy link nie jest już dostępny, czuję się zobowiązany do podania dodatkowych informacji dla was wszystkich, którzy szukają tutaj odpowiedzi mysql pivot. Naprawdę miał ogromną ilość informacji i nie umieszczę tutaj wszystkiego (tym bardziej, że po prostu nie chcę kopiować ich ogromnej wiedzy), ale dam kilka wskazówek, jak radzić sobie z osią przestawną tabele sql ogólnie na przykładzie z peku, który zadał pytanie w pierwszej kolejności.

Może link wkrótce wróci, będę go pilnować.

Sposób arkusza kalkulacyjnego ...

Wiele osób korzysta w tym celu z narzędzi takich jak MSExcel, OpenOffice lub innych narzędzi do obsługi arkuszy kalkulacyjnych. To prawidłowe rozwiązanie, po prostu skopiuj tam dane i skorzystaj z narzędzi oferowanych przez GUI, aby to rozwiązać.

Ale ... to nie było pytanie, a może nawet prowadzić do pewnych wad, takich jak sposób wprowadzenia danych do arkusza kalkulacyjnego, problematyczne skalowanie i tak dalej.

Sposób SQL ...

Biorąc pod uwagę jego stół wygląda mniej więcej tak:

CREATE TABLE `test_pivot` (
  `pid` bigint(20) NOT NULL AUTO_INCREMENT,
  `company_name` varchar(32) DEFAULT NULL,
  `action` varchar(16) DEFAULT NULL,
  `pagecount` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`pid`)
) ENGINE=MyISAM;

Teraz spójrz na jego / jej pożądany stół:

company_name    EMAIL   PRINT 1 pages   PRINT 2 pages   PRINT 3 pages
-------------------------------------------------------------
CompanyA        0       0               1               3
CompanyB        1       1               2               0

Wiersze ( EMAIL, PRINT x pages) przypominają warunki. Główne ugrupowanie to company_name.

Aby skonfigurować warunki, raczej krzyczy o użyciu CASE-statement. W celu grupy przez coś, dobrze, stosowanie ... GROUP BY.

Podstawowy SQL zapewniający ten element przestawny może wyglądać mniej więcej tak:

SELECT  P.`company_name`,
    COUNT(
        CASE 
            WHEN P.`action`='EMAIL' 
            THEN 1 
            ELSE NULL 
        END
    ) AS 'EMAIL',
    COUNT(
        CASE 
            WHEN P.`action`='PRINT' AND P.`pagecount` = '1' 
            THEN P.`pagecount` 
            ELSE NULL 
        END
    ) AS 'PRINT 1 pages',
    COUNT(
        CASE 
            WHEN P.`action`='PRINT' AND P.`pagecount` = '2' 
            THEN P.`pagecount` 
            ELSE NULL 
        END
    ) AS 'PRINT 2 pages',
    COUNT(
        CASE 
            WHEN P.`action`='PRINT' AND P.`pagecount` = '3' 
            THEN P.`pagecount` 
            ELSE NULL 
        END
    ) AS 'PRINT 3 pages'
FROM    test_pivot P
GROUP BY P.`company_name`;

Powinno to zapewnić bardzo szybko pożądany wynik. Głównym minusem tego podejścia jest to, że im więcej wierszy chcesz w tabeli przestawnej, tym więcej warunków musisz zdefiniować w instrukcji SQL.

Można to również rozwiązać, dlatego ludzie używają gotowych instrukcji, procedur, liczników i tym podobnych.

Kilka dodatkowych linków na ten temat:

Bjoern
źródło
4
link wydaje się na razie działać ... jeśli kiedykolwiek znowu spadnie, wypróbuj te: pamięć podręczna Google webcache.googleusercontent.com/... lub Internet Wayback Machine ( web.archive.org/web/20070303120558 * / artfulsoftware.com/ infotree / queries.php )
Lykegenes
link jest dostępny pod tym adresem artfulsoftware.com/infotree/qrytip.php?id=78
MrPandav
1
Istnieje inny sposób na wygenerowanie tabeli przestawnej bez użycia „if”, „case” lub „GROUP_CONCAT”: en.wikibooks.org/wiki/MySQL/Pivot_table
user2513149
Możesz usunąć ELSE NULL ze swojego CASE, ponieważ kapelusz jest zachowaniem domyślnym (a agregacja warunkowa jest wystarczająco pracochłonna)
Caius Jard
86

Moje rozwiązanie jest w języku T-SQL bez żadnych elementów przestawnych:

SELECT
    CompanyName,  
    SUM(CASE WHEN (action='EMAIL') THEN 1 ELSE 0 END) AS Email,
    SUM(CASE WHEN (action='PRINT' AND pagecount=1) THEN 1 ELSE 0 END) AS Print1Pages,
    SUM(CASE WHEN (action='PRINT' AND pagecount=2) THEN 1 ELSE 0 END) AS Print2Pages,
    SUM(CASE WHEN (action='PRINT' AND pagecount=3) THEN 1 ELSE 0 END) AS Print3Pages
FROM 
    Company
GROUP BY 
    CompanyName
RRM
źródło
2
Działa to dla mnie nawet na PostgreSQL. Wolę tę metodę niż korzystanie z rozszerzenia tabeli
przestawnej
2
„Moje rozwiązanie jest w języku T-SQL bez żadnych elementów przestawnych:” Nie tylko SQL Server powinien działać na większości dostawców baz danych zgodnych ze standardami ANSI SQL. Zauważ, że SUM()może działać tylko z danymi liczbowymi, jeśli chcesz przestawić ciągi, których będziesz musiał użyćMAX()
Raymond Nijland
1
Myślę, że CASE jest niepotrzebny SUM(CASE WHEN (action='PRINT' AND pagecount=1) THEN 1 ELSE 0 END), możesz to zrobić, SUM(action='PRINT' AND pagecount=1)ponieważ warunek zostanie przekształcony na 1true, a 0false
kajacx
1
@ kajacx tak, chociaż jest to potrzebne w bazie danych, która nie ma tego rodzaju manipulacji logicznych. Biorąc pod uwagę wybór między „dłuższą składnią, która działa na wszystkich dB” i „krótszą składnią, która działa tylko na ...” Wybrałbym tę pierwszą
Caius Jard
66

W przypadku MySQL możesz bezpośrednio ustawić warunki w SUM()funkcji, która będzie oceniana jako wartość logiczna 0lub,1 dzięki czemu będziesz mógł liczyć na podstawie kryteriów bez użycia IF/CASEinstrukcji

SELECT
    company_name,  
    SUM(action = 'EMAIL')AS Email,
    SUM(action = 'PRINT' AND pagecount = 1)AS Print1Pages,
    SUM(action = 'PRINT' AND pagecount = 2)AS Print2Pages,
    SUM(action = 'PRINT' AND pagecount = 3)AS Print3Pages
FROM t
GROUP BY company_name

DEMO

M. Khalid Junaid
źródło
1
To naprawdę fajne. Czy wiesz, czy jest to zgodne ze standardami na innych platformach (takich jak Postgres)?
itsols
3
@itsols Nie dotyczy tylko Mysql
M Khalid Junaid
@itsols: Dodałem kolejną standardową wersję SQL . Postgres ma również dedykowaną crosstab()funkcję.
Erwin Brandstetter,
2
Działa również dla SQLite
SBF
37

W celu dynamicznego przestawienia użyj GROUP_CONCATz CONCAT. Funkcja GROUP_CONCAT łączy łańcuchy z grupy w jeden ciąg z różnymi opcjami.

SET @sql = NULL;
SELECT
    GROUP_CONCAT(DISTINCT
    CONCAT(
      'SUM(CASE WHEN action = "',
      action,'"  AND ', 
           (CASE WHEN pagecount IS NOT NULL 
           THEN CONCAT("pagecount = ",pagecount) 
           ELSE pagecount IS NULL END),
      ' THEN 1 ELSE 0 end) AS ',
      action, IFNULL(pagecount,'')

    )
  )
INTO @sql
FROM
  t;

SET @sql = CONCAT('SELECT company_name, ', @sql, ' 
                  FROM t 
                   GROUP BY company_name');

PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

DEMO TUTAJ

Abhishek Gupta
źródło
2
Pacerier, prawdziwy człowiek, ale dynamiczny obrót to jedno z najlepszych podejść
Abhishek Gupta
2
Działa to dobrze, jeśli masz wiele wartości w kolumnie „działania” lub oczekujesz, że lista będzie rosnąć w czasie, ponieważ pisanie instrukcji case dla każdej wartości może być czasochłonne i trudne do aktualizacji.
Patrick Murphy,
23

Wersja stardard-SQL wykorzystująca logikę logiczną :

SELECT company_name
     , COUNT(action = 'EMAIL' OR NULL) AS "Email"
     , COUNT(action = 'PRINT' AND pagecount = 1 OR NULL) AS "Print 1 pages"
     , COUNT(action = 'PRINT' AND pagecount = 2 OR NULL) AS "Print 2 pages"
     , COUNT(action = 'PRINT' AND pagecount = 3 OR NULL) AS "Print 3 pages"
FROM   tbl
GROUP  BY company_name;

SQL Fiddle.

W jaki sposób?

TRUE OR NULL daje TRUE.
FALSE OR NULLdaje NULL.
NULL OR NULLdaje NULL.
I COUNTliczy tylko wartości inne niż null. Voilá.

Erwin Brandstetter
źródło
@Erwin, Ale skąd miałbyś wiedzieć, że są trzy kolumny? Co jeśli jest 5? 10? 20?
Pacerier
@Pacerier: Sugeruje to przykład w pytaniu. Tak czy inaczej, SQL wymaga znajomości typu zwracanego. całkowicie dynamiczny zapytanie nie jest możliwa. Jeśli liczba kolumn wyjściowych może się różnić, potrzebujesz dwóch kroków: 1. zbuduj zapytanie, 2.: wykonaj je.
Erwin Brandstetter,
11

Prawidłowa odpowiedź to:

select table_record_id,
group_concat(if(value_name='note', value_text, NULL)) as note
,group_concat(if(value_name='hire_date', value_text, NULL)) as hire_date
,group_concat(if(value_name='termination_date', value_text, NULL)) as termination_date
,group_concat(if(value_name='department', value_text, NULL)) as department
,group_concat(if(value_name='reporting_to', value_text, NULL)) as reporting_to
,group_concat(if(value_name='shift_start_time', value_text, NULL)) as shift_start_time
,group_concat(if(value_name='shift_end_time', value_text, NULL)) as shift_end_time
from other_value
where table_name = 'employee'
and is_active = 'y'
and is_deleted = 'n'
GROUP BY table_record_id
Talha
źródło
1
Czy to tylko przykład, który miałeś pod ręką? Jaka jest struktura other_valuestołu?
Patrick Murphy,
1
„Prawidłowa odpowiedź to:” Najprawdopodobniej nie, ponieważ brakuje SETzapytania, aby zwiększyć wartość domyślną, która jest ograniczona do 1024 dla GROUP_CONCAT po 1024 GROUP_CONCAT po prostu obcina ciąg bez błędu, co oznacza, że ​​mogą wystąpić nieoczekiwane wyniki.
Raymond Nijland
przepraszam chłopaki nie pamiętają dalszych szczegółów. Robię rzeczy dla zabawy, a potem zapominam lub niszczę cały projekt. Ale kiedy napotykam wyzwanie, dzielę się tym, jak to naprawiłem. Wiem, że mój przykład nie jest zbyt szczegółowy, ale wydaje mi się, że może dać wskazówki tym, którzy wiedzą, co mają przeciwko :)
Talha
9

Istnieje narzędzie o nazwie Generator tabeli przestawnej MySQL, które może pomóc w tworzeniu internetowej tabeli przestawnej, którą można później wyeksportować do programu Excel (jeśli chcesz). może działać, jeśli dane znajdują się w jednej tabeli lub w kilku tabelach.

Wszystko, co musisz zrobić, to podać źródło danych kolumn (obsługuje kolumny dynamiczne), wiersze, wartości w treści tabeli i relacje między tabelami (jeśli takie istnieją) Tabela przestawna MySQL

Strona główna tego narzędzia to http://mysqlpivottable.net

Peter Green
źródło
3
select t3.name, sum(t3.prod_A) as Prod_A, sum(t3.prod_B) as Prod_B, sum(t3.prod_C) as    Prod_C, sum(t3.prod_D) as Prod_D, sum(t3.prod_E) as Prod_E  
from
(select t2.name as name, 
case when t2.prodid = 1 then t2.counts
else 0 end  prod_A, 

case when t2.prodid = 2 then t2.counts
else 0 end prod_B,

case when t2.prodid = 3 then t2.counts
else 0 end prod_C,

case when t2.prodid = 4 then t2.counts
else 0 end prod_D, 

case when t2.prodid = "5" then t2.counts
else 0 end prod_E

from 
(SELECT partners.name as name, sales.products_id as prodid, count(products.name) as counts
FROM test.sales left outer join test.partners on sales.partners_id = partners.id
left outer join test.products on sales.products_id = products.id 
where sales.partners_id = partners.id and sales.products_id = products.id group by partners.name, prodid) t2) t3

group by t3.name ;
irba
źródło