Se connecter
Se connecter

ou
Créer un compte

ou
FR
EN

Le pub des programmeurs

  • 1 927 réponses
  • 117 participants
  • 131 667 vues
  • 130 followers
Sujet de la discussion Le pub des programmeurs
Salut :coucou: y a des programeurs sur AF si oui vous bossez sous quoi ?
Afficher le sujet de la discussion
1876
Ca va aussi être une question de performances le fait d'utiliser une clé primaire composite vs une ID en clé primaire et un index unique sur les x colonnes en contrainte. Avec un index tu vas beaucoup augmenter la vitesse de SELECT mais ralentir (pas dans les mêmes proportions) les INSERT et les UPDATE.

Dans la pratique les clé primaire composite je trouve que c'est pas mal de complications (ou de facilités en moins) pour pas grand chose.
1877
En outil gratos pas mal pour faire ton modèle entité-relation y'a ça : https://dbdiagram.io
1878
Bon jvais faire mon chieur de stack overflow (mais si vous savez celui a qui on demande comment faire ca en php et qui te repond fais le en c++ c'est plus simple), mais est ce que tu as une contrainte technique pour mettre ta "logique metier" dans ton sql ? Pourquoi ne pas mettre ta contrainte d'unité dans ton code ?

Bon si jamais tu es oblige d'utiliser que sql, comme ont dit les copaings tu fais un index (auto incremente, uuid, ce que tu veux) unique qui te sert de clef primaire. Et ensuite pour verifier l'unicite du couple de relation_id tu ecris un trigger sql.

https://dev.mysql.com/doc/refman/8.0/en/trigger-syntax.html

Tu specifies que ta fonction s'active quand tu fais un INSERT ou un UPDATE sur la table relation, et hop c'est réglé

Le chien aboie mais n'invente pas le fil à décongeler le beurre.

Les 6l6, des lampes qui nous éclairaient

[ Dernière édition du message le 21/02/2020 à 11:30:44 ]

1879
Pas super d'accord pour la contrainte d'unicité, justement à mon sens ça porte bien son nom et ça a plus sa place dans une contrainte SQL. C'est une garantie qu'aucune donnée incorrecte ne peut être insérée dans la DB (ce qui ne sera pas le cas si tu déplaces ça dans le code application).

Après je pense que la question fait débat, non ?

[ Dernière édition du message le 21/02/2020 à 11:51:02 ]

1880
Ben c'est une question de philosophie.

Tout laisser en sql c'est la solution la plus logique et naturelle. Mais bon un index a 4 colonnes c'est n'imp. Faire un select dessus ca doit être marrant.

Tout mettre dans le code serait une heresie aussi. Les contraintes d'unicite c'est une fonctionnalite de base des sgbd, ca serait débile de s'en priver surtout pour une solution moins robuste.

Jserais partisant de faire moitie moitie. Tu geres la relation dans un sens en sql, ca te fait une primary key a deux colonnes, et tu geres la relation inverse dans ton code ou en trigger.

Ou alors tu concatenes les primary key de ta relation pour en faire un id unique, comme ca ta primary key sera composee d'une seule colonne. Niveau perf c'est au top, tu gères la contrainte dans un sens, manque plus que de la gerer dans l'autre

Le chien aboie mais n'invente pas le fil à décongeler le beurre.

Les 6l6, des lampes qui nous éclairaient

1881
Ha non l'index a 4 colonnes je suis d'accord c'est pas beau, là c'est sans doute plus un problème de besoin pas bien identifié comme a souligné Krisfrom.
1882
Citation de krisfrom78 :
Perso, identifier ton besoins en partant direct sur les CREATE TABLE ça me semble un peu compliqué.
Avant de modéliser, et pour notre (en tout cas la mienne) compréhension de ton objectif, je pense qu'il faudrait commencer par poser quelques questions basiques en mode texte du type :
- Un client peut-il avoir plusieurs fournisseurs ?
- Un fournisseur peut-il avoir plusieurs clients ?
- Un fournisseur à t'il plusieurs adresses ?
- etc...

ça permet d'identifier les relations 1,1 / 1,N / N,N pour ensuite construire un modèle relationnel...


- Une personne peut être ami avec plusieurs personnes.

Du coup je voyais le truc comme ca: A est ami avec B = Une relation (A, B) + relation inverse (B, A) et les informations de cette relation stockées dans une autre table.
1883
Oui là tu as deux tables : personne, relation.

Avec : relation(A,B) = relation(B,A)
1884
Après l'index a 4 colonnes je sais pas pourquoi j'ai fait ca, j'imaginais qu'il fallait faire en sorte qu'une `relationship_infos` soit bien lié à deux autre `relationship`.

Mais si un id suffit alors ca me va. Je ne connais pas bien les bonne pratiques en fait.

Pour la relation inverse je me basais sur différents posts sur dba.stackexchange et sur ce post notamment sur Quora:
Citation :
How does Facebook maintain a list of friends for each user? Does it maintain a separate table for each user?

Citation :
I work in Serwis społecznościowy nk.pl - platforma komunikacji dla wszystkich internautów - nk.pl which used to be the largest social network in Poland, and I can tell you one thing: such relations need to be stored "in both ways". Others have suggested here, that you can use a single table `friendship` with two columns `user_a`, `user_b` and maintain single row for each frendship. This is a very bad idea for scalability and simplicity of code. As you can see in Marcus Matos answer it quickly leads to massive UNIONS and complicated "ifology" in JOINs as each edge must be double checked in both directions. Moreover this makes sharding (spliting the database across many machines) almost impossible.
What we do instead is add two edges: for example if user #17 adds user #36 to her friends list, we perform two INSERTs for (17,36) and (36,17).
This way list of friends of a single user can be retrieved with a single, simple select.
Also, we shard the database using `shard_number=user_a MOD number_of_shards` which lets us retrieve whole friends list in a single query to a single machine.
It also makes "friends-of-friends" and "common-friends" problems a bit easier, but frankly, even Serwis społecznościowy nk.pl - platforma komunikacji dla wszystkich internautów - nk.pl has more than 4 bilion edges, and handling this in real time is a bit tricky, so we actually have batch jobs which run at night, so that propositions of people you might know are cached and available in the morning.

To make my point more convincing: we've actually used the approach with single friendship = single row, for two years IIRC, and it was a mess!

One more thing: you can make sure that you always perform the two INSERTs in the same order you perform two DELETEs (for example by using lexicographic ordering) and then if there is some integrity problem (one database has a row, but the symmetric row is missing) you can easily tell what was the last operation that failed and restore the consistent state.
For example if you see that there is (36,17) row, but (17,36) is missing, then it must mean, that there was DELETE (17,36), DELETE (36,17) transaction which failed in the middle.


Après j'ai aussi vu ce post qui a une approche différente: https://www.alibabacloud.com/blog/social-friend-relationship-system-practice-in-postgresql---accelerating-queries-of-positive-and-negative-relationships_595043

[ Dernière édition du message le 21/02/2020 à 14:44:33 ]

1885
Perso, pou rce genre de choses, je passe en NoSql et je stocke une liste de chaque cote avec les connexions. Tant que tu n'as pas des milliers de liens, ca peut passer.
1886
Qu'est-ce que ca apporte en vrai, le nosql par rapport a un sgbdr ? J'ai encore du mal a m'en rendre compte

Pour mon app je suis passe de mysql a mongodb. Parce que je traite que des donnees en json et que mongodb enregistre littéralement en json et que j'ai besoin de rajouter a la volée de nouvelles propriétés sans me prendre la tête avec des migrations. Mais aussi parce que c'est hype, mongodb.

Le chien aboie mais n'invente pas le fil à décongeler le beurre.

Les 6l6, des lampes qui nous éclairaient

1887
Citation de miles1981 :
Perso, pou rce genre de choses, je passe en NoSql et je stocke une liste de chaque cote avec les connexions. Tant que tu n'as pas des milliers de liens, ca peut passer.


Oui mais tu dois bien stocker les informations sur ces connexions quelque part?
1888
Citation :
Others have suggested here, that you can use a single table `friendship` with two columns `user_a`, `user_b` and maintain single row for each frendship. This is a very bad idea for scalability and simplicity of code.


Effectivement, la bonne solution n’est pas forcément la même pour mille personnes ou plus d’un million.
1889
Après quelques petites recherche en me basant sur le post du blog aliabacloud, je suis arrivé à un truc complètement différent:

CREATE TABLE account(
    id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    friends_id INTEGER[]
);

CREATE TABLE relationships_informations(
    id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    uids INTEGER[]
);
CREATE INDEX users_id ON relationships_informations USING GIN (uids);


On a deux tables:
- "account" avec l'id du compte plus un array d'entier contenant la liste d'id des amis "friends_id"
- "relationships_informations" pour les infos d'une relation entre X et Y, avec un id et un array d'entier pour stocker les id des deux account qui sont copaing.
- On créé un index sur le tableau d'entier.

Exemple:

-- account(id=A, friends_id =(A.friends_id + B))
-- account(id=B, friends_id =(B.friends_id + A))
-- relationships_informations(id, uids=int[A.id, B.id], foo=bar, baz=spam)


On a Deux comptes: A et B
A devient ami avec B.

Donc on ajoute B dans la liste d'amis de A: "account(id=A, friends_id=(A.friends_id + B))"
Puis on ajoute A dans la liste d'amis de B: "account(id=B, friends_id=(B.friends_id + A))"

Enfin on crée une "relationships_info": "relationships_informations(id, uids=int[A.id, B.id], foo=bar, baz=spam)" Avec un array d'entier contenant l'id des deux accounts + toutes les données sur la relation.

Conclusion:
Du coup j'ai plus de tables relation + relation inversé.

Pour récupérer les listes d'amis:
- Je peux facilement récupérer la liste de mes amis.
- Ou la liste des amis de mes amis en concaténant ces listes puis en faisant un uniq(sort(liste_damis_de_mes_amis)), idem pour amis des amis de mes amis et ainsi de suite.

Pour récupérer les infos sur les relations:
- Pour une relation il me suffit de faire un select et de vérifier que la colonne "uids" contient bien mes deux ids.
- Pour plusieurs relations avec l'id d'une personne je peux faire un select dans la table "relationships_infos" sur la colonne "uids" en vérifiant qu'elle contient bien mon id.
- Idem avec l'id de plusieurs personnes en utilisant un "ANY" ou un truc du genre(je ne sais pas si ca existe en SQL).

Après je ne sais pas si je peux créer un type d'array qui doit contenir obligatoirement deux entier différents.
Ce qui éviterait que des données foireuse soient rentrées genre un array de 0,1,3 ou plus d'items.

J'ai vu qu'on pouvait faire un "CREATE UNIQUE INDEX" mais j'imagine que sur une colonne de type array ca doit pas être possible.

Après j'imagine qu'il vaut mieux faire un sort() de chaque array avant enregistrement, pour éviter les doublons du type [1,2], [2,1].

Peut-être qu'un autre type serait envisageable sur la table relationships_infos, je sais pas.

[ Dernière édition du message le 21/02/2020 à 16:32:13 ]

1890
Citation de deozza :
Qu'est-ce que ca apporte en vrai, le nosql par rapport a un sgbdr ? J'ai encore du mal a m'en rendre compte

Pour mon app je suis passe de mysql a mongodb. Parce que je traite que des donnees en json et que mongodb enregistre littéralement en json et que j'ai besoin de rajouter a la volée de nouvelles propriétés sans me prendre la tête avec des migrations. Mais aussi parce que c'est hype, mongodb.

Principalement, tu n'as pas le concept de requetes join. Donc ca change fondamentalement la maniere de voir les schemas de bdd, puisqu'il n'y a plus de schema.
Pour des donnees peu structurees, avec des trous, des listes variables, c'est le top, je trouve.
Apres, c'est aussi que les schemas de bdd, ca me sort par les trous de nez.

Citation de Truelle :
Citation de miles1981 :
Perso, pou rce genre de choses, je passe en NoSql et je stocke une liste de chaque cote avec les connexions. Tant que tu n'as pas des milliers de liens, ca peut passer.


Oui mais tu dois bien stocker les informations sur ces connexions quelque part?

Oui, dans la structure associee a la personne. Donc typiquement:

{
id:"",
name:"toto",
connections:["tata", "tutu"]
}

Apres, les problemes de synchro quand on met a jour sont pas simple non plus, mais on peut faire un batch, et si je me souviens bien les batchs font effet de transaction. Donc si tu peux faire un batch, ca va.

[ Dernière édition du message le 21/02/2020 à 16:48:18 ]

1891
Ah pardon moi je parlais plutôt des donnés style date de début de relation, type de relation (ami, famille, collègue) plus tout les détails que tu peut être amener à ajouter.
1892
Citation de Truelle :
Ah pardon moi je parlais plutôt des donnés style date de début de relation, type de relation (ami, famille, collègue) plus tout les détails que tu peut être amener à ajouter.

Effectivement, dans ce cas, mieux vaut avoir une table dediee, ou alors un dictionnaire contact: connectionid qui pointe vers une autre table qui pourra avoir ces infos.
1893
Citation de Truelle :
Après quelques petites recherche en me basant sur le post du blog aliabacloud, je suis arrivé à un truc complètement différent:
...


D'accord sur le principe, mais une table de liaison à 3 colonnes (ID, A_ID, B_ID) marche tout aussi bien.
Pour l'unicité, tu pourrais passer par une procédure stockée pour réaliser l'insertion d'une relationship. Je ne suis pas sûr que tu puisses le faire simplement avec des constraints. Ou alors ça risque de compliquer ton design.
1894
@props:

Je viens de trouver ca pour une table du type relationships(ID, A_ID, B_ID):
https://dba.stackexchange.com/questions/202570/combine-indexes-to-optimize-or-queries
https://www.postgresql.org/docs/current/bloom.html

Du coup je pourrais utiliser des foreignkey pour A_ID, B_ID.
Et plus de table "relationships_informations".

Edit: Ouai nan en fait il peut y avoir des faux positif et c'est surtout utilisé quand on teste beaucoup de colonnes.

Tout connement je viens de voir dans le même post qu'on peut faire un simple 'WHERE id IN (a_id, b_id)'. :facepalm:

[ Dernière édition du message le 21/02/2020 à 20:39:56 ]

1895
Du coup retour à la case départ avec l'idée de Duch et Props:

-- # ENUMS
-- #######
-- Relation Status:
-- - P = Pending
-- - A = Accepted
-- - D = Declined
-- - B = Blocked
CREATE TYPE relation_status AS ENUM('P', 'A', 'D', 'B');


-- # FUNCTIONS
-- ###########
CREATE FUNCTION reverse_relationship_exists(account_id_a INTEGER, account_id_b INTEGER)
RETURNS BOOLEAN AS $$
BEGIN 
    RETURN EXISTS(
        SELECT
            1
        FROM relationships
        WHERE
          account_a_id = account_id_b
          AND account_b_id = account_id_a
    );
END;
$$
LANGUAGE plpgsql;


-- # TABLES
-- ########
CREATE TABLE accounts(
    id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
);

CREATE TABLE relationships(
    id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    account_a_id INTEGER NOT NULL REFERENCES accounts (id),
    account_b_id INTEGER NOT NULL REFERENCES accounts (id),
    CONSTRAINT accounts_are_differents CHECK (account_a_id != account_b_id),
    CHECK (NOT reverse_relationship_exists(account_a_id, account_b_id)),
    UNIQUE (account_a_id, account_b_id),

    -- Informations
    status relation_status NOT NULL,
    last_action_account_id INTEGER NOT NULL REFERENCES accounts (id),
    CONSTRAINT last_action_done_by_one_of_two_users CHECK(last_action_account_id IN (
        account_a_id, account_b_id
    ))
);


Du coup j'imagine qu'il faut créer un index pour chaques colonnes account_a_id, account_b_id.

CREATE INDEX a_id ON relationships(account_a_id);
CREATE INDEX b_id ON relationships(account_b_id);


Ou créer un index unique et supprimer le ligne "UNIQUE(account_a_id, account_b_id)":
CREATE UNIQUE INDEX a_id ON relationships(account_a_id, account_b_id);

[ Dernière édition du message le 21/02/2020 à 21:27:40 ]

1896
Puis-je vous inviter à une petite reflexion sur les formes normales d'une base de données relationnelle?
Je voudrais insister sur le design des tables.

L'élément le plus simple doit avoir une clé unique (primary key)

ex: "account" avec un identifiant ( id géneré, N° de passeport, etc) qui le rend unique:
- id_acc

Toutes les informations "uniques" qui se rapportent à cet objet peuvent, doivent être inclues dans cette table:

- nom
- prénom
- date de naissance
- nom du père
- ....

Donc, une autre table "Relation" qui identifie une relation entre deux "account" avec un identifiant propre :
- id_relation (primary key de forme atomique)
et toutes les infos qui caractérisent cette relation entre deux "accounts". Ici, pas d'information relative à l'account, juste un objet "relation"
- description ( frére, soeur, père, mère, patron, directeur, employé,...)
- ...

Une troisième table "relation_account":
Qui reflète les relations entre accounts avec une clé unique aussi (primary key)
PK = "id_account_A , id_account B, id_rel"

Les données supplémentaires de la relation:
- un statut ( active, pending, ...)
- date de création
- id_rel_inv

La clé primaire (PK) pourrait aussi être un "timestamp" ou une id unique, si plusieures relations identiques doivent se côtoyer.

Un trigger peut également générer une relation inverse dans la table "relation_account" à la création d'une entrée dans la table "account".

Bien qu'en ajoutant une clé "étrangère" (Foreing Key) avec l'identifiant de la relation inverse dans la table "relation", on peut évité cette étape. Il suffirait dans la requête SQL de lire la relation à l'envers en utilisant la FK pour retrouver la relation inverse.
genre :

Relation normale:

select id_account A, id_B, id_relation, A.nom, A.prenom, B.nom, B.prénom, type_relation, date_creation, status,....
from account_relation, relation, account A, account B
where id_account_A = A.id_acc
and id_account_b = B.id_acc
and id_relation = id_rel


Ou si relation inverse

[b/]and id_relation = id_rel_inverse[/b]

Tout ceci peut-être renforcé par des contraintes d'intégrité référentielles pour évité des non-sens et des liens "account_relation" n'ayant pas/ plus de clé consistantes ou executé de update en cascade.

On peut bien-sûr ajouter des clauses de tri (order by, group by) et de sélection (where, where in et like) pour affiner la recherche.

Si des problèmes de performance apparaîssent:

- n'hésitez pas à reccourrir au mode "explain " pour analyser vos querie et déterminer l'ajout d'un index pertinent sur l'un ou l'autre champs/groupe de champs de vos tables
- repartir vos tables/index sur des "locations" (tablespaces) différentes.

:clin:
AL1
1897
Ouai merci pour l'explain, connaissait pas, du coup j'ai maté une vidéo du pgday qui en parlait et lu un peu la doc.

Sinon j'ai passé du temps à trouver des solutions plutôt compliqué mais finalement est-ce qu'il serait pas plus simple que je fasse une vue sur ça:
select user_a, user_b from relationships union select user_b, user_a from relationships;


Plus de relation inverse.
Du coup je peux mettre les infos dans cette tables .
Les recherches sont aussi simple.
Un index sur user_a, un autre sur user_b. Les deux en clé primaire.

Et si j'utilise cette vue je ne peux pas créer de doublon inverse puisque la même rangé apparaît dans les deux sens. (Sauf si je fais un insert de (1,2),(2,1) puisqu'il se baserait sur le même résultat je pense, m'enfin un check ça coûte rien).

Après si je fais une vue là dessus et que je fais un select sur cette vue, est-ce qu'elle calcule la totalité des deux select avant de les renvoyer pour faire un select/insert/update/delete et du coup serait une grosse consommatrice de cpu/mémoire à chaque utilisation?

[ Dernière édition du message le 23/02/2020 à 23:58:21 ]

1898
Citation :
Un index sur user_a, un autre sur user_b. Les deux en clé primaire.


Une clé primaire est par définition un index.
L'ajout d'index sur des éléments de clé est souvent contre-productif, a essayer avec un "explain" :clin:
Citation :
select user_a, user_b from relationships union select user_b, user_a from relationships;

L'utilisation d'une vue n'est pas une mauvaise idée.
Mais une vue n'est jamais qu'un "select" formalisé. Pour les insert/update avec une vue, cela impose les mếmes contraintes que pour des insert/update sur les tables. Tous les champs obligatoires doivent être remplis.
Ici, cela ne donnera rien de mieux que deux fois la table relationship dans une table temporaire ce qui peut être gourmand en memoire/table temporaire.
Un select avec une clause where bien torchée me semble plus efficace (index scan plutôt que hash join) qu'une vue

select A.user_A, A.user_B from relation_ship A
where A.user_A = 'misterX'
union
select B.user_A, B.user_B from relation_ship B
where A.user_A = B.user_B


Citation :
Après si je fais une vue là dessus et que je fais un select sur cette vue, est-ce qu'elle calcule la totalité des deux select avant de les renvoyer pour faire un select/insert/update/delete et du coup serait une grosse consommatrice de cpu/mémoire à chaque utilisation?



Une "vue" crée une table temporaire avec les résultats du select qui la définit. Elle va occuper de la place en mémoire et forcément utiliser du CPU voire de l'espace disque . Elle aura le même coût qu'un select normal, d'où l'intérêt de bien l'écrire. Pour des résultats importants ( genre select * et des "hash join") sur des grosses tables, cela mets rapidement des machines à genoux -> calibrage des tablespace temporaires très important!.
Les delete sont uniquement des radiations d'index dans un premier temps. Les réorganisations/truncate (vacuum) feront le nettoyage à postériori.,
AL1

[ Dernière édition du message le 24/02/2020 à 10:04:33 ]

1899
Citation :
Une clé primaire est par définition un index.
L'ajout d'index sur des éléments de clé est souvent contre-productif, a essayer avec un "explain" https://static.audiofanzine.com/images/audiofanzine/interface/smileys/icon_wink.gif


Yep mais du coup si je fais une recherche uniquement sur une des deux colonnes elles doivent bien être indexé séparément également ?
1900
Citation :
Yep mais du coup si je fais une recherche uniquement sur une des deux colonnes elles doivent bien être indexé séparément également ?


Quelque soit la base de données utilisée (Postgresql, MySQL/MariaDB, Oracle, DB2,...), il y a derrière des algorythmes qui influencent le choix de la méthode d'accès. Ils tiennent compte de la taille des tables, des index et de l'usage de la machine (nombre et vitesse des CPU, mémoire disponible,...)

Dans ce cas précis, il y a fort à parier que tu aura un index-scan sur la première zone suivi d'un hash-join (recherche en séquence) sur la table pour la seconde zone de la clé.

C'est le principe d'une fonction 'like'.
AL1