Domanda

Lo so, non è una domanda...ehm comunque QUI è la domanda.

Ho ereditato un database che contiene 1 (una) tabella che assomiglia molto a questa.Il suo scopo è quello di registrare quali specie si trovano nei vari (200 e più) paesi.

ID 
Species
Afghanistan
Albania
Algeria
American Samoa
Andorra
Angola
....
Western Sahara
Yemen
Zambia
Zimbabwe

Un campione dei dati sarebbe qualcosa di simile

id Species Afghanistan Albania American Samoa
1  SP1         null     null        null
2  SP2          1         1         null
3  SP3         null      null         1

Mi sembra che questa sia una tipica situazione molti a molti e voglio 3 tavoli.Specie, Paese e Specietrovate nel paese

La tabella dei collegamenti (SpeciesFoundInCountry) avrebbe chiavi esterne sia nelle tabelle delle specie che in quelle dei paesi.

(È difficile tracciare il diagramma!)

Species
SpeciesID  SpeciesName

Country
CountryID CountryName

SpeciesFoundInCountry
CountryID SpeciesID

Esiste un modo magico per generare un'istruzione di inserimento che otterrà il CountryID dalla nuova tabella Country in base al nome della colonna e al SpeciesID dove c'è un 1 nella mega tabella originale?

Posso farlo per un Paese (questa è una selezione per mostrare ciò che voglio)

SELECT Species.ID, Country.CountryID
FROM Country, Species
WHERE (((Species.Afghanistan)=1)) AND (((Country.Country)="Afghanistan"));

(la mega tabella si chiama specie)

Ma utilizzando questa strategia avrei bisogno di eseguire la query per ogni colonna nella tabella originale.

C'è un modo per farlo in SQL?

Immagino di poter OPPURE un carico delle mie clausole where insieme e scrivere uno script per creare SQL, ma sembra inelegante!

Qualche idea (o chiarimento richiesto)?

È stato utile?

Soluzione

Utilizzerei uno script per generare tutte le singole query, poiché si tratta di un processo di importazione una tantum.

