Corriger la mauvaise conception de la base de données une fois que les données sont dans le système

StackOverflow https://stackoverflow.com/questions/76967

  •  09-06-2019
  •  | 
  •  

Question

Je sais que ce n’est pas une question ... euh de toute façon, voici la question.

J'ai hérité d'une base de données contenant une (une) table qui ressemble beaucoup à ceci. Son objectif est de répertorier les espèces présentes dans les différents pays (quelque 200).

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

Un échantillon de données ressemblerait à ceci

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

Il me semble que cette situation est typique de plusieurs à plusieurs et je veux trois tables. Espèce, pays et espèceFondenPays

La table de liens (SpeciesFoundInCountry) aurait des clés étrangères dans les tables d'espèces et de pays.

(Il est difficile de dessiner le diagramme!)

Species
SpeciesID  SpeciesName

Country
CountryID CountryName

SpeciesFoundInCountry
CountryID SpeciesID

Existe-t-il un moyen magique de générer une instruction Insert qui obtiendra le CountryID de la nouvelle table Country en fonction du nom de la colonne et du SpeciesID s'il existe un 1 dans la méga table d'origine?

Je peux le faire pour un pays (c'est une sélection pour montrer ce que je veux sortir)

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

(la méga table est appelée espèce)

Mais en utilisant cette stratégie, je devrais effectuer la requête pour chaque colonne de la table d'origine.

Y a-t-il un moyen de faire cela en SQL?

Je suppose que je peux OU un chargement de mes clauses where ensemble et écrire un script pour rendre le SQL, semble inélégant cependant!

Avez-vous des idées (ou des éclaircissements)?

Était-ce utile?

La solution

J'utiliserais un script pour générer toutes les requêtes individuelles, car il s'agit d'un processus d'importation unique.

Certains programmes, tels qu'Excel, permettent de mélanger différentes dimensions de données (en comparant les noms de colonne à ceux contenus dans des lignes), mais les bases de données relationnelles le sont rarement.

Toutefois, certains systèmes (tels que Microsoft Access, de manière surprenante) disposent d’outils pratiques que vous pouvez utiliser pour normaliser les données. Personnellement, je trouverais plus rapide d’écrire le script, mais vos compétences relatives à Access et à la rédaction de scripts pourraient être différentes des miennes.

Autres conseils

Pourquoi voulez-vous le faire en SQL? Il suffit d'écrire un petit script qui effectue la conversion.

Lorsque je rencontre ces problèmes, j’écris un script pour effectuer la conversion plutôt que d’essayer de le faire en SQL. C'est généralement beaucoup plus rapide et plus facile pour moi. Choisissez n'importe quelle langue avec laquelle vous êtes à l'aise.

S'il s'agissait de SQL Server, vous utiliseriez les commandes Unpivot, mais en regardant la balise que vous avez affectée, c'est pour l'accès - est-ce exact?

Bien qu'il existe une commande pivotante dans l'accès , il n'y a pas d'inverse. déclaration.

On dirait que cela peut être fait avec une jointure complexe. Consultez cet article intéressant pour savoir comment procéder. Unpivot dans une commande de sélection.

Vous allez probablement vouloir créer des tables de remplacement. Le type de script dépend du langage de script que vous avez à votre disposition, mais vous devriez pouvoir créer la table d'ID de pays simplement en listant les colonnes de la table que vous avez maintenant. Une fois que vous avez fait cela, vous pouvez effectuer quelques substitutions de chaînes pour passer en revue tous les noms de pays uniques et les insérer dans le tableau espèceFoundDansLePays où la colonne de pays donnée n’est pas nulle.

Vous pourriez probablement faire preuve d'intelligence et interroger les tables système pour connaître le nom des colonnes, puis créer une chaîne de requête dynamique à exécuter, mais honnêtement, cela sera probablement plus laid qu'un script rapide permettant de générer les instructions SQL pour vous.

Espérons que vous n’avez pas trop de code SQL dynamique qui accède aux anciennes tables enfouies dans votre base de code. Cela pourrait être la vraiment partie difficile.

Dans SQL Server, cela générera votre sélection personnalisée que vous démontrez. Vous pouvez extrapoler à un insert

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'

Comme pour les autres, je le ferais probablement comme une solution rapide, quelle que soit la méthode qui vous conviendrait.

Avec ces types de conversions, ce sont des éléments uniques, des solutions rapides et le code n'a pas à être élégant, il doit simplement fonctionner. Pour ce genre de choses, je l'ai fait de nombreuses façons.

S'il s'agit de SQL Server, vous pouvez utiliser la table sys.columns pour rechercher toutes les colonnes de la table d'origine. Ensuite, vous pouvez utiliser le SQL dynamique et la commande pivot pour faire ce que vous voulez. Recherchez la syntaxe en ligne.

Je suis tout à fait d'accord avec votre suggestion d'écrire un petit script pour produire votre code SQL avec une requête pour chaque colonne.

En fait, votre script aurait peut-être déjà été terminé pendant le temps que vous avez passé à réfléchir à cette requête magique (que vous utiliseriez une seule fois, puis que vous jeteriez, alors quelle est l'utilité de le rendre tout aussi magique et parfait)

Désolé, mais l'analyseur de publication sanglant a supprimé les espaces et la mise en forme de mon message. Cela rend le journal plus difficile à lire.

@stomp:

Au-dessus de la case où vous tapez la réponse, vous trouverez plusieurs boutons. Celui qui est 101010 est un exemple de code. Vous sélectionnez tout votre texte qui est du code, puis cliquez sur ce bouton. Ensuite, il ne sera pas trop dérangé.

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

Je voudrais utiliser une requête de l'Union, très approximativement:

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

Vous obtiendriez alors une vue qui pourrait être ajoutée à votre nouveau design.

Lorsque j'ai lu le titre "Mauvaise conception de base de données", j'étais curieux de savoir à quel point c'était mauvais. Tu ne m'as pas déçu:)

Comme d'autres l'ont mentionné, un script serait le moyen le plus simple. Ceci peut être accompli en écrivant environ 15 lignes de code en 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 (...)

Évidemment, ceci est un pseudo-code et ne fonctionnera pas tel quel. Vous pouvez également renseigner à la volée le tableau des pays et des espèces en ajoutant les instructions d'insertion ici.

Désolé, j'ai très peu programmé Access, mais je peux vous donner des conseils qui devraient vous aider.

Commençons par résoudre le problème. Il est supposé que vous devrez généralement générer plusieurs lignes dans SpeciesFoundInCountry pour chaque ligne de la table d'origine. En d'autres termes, les espèces ont tendance à se trouver dans plus d'un pays. C'est en fait facile à faire avec un produit cartésien, une jointure sans critère de jointure.

Pour créer un produit cartésien, vous devez créer la table Pays. La table doit avoir l'ID de pays compris entre 1 et N (N étant le nombre de pays uniques, environ 200) et le nom du pays. Pour vous simplifier la vie, utilisez simplement les chiffres 1 à N dans l’ordre des colonnes. Cela ferait l’Afghanistan 1 et l’Albanie 2 ... Zimbabwe N. Vous devriez pouvoir utiliser les tables système pour le faire.

Créez ensuite une table ou une vue à partir de la table originale contenant l’espèce et une aiguille avec un 0 ou un 1 pour chaque pays. Vous devrez convertir les valeurs null, et non null, en texte 0 ou 1 et concaténer toutes les valeurs en une seule chaîne. Une description de la table et un éditeur de texte avec des expressions régulières devraient vous faciliter la tâche. Expérimentez d’abord avec une seule colonne et une fois que vous travaillez, modifiez la création / insertion avec toutes les colonnes.

Ensuite, joignez les deux tables sans critère de jointure. Cela vous donnera un record pour chaque espèce dans chaque pays, vous y êtes presque.

Maintenant, tout ce que vous avez à faire est de filtrer les enregistrements qui ne sont pas valides, ils auront un zéro à l’emplacement correspondant dans la chaîne. Étant donné que la colonne country_code de la table country a l'emplacement de la sous-chaîne, il vous suffit de filtrer les enregistrements où il s'agit de 0.

where substring(new_column,country_code) = '1'

Vous aurez toujours besoin de créer la table des espèces et de vous y connecter

.
where a.species_name = b.species_name

a et b sont des alias de table.

J'espère que cette aide

OBTW,

Si des requêtes sont déjà exécutées sur l'ancienne table, vous devez créer une vue qui réplique les anciennes tables à l'aide des nouvelles tables. Vous devrez créer un groupe pour dénormaliser les tables.

Indiquez à vos utilisateurs que l'ancienne table / vue ne sera plus prise en charge à l'avenir et que toutes les nouvelles requêtes ou mises à jour d'anciennes requêtes devront utiliser les nouvelles tables.

Si je dois créer un chargement complet d'instructions SQL similaires et les exécuter toutes, je trouve souvent qu'Excel est très pratique. Prenez votre requête initiale. Si vous avez une liste de pays dans la colonne A et votre instruction SQL dans la colonne B, sous forme de texte (entre guillemets) avec les références de cellules insérées à l'endroit où le pays apparaît dans le sql

par exemple. = "INSERT INTO new_table SELECT ... (espèce." & A1; & amp;;)) ... ...)); "

puis copiez simplement la formule pour créer 200 instructions SQL différentes, copiez / collez la colonne dans votre éditeur et appuyez sur F5. Vous pouvez bien entendu le faire avec autant de variables que vous le souhaitez.

Lorsque j'ai eu à faire face à des problèmes similaires, j'ai trouvé pratique de générer un script qui génère des scripts SQL. Voici l'exemple que vous avez donné, résumé pour utiliser% PAR1% à la place de l'Afghanistan.

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

Le mot-clé union a également été ajouté afin de combiner tous les éléments sélectionnés.

Ensuite, vous avez besoin d’une liste de pays générée à partir de vos données existantes:

Afghanistan Albanie . , .

Ensuite, vous avez besoin d’un script capable de parcourir la liste des pays et, pour chaque itération, produire une sortie qui substitue l'Afghanistan à% PAR1% lors de la première itération, à l'Albanie à la deuxième et ainsi de suite. L'algorithme est comme une fusion de courrier dans un traitement de texte. C'est un petit travail d'écrire ce script. Mais une fois que vous l’avez, vous pouvez l’utiliser dans des dizaines de projets uniques comme celui-ci.

Enfin, vous devez modifier manuellement le dernier "UNION". retour à un point-virgule.

Si vous pouvez obtenir l'accès pour effectuer cette union géante, vous pouvez obtenir les données souhaitées sous la forme souhaitée et les insérer dans votre nouvelle table.