Alcuni programmi come Excel sono bravi a mescolare diverse dimensioni di dati (confrontando i nomi delle colonne con i dati all'interno delle righe), ma i database relazionali raramente lo fanno.

Tuttavia, potresti scoprire che alcuni sistemi (come Microsoft Access, sorprendentemente) dispongono di strumenti convenienti che puoi utilizzare per normalizzare i dati.Personalmente troverei più veloce scrivere la sceneggiatura, ma le tue competenze relative con Access e scripting potrebbero essere diverse dalle mie.

Altri suggerimenti

Perché vuoi farlo in SQL?Basta scrivere un piccolo script che esegua la conversione.

Quando mi imbatto in questi, scrivo uno script per eseguire la conversione anziché provare a farlo in SQL.In genere è molto più veloce e più facile per me.Scegli la lingua con cui ti trovi a tuo agio.

Se si trattasse di SQL Server, utilizzeresti i comandi Unpivot, ma guardando il tag che hai assegnato è per l'accesso: ho ragione?

Sebbene esista un comando basculante in accesso, non esiste alcuna affermazione inversa.

Sembra che possa essere fatto con un join complesso.Controllare questo articolo interessante per informazioni dettagliate su come annullare il pivot in un comando di selezione.

Probabilmente vorrai creare tabelle sostitutive sul posto.Lo script dipende dal linguaggio di scripting che hai a disposizione, ma dovresti essere in grado di creare la tabella ID paese semplicemente elencando le colonne della tabella che hai ora.Dopo averlo fatto, puoi eseguire alcune sostituzioni di stringhe per esaminare tutti i nomi di paesi univoci e inserirli nella tabella specieFoundInCountry dove la colonna del paese specificata non è nulla.

Probabilmente potresti diventare furbo e interrogare le tabelle di sistema per i nomi delle colonne, quindi creare una stringa di query dinamica da eseguire, ma onestamente sarà probabilmente più brutto di uno script rapido per generare le istruzioni SQL per te.

Spero che tu non abbia troppo codice SQL dinamico che accede alle vecchie tabelle sepolte nella tua base di codice.Potrebbe essere quello Veramente parte difficile.

In SQL Server questo genererà la selezione personalizzata che dimostrerai.È possibile estrapolare un inserto

select 
  'SELECT Species.ID, Country.CountryID FROM Country, Species WHERE (((Species.' + 
 c.name + 
 ')=1)) AND (((Country.Country)="' +
 c.name + 
 '"))'
from syscolumns c
inner join sysobjects o
on o.id = c.id
where o.name = 'old_table_name'

Come con gli altri, molto probabilmente lo farei semplicemente come una soluzione rapida una tantum in qualunque modo funzioni per te.

Con questi tipi di conversioni, si tratta di elementi unici, soluzioni rapide e il codice non deve essere elegante, deve solo funzionare.Per questo tipo di cose l'ho fatto in molti modi.

Se si tratta di SQL Server, è possibile utilizzare la tabella sys.columns per trovare tutte le colonne della tabella originale.Quindi puoi utilizzare SQL dinamico e il comando pivot per fare ciò che desideri.Cerca quelli online per la sintassi.

Sarei sicuramente d'accordo con il tuo suggerimento di scrivere un piccolo script per produrre il tuo SQL con una query per ogni colonna.

In effetti il ​​tuo script potrebbe essere già finito nel tempo che hai passato a pensare a questa magica domanda (che utilizzeresti solo una volta e poi butteresti via, quindi a che serve renderlo tutto magico e perfetto)

Mi spiace, ma il dannato parser dei post ha rimosso gli spazi bianchi e la formattazione nel mio post.Rende il registro più difficile da leggere.

@stomp:

Sopra la casella in cui digiti la risposta, ci sono diversi pulsanti.Quello 101010 è un esempio di codice.Seleziona tutto il testo che è il codice, quindi fai clic su quel pulsante.Quindi non si incasina molto.

cout>>"I don't know C"
cout>>"Hello World"

Vorrei utilizzare una query Union, in modo molto approssimativo:

Dim db As Database
Dim tdf As TableDef

Set db = CurrentDb

Set tdf = db.TableDefs("SO")

strSQL = "SELECT ID, Species, """ & tdf.Fields(2).Name _
    & """ AS Country, [" & tdf.Fields(2).Name & "] AS CountryValue FROM SO "

For i = 3 To tdf.Fields.Count - 1
    strSQL = strSQL & vbCrLf & "UNION SELECT ID, Species, """ & tdf.Fields(i).Name _
    & """ AS Country, [" & tdf.Fields(i).Name & "] AS CountryValue FROM SO "
Next

db.CreateQueryDef "UnionSO", strSQL

Avresti quindi una vista che potrebbe essere aggiunta al tuo nuovo progetto.

Quando ho letto il titolo "design di database pessimo e BAD", ero curioso di scoprire quanto fosse pessimo.Non mi hai deluso :)

Come altri hanno già detto, uno script sarebbe il modo più semplice.Ciò può essere ottenuto scrivendo circa 15 righe di codice in PHP.

SELECT * FROM ugly_table;
while(row)
foreach(row as field => value)
if(value == 1)
SELECT country_id from country_table WHERE country_name = field;

if(field == 'Species')
SELECT species_id from species_table WHERE species_name = value;

INSERT INTO better_table (...)

Ovviamente questo è pseudo codice e non funzionerà così com'è.Puoi anche compilare al volo la tabella dei paesi e delle specie aggiungendo istruzioni di inserimento qui.

Mi dispiace, ho fatto pochissima programmazione di Access ma posso offrire alcune indicazioni che dovrebbero aiutare.

Per prima cosa esaminiamo il problema.Si presuppone che in genere sarà necessario generare più righe in SpeciesFoundInCountry per ogni riga nella tabella originale.In altre parole le specie tendono a trovarsi in più di un paese.In realtà questo è facile da fare con un prodotto cartesiano, un'unione senza criteri di unione.

Per realizzare un prodotto cartesiano dovrai creare la tabella Paese.La tabella dovrebbe avere il country_id da 1 a N (N è il numero di paesi univoci, circa 200) e il nome del paese.Per semplificarti la vita basta usare i numeri da 1 a N in ordine di colonna.Ciò renderebbe l’Afghanistan 1 e l’Albania 2…Zimbabwe N.Dovresti essere in grado di utilizzare le tabelle di sistema per farlo.

Successivamente crea una tabella o una vista dalla tabella originale che contiene le specie e una puntura con uno 0 o 1 per ciascun paese.Dovrai convertire il valore nullo, non nullo, in un testo 0 o 1 e concatenare tutti i valori in un'unica stringa.Una descrizione della tabella e un editor di testo con espressioni regolari dovrebbero facilitare l'operazione.Sperimenta prima con una singola colonna e una volta che funziona, modifica la vista di creazione/inserimento con tutte le colonne.

Quindi unisci le due tabelle insieme senza criteri di unione.Questo ti darà un record per ogni specie in ogni paese, ci sei quasi.

Ora tutto quello che devi fare è filtrare i record che non sono validi, avranno uno zero nella posizione corrispondente nella stringa.Poiché la colonna country_code della tabella country ha la posizione della sottostringa, tutto ciò che devi fare è filtrare i record in cui è 0.

where substring(new_column,country_code) = '1'

Dovrai comunque creare la tabella delle specie e unirti a essa

where a.species_name = b.species_name

aeb sono alias di tabella.

Spero che questo aiuto

A proposito,

Se disponi di query già eseguite sulla vecchia tabella, dovrai creare una vista che replichi le vecchie tabelle utilizzando le nuove tabelle.Dovrai creare un gruppo per denormalizzare le tabelle.

Spiega ai tuoi utenti che la vecchia tabella/vista non sarà supportata in futuro e che tutte le nuove query o gli aggiornamenti alle query precedenti dovranno utilizzare le nuove tabelle.

Se mai dovessi creare un carico di istruzioni SQL simili ed eseguirle tutte, spesso trovo che Excel sia molto utile.Prendi la tua domanda originale.Se hai un elenco di paesi nella colonna A e la tua istruzione SQL nella colonna B, formattata come testo (tra virgolette) con riferimenti di cella inseriti dove appare il paese nell'SQL

per esempio.="INSERISCI IN NUOVA_TABELLA SELEZIONA ...(specie." & A1 & ")= ...));"

quindi copia semplicemente la formula per creare 200 diverse istruzioni SQL, copia/incolla la colonna nel tuo editor e premi F5.Ovviamente puoi farlo con tutte le variabili che desideri.

Quando mi sono trovato di fronte a problemi simili, ho trovato conveniente generare uno script che generi script SQL.Ecco l'esempio che hai fornito, estratto per utilizzare %PAR1% al posto dell'Afghanistan.

SELECT Species.ID, Country.CountryID
FROM Country, Species
WHERE (((Species.%PAR1%)=1)) AND (((Country.Country)="%PAR1%"))
UNION

Inoltre è stata aggiunta la parola chiave unione come modo per combinare tutte le selezioni.

Successivamente, hai bisogno di un elenco di paesi, generato dai tuoi dati esistenti:

Afghanistan Albania.,.

Successivamente hai bisogno di una sceneggiatura che possa iterare attraverso l'elenco dei paesi e, per ogni iterazione, produrre un output che sostituisce l'Afghanistan per % par1 % sulla prima iterazione, in Albania per la seconda iterazione e così via.L'algoritmo è proprio come la stampa unione in un elaboratore di testi.È un po' di lavoro scrivere questa sceneggiatura.Ma, una volta ottenuto, puoi utilizzarlo in dozzine di progetti unici come questo.

Infine, è necessario modificare manualmente l'ultima "UNION" in un punto e virgola.

Se riesci a ottenere Access per eseguire questa gigantesca unione, puoi ottenere i dati desiderati nel formato desiderato e inserirli nella nuova tabella.

Lo renderei un processo in tre fasi con una leggera modifica temporanea alla tabella SpeciesFoundInCountry.Vorrei aggiungere una colonna a quella tabella per memorizzare il nome del Paese.Quindi i passaggi sarebbero i seguenti.

1) Crea/esegui uno script che esamina le colonne nella tabella di origine e crea un record in SpeciesFoundInCountry per ogni colonna che ha un valore vero.Questo record conterrebbe il nome del paese.2) Eseguire un'istruzione SQL che aggiorna il campo SpeciesFoundInCountry.CountryID unendosi alla tabella Country su Country Name.3) Pulisci la tabella SpeciesFoundInCountry rimuovendo la colonna CountryName.

Ecco un piccolo pseudocodice VB/VBA di MS Access per darti l'essenza

Public Sub CreateRelationshipRecords()

  Dim rstSource as DAO.Recordset
  Dim rstDestination as DAO.Recordset
  Dim fld as DAO.Field
  dim strSQL as String
  Dim lngSpeciesID as Long

  strSQL = "SELECT * FROM [ORIGINALTABLE]"
  Set rstSource = CurrentDB.OpenRecordset(strSQL)
  set rstDestination = CurrentDB.OpenRecordset("SpeciesFoundInCountry")

  rstSource.MoveFirst

  ' Step through each record in the original table
  Do Until rstSource.EOF
    lngSpeciesID = rstSource.ID
    ' Now step through the fields(columns). If the field
    ' value is one (1), then create a relationship record
    ' using the field name as the Country Name
    For Each fld in rstSource.Fields
      If fld.Value = 1 then
        with rstDestination
          .AddNew
          .Fields("CountryID").Value = Null
          .Fields("CountryName").Value = fld.Name
          .Fields("SpeciesID").Value = lngSpeciesID
          .Update
        End With
      End IF
    Next fld  
    rstSource.MoveNext
  Loop

  ' Clean up
  rstSource.Close
  Set rstSource = nothing
  ....

End Sub

Successivamente potresti eseguire una semplice istruzione SQL per aggiornare i valori CountryID nella tabella SpeciesFoundInCountry.

AGGIORNA SpeciesFoundInCountry INNER JOIN Paese ON SpeciesFoundInCountry.CountryName = Country.CountryName SET SpeciesFoundInCountry.CountryID = Country.CountryID;

Infine, tutto ciò che devi fare è pulire la tabella SpeciesFoundInCountry rimuovendo la colonna CountryName.

****NOTA A MARGINE:Ho trovato utile avere tabelle dei paesi che includano anche le abbreviazioni ISO (codici paese).Occasionalmente vengono utilizzate come chiavi esterne in altre tabelle in modo che nelle query non sia necessario includere un join alla tabella Country.

Per maggiori informazioni: http://en.wikipedia.org/wiki/Iso_country_codes

Questo è (si spera) un esercizio una tantum, quindi una soluzione poco elegante potrebbe non essere così negativa come sembra.

Il problema (come sono sicuro che tu ne sia fin troppo consapevole!) è che ad un certo punto della tua query devi elencare tutte quelle colonne.:( La domanda è: qual è il modo più elegante per farlo?Di seguito è riportato il mio tentativo.Sembra poco maneggevole perché ci sono così tante colonne, ma potrebbe essere quello che stai cercando, o almeno potrebbe indirizzarti nella giusta direzione.

Possibile soluzione SQL:

/* if you have N countries */
CREATE TABLE Country
(id    int, 
 name  varchar(50)) 

INSERT Country
      SELECT 1, 'Afghanistan'
UNION SELECT 2, 'Albania', 
UNION SELECT 3, 'Algeria' ,
UNION SELECT 4, 'American Samoa' ,
UNION SELECT 5, 'Andorra' ,
UNION SELECT 6, 'Angola' ,
...
UNION SELECT N-3, 'Western Sahara', 
UNION SELECT N-2, 'Yemen', 
UNION SELECT N-1, 'Zambia', 
UNION SELECT N, 'Zimbabwe', 



CREATE TABLE #tmp
(key        varchar(N),  
 country_id int) 
/* "key" field needs to be as long as N */  


INSERT #tmp 
SELECT '1________ ... _', 'Afghanistan' 
/* '1' followed by underscores to make the length = N */

UNION SELECT '_1_______ ... ___', 'Albania'
UNION SELECT '__1______ ... ___', 'Algeria'
...
UNION SELECT '________ ... _1_', 'Zambia'
UNION SELECT '________ ... __1', 'Zimbabwe'

CREATE TABLE new_table
(country_id int, 
species_id int) 

INSERT new_table
SELECT species.id, country_id
FROM   species s , 
       #tmp    t
WHERE  isnull( s.Afghanistan, ' ' ) +  
       isnull( s.Albania, ' ' ) +  
       ... +  
       isnull( s.Zambia, ' ' ) +  
       isnull( s.Zimbabwe, ' ' ) like t.key 

Il mio consiglio

Personalmente, non lo farei.Farei una soluzione rapida e sporca come quella a cui alludi, tranne per il fatto che codificarei gli ID dei paesi (perché lo farai solo una volta, giusto?E puoi farlo subito dopo aver creato la tabella dei paesi, in modo da sapere quali sono tutti gli ID):

INSERT new_table SELECT Species.ID, 1 FROM Species WHERE Species.Afghanistan = 1 
INSERT new_table SELECT Species.ID, 2 FROM Species WHERE Species.Albania= 1 
...
INSERT new_table SELECT Species.ID, 999 FROM Species WHERE Species.Zambia= 1 
INSERT new_table SELECT Species.ID, 1000 FROM Species WHERE Species.Zimbabwe= 1 
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top