Je voudrais en faire un processus en trois étapes avec une légère modification temporaire de votre table SpeciesFoundInCountry. J'ajouterais une colonne à cette table pour stocker le nom du pays. Ensuite, les étapes seraient les suivantes.

1) Créez / Exécutez un script qui parcourt les colonnes de la table source et crée un enregistrement dans SpeciesFoundInCountry pour chaque colonne ayant une valeur true. Cet enregistrement contiendrait le nom du pays. 2) Exécutez une instruction SQL qui met à jour le champ SpeciesFoundInCountry.CountryID en rejoignant la table Country sur Nom du pays. 3) Nettoyez la table SpeciesFoundInCountry en supprimant la colonne CountryName.

Voici un petit pseudo-code MS Access VB / VBA pour vous donner l’essentiel

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

Ensuite, vous pouvez exécuter une instruction SQL simple pour mettre à jour les valeurs CountryID de la table SpeciesFoundInCountry.

UPDATE SpeciesFoundInCountry INNER JOIN Country ON SpeciesFoundInCountry.CountryName = Country.CountryName SET SpeciesFoundInCountry.CountryID = Country.CountryID;

Enfin, tout ce que vous avez à faire est de nettoyer la table SpeciesFoundInCountry en supprimant la colonne CountryName.

**** NOTE LATÉRALE: J'ai trouvé utile de disposer de tables de pays qui incluent également les abréviations ISO (codes de pays). Occasionnellement, elles sont utilisées comme clés étrangères dans d'autres tables, de sorte qu'une jointure à la table Pays ne doit pas nécessairement être incluse dans les requêtes.

Pour plus d'informations: http://en.wikipedia.org/wiki/Iso_country_codes

Il s’agit (espérons-le) d’un exercice ponctuel, de sorte qu’une solution peu élégante pourrait ne pas être aussi mauvaise qu’elle en a l’air.

Le problème (en tant que, je suis sûr que vous n'êtes que trop au courant!), c'est qu'à un moment donné de votre requête, vous devez répertorier toutes ces colonnes. :( La question est, quel est le moyen le plus élégant de faire cela? Voici ma tentative. Cela semble difficile à manier car il y a tant de colonnes, mais cela pourrait être ce que vous cherchez, ou du moins cela pourrait vous indiquer la bonne direction.

Solution SQL possible:

/* 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 

Ma suggestion

Personnellement, je ne le ferais pas. Je ferais une solution rapide et sale comme celle à laquelle vous faites allusion, sauf que je coderais en dur les identifiants de pays (parce que vous ne le ferez qu'une fois, n'est-ce pas? Et vous pouvez le faire juste après avoir créé le fichier table des pays, pour que vous sachiez quels sont tous les 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 
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top